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_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_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_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_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_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 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 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)
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 test_zHop(self): """Test zHop detection.""" # set zhop distance self.Settings.current_printer().z_hop = .5 position = Position(self.Settings, self.OctoprintPrinterProfile, False) # test initial state self.assertFalse(position.is_zhop()) # check without homed axis position.update(Commands.parse("G1 x0 y0 z0")) self.assertFalse(position.is_zhop()) position.update(Commands.parse("G1 x0 y0 z0.5")) self.assertFalse(position.is_zhop()) # set relative extruder, absolute xyz, home axis, check again position.update(Commands.parse("M83")) position.update(Commands.parse("G90")) position.update(Commands.parse("G28")) self.assertFalse(position.is_zhop()) # Position reports as NotHomed (misnomer, need to replace), needs to get # coordinates position.update(Commands.parse("G1 x0 y0 z0")) # Move up without extrude, this is not a zhop since we haven't extruded # anything! position.update(Commands.parse("g0 z0.5")) self.assertFalse(position.is_zhop()) # move back down to 0 and extrude position.update(Commands.parse("g0 z0 e1")) self.assertFalse(position.is_zhop()) # Move up without extrude, this should trigger zhop start position.update(Commands.parse("g0 z0.5")) self.assertTrue(position.is_zhop()) # move below zhop threshold position.update(Commands.parse("g0 z0.3")) self.assertFalse(position.is_zhop()) # move right up to zhop without going over, we are within the rounding error position.update(Commands.parse("g0 z0.4999")) self.assertTrue(position.is_zhop()) # Extrude on z5 position.update(Commands.parse("g0 z0.5 e1")) self.assertFalse(position.is_zhop()) # partial z lift, , we are within the rounding error position.update(Commands.parse("g0 z0.9999")) self.assertTrue(position.is_zhop()) # Still hopped! position.update(Commands.parse("g0 z1")) self.assertTrue(position.is_zhop()) # test with extrusion start at 1.5 position.update(Commands.parse("g0 z1.5 e1")) self.assertFalse(position.is_zhop()) # test with extrusion at 2 position.update(Commands.parse("g0 z2 e1")) self.assertFalse(position.is_zhop()) # zhop position.update(Commands.parse("g0 z2.5 e0")) self.assertTrue(position.is_zhop()) position.update(Commands.parse("no-command")) self.assertTrue(position.is_zhop())
def test_comments(self): """Try to parse the G0 Command, parsed.parameters and comment""" gcode = ";" parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNone(parsed.parameters) gcode = " ;" parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNone(parsed.parameters) gcode = "; " parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNone(parsed.parameters) gcode = "; " parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNone(parsed.parameters) gcode = "%" parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNone(parsed.parameters) gcode = "% " parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNone(parsed.parameters) gcode = "% " parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNone(parsed.parameters) gcode = " % this is a comment" parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNone(parsed.parameters) 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) gcode = "g0x100y200.0z3.0001e1.1f7200.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) # test signs 1 gcode = "g0x+100y-200.0z - 3.0001e +1.1f 7200.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) # test signs 2 gcode = "g0x-100y + 200.0z+ 3.0001e -1.1f + 7200.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) gcode = "g28xyz; Here is a 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"])
def test_unknown_word(self): gcode = "K100" parsed = Commands.parse(gcode) self.assertIsNone(parsed.cmd) self.assertIsNone(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 create_snapshot_gcode(self, position, trigger, gcode, cmd, parameters, triggering_command_position, triggering_extruder_position): assert (isinstance(triggering_command_position, Pos)) x_return = position.x() y_return = position.y() z_return = position.z() f_return = position.f() # e_return = position.e() is_relative = position.is_relative() is_extruder_relative = position.is_extruder_relative() is_metric = position.is_metric() z_lift = position.distance_to_zlift() if z_lift is None: pos = position.get_position(0) if pos is not None: self.Settings.current_debug_profile().log_warning( "gcode.py - ZLift is none: Z:{0}, LastExtrusionHeight:{1}". format(pos.Z, pos.LastExtrusionHeight)) length_to_retract = position.Extruder.length_to_retract() final_command = gcode self.reset() if x_return is None or y_return is None or z_return is None: self.HasSnapshotPositionErrors = True message = "Cannot create GCode when x,y,or z is None. Values: x:{0} y:{1} z:{2}".format( x_return, y_return, z_return) self.SnapshotPositionErrors = message self.Settings.current_debug_profile().log_error( "gcode.py - CreateSnapshotGcode - {0}".format(message)) return None # Todo: Clean this mess up (used to take separate params in the fn, now we take a position object) self.ReturnWhenComplete = True self.FOriginal = f_return self.FCurrent = f_return self.RetractedBySnapshotStartGcode = False self.RetractedLength = 0 self.ZhopBySnapshotStartGcode = False self.ZLift = z_lift self.IsRelativeOriginal = is_relative self.IsRelativeCurrent = is_relative self.IsExtruderRelativeOriginal = is_extruder_relative self.IsExtruderRelativeCurrent = is_extruder_relative # check the units if is_metric is None or not is_metric: self.Settings.current_debug_profile().log_error( "No unit of measurement has been set and the current" " printer profile is set to require explicit G20/G21, or the unit of measurement is inches. " ) return None # create our gcode object new_snapshot_gcode = SnapshotGcode(self.IsTestMode) # create the start and end gcode, which would include any split gcode (position restriction intersection) # or any saved command that needs to be appended # Flag to indicate that we should make sure the final feedrate = self.FOriginal # by default we want to change the feedrate if FCurrent != FOriginal reset_feedrate_before_end_gcode = True triggered_type = trigger.triggered_type(0) if triggered_type is None: triggered_type = trigger.triggered_type(1) # handle the trigger types if triggered_type == Triggers.TRIGGER_TYPE_DEFAULT: if triggering_command_position.IsTravelOnly: self.Settings.current_debug_profile().log_snapshot_gcode( "The triggering command is travel only, skipping return command generation" ) # No need to perform the return step! We'll go right the the next travel location after # taking the snapshot! self.ReturnWhenComplete = False elif triggered_type == Triggers.TRIGGER_TYPE_IN_PATH: # see if the snapshot command is a g1 or g0 if cmd: if cmd not in ["G0", "G1"]: # if this isn't a g0 or g1, I don't know what's up! return None # get the data necessary to split the command up in_path_position = trigger.in_path_position(0) intersection = in_path_position["intersection"] path_ratio_1 = in_path_position["path_ratio_1"] path_ratio_2 = in_path_position["path_ratio_2"] _x1 = intersection[0] # will be in absolute coordinates _y1 = intersection[1] # will be in absolute coordinates _x2 = parameters[ "X"] if "X" in parameters else None # should remain in the original coordinate system _y2 = parameters[ "Y"] if "Y" in parameters else None # should remain in the original coordinate system _z = parameters[ "Z"] if "Z" in parameters else None # should remain in the original coordinate system _e = parameters["E"] if "E" in parameters else None _f = parameters["F"] if "F" in parameters else None # if the command has an F parameter, update FCurrent if _f: _f = float(_f) if self.FCurrent != _f: # if we have a new speed here, set it as the original self.FCurrent = _f self.FOriginal = _f _e1 = None _e2 = None # calculate e if _e: _e = float(_e) if not self.IsExtruderRelativeCurrent: _extrusion_amount = position.e_relative(e=_e) # set e1 absolute _e1 = _e - _extrusion_amount * path_ratio_2 _e2 = _e else: _e1 = _e * path_ratio_1 _e2 = _e * path_ratio_2 # Convert X1 and y1 to relative if self.IsRelativeCurrent: if _x1: _x1 = position.x_relative(_x1) if _y1: _y1 = position.y_relative(_y1) if _x2: _x2 = float(_x2) if _y2: _y2 = float(_y2) if _z: _z = float(_z) if _f: _f = float(_f) if (self.IsTestMode): _e1 = None _e2 = None gcode1 = self.get_g_command(cmd, _x1, _y1, _z, _e1, _f) # create the second command gcode2 = self.get_g_command(cmd, _x2, _y2, _z, _e2, _f) # append both commands new_snapshot_gcode.append(SnapshotGcode.START_GCODE, gcode1) final_command = gcode2 # set the return x and return y to the intersection point # must be in absolute coordinates x_return = intersection[0] # will be in absolute coordinates y_return = intersection[1] # will be in absolute coordinates # recalculate z_lift and retract distance since we have moved a bit cmd1, cmd1_parameters = Commands.parse(gcode1) position.update(gcode, cmd1, cmd1_parameters) # set z_return to the new z position # must be absolute z_return = position.z() self.ZLift = position.distance_to_zlift() length_to_retract = position.Extruder.length_to_retract() # undo the update since the position has not changed, only the zlift value and potentially the # retraction length position.undo_update() else: return None if (self.Snapshot.retract_before_move and length_to_retract > 0): if not self.IsExtruderRelativeCurrent: new_snapshot_gcode.append(SnapshotGcode.START_GCODE, self.get_gcode_extruder_relative()) self.IsExtruderRelativeCurrent = True if self.RetractSpeed != self.FCurrent: new_f = self.RetractSpeed self.FCurrent = new_f else: new_f = None if length_to_retract > 0: new_snapshot_gcode.append( SnapshotGcode.START_GCODE, self.get_gcode_retract(length_to_retract, new_f)) self.RetractedLength = length_to_retract self.RetractedBySnapshotStartGcode = True # Can we hop or are we too close to the top? can_zhop = self.ZLift is not None and self.Printer.z_hop > 0 and utility.is_in_bounds( self.BoundingBox, z=z_return + self.ZLift) # if we can ZHop, do if can_zhop and self.ZLift > 0 and self.Snapshot.lift_before_move: if not self.IsRelativeCurrent: # must be in relative mode new_snapshot_gcode.append(SnapshotGcode.START_GCODE, self.get_gcode_axes_relative()) self.IsRelativeCurrent = True if self.ZHopSpeed != self.FCurrent: new_f = self.ZHopSpeed self.FCurrent = new_f else: new_f = None # append to snapshot gcode new_snapshot_gcode.append( SnapshotGcode.START_GCODE, self.get_gcode_z_lift_relative(self.ZLift, new_f)) self.ZhopBySnapshotStartGcode = True # Create code to move from the current extruder position to the snapshot position # get the X and Y coordinates of the snapshot snapshot_position = self.get_snapshot_position(x_return, y_return) new_snapshot_gcode.X = snapshot_position["X"] new_snapshot_gcode.Y = snapshot_position["Y"] if new_snapshot_gcode.X is None or new_snapshot_gcode.Y is None: # either x or y is out of bounds. return None # Move back to the snapshot position - make sure we're in absolute mode for this if self.IsRelativeCurrent: # must be in absolute mode new_snapshot_gcode.append(SnapshotGcode.SNAPSHOT_COMMANDS, self.get_gcode_axes_absolute()) self.IsRelativeCurrent = False # detect speed change if self.FCurrent != self.TravelSpeed: new_f = self.TravelSpeed self.FCurrent = new_f else: new_f = None # Move to Snapshot Position new_snapshot_gcode.append( SnapshotGcode.SNAPSHOT_COMMANDS, self.get_gcode_travel(new_snapshot_gcode.X, new_snapshot_gcode.Y, new_f)) # End Snapshot Gcode # Start Return Gcode if self.ReturnWhenComplete: # Only return to the previous coordinates if we need to (which will be most cases, # except when the triggering command is a travel only (moves both X and Y) # record our previous position for posterity new_snapshot_gcode.ReturnX = x_return new_snapshot_gcode.ReturnY = y_return new_snapshot_gcode.ReturnZ = z_return # Move back to previous position - make sure we're in absolute mode for this (hint: we already are right now) # also, our current speed will be correct, no need to append F if x_return is not None and y_return is not None: new_snapshot_gcode.append( SnapshotGcode.RETURN_COMMANDS, self.get_gcode_travel(x_return, y_return)) else: # record the final position new_snapshot_gcode.ReturnX = triggering_command_position.X new_snapshot_gcode.ReturnY = triggering_command_position.Y # see about Z, we may need to suppress our new_snapshot_gcode.ReturnZ = triggering_command_position.Z self.Settings.current_debug_profile().log_snapshot_gcode( "Skipping return position, traveling to the triggering command position: X={0}, y={0}" .format(triggering_command_position.X, triggering_command_position.Y)) new_snapshot_gcode.append( SnapshotGcode.RETURN_COMMANDS, self.get_gcode_travel(triggering_command_position.X, triggering_command_position.Y)) # Return to the previous feedrate if it's changed # If we zhopped in the beginning, lower z if self.ZhopBySnapshotStartGcode: if not self.IsRelativeCurrent: new_snapshot_gcode.append(SnapshotGcode.END_GCODE, self.get_gcode_axes_relative()) self.IsRelativeCurrent = True if self.ZHopSpeed != self.FCurrent: new_f = self.ZHopSpeed self.FCurrent = new_f else: new_f = None new_snapshot_gcode.append( SnapshotGcode.END_GCODE, self.get_gocde_z_lower_relative(self.ZLift, new_f)) # detract if self.RetractedBySnapshotStartGcode: if not self.IsExtruderRelativeCurrent: new_snapshot_gcode.append(SnapshotGcode.END_GCODE, self.get_gcode_extruder_relative()) self.IsExtruderRelativeCurrent = True if self.DetractSpeed != self.FCurrent: new_f = self.DetractSpeed self.FCurrent = new_f else: new_f = None if self.RetractedLength > 0: new_snapshot_gcode.append( SnapshotGcode.END_GCODE, self.get_gcode_detract(self.RetractedLength, new_f)) # reset the coordinate systems for the extruder and axis if self.IsRelativeOriginal != self.IsRelativeCurrent: if self.IsRelativeCurrent: new_snapshot_gcode.append(SnapshotGcode.END_GCODE, self.get_gcode_axes_absolute()) else: new_snapshot_gcode.append(SnapshotGcode.END_GCODE, self.get_gcode_axes_relative()) self.IsRelativeCurrent = self.IsRelativeOriginal if self.IsExtruderRelativeOriginal != self.IsExtruderRelativeCurrent: if self.IsExtruderRelativeOriginal: new_snapshot_gcode.append(SnapshotGcode.END_GCODE, self.get_gcode_extruder_relative()) else: new_snapshot_gcode.append(SnapshotGcode.END_GCODE, self.get_gcode_extruder_absolute()) # Make sure we return to the original feedrate if reset_feedrate_before_end_gcode and self.FOriginal != self.FCurrent: # we can't count on the end gcode to set f, set it here new_snapshot_gcode.append(SnapshotGcode.END_GCODE, self.get_gcode_feedrate(self.FOriginal)) new_snapshot_gcode.append(SnapshotGcode.END_GCODE, final_command) if self.IsTestMode: self.Settings.current_debug_profile().log_snapshot_gcode( "The print is in test mode, so all extrusion has been stripped from the following gcode." ) self.Settings.current_debug_profile().log_snapshot_gcode( "Snapshot Gcode - SnapshotCommandIndex:{0}, EndIndex:{1}, Triggering Command:{2}" .format(new_snapshot_gcode.snapshot_index(), new_snapshot_gcode.end_index(), gcode)) for gcode in new_snapshot_gcode.snapshot_gcode(): self.Settings.current_debug_profile().log_snapshot_gcode( " {0}".format(gcode)) return new_snapshot_gcode