예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
    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)
예제 #4
0
 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.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))
예제 #5
0
    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)
예제 #6
0
    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)
예제 #7
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, {})
예제 #8
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)
예제 #9
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)
예제 #10
0
    def setUp(self):

        new_settings, defaults_loaded = OctolapseSettings.load(
            "C:\\Users\\Brad\\AppData\\Roaming\\OctoPrint\\data\\octolapse\\settings.json",
            "0.4.0rc1.dev0",
            "C:\\Users\\Brad\\AppData\\Roaming\\OctoPrint\\data\\octolapse\\",
            "settings.json")

        self.Commands = Commands()
        self.Settings = new_settings
        self.Printer = self.Settings.profiles.current_printer()
        self.Stabilization = self.Settings.profiles.current_stabilization()
        self.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.Printer.home_x = 0
        self.Printer.home_y = 0
        self.Printer.home_z = 0
        assert (isinstance(self.Printer, PrinterProfile))
        self.Printer.slicer_type = SlicerSettings.SlicerTypeSlic3rPe
        slicer_settings = self.Printer.get_current_slicer_settings()
        assert (isinstance(slicer_settings, Slic3rPeSettings))
        slicer_settings.retract_length = 0.8
        slicer_settings.retract_speed = 2400 / 60
        self.OctoprintPrinterProfile = self.create_octolapse_printer_profile()
예제 #11
0
 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)
예제 #12
0
    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)
예제 #13
0
    def test_g90_influences_extruder_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)
예제 #14
0
    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)
예제 #15
0
    def test_t(self):
        gcode = "T?"
        parsed = Commands.parse(gcode)
        self.assertEqual(parsed.cmd, "T")
        self.assertDictEqual(parsed.parameters, {"T": None})

        gcode = " T? "
        parsed = Commands.parse(gcode)
        self.assertEqual(parsed.cmd, "T")
        self.assertDictEqual(parsed.parameters, {"T": None})

        gcode = " T ? "
        parsed = Commands.parse(gcode)
        self.assertEqual(parsed.cmd, "T")
        self.assertDictEqual(parsed.parameters, {"T": None})

        gcode = "T1;"
        parsed = Commands.parse(gcode)
        self.assertEqual(parsed.cmd, "T")
        self.assertDictEqual(parsed.parameters, {"T": 1})

        gcode = "T 1 ;"
        parsed = Commands.parse(gcode)
        self.assertEqual(parsed.cmd, "T")
        self.assertDictEqual(parsed.parameters, {"T": 1})

        gcode = "T 1.11 ;"
        parsed = Commands.parse(gcode)
        self.assertEqual(parsed.cmd, "T")
        self.assertDictEqual(parsed.parameters, {"T": 1})
예제 #16
0
    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.profiles.current_stabilization().x_type = "fixed_coordinate"
        self.Settings.profiles.current_stabilization().x_fixed_coordinate = 10
        self.Settings.profiles.current_stabilization().y_type = "fixed_coordinate"
        self.Settings.profiles.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)
예제 #17
0
    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)
예제 #18
0
    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)
예제 #19
0
    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)
예제 #20
0
    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)
예제 #21
0
    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)
