def test_g28(self): # no parameters gcode = "G28" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G28") self.assertDictEqual({}, parsed.parameters) # all parameters, funky spaces gcode = "g 2 8 xy zw; some kind of comment" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G28") self.assertIsNone(parsed.parameters["X"]) self.assertIsNone(parsed.parameters["Y"]) self.assertIsNone(parsed.parameters["Z"]) self.assertIsNone(parsed.parameters["W"]) # all parameters, no spaces gcode = "g28wzxy; some kind of comment" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G28") self.assertIsNone(parsed.parameters["X"]) self.assertIsNone(parsed.parameters["Y"]) self.assertIsNone(parsed.parameters["Z"]) self.assertIsNone(parsed.parameters["W"]) # some parameters gcode = "g28 xy; some kind of comment" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G28") self.assertIsNone(parsed.parameters["X"]) self.assertIsNone(parsed.parameters["Y"]) self.assertNotIn("Z", parsed.parameters) self.assertNotIn("W", parsed.parameters)
def test_parse_int_negative(self): # test negative F error gcode = "G00 F- 1 09 " with self.assertRaises(Exception) as context: Commands.parse(gcode) self.assertTrue( "The parameter value is negative, which is not allowed." in context.exception)
def test_multiple_decimals_command(self): # test multiple decimal points in parameter 2 gcode = "G28.0." with self.assertRaises(Exception) as context: Commands.parse(gcode) self.assertTrue( "Cannot parse the gcode address, multiple periods seen." in context.exception)
def test_unknown_command(self): gcode = "G9999fdafdsafdafsd" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G9999") self.assertIsNone(parsed.parameters) gcode = "G9999" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G9999") self.assertIsNone(parsed.parameters)
def test_ExtruderMovement(self): """Test the M82 and M83 command.""" position = Position(self.Settings, self.OctoprintPrinterProfile, False) previous_pos = Pos(self.Settings.current_printer(), self.OctoprintPrinterProfile) # test initial position self.assertIsNone(position.e()) self.assertIsNone(position.is_extruder_relative()) self.assertIsNone(position.e_relative_pos(previous_pos)) # set extruder to relative coordinates position.update(Commands.parse("M83")) # test movement previous_pos = Pos(self.Settings.current_printer(), self.OctoprintPrinterProfile, position.get_position()) position.update(Commands.parse("G0 E100")) self.assertEqual(position.e(), 100) # this is somewhat reversed from what we do in the position.py module # there we update the pos() object and compare to the current state, so # comparing the current state to the # previous will result in the opposite sign self.assertEqual(position.e_relative_pos(previous_pos), -100) # switch to absolute movement previous_pos = Pos(self.Settings.current_printer(), self.OctoprintPrinterProfile, position.get_position()) position.update(Commands.parse("M82")) self.assertFalse(position.is_extruder_relative()) self.assertEqual(position.e(), 100) self.assertEqual(position.e_relative_pos(previous_pos), 0) # move to -25 previous_pos = Pos(self.Settings.current_printer(), self.OctoprintPrinterProfile, position.get_position()) position.update(Commands.parse("G0 E-25")) self.assertEqual(position.e(), -25) self.assertEqual(position.e_relative_pos(previous_pos), 125) # test movement to origin previous_pos = Pos(self.Settings.current_printer(), self.OctoprintPrinterProfile, position.get_position()) position.update(Commands.parse("G0 E0")) self.assertEqual(position.e(), 0) self.assertEqual(position.e_relative_pos(previous_pos), -25) # switch to relative position previous_pos = Pos(self.Settings.current_printer(), self.OctoprintPrinterProfile, position.get_position()) position.update(Commands.parse("M83")) position.update(Commands.parse("G0 e1.1")) self.assertEqual(position.e(), 1.1) self.assertEqual(position.e_relative_pos(previous_pos), -1.1) # move and test previous_pos = Pos(self.Settings.current_printer(), self.OctoprintPrinterProfile, position.get_position()) position.update(Commands.parse("G0 e1.1")) self.assertEqual(position.e(), 2.2) self.assertEqual(position.e_relative_pos(previous_pos), -1.1) # move and test previous_pos = Pos(self.Settings.current_printer(), self.OctoprintPrinterProfile, position.get_position()) position.update(Commands.parse("G0 e-2.2")) self.assertEqual(position.e(), 0) self.assertEqual(position.e_relative_pos(previous_pos), 2.2)
def test_g21(self): # no parameters gcode = "G21" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G21") self.assertDictEqual({}, parsed.parameters) # with parameters (bogus) gcode = "G21X100fdafdsa; Here is a comment" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G21") self.assertDictEqual({}, parsed.parameters)
def test_g20(self): # no parameters gcode = "G20" cmd, parameters = Commands.parse(gcode) self.assertEqual(cmd, "G20") self.assertDictEqual({}, parameters) # with parameters (bogus) gcode = "G20X100fdafdsa; Here is a comment" cmd, parameters = Commands.parse(gcode) self.assertEqual(cmd, "G20") self.assertDictEqual({}, parameters)
def test_reset(self): """Test init state.""" position = Position(self.Settings, self.OctoprintPrinterProfile, False) # reset all initialized vars to something else position.update(Commands.parse("G28")) position.update(Commands.parse("G0 X1 Y1 Z1")) # reset position = Position(self.Settings, self.OctoprintPrinterProfile, False) # test initial state self.assertEqual(len(position.Positions), 0) self.assertIsNone(position.SavedPosition)
def test_IsAtCurrentPosition(self): # Received: x:119.91,y:113.34,z:2.1,e:0.0, Expected: # x:119.9145519,y:113.33847,z:2.1 # G1 X119.915 Y113.338 F7200 position = Position(self.Settings, self.OctoprintPrinterProfile, False) position.Printer.printer_position_confirmation_tolerance = .0051 position.update(Commands.parse("M83")) position.update(Commands.parse("G90")) position.update(Commands.parse("G28")) position.update(Commands.parse("G1 X119.915 Y113.338 Z2.1 F7200")) self.assertTrue(position.is_at_current_position(119.91, 113.34, 2.1)) position.update(Commands.parse("g0 x120 y121 z2.1")) self.assertTrue(position.is_at_previous_position(119.91, 113.34, 2.1))
def test_parameter_repetition(self): # test parameter repetition gcode = "g28 xxz" parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNotNone(parsed.error) self.assertNotEqual(len(parsed.error), 0) # test parameter repetition wrapped in comments gcode = "(comment in the front)g(comment in middle)2()8x(another comment in middle)x(comment between" \ " address and value)100()1 . 1(another); Here is a comment" parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNotNone(parsed.error) self.assertNotEqual(len(parsed.error), 0)
def test_multiple_signs_parameter(self): # test multiple signs in parameter 1 gcode = "(comment in the front)g(comment in middle)2()8(another comment in middle)x(comment between" \ " address and value)+100()1 . 1+(another); Here is a comment" parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNotNone(parsed.error) self.assertNotEqual(len(parsed.error), 0) # test multiple signs in parameter 2 gcode = "(comment in the front)g(comment in middle)2()8x(another comment in middle)x(comment between" \ " address and value)++100()1 . 1(another); Here is a comment" parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNotNone(parsed.error) self.assertNotEqual(len(parsed.error), 0)
def test_parameter_repetition(self): # test parameter repetition gcode = "g28 xxz" with self.assertRaises(Exception) as context: Commands.parse(gcode) self.assertTrue('A parameter value was repeated, cannot parse gcode.' in context.exception) # test parameter repetition wrapped in comments gcode = "(comment in the front)g(comment in middle)2()8x(another comment in middle)x(comment between" \ " address and value)100()1 . 1(another); Here is a comment" with self.assertRaises(Exception) as context: Commands.parse(gcode) self.assertTrue('A parameter value was repeated, cannot parse gcode.' in context.exception)
def test_m105(self): gcode = "m105;" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "M105") self.assertDictEqual(parsed.parameters, {}) gcode = "m105X1;" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "M105") self.assertDictEqual(parsed.parameters, {}) gcode = "m105fdsafdsafdsfsdfsd;" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "M105") self.assertDictEqual(parsed.parameters, {})
def test_parse_int_negative(self): # test negative F error gcode = "G00 F- 1 09 " parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNotNone(parsed.error) self.assertNotEqual(len(parsed.error), 0)
def test_multiple_decimals_command(self): # test multiple decimal points in parameter 2 gcode = "G28.0." parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNotNone(parsed.error) self.assertNotEqual(len(parsed.error), 0)
def __init__(self, octolapse_settings, octoprint_printer_profile): self.Commands = Commands() self.Settings = octolapse_settings # type: OctolapseSettings self.StabilizationPaths = self.Settings.current_stabilization( ).get_stabilization_paths() self.Snapshot = Snapshot(self.Settings.current_snapshot()) self.Printer = Printer(self.Settings.current_printer()) self.OctoprintPrinterProfile = octoprint_printer_profile self.BoundingBox = utility.get_bounding_box(self.Printer, octoprint_printer_profile) self.IsTestMode = self.Settings.current_debug_profile().is_test_mode self.RetractedBySnapshotStartGcode = None self.ZhopBySnapshotStartGcode = None self.IsRelativeOriginal = None self.IsRelativeCurrent = None self.IsMetricOriginal = None self.IsMetricCurrent = None self.IsExtruderRelativeOriginal = None self.IsExtruderRelativeCurrent = None self.FOriginal = None self.FCurrent = None self.HasSnapshotPositionErrors = False self.SnapshotPositionErrors = "" self.ZLift = 0 self.RetractedLength = 0 self.AxisSpeedUnits = self.Printer.axis_speed_display_units self.ReturnWhenComplete = True if self.AxisSpeedUnits not in ["mm-min", "mm-sec"]: self.AxisSpeedUnits = "mm-min" self.RetractSpeed = self.convert_to_mm_min(self.Printer.retract_speed) self.DetractSpeed = self.convert_to_mm_min(self.Printer.detract_speed) self.TravelSpeed = self.convert_to_mm_min(self.Printer.movement_speed) self.ZHopSpeed = self.convert_to_mm_min(self.Printer.z_hop_speed)
def test_multiple_decimals_parameter(self): # test multiple decimal points in parameter 1 gcode = "(comment in the front)g(comment in middle)2()8x(another comment in middle)x(comment between" \ "address and value)+100()1 . 1(another).; Here is a comment" with self.assertRaises(Exception) as context: Commands.parse(gcode) self.assertTrue( "Could not parse float from parameter string, saw multiple decimal points." in context.exception) # test multiple decimal points in parameter 2 gcode = "(comment in the front)g(comment in middle)2()8x(another comment in middle)x(comment between" \ " address and value)1.00()1 . 1(another); Here is a comment" with self.assertRaises(Exception) as context: Commands.parse(gcode) self.assertTrue( "Could not parse float from parameter string, saw multiple decimal points." in context.exception)
def test_g0(self): # no parameters gcode = "g0" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G0") self.assertDictEqual({}, parsed.parameters) # no parameters, double 0 gcode = "g00" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G0") self.assertDictEqual({}, parsed.parameters) # all parameters with comment gcode = "g0 x100 y200.0 z3.0001 e1.1 f7200.000; Here is a comment" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G0") self.assertEqual(parsed.parameters["X"], 100) self.assertEqual(parsed.parameters["Y"], 200.0) self.assertEqual(parsed.parameters["Z"], 3.0001) self.assertEqual(parsed.parameters["E"], 1.1) self.assertEqual(parsed.parameters["F"], 7200.000) # all parameters, no spaces gcode = "g0x100y200.0z3.0001e1.1f7200.000" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G0") self.assertEqual(parsed.parameters["X"], 100) self.assertEqual(parsed.parameters["Y"], 200.0) self.assertEqual(parsed.parameters["Z"], 3.0001) self.assertEqual(parsed.parameters["E"], 1.1) self.assertEqual(parsed.parameters["F"], 7200.000) # all parameters, funky spaces gcode = "g 0 x 10 0 y2 00 .0z 3.0 001 e1. 1 f72 00 .000 " parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G0") self.assertEqual(parsed.parameters["X"], 100) self.assertEqual(parsed.parameters["Y"], 200.0) self.assertEqual(parsed.parameters["Z"], 3.0001) self.assertEqual(parsed.parameters["E"], 1.1) self.assertEqual(parsed.parameters["F"], 7200.000)
def test_G90InfluencesExtruder_UpdatePosition(self): """Test G90 for machines where it influences the coordinate system of the extruder.""" position = Position(self.Settings, self.OctoprintPrinterProfile, True) # Make sure the axis is homed position.update(Commands.parse("G28")) # set absolute mode with G90 position.update(Commands.parse("g90")) # update the position to 10 (absolute) position.update_position(e=10) self.assertEqual(position.e(), 10) # update the position to 10 again (absolute) to make sure we are in absolute # coordinates. position.update_position(e=10) self.assertEqual(position.e(), 10) # set relative mode with G90 position.update(Commands.parse("g91")) # update the position to 20 (relative) position.update_position(e=20) self.assertEqual(position.e(), 30)
def test_G92AbsoluteMovement(self): """Test the G92 command, move in absolute mode and test results.""" position = Position(self.Settings, self.OctoprintPrinterProfile, False) # set homed axis, absolute coordinates, and set position position.update(Commands.parse("G28")) position.update(Commands.parse("G90")) position.update(Commands.parse("G1 x100 y200 z150")) position.update(Commands.parse("G92 x10 y20 z30")) self.assertEqual(position.x(), 100) self.assertEqual(position.x_offset(), 90) self.assertEqual(position.y(), 200) self.assertEqual(position.y_offset(), 180) self.assertEqual(position.z(), 150) self.assertEqual(position.z_offset(), 120) # move to origin position.update(Commands.parse("G1 x-90 y-180 z-120")) self.assertEqual(position.x(), 0) self.assertEqual(position.x_offset(), 90) self.assertEqual(position.y(), 0) self.assertEqual(position.y_offset(), 180) self.assertEqual(position.z(), 0) self.assertEqual(position.z_offset(), 120) # move back position.update(Commands.parse("G1 x0 y0 z0")) self.assertEqual(position.x(), 90) self.assertEqual(position.x_offset(), 90) self.assertEqual(position.y(), 180) self.assertEqual(position.y_offset(), 180) self.assertEqual(position.z(), 120) self.assertEqual(position.z_offset(), 120)
def test_G92SetPosition(self): """Test the G92 command, settings the position.""" position = Position(self.Settings, self.OctoprintPrinterProfile, False) # no homed axis position.update(Commands.parse("G92 x10 y20 z30")) self.assertEqual(position.x(), 10) self.assertEqual(position.y(), 20) self.assertEqual(position.z(), 30) self.assertFalse(position.has_homed_axes()) # set homed axis, absolute coordinates, and set position position.update(Commands.parse("G28")) position.update(Commands.parse("G90")) position.update(Commands.parse("G1 x100 y200 z150")) position.update(Commands.parse("G92 x10 y20 z30")) self.assertEqual(position.x(), 100) self.assertEqual(position.x_offset(), 90) self.assertEqual(position.y(), 200) self.assertEqual(position.y_offset(), 180) self.assertEqual(position.z(), 150) self.assertEqual(position.z_offset(), 120) # Move to same position and retest position.update(Commands.parse("G1 x0 y0 z0")) self.assertEqual(position.x(), 90) self.assertEqual(position.x_offset(), 90) self.assertEqual(position.y(), 180) self.assertEqual(position.y_offset(), 180) self.assertEqual(position.z(), 120) self.assertEqual(position.z_offset(), 120) # Move and retest position.update(Commands.parse("G1 x-10 y10 z20")) self.assertEqual(position.x(), 80) self.assertEqual(position.x_offset(), 90) self.assertEqual(position.y(), 190) self.assertEqual(position.y_offset(), 180) self.assertEqual(position.z(), 140) self.assertEqual(position.z_offset(), 120) # G92 with no parameters position.update(Commands.parse("G92")) self.assertEqual(position.x(), 80) self.assertEqual(position.x_offset(), 80) self.assertEqual(position.y(), 190) self.assertEqual(position.y_offset(), 190) self.assertEqual(position.z(), 140) self.assertEqual(position.z_offset(), 140)
def test_GetSnapshotGcode_Fixed_AbsoluteCoordintes_ExtruderRelative(self): """Test snapshot gcode in absolute coordinate system with relative extruder and fixed coordinate stabilization """ # adjust the settings for absolute position and create the snapshot gcode generator self.Settings.current_stabilization().x_type = "fixed_coordinate" self.Settings.current_stabilization().x_fixed_coordinate = 10 self.Settings.current_stabilization().y_type = "fixed_coordinate" self.Settings.current_stabilization().y_fixed_coordinate = 20 snapshot_gcode_generator = SnapshotGcodeGenerator( self.Settings, self.create_octoprint_printer_profile()) self.Extruder.is_retracted = lambda: True self.Position.update(Commands.parse("G90")) self.Position.update(Commands.parse("M83")) self.Position.update(Commands.parse("G28")) self.Position.update(Commands.parse("G0 X95 Y95 Z0.2 F3600")) parsed_command = Commands.parse("G0 X100 Y100") self.Position.update(parsed_command) snapshot_gcode = snapshot_gcode_generator.create_snapshot_gcode( self.Position, None, parsed_command) gcode_commands = snapshot_gcode.snapshot_gcode() # verify the created gcodegcode_commands self.assertEqual(gcode_commands[0], "G1 E-4.00000 F4800") self.assertEqual(gcode_commands[1], "G1 X10.000 Y20.000 F10800") self.assertEqual(gcode_commands[2], "G1 X100.000 Y100.000") self.assertEqual(gcode_commands[3], "G1 E4.00000 F3000") self.assertEqual(gcode_commands[4], "G1 F3600") self.assertEqual(gcode_commands[5], parsed_command.gcode) # verify the return coordinates self.assertEqual(snapshot_gcode.ReturnX, 100) self.assertEqual(snapshot_gcode.ReturnY, 100) self.assertEqual(snapshot_gcode.ReturnZ, 0.2) self.assertEqual(snapshot_gcode.X, 10) self.assertEqual(snapshot_gcode.Y, 20) self.assertEqual(snapshot_gcode.Z, None)
def setUp(self): self.Commands = Commands() self.Settings = OctolapseSettings(NamedTemporaryFile().name) # in the general test case we want auto_detect_position to be false # else we'll have to simulate a position update (m114 return) after # a home (g28) command self.Settings.current_printer().auto_detect_position = False # since we've set auto_detect_position to false, we need to set # an origin, else X,Y and Z will still be None after a home command self.Settings.current_printer().origin_x = 0 self.Settings.current_printer().origin_y = 0 self.Settings.current_printer().origin_z = 0 self.OctoprintPrinterProfile = self.create_octolapse_printer_profile()
def test_g1(self): # no parameters gcode = "g1" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G1") self.assertDictEqual({}, parsed.parameters) # no parameters, double 0 gcode = "g01" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G1") self.assertDictEqual({}, parsed.parameters) # all parameters with comment gcode = "g1 x100 y200.0 z3.0001 e1.1 f7200.000; Here is a comment" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G1") self.assertEqual(parsed.parameters["X"], 100) self.assertEqual(parsed.parameters["Y"], 200.0) self.assertEqual(parsed.parameters["Z"], 3.0001) self.assertEqual(parsed.parameters["E"], 1.1) self.assertEqual(parsed.parameters["F"], 7200.000) # all parameters, no spaces gcode = "g1x100y200.0z3.0001e1.1f7200.000" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G1") self.assertEqual(parsed.parameters["X"], 100) self.assertEqual(parsed.parameters["Y"], 200.0) self.assertEqual(parsed.parameters["Z"], 3.0001) self.assertEqual(parsed.parameters["E"], 1.1) self.assertEqual(parsed.parameters["F"], 7200.000) # all parameters, funky spaces gcode = "g 01 x 10 0 y2 00 .0z 3.0 001 e1. 1 f72 00 .000 " parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G1") self.assertEqual(parsed.parameters["X"], 100) self.assertEqual(parsed.parameters["Y"], 200.0) self.assertEqual(parsed.parameters["Z"], 3.0001) self.assertEqual(parsed.parameters["E"], 1.1) self.assertEqual(parsed.parameters["F"], 7200.000) # from issue 86 gcode = "G1 X -18 Y95 F1000" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G1") self.assertEqual(parsed.parameters["X"], -18) self.assertEqual(parsed.parameters["Y"], 95) self.assertNotIn("Z", parsed.parameters) self.assertNotIn("E", parsed.parameters) self.assertEqual(parsed.parameters["F"], 1000)
class SnapshotGcode(object): CommandsDictionary = Commands() START_GCODE = 'start-gcode' SNAPSHOT_COMMANDS = 'snapshot-commands' RETURN_COMMANDS = 'return-commands' END_GCODE = 'end-gcode' def __init__(self): self.StartGcode = [] self.SnapshotCommands = [] self.ReturnCommands = [] self.EndGcode = [] self.X = None self.ReturnX = None self.Y = None self.ReturnY = None self.Z = None self.ReturnZ = None self.SnapshotIndex = -1 def snapshot_gcode(self): return self.StartGcode + self.SnapshotCommands + self.ReturnCommands + self.EndGcode def append(self, command_type, command): if command_type == self.START_GCODE: self.StartGcode.append(command) elif command_type == self.SNAPSHOT_COMMANDS: self.SnapshotCommands.append(command) elif command_type == self.RETURN_COMMANDS: self.ReturnCommands.append(command) elif command_type == self.END_GCODE: self.EndGcode.append(command) def end_index(self): return len(self.StartGcode) + len(self.SnapshotCommands) + len( self.ReturnCommands) + len(self.EndGcode) - 1 def snapshot_index(self): return len(self.StartGcode) + len(self.SnapshotCommands) - 1
def test_G92RelativeMovement(self): """Test the G92 command, move in relative mode and test results.""" position = Position(self.Settings, self.OctoprintPrinterProfile, False) # set homed axis, relative coordinates, and set position position.update(Commands.parse("G28")) position.update(Commands.parse("G91")) position.update(Commands.parse("G1 x100 y200 z150")) position.update(Commands.parse("G92 x10 y20 z30")) self.assertEqual(position.x(), 100) self.assertEqual(position.x_offset(), 90) self.assertEqual(position.y(), 200) self.assertEqual(position.y_offset(), 180) self.assertEqual(position.z(), 150) self.assertEqual(position.z_offset(), 120) # move to origin position.update(Commands.parse("G1 x-100 y-200 z-150")) self.assertEqual(position.x(), 0) self.assertEqual(position.x_offset(), 90) self.assertEqual(position.y(), 0) self.assertEqual(position.y_offset(), 180) self.assertEqual(position.z(), 0) self.assertEqual(position.z_offset(), 120) # advance each axis position.update(Commands.parse("G1 x1 y2 z3")) self.assertEqual(position.x(), 1) self.assertEqual(position.x_offset(), 90) self.assertEqual(position.y(), 2) self.assertEqual(position.y_offset(), 180) self.assertEqual(position.z(), 3) self.assertEqual(position.z_offset(), 120) # advance again position.update(Commands.parse("G1 x1 y2 z3")) self.assertEqual(position.x(), 2) self.assertEqual(position.x_offset(), 90) self.assertEqual(position.y(), 4) self.assertEqual(position.y_offset(), 180) self.assertEqual(position.z(), 6) self.assertEqual(position.z_offset(), 120)
def test_UpdatePosition_force(self): """Test the UpdatePosition function with the force option set to true.""" position = Position(self.Settings, self.OctoprintPrinterProfile, False) position.update(Commands.parse("G28")) position.update_position(x=0, y=0, z=0, e=0, force=True) self.assertEqual(position.x(), 0) self.assertEqual(position.y(), 0) self.assertEqual(position.z(), 0) self.assertEqual(position.e(), 0) position.update_position(x=1, y=2, z=3, e=4, force=True) self.assertEqual(position.x(), 1) self.assertEqual(position.y(), 2) self.assertEqual(position.z(), 3) self.assertEqual(position.e(), 4) position.update_position(x=None, y=None, z=None, e=None, force=True) self.assertEqual(position.x(), 1) self.assertEqual(position.y(), 2) self.assertEqual(position.z(), 3) self.assertEqual(position.e(), 4)
def test_inline_comments(self): gcode = "g28(this is an inline commentx)yz; Here is a comment" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G28") self.assertFalse("X" in parsed.parameters) self.assertIsNone(parsed.parameters["Y"]) self.assertIsNone(parsed.parameters["Z"]) gcode = "g28(this is an inline commentx); Here is a comment" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G28") self.assertFalse("X" in parsed.parameters) self.assertFalse("Y" in parsed.parameters) self.assertFalse("Z" in parsed.parameters) gcode = "(comment in the front)g28; Here is a comment" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G28") self.assertFalse("X" in parsed.parameters) self.assertFalse("Y" in parsed.parameters) self.assertFalse("Z" in parsed.parameters) gcode = "(comment in the front)g28(comment in back); Here is a comment" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G28") self.assertFalse("X" in parsed.parameters) self.assertFalse("Y" in parsed.parameters) self.assertFalse("Z" in parsed.parameters) gcode = "(comment in the front)g(comment in middle)28(comment in back); Here is a comment" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G28") self.assertFalse("X" in parsed.parameters) self.assertFalse("Y" in parsed.parameters) self.assertFalse("Z" in parsed.parameters) gcode = "(comment in the front)g(comment in middle)2()8(another comment in middle)x(comment between" \ "address and value)100()1 . 1(another); Here is a comment" parsed = Commands.parse(gcode) self.assertEqual(parsed.cmd, "G28") self.assertEqual(parsed.parameters["X"], 1001.1) self.assertFalse("Y" in parsed.parameters) self.assertFalse("Z" in parsed.parameters)
def on_gcode_queuing(self, command_string, cmd_type, gcode, tags): self.detect_timelapse_start(command_string, tags) # if the timelapse is not active, exit without changing any gcode if not self.is_timelapse_active(): return self.check_current_line_number(tags) # update the position tracker so that we know where all of the axis are. # We will need this later when generating snapshot gcode so that we can return to the previous # position is_snapshot_gcode_command = self._is_snapshot_command(command_string) try: self.Settings.current_debug_profile().log_gcode_queuing( "Queuing Command: Command Type:{0}, gcode:{1}, cmd: {2}, tags: {3}".format( cmd_type, gcode, command_string, tags ) ) try: cmd, parameters = Commands.parse(command_string) except ValueError as e: message = "An error was thrown by the gcode parser, stopping timelapse. Details: {0}".format(e.message) self.Settings.current_debug_profile().log_warning( message ) self.stop_snapshots(message, True) return None # get the position state in case it has changed # if there has been a position or extruder state change, inform any listener if cmd is not None and not is_snapshot_gcode_command: # create our state change dictionaries self.Position.update(command_string, cmd, parameters) # if this code is snapshot gcode, simply return it to the printer. if {'plugin:octolapse', 'snapshot_gcode'}.issubset(tags): return None if not self.check_for_non_metric_errors(): if self.Position.has_position_error(0): # There are position errors, report them! self._on_position_error() elif (self.State == TimelapseState.WaitingForTrigger and (self.Position.requires_location_detection(1)) and self.OctoprintPrinter.is_printing()): self.State = TimelapseState.AcquiringLocation if self.OctoprintPrinter.set_job_on_hold(True): thread = threading.Thread(target=self.acquire_position, args=[command_string, cmd, parameters]) thread.daemon = True thread.start() return None, elif (self.State == TimelapseState.WaitingForTrigger and self.OctoprintPrinter.is_printing() and not self.Position.has_position_error(0)): # update the triggers with the current position self.Triggers.update(self.Position, command_string) # see if at least one trigger is triggering _first_triggering = self.get_first_triggering() if _first_triggering: # We are triggering, take a snapshot self.State = TimelapseState.TakingSnapshot # pause any timer triggers that are enabled self.Triggers.pause() # get the job lock if self.OctoprintPrinter.set_job_on_hold(True): # take the snapshot on a new thread thread = threading.Thread( target=self.acquire_snapshot, args=[command_string, cmd, parameters, _first_triggering] ) thread.daemon = True thread.start() # suppress the current command, we'll send it later return None, elif self.State == TimelapseState.TakingSnapshot: # Don't do anything further to any commands unless we are # taking a timelapse , or if octolapse paused the print. # suppress any commands we don't, under any cirumstances, # to execute while we're taking a snapshot if cmd in self.Commands.SuppressedSnapshotGcodeCommands: command_string = None, # suppress the command if is_snapshot_gcode_command: # in all cases do not return the snapshot command to the printer. # It is NOT a real gcode and could cause errors. command_string = None, except Exception as e: self.Settings.current_debug_profile().log_exception(e) raise # notify any callbacks self._send_state_changed_message() # do any post processing for test mode if command_string != (None,): command_string = self._get_command_for_octoprint(command_string, cmd,parameters) return command_string
def __init__( self, settings, octoprint_printer, data_folder, timelapse_folder, on_print_started=None, on_print_start_failed=None, on_snapshot_start=None, on_snapshot_end=None, on_render_start=None, on_render_end=None, on_timelapse_stopping=None, on_timelapse_stopped=None, on_state_changed=None, on_timelapse_start=None, on_timelapse_end = None, on_snapshot_position_error=None, on_position_error=None): # config variables - These don't change even after a reset self.DataFolder = data_folder self.Settings = settings # type: OctolapseSettings self.OctoprintPrinter = octoprint_printer self.DefaultTimelapseDirectory = timelapse_folder self.OnPrintStartCallback = on_print_started self.OnPrintStartFailedCallback = on_print_start_failed self.OnRenderStartCallback = on_render_start self.OnRenderEndCallback = on_render_end self.OnSnapshotStartCallback = on_snapshot_start self.OnSnapshotCompleteCallback = on_snapshot_end self.TimelapseStoppingCallback = on_timelapse_stopping self.TimelapseStoppedCallback = on_timelapse_stopped self.OnStateChangedCallback = on_state_changed self.OnTimelapseStartCallback = on_timelapse_start self.OnTimelapseEndCallback = on_timelapse_end self.OnSnapshotPositionErrorCallback = on_snapshot_position_error self.OnPositionErrorCallback = on_position_error self.Commands = Commands() # used to parse and generate gcode self.Triggers = None self.PrintEndStatus = "Unknown" self.LastStateChangeMessageTime = None self.StateChangeMessageThread = None # Settings that may be different after StartTimelapse is called self.OctoprintPrinterProfile = None self.PrintStartTime = None self.FfMpegPath = None self.Snapshot = None self.Gcode = None self.Printer = None self.CaptureSnapshot = None self.Position = None self.Rendering = None self.State = TimelapseState.Idle self.IsTestMode = False # State Tracking that should only be reset when starting a timelapse self.SnapshotCount = 0 self.HasBeenStopped = False self.TimelapseStopRequested = False self.SavedCommand = None self.SecondsAddedByOctolapse = 0 # State tracking variables self.RequiresLocationDetectionAfterHome = False # fetch position private variables self._position_payload = None self._position_timeout_long = 600.0 self._position_timeout_short = 10.0 self._position_signal = threading.Event() self._position_signal.set() # get snapshot async private variables self._snapshot_success = False # It shouldn't take more than 5 seconds to take a snapshot! self._snapshot_timeout = 5.0 self._snapshot_signal = threading.Event() self._snapshot_signal.set() self._most_recent_snapshot_payload = None self.CurrentProfiles = {} self.CurrentFileLine = 0 # snapshot thread queue self._snapshot_task_queue = Queue(maxsize=1) self._rendering_task_queue = Queue(maxsize=1) self._reset()