From 4b6df74c2efa3ef491d0a456c705594ee667faa7 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 19 Jan 2019 02:31:47 +0200 Subject: [PATCH 1/6] - added initial implementation of HPGL postprocessor --- FlatCAMObj.py | 52 ++++++++++++++++++++++++---------- README.md | 4 +++ postprocessors/hpgl.py | 63 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 postprocessors/hpgl.py diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 10b3ac7b..d0a0a520 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -3819,11 +3819,16 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): log.debug("FlatCAMCNCJob.gcode_header()") time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) marlin = False + hpgl = False + try: for key in self.cnc_tools: if self.cnc_tools[key]['data']['ppname_g'] == 'marlin': marlin = True break + if self.cnc_tools[key]['data']['ppname_g'] == 'hpgl': + hpgl = True + break except Exception as e: log.debug("FlatCAMCNCJob.gcode_header() error: --> %s" % str(e)) try: @@ -3834,7 +3839,31 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): except: pass - if marlin is False: + if marlin is True: + gcode = ';Marlin G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s\n' % \ + (str(self.app.version), str(self.app.version_date)) + '\n' + + gcode += ';Name: ' + str(self.options['name']) + '\n' + gcode += ';Type: ' + "G-code from " + str(self.options['type']) + '\n' + + # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': + # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n' + + gcode += ';Units: ' + self.units.upper() + '\n' + "\n" + gcode += ';Created on ' + time_str + '\n' + '\n' + elif hpgl is True: + gcode = 'CO "HPGL CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s' % \ + (str(self.app.version), str(self.app.version_date)) + '";\n' + + gcode += 'CO "Name: ' + str(self.options['name']) + '";\n' + gcode += 'CO "Type: ' + "G-code from " + str(self.options['type']) + '";\n' + + # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': + # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n' + + gcode += 'CO "Units: ' + self.units.upper() + '";\n' + gcode += 'CO "Created on ' + time_str + '";\n' + else: gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \ (str(self.app.version), str(self.app.version_date)) + '\n' @@ -3847,24 +3876,12 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): gcode += '(Units: ' + self.units.upper() + ')\n' + "\n" gcode += '(Created on ' + time_str + ')\n' + '\n' - else: - gcode = ';G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s\n' % \ - (str(self.app.version), str(self.app.version_date)) + '\n' - - gcode += ';Name: ' + str(self.options['name']) + '\n' - gcode += ';Type: ' + "G-code from " + str(self.options['type']) + '\n' - - # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': - # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n' - - gcode += ';Units: ' + self.units.upper() + '\n' + "\n" - gcode += ';Created on ' + time_str + '\n' + '\n' - return gcode def export_gcode(self, filename=None, preamble='', postamble='', to_file=False): gcode = '' roland = False + hpgl = False # detect if using Roland postprocessor try: @@ -3872,6 +3889,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): if self.cnc_tools[key]['data']['ppname_g'] == 'Roland_MDX_20': roland = True break + if self.cnc_tools[key]['data']['ppname_g'] == 'hpgl': + hpgl = True + break except: try: for key in self.cnc_tools: @@ -3882,7 +3902,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): pass # do not add gcode_header when using the Roland postprocessor, add it for every other postprocessor - if roland is False: + if roland is False and hpgl is False: gcode = self.gcode_header() # detect if using multi-tool and make the Gcode summation correctly for each case @@ -3897,6 +3917,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): if roland is True: g = preamble + gcode + postamble + elif hpgl is True: + g = self.gcode_header() + preamble + gcode + postamble else: # fix so the preamble gets inserted in between the comments header and the actual start of GCODE g_idx = gcode.rfind('G20') diff --git a/README.md b/README.md index 2fb06a49..507ee573 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing. ================================================= +19.01.2019 + +- added initial implementation of HPGL postprocessor + 11.01.2019 - added a status message for font parsing diff --git a/postprocessors/hpgl.py b/postprocessors/hpgl.py new file mode 100644 index 00000000..aed67b51 --- /dev/null +++ b/postprocessors/hpgl.py @@ -0,0 +1,63 @@ +from FlatCAMPostProc import * + + +# for Roland Postprocessors it is mandatory for the postprocessor name (python file and class name, both of them must be +# the same) to contain the following keyword, case-sensitive: 'Roland' without the quotes. +class hpgl(FlatCAMPostProc): + + coordinate_format = "%.*f" + feedrate_format = '%.1f' + feedrate_rapid_format = '%.1f' + + def start_code(self, p): + gcode = 'IN;' + return gcode + + def startz_code(self, p): + return 'SP%d' % int(p.tool) + + def lift_code(self, p): + gcode = 'PU;' + '\n' + return gcode + + def down_code(self, p): + gcode = 'PD;' + '\n' + return gcode + + def toolchange_code(self, p): + return '' + + def up_to_zero_code(self, p): + return '' + + def position_code(self, p): + return ('PA' + self.coordinate_format + ',' + self.coordinate_format) % \ + (p.coords_decimals, p.x, p.coords_decimals, p.y) + + def rapid_code(self, p): + return self.position_code(p).format(**p) + + def linear_code(self, p): + return self.position_code(p).format(**p) + + def end_code(self, p): + gcode = self.position_code(p).format(**p) + return gcode + + def feedrate_code(self, p): + return '' + + def feedrate_z_code(self, p): + return '' + + def feedrate_rapid_code(self, p): + return '' + + def spindle_code(self, p): + return '' + + def dwell_code(self, p): + return '' + + def spindle_stop_code(self,p): + return '' From 43f905540848f663478d672ce2b52f24f7c8a6f7 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 19 Jan 2019 02:33:05 +0200 Subject: [PATCH 2/6] - added initial implementation of HPGL postprocessor --- FlatCAMApp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index e8278c4f..f6b27be4 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -87,8 +87,8 @@ class App(QtCore.QObject): log.addHandler(handler) # Version - version = 8.901 - version_date = "2019/01/09" + version = 8.902 + version_date = "2019/01/19" beta = True # URL for update checks and statistics From 51b96af1908c1322186ddcb4b2f285dc8a345e6f Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 19 Jan 2019 02:38:44 +0200 Subject: [PATCH 3/6] - corrected issues in the hpgl postprocessor file --- postprocessors/hpgl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/postprocessors/hpgl.py b/postprocessors/hpgl.py index aed67b51..1834be7a 100644 --- a/postprocessors/hpgl.py +++ b/postprocessors/hpgl.py @@ -14,7 +14,7 @@ class hpgl(FlatCAMPostProc): return gcode def startz_code(self, p): - return 'SP%d' % int(p.tool) + return '' def lift_code(self, p): gcode = 'PU;' + '\n' @@ -25,13 +25,13 @@ class hpgl(FlatCAMPostProc): return gcode def toolchange_code(self, p): - return '' + return 'SP%d;' % int(p.tool) def up_to_zero_code(self, p): return '' def position_code(self, p): - return ('PA' + self.coordinate_format + ',' + self.coordinate_format) % \ + return ('PA' + self.coordinate_format + ',' + self.coordinate_format + ';') % \ (p.coords_decimals, p.x, p.coords_decimals, p.y) def rapid_code(self, p): From b9cbe97f4d95fa13f4d9e2ef01a246b0ce4dd41a Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 19 Jan 2019 03:02:47 +0200 Subject: [PATCH 4/6] - fixed display HPGL code geometry on canvas - added build folder to gitignore list --- .gitignore | 3 ++- README.md | 1 + camlib.py | 9 +++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7d46f40a..ce33b515 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc .idea/ -tests/tmp/ \ No newline at end of file +tests/tmp/ +build/ \ No newline at end of file diff --git a/README.md b/README.md index 507ee573..44062523 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ CAD program, and create G-Code for Isolation routing. 19.01.2019 - added initial implementation of HPGL postprocessor +- fixed display HPGL code geometry on canvas 11.01.2019 diff --git a/camlib.py b/camlib.py index abcfc195..f9412835 100644 --- a/camlib.py +++ b/camlib.py @@ -5212,6 +5212,13 @@ class CNCjob(Geometry): command['Y'] = float(match_z.group(2).replace(" ", "")) * 0.025 command['Z'] = float(match_z.group(3).replace(" ", "")) * 0.025 + elif 'hpgl' in self.pp_excellon_name or 'hpgl' in self.pp_geometry_name: + match_pa = re.search(r"^PA(\s*-?\d+\.\d+?),(\s*\s*-?\d+\.\d+?)*;$", gline) + if match_pa: + command['G'] = 0 + command['X'] = float(match_pa.group(1).replace(" ", "")) + command['Y'] = float(match_pa.group(2).replace(" ", "")) + else: match = re.search(r'^\s*([A-Z])\s*([\+\-\.\d\s]+)', gline) while match: @@ -5260,6 +5267,8 @@ class CNCjob(Geometry): if 'Z' in gobj: if 'Roland' in self.pp_excellon_name or 'Roland' in self.pp_geometry_name: pass + elif 'hpgl' in self.pp_excellon_name or 'hpgl' in self.pp_geometry_name: + pass elif ('X' in gobj or 'Y' in gobj) and gobj['Z'] != current['Z']: log.warning("Non-orthogonal motion: From %s" % str(current)) log.warning(" To: %s" % str(gobj)) From 7ea6ee4a85601697c2e46665565b6c4d25a87fbc Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 20 Jan 2019 02:46:42 +0200 Subject: [PATCH 5/6] - fixed the HPGL code geometry rendering when travel - fixed the message box layout when asking to save the current work - made sure that whenever the HPGL postprocessor is selected the Toolchange is always ON and the MultiDepth is OFF - the HPGL postprocessor entry is not allowed in Excellon Object postprocessor selection combobox as it is only applicable for Geometry --- FlatCAMApp.py | 20 ++++++++++---------- FlatCAMObj.py | 31 +++++++++++++++++++++++++++++-- ObjectUI.py | 4 ++-- README.md | 7 +++++++ camlib.py | 8 ++++++++ 5 files changed, 56 insertions(+), 14 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index f6b27be4..8b714c60 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -88,7 +88,7 @@ class App(QtCore.QObject): # Version version = 8.902 - version_date = "2019/01/19" + version_date = "2019/01/20" beta = True # URL for update checks and statistics @@ -2093,9 +2093,9 @@ class App(QtCore.QObject): if self.collection.get_list(): msgbox = QtWidgets.QMessageBox() # msgbox.setText("Save changes ...") - msgbox.setInformativeText("There are files/objects opened in FlatCAM. " - "\n\n" - "Do you want to Save the project?") + msgbox.setText("There are files/objects opened in FlatCAM. " + "\n" + "Do you want to Save the project?") msgbox.setWindowTitle("Save changes") msgbox.setWindowIcon(QtGui.QIcon('share/save_as.png')) msgbox.setStandardButtons(QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok) @@ -2229,9 +2229,9 @@ class App(QtCore.QObject): if self.collection.get_list(): msgbox = QtWidgets.QMessageBox() # msgbox.setText("Save changes ...") - msgbox.setInformativeText("There are files/objects opened in FlatCAM. " - "\n\n" - "Do you want to Save the project?") + msgbox.setText("There are files/objects opened in FlatCAM. " + "\n" + "Do you want to Save the project?") msgbox.setWindowTitle("Save changes") msgbox.setWindowIcon(QtGui.QIcon('share/save_as.png')) msgbox.setStandardButtons(QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok) @@ -3962,9 +3962,9 @@ class App(QtCore.QObject): if self.collection.get_list(): msgbox = QtWidgets.QMessageBox() # msgbox.setText("Save changes ...") - msgbox.setInformativeText("There are files/objects opened in FlatCAM. " - "Creating a New project will delete them.\n\n" - "Do you want to Save the project?") + msgbox.setText("There are files/objects opened in FlatCAM.\n" + "Creating a New project will delete them.\n" + "Do you want to Save the project?") msgbox.setWindowTitle("Save changes") msgbox.setWindowIcon(QtGui.QIcon('share/save_as.png')) msgbox.setStandardButtons(QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index d0a0a520..dabaff6c 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -1134,6 +1134,9 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): }) for name in list(self.app.postprocessors.keys()): + # the HPGL postprocessor is only for Geometry not for Excellon job therefore don't add it + if name == 'hpgl': + continue self.ui.pp_excellon_name_cb.addItem(name) # Fill form fields @@ -2104,8 +2107,13 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): self.ui.tool_offset_entry.hide() self.ui.tool_offset_lbl.hide() - assert isinstance(self.ui, GeometryObjectUI), \ - "Expected a GeometryObjectUI, got %s" % type(self.ui) + # used to store the state of the mpass_cb if the selected postproc for geometry is hpgl + self.old_pp_state = self.default_data['multidepth'] + self.old_toolchangeg_state = self.default_data['toolchange'] + + if not isinstance(self.ui, GeometryObjectUI): + log.debug("Expected a GeometryObjectUI, got %s" % type(self.ui)) + return self.ui.geo_tools_table.setupContextMenu() self.ui.geo_tools_table.addContextMenu( @@ -2116,6 +2124,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click) self.ui.paint_tool_button.clicked.connect(self.app.paint_tool.run) + self.ui.pp_geometry_name_cb.activated.connect(self.on_pp_changed) def set_tool_offset_visibility(self, current_row): if current_row is None: @@ -2793,6 +2802,24 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): item[0] = str(item[0]) return table_tools_items + def on_pp_changed(self): + current_pp = self.ui.pp_geometry_name_cb.get_value() + if current_pp == 'hpgl': + self.old_pp_state = self.ui.mpass_cb.get_value() + self.old_toolchangeg_state = self.ui.toolchangeg_cb.get_value() + + self.ui.mpass_cb.set_value(False) + self.ui.mpass_cb.setDisabled(True) + + self.ui.toolchangeg_cb.set_value(True) + self.ui.toolchangeg_cb.setDisabled(True) + else: + self.ui.mpass_cb.set_value(self.old_pp_state) + self.ui.mpass_cb.setDisabled(False) + + self.ui.toolchangeg_cb.set_value(self.old_toolchangeg_state) + self.ui.toolchangeg_cb.setDisabled(False) + def on_generatecnc_button_click(self, *args): self.app.report_usage("geometry_on_generatecnc_button") diff --git a/ObjectUI.py b/ObjectUI.py index c06f2402..6adc8426 100644 --- a/ObjectUI.py +++ b/ObjectUI.py @@ -882,7 +882,7 @@ class GeometryObjectUI(ObjectUI): self.toolchangeg_cb = FCCheckBox("Tool change") self.toolchangeg_cb.setToolTip( "Include tool-change sequence\n" - "in G-Code (Pause for tool change)." + "in the Machine Code (Pause for tool change)." ) self.toolchangez_entry = LengthEntry() @@ -982,7 +982,7 @@ class GeometryObjectUI(ObjectUI): pp_label = QtWidgets.QLabel("PostProcessor:") pp_label.setToolTip( "The Postprocessor file that dictates\n" - "Gcode output." + "the Machine Code (like GCode, RML, HPGL) output." ) self.grid3.addWidget(pp_label, 16, 0) self.pp_geometry_name_cb = FCComboBox() diff --git a/README.md b/README.md index 44062523..4b34b86a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,13 @@ CAD program, and create G-Code for Isolation routing. ================================================= +20.01.2019 + +- fixed the HPGL code geometry rendering when travel +- fixed the message box layout when asking to save the current work +- made sure that whenever the HPGL postprocessor is selected the Toolchange is always ON and the MultiDepth is OFF +- the HPGL postprocessor entry is not allowed in Excellon Object postprocessor selection combobox as it is only applicable for Geometry + 19.01.2019 - added initial implementation of HPGL postprocessor diff --git a/camlib.py b/camlib.py index f9412835..0c469bcd 100644 --- a/camlib.py +++ b/camlib.py @@ -5218,6 +5218,14 @@ class CNCjob(Geometry): command['G'] = 0 command['X'] = float(match_pa.group(1).replace(" ", "")) command['Y'] = float(match_pa.group(2).replace(" ", "")) + match_pen = re.search(r"^(P[U|D])", gline) + if match_pen: + if match_pen.group(1) == 'PU': + # the value does not matter, only that it is positive so the gcode_parse() know it is > 0, + # therefore the move is of kind T (travel) + command['Z'] = 1 + else: + command['Z'] = 0 else: match = re.search(r'^\s*([A-Z])\s*([\+\-\.\d\s]+)', gline) From b9a062a84ec30ccb97236e50d1ffc9c155bcd4e2 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 20 Jan 2019 04:11:34 +0200 Subject: [PATCH 6/6] - when saving HPGL code it will be saved as a file with extension .plt - the units mentioned in HPGL format are only METRIC therefore if FlatCAM units are in INCH they will be transform to METRIC - the minimum unit in HPGL is 0.025mm therefore the coordinates are rounded to a multiple of 0.025mm --- FlatCAMObj.py | 11 +++++++---- README.md | 3 +++ postprocessors/hpgl.py | 19 ++++++++++++++++--- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index dabaff6c..a3c04d86 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -3798,21 +3798,24 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): if 'Roland' in self.pp_excellon_name or 'Roland' in self.pp_geometry_name: _filter_ = "RML1 Files (*.rol);;" \ "All Files (*.*)" + elif 'hpgl' in self.pp_geometry_name: + _filter_ = "HPGL Files (*.plt);;" \ + "All Files (*.*)" else: _filter_ = "G-Code Files (*.nc);;G-Code Files (*.txt);;G-Code Files (*.tap);;G-Code Files (*.cnc);;" \ "G-Code Files (*.g-code);;All Files (*.*)" try: filename = str(QtWidgets.QFileDialog.getSaveFileName( - caption="Export G-Code ...", directory=self.app.get_last_save_folder(), filter=_filter_)[0]) + caption="Export Machine Code ...", directory=self.app.get_last_save_folder(), filter=_filter_)[0]) except TypeError: - filename = str(QtWidgets.QFileDialog.getSaveFileName(caption="Export G-Code ...", filter=_filter_)[0]) + filename = str(QtWidgets.QFileDialog.getSaveFileName(caption="Export Machine Code ...", filter=_filter_)[0]) preamble = str(self.ui.prepend_text.get_value()) postamble = str(self.ui.append_text.get_value()) self.export_gcode(filename, preamble=preamble, postamble=postamble) self.app.file_saved.emit("gcode", filename) - self.app.inform.emit("[success] G-Code file saved to: %s" % filename) + self.app.inform.emit("[success] Machine Code file saved to: %s" % filename) def on_modifygcode_button_click(self, *args): # add the tab if it was closed @@ -3883,7 +3886,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): (str(self.app.version), str(self.app.version_date)) + '";\n' gcode += 'CO "Name: ' + str(self.options['name']) + '";\n' - gcode += 'CO "Type: ' + "G-code from " + str(self.options['type']) + '";\n' + gcode += 'CO "Type: ' + "HPGL code from " + str(self.options['type']) + '";\n' # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n' diff --git a/README.md b/README.md index 4b34b86a..89b51ba4 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ CAD program, and create G-Code for Isolation routing. - fixed the message box layout when asking to save the current work - made sure that whenever the HPGL postprocessor is selected the Toolchange is always ON and the MultiDepth is OFF - the HPGL postprocessor entry is not allowed in Excellon Object postprocessor selection combobox as it is only applicable for Geometry +- when saving HPGL code it will be saved as a file with extension .plt +- the units mentioned in HPGL format are only METRIC therefore if FlatCAM units are in INCH they will be transform to METRIC +- the minimum unit in HPGL is 0.025mm therefore the coordinates are rounded to a multiple of 0.025mm 19.01.2019 diff --git a/postprocessors/hpgl.py b/postprocessors/hpgl.py index 1834be7a..93086f98 100644 --- a/postprocessors/hpgl.py +++ b/postprocessors/hpgl.py @@ -6,8 +6,6 @@ from FlatCAMPostProc import * class hpgl(FlatCAMPostProc): coordinate_format = "%.*f" - feedrate_format = '%.1f' - feedrate_rapid_format = '%.1f' def start_code(self, p): gcode = 'IN;' @@ -31,8 +29,23 @@ class hpgl(FlatCAMPostProc): return '' def position_code(self, p): + units = str(p['units']).lower() + + # we work only with METRIC units because HPGL mention only metric units so if FlatCAM units are INCH we + # transform them in METRIC + if units == 'in': + x = p.x * 25.4 + y = p.y * 25.4 + else: + x = p.x + y = p.y + + # we need to have the coordinates as multiples of 0.025mm + x = round(x / 0.025) * 25 / 1000 + y = round(y / 0.025) * 25 / 1000 + return ('PA' + self.coordinate_format + ',' + self.coordinate_format + ';') % \ - (p.coords_decimals, p.x, p.coords_decimals, p.y) + (p.coords_decimals, x, p.coords_decimals, y) def rapid_code(self, p): return self.position_code(p).format(**p)