예제 #22
0
    def take_snapshots(self, metadata={}, no_wait=False):
        logger.info("Starting snapshot acquisition")
        start_time = time()

        before_snapshot_threads = []
        snapshot_threads = []
        after_snapshot_threads = []
        results = []

        for current_camera in self.Cameras:
            camera_info = self.CameraInfos["{}".format(current_camera.guid)]

            # pre_snapshot threads
            if current_camera.on_before_snapshot_script:
                before_snapshot_job_info = SnapshotJobInfo(
                    self.TimelapseJobInfo,
                    self.temporary_directory,
                    camera_info.snapshot_attempt,
                    current_camera,
                    'before-snapshot',
                    metadata=metadata)
                thread = ExternalScriptSnapshotJob(before_snapshot_job_info,
                                                   'before-snapshot')
                thread.daemon = True
                before_snapshot_threads.append(thread)

            snapshot_job_info = SnapshotJobInfo(self.TimelapseJobInfo,
                                                self.temporary_directory,
                                                camera_info.snapshot_attempt,
                                                current_camera,
                                                'snapshot',
                                                metadata=metadata)
            if current_camera.camera_type == "script":
                thread = ExternalScriptSnapshotJob(
                    snapshot_job_info,
                    'snapshot',
                    on_new_thumbnail_available_callback=self.
                    OnNewThumbnailAvailableCallback,
                    on_post_processing_error_callback=self.
                    on_post_processing_error_callback)
                thread.daemon = True
                snapshot_threads.append((thread, snapshot_job_info, None))
            elif current_camera.camera_type == "webcam":
                download_started_event = Event()
                thread = WebcamSnapshotJob(
                    snapshot_job_info,
                    download_started_event=download_started_event,
                    on_new_thumbnail_available_callback=self.
                    OnNewThumbnailAvailableCallback,
                    on_post_processing_error_callback=self.
                    on_post_processing_error_callback)
                thread.daemon = True
                snapshot_threads.append(
                    (thread, snapshot_job_info, download_started_event))

            # post_snapshot threads
            if current_camera.on_after_snapshot_script:
                after_snapshot_job_info = SnapshotJobInfo(
                    self.TimelapseJobInfo,
                    self.temporary_directory,
                    camera_info.snapshot_attempt,
                    current_camera,
                    'after-snapshot',
                    metadata=metadata)
                thread = ExternalScriptSnapshotJob(after_snapshot_job_info,
                                                   'after-snapshot')
                thread.daemon = True
                after_snapshot_threads.append(thread)

        # Now that the before snapshot threads are prepared, send any before snapshot gcodes
        for current_camera in self.Cameras:
            if current_camera.on_before_snapshot_gcode:
                on_before_snapshot_gcode = Commands.string_to_gcode_array(
                    current_camera.on_before_snapshot_gcode)
                if len(on_before_snapshot_gcode) > 0:
                    logger.info(
                        "Sending on_before_snapshot_gcode for the %s camera.",
                        current_camera.name)
                    self.SendGcodeArrayCallback(
                        on_before_snapshot_gcode,
                        current_camera.timeout_ms / 1000.0,
                        wait_for_completion=not no_wait,
                        tags={'before-snapshot-gcode'})

        if len(before_snapshot_threads) > 0:
            logger.info("Starting %d before snapshot threads",
                        len(before_snapshot_threads))

        # start the pre-snapshot threads
        for t in before_snapshot_threads:
            t.start()

        # join the pre-snapshot threads
        for t in before_snapshot_threads:
            if not no_wait:
                snapshot_job_info = t.join()
                assert (isinstance(snapshot_job_info, SnapshotJobInfo))
                if t.snapshot_thread_error:
                    snapshot_job_info.success = False
                    snapshot_job_info.error = t.snapshot_thread_error
                else:
                    snapshot_job_info.success = True
            else:
                snapshot_job_info.success = True
            results.append(snapshot_job_info)

        if len(before_snapshot_threads) > 0:
            logger.info("Before snapshot threads finished.")

        if len(snapshot_threads) > 0:
            logger.info("Starting %d snapshot threads.", len(snapshot_threads))
        # start the snapshot threads, then wait for all threads to signal before continuing
        for t in snapshot_threads:
            t[0].start()

        # now send any gcode for gcode cameras
        for current_camera in self.Cameras:
            if current_camera.camera_type == "gcode":
                script_sent = False
                if current_camera.gcode_camera_script:
                    gcode_camera_script = Commands.string_to_gcode_array(
                        current_camera.gcode_camera_script)
                    if len(gcode_camera_script) > 0:
                        logger.info("Sending snapshot gcode array to %s.",
                                    current_camera.name)
                        # just send the gcode now so it all goes in order
                        self.SendGcodeArrayCallback(
                            Commands.string_to_gcode_array(
                                current_camera.gcode_camera_script),
                            current_camera.timeout_ms / 1000.0,
                            wait_for_completion=not no_wait)
                        script_sent = True
                    if not script_sent:
                        logger.warning(
                            "The gcode camera '%s' is enabled, but failed to produce any snapshot gcode.",
                            current_camera.name)

        for t, snapshot_job_info, event in snapshot_threads:
            if not no_wait:
                if event:
                    event.wait()
                else:
                    snapshot_job_info = t.join()
                if t.snapshot_thread_error:
                    snapshot_job_info.success = False
                    snapshot_job_info.error = t.snapshot_thread_error
                elif t.post_processing_error:
                    snapshot_job_info.success = False
                    snapshot_job_info.error = t.post_processing_error
                else:
                    snapshot_job_info.success = True
            else:
                snapshot_job_info.success = True

            info = self.CameraInfos[snapshot_job_info.camera_guid]
            info.snapshot_attempt += 1
            if snapshot_job_info.success:
                info.snapshot_count += 1
                self.SnapshotsTotal += 1
            else:
                info.errors_count += 1
                self.ErrorsTotal += 1
            info.save(self.temporary_directory, self.TimelapseJobInfo.JobGuid,
                      snapshot_job_info.camera_guid)
            results.append(snapshot_job_info)

        if len(snapshot_threads) > 0:
            logger.info(
                "Snapshot threads complete, but may be post-processing.")

        if len(after_snapshot_threads) > 0:
            logger.info("Starting %d after snapshot threads.",
                        len(after_snapshot_threads))

        # Now that the after snapshot threads are prepared, send any after snapshot gcodes
        for current_camera in self.Cameras:
            if current_camera.on_after_snapshot_gcode:
                on_after_snapshot_gcode = Commands.string_to_gcode_array(
                    current_camera.on_after_snapshot_gcode)
                if len(on_after_snapshot_gcode) > 0:
                    logger.info(
                        "Sending on_after_snapshot_gcode for the %s camera.",
                        current_camera.name)
                    self.SendGcodeArrayCallback(
                        on_after_snapshot_gcode,
                        current_camera.timeout_ms / 1000.0,
                        wait_for_completion=not no_wait,
                        tags={'after-snapshot-gcode'})

        # start the after-snapshot threads
        for t in after_snapshot_threads:
            t.start()

        # join the after-snapshot threads
        for t in after_snapshot_threads:
            if not no_wait:
                snapshot_job_info = t.join()
                assert (isinstance(snapshot_job_info, SnapshotJobInfo))
                info = self.CameraInfos[snapshot_job_info.camera_guid]
                if t.snapshot_thread_error:
                    snapshot_job_info.success = False
                    snapshot_job_info.error = t.snapshot_thread_error
                else:
                    snapshot_job_info.success = True
            else:
                snapshot_job_info.success = True
            results.append(snapshot_job_info)

        if len(after_snapshot_threads) > 0:
            logger.info("After snapshot threads complete.")

        logger.info("Snapshot acquisition completed in %.3f seconds.",
                    time() - start_time)

        return results
