-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactor(api): Refactor supported text modes * supported_text_modes: Provide lookup function inside of Enum so text mode can be looked up by "nice" value * Add tests for it * refactor(api): Refactor supported_text_modes * Won't be useful because response is needed. But if the response was added it is a single line difference from default and seems kinda useless * Add InvalidTextModeError * Refactor(api): Expand test coverage to 100% * Refactor(api): Fix linting * Refactor(api): Remove enum value * feat(api): G-Code Parsing * feat(api): Adding pipfile * feat(api): Move package to dev-packages
- Loading branch information
1 parent
d351611
commit 33e3ee3
Showing
4 changed files
with
337 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
53 changes: 53 additions & 0 deletions
53
api/src/opentrons/hardware_control/g_code_parsing/g_code_differ.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Tuple | ||
from diff_match_patch import diff_match_patch as dmp # type: ignore | ||
from opentrons.hardware_control.g_code_parsing.g_code_program.g_code_program import \ | ||
GCodeProgram | ||
from opentrons.hardware_control.g_code_parsing.g_code_program.supported_text_modes \ | ||
import SupportedTextModes | ||
|
||
|
||
class GCodeDiffer: | ||
INSERTION_TYPE = 'Insertion' | ||
EQUALITY_TYPE = 'Equality' | ||
DELETION_TYPE = 'Deletion' | ||
|
||
DIFF_TYPE_LOOKUP = { | ||
-1: DELETION_TYPE, | ||
0: EQUALITY_TYPE, | ||
1: INSERTION_TYPE | ||
} | ||
|
||
@classmethod | ||
def from_g_code_program( | ||
cls, | ||
program_1: GCodeProgram, | ||
program_2: GCodeProgram | ||
) -> GCodeDiffer: | ||
string_1 = program_1.get_text_explanation(SupportedTextModes.CONCISE.value) | ||
string_2 = program_2.get_text_explanation(SupportedTextModes.CONCISE.value) | ||
return cls(string_1, string_2) | ||
|
||
def __init__(self, string_1: str, string_2: str): | ||
self._string_1 = string_1 | ||
self._string_2 = string_2 | ||
|
||
def get_diff(self, timeout_secs=10): | ||
differ = dmp() | ||
differ.Diff_Timeout = timeout_secs | ||
return differ.diff_main( | ||
text1=self._string_1, | ||
text2=self._string_2 | ||
) | ||
|
||
def get_html_diff(self): | ||
return dmp().diff_prettyHtml(self.get_diff()) | ||
|
||
@classmethod | ||
def get_diff_type(cls, diff_tuple: Tuple[int, str]): | ||
return cls.DIFF_TYPE_LOOKUP[diff_tuple[0]] | ||
|
||
@classmethod | ||
def get_diff_content(cls, diff_tuple: Tuple[int, str]): | ||
return diff_tuple[1] |
275 changes: 275 additions & 0 deletions
275
api/tests/opentrons/hardware_control/g_code_parsing/test_g_code_differ.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,275 @@ | ||
import pytest | ||
from textwrap import dedent | ||
from opentrons.hardware_control.g_code_parsing.g_code_differ import GCodeDiffer | ||
from opentrons.hardware_control.g_code_parsing.g_code import GCode | ||
from opentrons.hardware_control.g_code_parsing.g_code_program.g_code_program import \ | ||
GCodeProgram | ||
from opentrons.hardware_control.g_code_parsing.g_code_program.supported_text_modes \ | ||
import SupportedTextModes | ||
|
||
# Naive Strings | ||
HELLO = 'Hello' | ||
HELLO_WORLD = 'Hello World' | ||
|
||
# G-Codes Only | ||
G_CODE_1 = dedent( | ||
""" | ||
G28.2 ABC | ||
G0 F5.005 | ||
""" | ||
).strip() | ||
G_CODE_2 = dedent( | ||
""" | ||
G28.2 ABC | ||
M400 | ||
G0 F5.005 | ||
""" | ||
).strip() | ||
|
||
|
||
# Concise G-Code Text Explanations | ||
@pytest.fixture | ||
def first_g_code_explanation() -> str: | ||
g_code_line_1 = GCode.from_raw_code( | ||
'G28.2 ABCXYZ', | ||
GCode.SMOOTHIE_IDENT, | ||
'ok\r\nok\r\n' | ||
)[0] | ||
g_code_line_2 = GCode.from_raw_code( | ||
'G38.2 F420Y-40.0', | ||
GCode.SMOOTHIE_IDENT, | ||
'G38.2 F420Y-40.0\r\n\r\n[PRB:296.825,292.663,218.000:1]\nok\r\nok\r\n' | ||
)[0] | ||
g_code_line_3 = GCode.from_raw_code( | ||
'M203.1 A125 B40 C40 X600 Y400 Z125\r\n\r\n', | ||
GCode.SMOOTHIE_IDENT, | ||
'' | ||
)[0] | ||
mode = SupportedTextModes.get_text_mode(SupportedTextModes.CONCISE.value) | ||
return '\n'.join( | ||
[ | ||
mode.builder(line) | ||
for line in [g_code_line_1, g_code_line_2, g_code_line_3] | ||
] | ||
) | ||
|
||
|
||
@pytest.fixture | ||
def second_g_code_explanation() -> str: | ||
g_code_line_1 = GCode.from_raw_code( | ||
'G28.2 ABCXYZ', | ||
GCode.SMOOTHIE_IDENT, | ||
'ok\r\nok\r\n' | ||
)[0] | ||
g_code_line_2 = GCode.from_raw_code( | ||
'M203.1 A125 B40 C40 X600 Y400 Z125\r\n\r\n', | ||
GCode.SMOOTHIE_IDENT, | ||
'' | ||
)[0] | ||
|
||
mode = SupportedTextModes.get_text_mode(SupportedTextModes.CONCISE.value) | ||
return '\n'.join( | ||
[ | ||
mode.builder(line) | ||
for line in [g_code_line_1, g_code_line_2] | ||
] | ||
) | ||
|
||
|
||
@pytest.fixture | ||
def first_g_code_program() -> GCodeProgram: | ||
raw_codes = [ | ||
['G28.2 ABCXYZ', 'ok\r\nok\r\n'], | ||
['G0 X113.38 Y11.24', 'ok\r\nok\r\n'], | ||
['G4 P555', 'ok\r\nok\r\n'], | ||
[ | ||
'M114.2', | ||
'M114.2\r\n\r\nok MCS: A:218.0 B:0.0 C:0.0 X:418.0 Y:-3.0 Z:218.0' | ||
], | ||
] | ||
|
||
g_code_list = [ | ||
GCode.from_raw_code(code, 'smoothie', response) | ||
for code, response in raw_codes | ||
] | ||
|
||
return GCodeProgram([code[0] for code in g_code_list]) | ||
|
||
|
||
@pytest.fixture | ||
def second_g_code_program() -> GCodeProgram: | ||
raw_codes = [ | ||
['G28.2 ABCXYZ', 'ok\r\nok\r\n'], | ||
['G0 X113.01 Y11.24', 'ok\r\nok\r\n'], | ||
['G4 P555', 'ok\r\nok\r\n'], | ||
] | ||
|
||
g_code_list = [ | ||
GCode.from_raw_code(code, 'smoothie', response) | ||
for code, response in raw_codes | ||
] | ||
|
||
return GCodeProgram([code[0] for code in g_code_list]) | ||
|
||
|
||
def test_naive_insertion(): | ||
diff = GCodeDiffer(HELLO, HELLO_WORLD).get_diff() | ||
hello = diff[0] | ||
world = diff[1] | ||
assert GCodeDiffer.get_diff_type(hello) == GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content(hello) == 'Hello' | ||
assert GCodeDiffer.get_diff_type(world) == GCodeDiffer.INSERTION_TYPE | ||
assert GCodeDiffer.get_diff_content(world) == ' World' | ||
|
||
|
||
def test_naive_deletion(): | ||
diff = GCodeDiffer(HELLO_WORLD, HELLO).get_diff() | ||
hello = diff[0] | ||
world = diff[1] | ||
assert GCodeDiffer.get_diff_type(hello) == GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content(hello) == 'Hello' | ||
assert GCodeDiffer.get_diff_type(world) == GCodeDiffer.DELETION_TYPE | ||
assert GCodeDiffer.get_diff_content(world) == ' World' | ||
|
||
|
||
def test_g_code_insertion(): | ||
diff = GCodeDiffer(G_CODE_1, G_CODE_2).get_diff() | ||
line_1 = diff[0] | ||
line_2 = diff[1] | ||
line_3 = diff[2] | ||
|
||
assert GCodeDiffer.get_diff_type(line_1) == GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content(line_1) == 'G28.2 ABC\n' | ||
assert GCodeDiffer.get_diff_type(line_2) == GCodeDiffer.INSERTION_TYPE | ||
assert GCodeDiffer.get_diff_content(line_2) == 'M400\n' | ||
assert GCodeDiffer.get_diff_type(line_3) == GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content(line_3) == 'G0 F5.005' | ||
|
||
|
||
def test_g_code_deletion(): | ||
diff = GCodeDiffer(G_CODE_2, G_CODE_1).get_diff() | ||
line_1 = diff[0] | ||
line_2 = diff[1] | ||
line_3 = diff[2] | ||
|
||
assert GCodeDiffer.get_diff_type(line_1) == GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content(line_1) == 'G28.2 ABC\n' | ||
assert GCodeDiffer.get_diff_type(line_2) == GCodeDiffer.DELETION_TYPE | ||
assert GCodeDiffer.get_diff_content(line_2) == 'M400\n' | ||
assert GCodeDiffer.get_diff_type(line_3) == GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content(line_3) == 'G0 F5.005' | ||
|
||
|
||
def test_g_code_explanation_insertion( | ||
first_g_code_explanation, | ||
second_g_code_explanation | ||
): | ||
diff = GCodeDiffer(second_g_code_explanation, first_g_code_explanation).get_diff() | ||
line_1 = diff[0] | ||
line_2 = diff[1] | ||
line_3 = diff[2] | ||
|
||
assert GCodeDiffer.get_diff_type(line_1) == GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content(line_1) == \ | ||
'G28.2 A B C X Y Z -> Homing the following axes: X, Y, Z, A, B, C ->\n' | ||
|
||
assert GCodeDiffer.get_diff_type(line_2) == GCodeDiffer.INSERTION_TYPE | ||
assert GCodeDiffer.get_diff_content(line_2) == \ | ||
'G38.2 F420.0 Y-40.0 -> Probing -40.0 on the Y axis, at a speed of 420.0 ' \ | ||
'-> Probed to : X Axis: 296.825 Y Axis: 292.663 Z Axis: 218.000\n' | ||
assert GCodeDiffer.get_diff_type(line_3) == GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content(line_3) == \ | ||
'M203.1 A125.0 B40.0 C40.0 X600.0 Y400.0 Z125.0 -> Setting the max speed ' \ | ||
'for the following axes: X-Axis: 600.0 Y-Axis: 400.0 Z-Axis: 125.0 ' \ | ||
'A-Axis: 125.0 B-Axis: 40.0 C-Axis: 40.0 ->' | ||
|
||
|
||
def test_g_code_explanation_deletion( | ||
first_g_code_explanation, | ||
second_g_code_explanation | ||
): | ||
diff = GCodeDiffer(first_g_code_explanation, second_g_code_explanation).get_diff() | ||
line_1 = diff[0] | ||
line_2 = diff[1] | ||
line_3 = diff[2] | ||
|
||
assert GCodeDiffer.get_diff_type(line_1) == GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content(line_1) == \ | ||
'G28.2 A B C X Y Z -> Homing the following axes: X, Y, Z, A, B, C ->\n' | ||
|
||
assert GCodeDiffer.get_diff_type(line_2) == GCodeDiffer.DELETION_TYPE | ||
assert GCodeDiffer.get_diff_content(line_2) == \ | ||
'G38.2 F420.0 Y-40.0 -> Probing -40.0 on the Y axis, at a speed of 420.0 ' \ | ||
'-> Probed to : X Axis: 296.825 Y Axis: 292.663 Z Axis: 218.000\n' | ||
assert GCodeDiffer.get_diff_type(line_3) == GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content(line_3) == \ | ||
'M203.1 A125.0 B40.0 C40.0 X600.0 Y400.0 Z125.0 -> Setting the max speed ' \ | ||
'for the following axes: X-Axis: 600.0 Y-Axis: 400.0 Z-Axis: 125.0 ' \ | ||
'A-Axis: 125.0 B-Axis: 40.0 C-Axis: 40.0 ->' | ||
|
||
|
||
def test_g_code_program_diff(first_g_code_program, second_g_code_program): | ||
diff = GCodeDiffer.from_g_code_program( | ||
first_g_code_program, second_g_code_program | ||
).get_diff() | ||
line_1 = diff[0] | ||
remove_38 = diff[1] | ||
add_01 = diff[2] | ||
beginning_partial_response = diff[3] | ||
remove_response_38 = diff[4] | ||
add_response_01 = diff[5] | ||
remaining_partial_response = diff[6] | ||
remove_last_line = diff[7] | ||
|
||
assert GCodeDiffer.get_diff_type(line_1) == GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content( | ||
line_1 | ||
) == 'G28.2 A B C X Y Z -> Homing the following axes: X, Y, Z, A, B, C ->\nG0 X113.' | ||
|
||
assert GCodeDiffer.get_diff_type(remove_38) == GCodeDiffer.DELETION_TYPE | ||
assert GCodeDiffer.get_diff_content(remove_38) == '38' | ||
|
||
assert GCodeDiffer.get_diff_type(add_01) == GCodeDiffer.INSERTION_TYPE | ||
assert GCodeDiffer.get_diff_content(add_01) == '01' | ||
|
||
assert GCodeDiffer.get_diff_type(beginning_partial_response) == \ | ||
GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content( | ||
beginning_partial_response | ||
) == ' Y11.24 -> Moving the robot as follows: The gantry to 113.' | ||
|
||
assert GCodeDiffer.get_diff_type(remove_response_38) == GCodeDiffer.DELETION_TYPE | ||
assert GCodeDiffer.get_diff_content(remove_response_38) == '38' | ||
|
||
assert GCodeDiffer.get_diff_type(add_response_01) == GCodeDiffer.INSERTION_TYPE | ||
assert GCodeDiffer.get_diff_content(add_response_01) == '01' | ||
|
||
assert GCodeDiffer.get_diff_type(remaining_partial_response) == \ | ||
GCodeDiffer.EQUALITY_TYPE | ||
assert GCodeDiffer.get_diff_content( | ||
remaining_partial_response | ||
) == ' on the X-Axis The gantry to 11.24 on the Y-Axis ->\nG4 P555.0 -> Pausing ' \ | ||
'movement for 555.0ms ->' | ||
|
||
assert GCodeDiffer.get_diff_type(remove_last_line) == GCodeDiffer.DELETION_TYPE | ||
assert GCodeDiffer.get_diff_content(remove_last_line) == \ | ||
'\nM114.2 -> Getting current position for all axes -> The current ' \ | ||
'position of the robot is: A Axis: 218.0 B Axis: 0.0 C Axis: 0.0 ' \ | ||
'X Axis: 418.0 Y Axis: -3.0 Z Axis: 218.0' | ||
|
||
|
||
def test_html_diff( | ||
first_g_code_explanation, | ||
second_g_code_explanation | ||
): | ||
expected_html = '<span>G28.2 A B C X Y Z -> Homing the following axes: ' \ | ||
'X, Y, Z, A, B, C ->¶<br></span><del style="background:' \ | ||
'#ffe6e6;">G38.2 F420.0 Y-40.0 -> Probing -40.0 on the Y ' \ | ||
'axis, at a speed of 420.0 -> Probed to : X Axis: 296.825 Y ' \ | ||
'Axis: 292.663 Z Axis: 218.000¶<br></del><span>M203.1 ' \ | ||
'A125.0 B40.0 C40.0 X600.0 Y400.0 Z125.0 -> Setting the max ' \ | ||
'speed for the following axes: X-Axis: 600.0 Y-Axis: 400.0 ' \ | ||
'Z-Axis: 125.0 A-Axis: 125.0 B-Axis: 40.0 C-Axis: 40.0 -></span>' | ||
diff = GCodeDiffer(first_g_code_explanation, second_g_code_explanation) | ||
html = diff.get_html_diff() | ||
assert html == expected_html |