예제 #23
0
    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"])
예제 #24
0
    def test_UpdatePosition_noforce(self):
        """Test the UpdatePosition function with the force option set to true."""
        position = Position(self.Settings, self.OctoprintPrinterProfile, False)
        # no homed axis
        position.update_position(x=0, y=0, z=0, e=0)
        self.assertIsNone(position.x())
        self.assertIsNone(position.y())
        self.assertIsNone(position.z())
        self.assertIsNone(position.e(), 0)

        # set homed axis, test absolute position (default)
        position.update(Commands.parse("G28"))
        position.update_position(x=0, y=0, z=0)
        self.assertEqual(position.x(), 0)
        self.assertEqual(position.y(), 0)
        self.assertEqual(position.z(), 0)
        self.assertEqual(position.e(), 0)

        # update absolute position
        position.update_position(x=1, y=2, z=3)
        self.assertEqual(position.x(), 1)
        self.assertEqual(position.y(), 2)
        self.assertEqual(position.z(), 3)
        self.assertEqual(position.e(), 0)

        # set relative position
        position.update(Commands.parse("G91"))
        position.update_position(x=1, y=1, z=1)
        self.assertEqual(position.x(), 2)
        self.assertEqual(position.y(), 3)
        self.assertEqual(position.z(), 4)
        self.assertEqual(position.e(), 0)

        # set extruder absolute
        position.update(Commands.parse("M82"))
        position.update_position(e=100)
        self.assertEqual(position.x(), 2)
        self.assertEqual(position.y(), 3)
        self.assertEqual(position.z(), 4)
        self.assertEqual(position.e(), 100)
        position.update_position(e=-10)
        self.assertEqual(position.x(), 2)
        self.assertEqual(position.y(), 3)
        self.assertEqual(position.z(), 4)
        self.assertEqual(position.e(), -10)

        # set extruder relative
        position.update(Commands.parse("M83"))
        position.update_position(e=20)
        self.assertEqual(position.x(), 2)
        self.assertEqual(position.y(), 3)
        self.assertEqual(position.z(), 4)
        self.assertEqual(position.e(), 10)
        position.update_position(e=-1)
        self.assertEqual(position.x(), 2)
        self.assertEqual(position.y(), 3)
        self.assertEqual(position.z(), 4)
        self.assertEqual(position.e(), 9)

        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)
예제 #25
0
    def test_HeightAndLayerChanges(self):
        """Test the height and layer changes."""
        position = Position(self.Settings, self.OctoprintPrinterProfile, False)

        # test initial state
        self.assertIsNone(position.height())
        self.assertIsNone(position.layer(), None)
        self.assertFalse(position.is_layer_change())

        # check without homed axis
        position.update(Commands.parse("G1 x0 y0 z0.20000 e1"))
        self.assertEqual(position.height(), 0)
        self.assertEqual(position.layer(), 0)
        self.assertFalse(position.is_layer_change())

        # set homed axis, absolute xyz coordinates, relative extruder coordinates and check height and layer
        position.update(Commands.parse("M83"))
        position.update(Commands.parse("G90"))
        position.update(Commands.parse("G28"))
        self.assertEqual(position.height(), 0)
        self.assertEqual(position.layer(), 0)
        self.assertFalse(position.is_layer_change())

        # move without extruding, height and layer should not change
        position.update(Commands.parse("G1 x100 y200 z150"))
        self.assertEqual(position.height(), 0)
        self.assertEqual(position.layer(), 0)
        self.assertFalse(position.is_layer_change())

        # move to origin, height and layer stuff should stay the same
        position.update(Commands.parse("G1 x0 y0 z0"))
        self.assertEqual(position.height(), 0)
        self.assertEqual(position.layer(), 0)
        self.assertFalse(position.is_layer_change())

        # extrude, height change!
        position.update(Commands.parse("G1 x0 y0 z0 e1"))
        self.assertEqual(position.height(), 0)
        self.assertEqual(position.layer(), 1)
        self.assertTrue(position.is_layer_change())

        # extrude higher, update layer., this will get rounded to 0.2
        position.update(Commands.parse("G1 x0 y0 z0.1999 e1"))
        self.assertEqual(position.height(), 0.2)
        self.assertEqual(position.layer(), 2)
        self.assertTrue(position.is_layer_change())

        # extrude just slightly higher, but with rounding on the same layer
        position.update(Commands.parse("G1 x0 y0 z0.20000 e1"))
        self.assertEqual(position.height(), .2)
        self.assertEqual(position.layer(), 2)
        self.assertFalse(position.is_layer_change())

        # extrude again on same layer - Height Previous should now be updated, and
        # is_layer_change should be false
        position.update(Commands.parse("G1 x0 y0 z0.20000 e1"))
        self.assertEqual(position.height(), .2)
        self.assertEqual(position.layer(), 2)
        self.assertFalse(position.is_layer_change())

        # extrude again on same layer - No changes
        position.update(Commands.parse("G1 x0 y0 z0.20000 e1"))
        self.assertEqual(position.height(), .2)
        self.assertEqual(position.layer(), 2)
        self.assertFalse(position.is_layer_change())

        # extrude below the current layer - No changes
        position.update(Commands.parse("G1 x0 y0 z0.00000 e1"))
        self.assertEqual(position.height(), .2)
        self.assertEqual(position.layer(), 2)
        self.assertFalse(position.is_layer_change())

        # extrude up higher and change the height/layer.  Should never happen, but
        # it's an interesting test case
        position.update(Commands.parse("G1 x0 y0 z0.60000 e1"))
        self.assertEqual(position.height(), .6)
        self.assertEqual(position.layer(), 3)
        self.assertTrue(position.is_layer_change())

        # extrude up again
        position.update(Commands.parse("G1 x0 y0 z0.65000 e1"))
        self.assertEqual(position.height(), .65)
        self.assertEqual(position.layer(), 4)
        self.assertTrue(position.is_layer_change())

        # extrude on previous layer
        position.update(Commands.parse("G1 x0 y0 z0.60000 e1"))
        self.assertEqual(position.height(), .65)
        self.assertEqual(position.layer(), 4)
        self.assertFalse(position.is_layer_change())

        # extrude on previous layer again
        position.update(Commands.parse("G1 x0 y0 z0.60000 e1"))
        self.assertEqual(position.height(), .65)
        self.assertEqual(position.layer(), 4)
        self.assertFalse(position.is_layer_change())

        # move up but do not extrude
        position.update(Commands.parse("G1 x0 y0 z0.70000"))
        self.assertEqual(position.height(), .65)
        self.assertEqual(position.layer(), 4)
        self.assertFalse(position.is_layer_change())

        # move up but do not extrude a second time
        position.update(Commands.parse("G1 x0 y0 z0.80000"))
        self.assertEqual(position.height(), .65)
        self.assertEqual(position.layer(), 4)
        self.assertFalse(position.is_layer_change())

        # extrude at a different height
        position.update(Commands.parse("G1 x0 y0 z0.80000 e.1"))
        position.update(Commands.parse("G1 x0 y0 z0.85000 e.1"))
        self.assertEqual(.85, position.height())
        self.assertEqual(6, position.layer())
        self.assertTrue(position.is_layer_change())
예제 #26
0
    def test_ExtruderMovement(self):
        """Test the M82 and M83 command."""
        position = Position(self.Settings, self.OctoprintPrinterProfile, False)
        previous_pos = Pos(self.Settings.profiles.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.profiles.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.profiles.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.profiles.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.profiles.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.profiles.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.profiles.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.profiles.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)
예제 #27
0
    def test_zHop(self):
        """Test zHop detection."""
        # set zhop distance
        self.Settings.profiles.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())
예제 #28
0
 def test_unknown_word(self):
     gcode = "K100"
     parsed = Commands.parse(gcode)
     self.assertIsNone(parsed.cmd)
     self.assertIsNone(parsed.parameters)
예제 #29
0
 def setUp(self):
     self.Commands = Commands()
     self.Comments = ""
예제 #30
0
    def test_Update(self):
        """Test the Update() function, which accepts gcode and updates the current position state and extruder state."""
        position = Position(self.Settings, self.OctoprintPrinterProfile, False)
        # no homed axis
        position.update(Commands.parse("G1 x100 y200 z300"))
        self.assertIsNone(position.x())
        self.assertIsNone(position.y())
        self.assertIsNone(position.z())

        # set relative extruder and absolute xyz, home axis and update absolute position
        position.update(Commands.parse("M83"))
        position.update(Commands.parse("G90"))
        position.update(Commands.parse("G28"))
        position.update(Commands.parse("G1 x100 y200 z150"))
        self.assertEqual(position.x(), 100)
        self.assertEqual(position.y(), 200)
        self.assertEqual(position.z(), 150)

        # move again and retest
        position.update(Commands.parse("G1 x101 y199 z151"))
        self.assertEqual(position.x(), 101)
        self.assertEqual(position.y(), 199)
        self.assertEqual(position.z(), 151)

        # switch to relative and update position
        position.update(Commands.parse("G91"))
        position.update(Commands.parse("G1 x-1 y-1 z1.0"))
        self.assertEqual(position.x(), 100)
        self.assertEqual(position.y(), 198)
        self.assertEqual(position.z(), 152)

        # move again and retest
        position.update(Commands.parse("G1 x-99 y-196 z-149.0"))
        self.assertEqual(position.x(), 1)
        self.assertEqual(position.y(), 2)
        self.assertEqual(position.z(), 3)

        # go back to absolute and move to origin
        position.update(Commands.parse("G90"))
        position.update(Commands.parse("G1 x0 y0 z0.0"))
        self.assertEqual(position.x(), 0)
        self.assertEqual(position.y(), 0)
        self.assertEqual(position.z(), 0)