From 260b77c44a14bfc9a8c42c5696c2e7e2dddf74c4 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 31 Mar 2024 18:43:44 +0300 Subject: [PATCH] - added the ability to use no path optimization - camblib.generate_from_geometry_2(): added the usage for different kinds of path optimizations set in the Preferences -> Geometry - some minor objects name refactoring --- CHANGELOG.md | 6 + appEditors/appGeoEditor.py | 2 + .../geometry/GeometryGenPrefGroupUI.py | 3 +- appObjects/GeometryObject.py | 105 ++++---- appPlugins/ToolMilling.py | 203 ++++++++-------- camlib.py | 228 ++++++++++-------- 6 files changed, 298 insertions(+), 249 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 077fbbc5..e3827750 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ CHANGELOG for FlatCAM Evo beta ================================================= +31.03.2024 + +- added the ability to use no path optimization +- camblib.generate_from_geometry_2(): added the usage for different kinds of path optimizations set in the Preferences -> Geometry +- some minor objects name refactoring + 29.03.2024 - continue refactoring methods from the appMain diff --git a/appEditors/appGeoEditor.py b/appEditors/appGeoEditor.py index 628a0c32..b3624ffe 100644 --- a/appEditors/appGeoEditor.py +++ b/appEditors/appGeoEditor.py @@ -718,6 +718,8 @@ class AppGeoEditor(QtCore.QObject): self.app.ui.popmenu_save.setVisible(False) self.disconnect_canvas_event_handlers() + self.storage = self.make_storage() + self.clear() self.app.ui.geo_edit_toolbar.setDisabled(True) diff --git a/appGUI/preferences/geometry/GeometryGenPrefGroupUI.py b/appGUI/preferences/geometry/GeometryGenPrefGroupUI.py index f5cd6a54..16c3f307 100644 --- a/appGUI/preferences/geometry/GeometryGenPrefGroupUI.py +++ b/appGUI/preferences/geometry/GeometryGenPrefGroupUI.py @@ -96,7 +96,8 @@ class GeometryGenPrefGroupUI(OptionsGroupUI): {'label': _('Rtree'), 'value': 'R'}, {'label': _('MetaHeuristic'), 'value': 'M'}, {'label': _('Basic'), 'value': 'B'}, - {'label': _('TSA'), 'value': 'T'} + {'label': _('TSA'), 'value': 'T'}, + {'label': _('None'), 'value': 'N'} ], orientation='vertical', compact=True) opt_grid.addWidget(self.opt_algorithm_label, 0, 0) diff --git a/appObjects/GeometryObject.py b/appObjects/GeometryObject.py index 38d18d31..a72a7ef0 100644 --- a/appObjects/GeometryObject.py +++ b/appObjects/GeometryObject.py @@ -94,7 +94,7 @@ class GeometryObject(FlatCAMObj, Geometry): }) if "tools_mill_tooldia" not in self.obj_options: - if type(self.app.options["tools_mill_tooldia"]) == float: + if isinstance(self.app.options["tools_mill_tooldia"], float): self.obj_options["tools_mill_tooldia"] = self.app.options["tools_mill_tooldia"] else: try: @@ -328,7 +328,7 @@ class GeometryObject(FlatCAMObj, Geometry): # fill in self.default_data values from self.obj_options self.default_data.update(self.obj_options) - if type(self.obj_options["tools_mill_tooldia"]) == float: + if isinstance(self.obj_options["tools_mill_tooldia"], float): tools_list = [self.obj_options["tools_mill_tooldia"]] else: try: @@ -357,13 +357,13 @@ class GeometryObject(FlatCAMObj, Geometry): else: # if self.tools is not empty then it can safely be assumed that it comes from an opened project. # Because of the serialization the self.tools list on project save, the dict keys (members of self.tools - # are each a dict) are turned into strings so we rebuild the self.tools elements so the keys are - # again float type; dict's don't like having keys changed when iterated through therefore the need for the + # are each a dict) are turned into strings, so we rebuild the self.tools elements so the keys are + # again float type; dicts don't like having keys changed when iterated through therefore the need for the # following convoluted way of changing the keys from string to float type temp_tools = {} - for tooluid_key in self.tools: - val = deepcopy(self.tools[int(tooluid_key)]) - new_key = deepcopy(int(tooluid_key)) + for tool_uid_key in self.tools: + val = deepcopy(self.tools[int(tool_uid_key)]) + new_key = deepcopy(int(tool_uid_key)) temp_tools[new_key] = val self.tools.clear() @@ -426,7 +426,7 @@ class GeometryObject(FlatCAMObj, Geometry): sel_model = self.ui.geo_tools_table.selectionModel() sel_indexes = sel_model.selectedIndexes() - # it will iterate over all indexes which means all items in all columns too but I'm interested only on rows + # it will iterate over all indexes which means all items in all columns too, but I'm interested only on rows sel_rows = set() for idx in sel_indexes: sel_rows.add(idx.row()) @@ -618,7 +618,7 @@ class GeometryObject(FlatCAMObj, Geometry): w_geo = multigeo_solid.geoms \ if isinstance(multigeo_solid, (MultiPolygon, MultiLineString)) else multigeo_solid for geo in w_geo: - if type(geo) == list: + if isinstance(geo, list): for g in geo: g2dxf(msp, g) else: @@ -700,11 +700,11 @@ class GeometryObject(FlatCAMObj, Geometry): job_obj.feedrate_probe = float(self.app.options["tools_mill_feedrate_probe"]) total_gcode = '' - for tooluid_key in list(tools_dict.keys()): + for tool_uid_key in list(tools_dict.keys()): tool_cnt += 1 - dia_cnc_dict = deepcopy(tools_dict[tooluid_key]) - tooldia_val = app_obj.dec_format(float(tools_dict[tooluid_key]['tooldia']), self.decimals) + dia_cnc_dict = deepcopy(tools_dict[tool_uid_key]) + tooldia_val = app_obj.dec_format(float(tools_dict[tool_uid_key]['tooldia']), self.decimals) dia_cnc_dict.update({ 'tooldia': tooldia_val }) @@ -736,25 +736,25 @@ class GeometryObject(FlatCAMObj, Geometry): dia_cnc_dict['data']['tools_mill_offset_type'] = tool_offset - z_cut = tools_dict[tooluid_key]['data']["tools_mill_cutz"] - z_move = tools_dict[tooluid_key]['data']["tools_mill_travelz"] - feedrate = tools_dict[tooluid_key]['data']["tools_mill_feedrate"] - feedrate_z = tools_dict[tooluid_key]['data']["tools_mill_feedrate_z"] - feedrate_rapid = tools_dict[tooluid_key]['data']["tools_mill_feedrate_rapid"] - multidepth = tools_dict[tooluid_key]['data']["tools_mill_multidepth"] - extracut = tools_dict[tooluid_key]['data']["tools_mill_extracut"] - extracut_length = tools_dict[tooluid_key]['data']["tools_mill_extracut_length"] - depthpercut = tools_dict[tooluid_key]['data']["tools_mill_depthperpass"] - toolchange = tools_dict[tooluid_key]['data']["tools_mill_toolchange"] - toolchangez = tools_dict[tooluid_key]['data']["tools_mill_toolchangez"] - toolchangexy = tools_dict[tooluid_key]['data']["tools_mill_toolchangexy"] - startz = tools_dict[tooluid_key]['data']["tools_mill_startz"] - endz = tools_dict[tooluid_key]['data']["tools_mill_endz"] + z_cut = tools_dict[tool_uid_key]['data']["tools_mill_cutz"] + z_move = tools_dict[tool_uid_key]['data']["tools_mill_travelz"] + feedrate = tools_dict[tool_uid_key]['data']["tools_mill_feedrate"] + feedrate_z = tools_dict[tool_uid_key]['data']["tools_mill_feedrate_z"] + feedrate_rapid = tools_dict[tool_uid_key]['data']["tools_mill_feedrate_rapid"] + multidepth = tools_dict[tool_uid_key]['data']["tools_mill_multidepth"] + extracut = tools_dict[tool_uid_key]['data']["tools_mill_extracut"] + extracut_length = tools_dict[tool_uid_key]['data']["tools_mill_extracut_length"] + depthpercut = tools_dict[tool_uid_key]['data']["tools_mill_depthperpass"] + toolchange = tools_dict[tool_uid_key]['data']["tools_mill_toolchange"] + toolchangez = tools_dict[tool_uid_key]['data']["tools_mill_toolchangez"] + toolchangexy = tools_dict[tool_uid_key]['data']["tools_mill_toolchangexy"] + startz = tools_dict[tool_uid_key]['data']["tools_mill_startz"] + endz = tools_dict[tool_uid_key]['data']["tools_mill_endz"] endxy = self.obj_options["tools_mill_endxy"] - spindlespeed = tools_dict[tooluid_key]['data']["tools_mill_spindlespeed"] - dwell = tools_dict[tooluid_key]['data']["tools_mill_dwell"] - dwelltime = tools_dict[tooluid_key]['data']["tools_mill_dwelltime"] - pp_geometry_name = tools_dict[tooluid_key]['data']["tools_mill_ppname_g"] + spindlespeed = tools_dict[tool_uid_key]['data']["tools_mill_spindlespeed"] + dwell = tools_dict[tool_uid_key]['data']["tools_mill_dwell"] + dwelltime = tools_dict[tool_uid_key]['data']["tools_mill_dwelltime"] + pp_geometry_name = tools_dict[tool_uid_key]['data']["tools_mill_ppname_g"] spindledir = self.app.options['tools_mill_spindledir'] tool_solid_geometry = self.solid_geometry @@ -768,16 +768,16 @@ class GeometryObject(FlatCAMObj, Geometry): job_obj.obj_options['tool_dia'] = tooldia_val tool_lst = list(tools_dict.keys()) - is_first = True if tooluid_key == tool_lst[0] else False + is_first = True if tool_uid_key == tool_lst[0] else False - # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially + # it seems that the tolerance needs to be a lot lower value than 0.01, and it was hardcoded initially # to a value of 0.0005 which is 20 times less than 0.01 tol = float(self.app.options['global_tolerance']) / 20 res, start_gcode = job_obj.generate_from_geometry_2( self, tooldia=tooldia_val, offset=tool_offset, tolerance=tol, z_cut=z_cut, z_move=z_move, feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, - spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime, + spindlespeed=spindlespeed, spindle_dir=spindledir, dwell=dwell, dwelltime=dwelltime, multidepth=multidepth, depthpercut=depthpercut, extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy, toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy, @@ -795,7 +795,7 @@ class GeometryObject(FlatCAMObj, Geometry): total_gcode += res self.app.inform.emit('[success] %s' % _("G-Code parsing in progress...")) - dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse(tool_data=tools_dict[tooluid_key]['data']) + dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse(tool_data=tools_dict[tool_uid_key]['data']) app_obj.inform.emit('[success] %s' % _("G-Code parsing finished...")) # commented this; there is no need for the actual GCode geometry - the original one will serve as well @@ -808,7 +808,7 @@ class GeometryObject(FlatCAMObj, Geometry): app_obj.inform.emit('[ERROR] %s: %s' % (_("G-Code processing failed with error"), str(er))) job_obj.tools.update({ - tooluid_key: deepcopy(dia_cnc_dict) + tool_uid_key: deepcopy(dia_cnc_dict) }) dia_cnc_dict.clear() @@ -860,8 +860,8 @@ class GeometryObject(FlatCAMObj, Geometry): 'tooldia': tooldia_val }) if "optimization_type" not in tools_dict[tooluid_key]['data']: - tools_dict[tooluid_key]['data']["tools_mill_optimization_type"] = \ - self.app.options["tools_mill_optimization_type"] + optimization_type = self.app.options["tools_mill_optimization_type"] + tools_dict[tooluid_key]['data']["tools_mill_optimization_type"] = optimization_type # find the tool_dia associated with the tooluid_key # search in the self.tools for the sel_tool_dia and when found see what tooluid has @@ -921,7 +921,7 @@ class GeometryObject(FlatCAMObj, Geometry): job_obj.obj_options['type'] = 'Geometry' job_obj.obj_options['tool_dia'] = tooldia_val - # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially + # it seems that the tolerance needs to be a lot lower value than 0.01, and it was hardcoded initially # to a value of 0.0005 which is 20 times less than 0.01 tol = float(self.app.options['global_tolerance']) / 20 @@ -1110,7 +1110,7 @@ class GeometryObject(FlatCAMObj, Geometry): job_obj.obj_options['xmax'] = self.obj_options['xmax'] job_obj.obj_options['ymax'] = self.obj_options['ymax'] - # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially + # it seems that the tolerance needs to be a lot lower value than 0.01, and it was hardcoded initially # to a value of 0.0005 which is 20 times less than 0.01 tol = float(self.app.options['global_tolerance']) / 20 res, start_gcode = job_obj.generate_from_geometry_2( @@ -1152,7 +1152,6 @@ class GeometryObject(FlatCAMObj, Geometry): :type yfactor: float :param point: Point around which to scale :return: None - :rtype: None """ self.app.log.debug("FlatCAMObj.GeometryObject.scale()") @@ -1240,7 +1239,6 @@ class GeometryObject(FlatCAMObj, Geometry): :param vect: (x, y) vector by which to offset the object's geometry. :type vect: tuple :return: None - :rtype: None """ self.app.log.debug("FlatCAMObj.GeometryObject.offset()") @@ -1435,7 +1433,7 @@ class GeometryObject(FlatCAMObj, Geometry): Plot the object. :param visible: Controls if the added shape is visible of not - :param kind: added so there is no error when a project is loaded and it has both geometry and CNCJob, + :param kind: added so there is no error when a project is loaded, and it has both geometry and CNCJob, because CNCJob require the 'kind' parameter. Perhaps the FlatCAMObj.plot() has to be rewritten :param plot_tool: plot a specific tool for multigeo objects @@ -1492,7 +1490,7 @@ class GeometryObject(FlatCAMObj, Geometry): self.plot_element(solid_geometry, visible=visible, color=color) else: - # plot solid geometry that may be an direct attribute of the geometry object + # plot solid geometry that may be a direct attribute of the geometry object # for SingleGeo if self.solid_geometry: solid_geometry = self.solid_geometry @@ -1552,8 +1550,8 @@ class GeometryObject(FlatCAMObj, Geometry): self.plot_element(element=solid_geometry, visible=True) self.shapes.redraw() - # make sure that the general plot is disabled if one of the row plot's are disabled and - # if all the row plot's are enabled also enable the general plot checkbox + # make sure that the general plot is disabled if one of the row plots are disabled and + # if all the row plots are enabled also enable the general plot checkbox cb_cnt = 0 total_row = self.ui.geo_tools_table.rowCount() for row in range(total_row): @@ -1662,6 +1660,7 @@ class GeometryObject(FlatCAMObj, Geometry): for ty, type_list in same_type.items(): for t_ty, tool_type_list in same_tool_type.items(): intersection = reduce(np.intersect1d, (dia_list, type_list, tool_type_list)).tolist() + # intersection = list(set(dia_list) & set(type_list) & set(tool_type_list)) if intersection: intersect_list.append(intersection) @@ -1702,22 +1701,22 @@ class GeometryObject(FlatCAMObj, Geometry): # Iterable: descend into each item. try: - for subo in o: - pts += GeometryObject.get_pts(subo) + for sub_o in o: + pts += GeometryObject.get_pts(sub_o) # Non-iterable except TypeError: if o is not None: - if type(o) == MultiPolygon: - for poly in o: + if isinstance(o, MultiPolygon): + for poly in o.geoms: pts += GeometryObject.get_pts(poly) - # ## Descend into .exerior and .interiors - elif type(o) == Polygon: + # ## Descend into .exterior and .interiors + elif isinstance(o, Polygon): pts += GeometryObject.get_pts(o.exterior) for i in o.interiors: pts += GeometryObject.get_pts(i) - elif type(o) == MultiLineString: - for line in o: + elif isinstance(o, MultiLineString): + for line in o.geoms: pts += GeometryObject.get_pts(line) # ## Has .coords: list them. else: diff --git a/appPlugins/ToolMilling.py b/appPlugins/ToolMilling.py index e6de180e..a6888a64 100644 --- a/appPlugins/ToolMilling.py +++ b/appPlugins/ToolMilling.py @@ -1500,31 +1500,31 @@ class ToolMilling(AppTool, Excellon): # Tool Parameters for opt in self.form_fields: - current_widget = self.form_fields[opt] + current_widget: QtWidgets.QWidget = self.form_fields[opt] if isinstance(current_widget, FCCheckBox): - current_widget.stateChanged.connect(self.form_to_storage) + current_widget.stateChanged.connect(self.form_to_storage) # noqa if isinstance(current_widget, RadioSet): current_widget.activated_custom.connect(self.form_to_storage) elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner): current_widget.returnPressed.connect(self.form_to_storage) elif isinstance(current_widget, FCComboBox): - current_widget.currentIndexChanged.connect(self.form_to_storage) + current_widget.currentIndexChanged.connect(self.form_to_storage) # noqa elif isinstance(current_widget, FCComboBox2): - current_widget.currentIndexChanged.connect(self.form_to_storage) + current_widget.currentIndexChanged.connect(self.form_to_storage) # noqa # General Parameters for opt in self.general_form_fields: current_widget = self.general_form_fields[opt] if isinstance(current_widget, FCCheckBox): - current_widget.stateChanged.connect(self.form_to_storage) + current_widget.stateChanged.connect(self.form_to_storage) # noqa if isinstance(current_widget, RadioSet): current_widget.activated_custom.connect(self.form_to_storage) elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner): current_widget.returnPressed.connect(self.form_to_storage) elif isinstance(current_widget, FCComboBox): - current_widget.currentIndexChanged.connect(self.form_to_storage) + current_widget.currentIndexChanged.connect(self.form_to_storage) # noqa elif isinstance(current_widget, FCComboBox2): - current_widget.currentIndexChanged.connect(self.form_to_storage) + current_widget.currentIndexChanged.connect(self.form_to_storage) # noqa self.ui.order_combo.currentIndexChanged.connect(self.on_order_changed) @@ -1576,7 +1576,7 @@ class ToolMilling(AppTool, Excellon): current_widget = self.form_fields[opt] if isinstance(current_widget, FCCheckBox): try: - current_widget.stateChanged.disconnect(self.form_to_storage) + current_widget.stateChanged.disconnect(self.form_to_storage) # noqa except (TypeError, ValueError, RuntimeError): pass if isinstance(current_widget, RadioSet): @@ -1591,12 +1591,12 @@ class ToolMilling(AppTool, Excellon): pass elif isinstance(current_widget, FCComboBox): try: - current_widget.currentIndexChanged.disconnect(self.form_to_storage) + current_widget.currentIndexChanged.disconnect(self.form_to_storage) # noqa except (TypeError, ValueError, RuntimeError): pass elif isinstance(current_widget, FCComboBox2): try: - current_widget.currentIndexChanged.disconnect(self.form_to_storage) + current_widget.currentIndexChanged.disconnect(self.form_to_storage) # noqa except (TypeError, ValueError, RuntimeError): pass @@ -1610,10 +1610,10 @@ class ToolMilling(AppTool, Excellon): current_widget = self.general_form_fields[opt] if isinstance(current_widget, FCCheckBox): try: - current_widget.stateChanged.disconnect(self.form_to_storage) + current_widget.stateChanged.disconnect(self.form_to_storage) # noqa except (TypeError, ValueError, RuntimeError): pass - if isinstance(current_widget, RadioSet): + if isinstance(current_widget, RadioSet): # noqa try: current_widget.activated_custom.disconnect(self.form_to_storage) except (TypeError, ValueError, RuntimeError): @@ -1625,12 +1625,12 @@ class ToolMilling(AppTool, Excellon): pass elif isinstance(current_widget, FCComboBox): try: - current_widget.currentIndexChanged.disconnect(self.form_to_storage) + current_widget.currentIndexChanged.disconnect(self.form_to_storage) # noqa except (TypeError, ValueError, RuntimeError): pass elif isinstance(current_widget, FCComboBox2): try: - current_widget.currentIndexChanged.disconnect(self.form_to_storage) + current_widget.currentIndexChanged.disconnect(self.form_to_storage) # noqa except (TypeError, ValueError, RuntimeError): pass @@ -2752,9 +2752,9 @@ class ToolMilling(AppTool, Excellon): if self.target_obj.kind == 'geometry': self.on_generate_cnc_from_geo() elif self.target_obj.kind == 'excellon': - self.on_generatecnc_from_exc() + self.on_generate_cnc_from_exc() - def on_generatecnc_from_exc(self): + def on_generate_cnc_from_exc(self): self.app.log.debug("Generating CNCJob from milling Excellon ...") self.sel_tools.clear() @@ -3071,7 +3071,7 @@ class ToolMilling(AppTool, Excellon): # force everything as MULTI-GEO # self.multigeo = True - is_toolchange = toolchange if toolchange is not None else self.ui.toolchange_cb.get_value() + is_tool_change = toolchange if toolchange is not None else self.ui.toolchange_cb.get_value() # Object initialization function for app.app_obj.new_object() # RUNNING ON SEPARATE THREAD! @@ -3104,17 +3104,17 @@ class ToolMilling(AppTool, Excellon): new_cncjob_obj.used_tools = used_tools total_gcode = '' - for tooluid_key in used_tools: + for tool_uid_key in used_tools: tool_cnt += 1 - dia_cnc_dict = deepcopy(tools_dict[tooluid_key]) + dia_cnc_dict = deepcopy(tools_dict[tool_uid_key]) tooldia_val = app_obj.dec_format( - float(tools_dict[tooluid_key]['data']['tools_mill_tooldia']), self.decimals) + float(tools_dict[tool_uid_key]['data']['tools_mill_tooldia']), self.decimals) dia_cnc_dict['data']['tools_mill_tooldia'] = tooldia_val - if "optimization_type" not in tools_dict[tooluid_key]['data']: + if "optimization_type" not in tools_dict[tool_uid_key]['data']: def_optimization_type = geo_obj.obj_options["tools_mill_optimization_type"] - tools_dict[tooluid_key]['data']["tools_mill_optimization_type"] = def_optimization_type + tools_dict[tool_uid_key]['data']["tools_mill_optimization_type"] = def_optimization_type if dia_cnc_dict['data']['tools_mill_offset_type'] == 1: # 'in' tool_offset = -dia_cnc_dict['tools_mill_tooldia'] / 2 @@ -3146,29 +3146,29 @@ class ToolMilling(AppTool, Excellon): dia_cnc_dict['data']['tools_mill_offset_value'] = tool_offset - z_cut = tools_dict[tooluid_key]['data']["tools_mill_cutz"] - z_move = tools_dict[tooluid_key]['data']["tools_mill_travelz"] - feedrate = tools_dict[tooluid_key]['data']["tools_mill_feedrate"] - feedrate_z = tools_dict[tooluid_key]['data']["tools_mill_feedrate_z"] - feedrate_rapid = tools_dict[tooluid_key]['data']["tools_mill_feedrate_rapid"] - multidepth = tools_dict[tooluid_key]['data']["tools_mill_multidepth"] - extracut = tools_dict[tooluid_key]['data']["tools_mill_extracut"] - extracut_length = tools_dict[tooluid_key]['data']["tools_mill_extracut_length"] - depthpercut = tools_dict[tooluid_key]['data']["tools_mill_depthperpass"] - toolchange = tools_dict[tooluid_key]['data']["tools_mill_toolchange"] - toolchangez = tools_dict[tooluid_key]['data']["tools_mill_toolchangez"] - toolchangexy = tools_dict[tooluid_key]['data']["tools_mill_toolchangexy"] - startz = tools_dict[tooluid_key]['data']["tools_mill_startz"] - endz = tools_dict[tooluid_key]['data']["tools_mill_endz"] - endxy = tools_dict[tooluid_key]['data']["tools_mill_endxy"] - spindlespeed = tools_dict[tooluid_key]['data']["tools_mill_spindlespeed"] - dwell = tools_dict[tooluid_key]['data']["tools_mill_dwell"] - dwelltime = tools_dict[tooluid_key]['data']["tools_mill_dwelltime"] - laser_min_power = tools_dict[tooluid_key]['data']["tools_mill_min_power"] - laser_on_code = tools_dict[tooluid_key]['data']["tools_mill_laser_on"] - pp_geometry_name = tools_dict[tooluid_key]['data']["tools_mill_ppname_g"] + z_cut = tools_dict[tool_uid_key]['data']["tools_mill_cutz"] + z_move = tools_dict[tool_uid_key]['data']["tools_mill_travelz"] + feedrate = tools_dict[tool_uid_key]['data']["tools_mill_feedrate"] + feedrate_z = tools_dict[tool_uid_key]['data']["tools_mill_feedrate_z"] + feedrate_rapid = tools_dict[tool_uid_key]['data']["tools_mill_feedrate_rapid"] + multidepth = tools_dict[tool_uid_key]['data']["tools_mill_multidepth"] + extracut = tools_dict[tool_uid_key]['data']["tools_mill_extracut"] + extracut_length = tools_dict[tool_uid_key]['data']["tools_mill_extracut_length"] + depthpercut = tools_dict[tool_uid_key]['data']["tools_mill_depthperpass"] + toolchange = tools_dict[tool_uid_key]['data']["tools_mill_toolchange"] + toolchangez = tools_dict[tool_uid_key]['data']["tools_mill_toolchangez"] + toolchangexy = tools_dict[tool_uid_key]['data']["tools_mill_toolchangexy"] + startz = tools_dict[tool_uid_key]['data']["tools_mill_startz"] + endz = tools_dict[tool_uid_key]['data']["tools_mill_endz"] + endxy = tools_dict[tool_uid_key]['data']["tools_mill_endxy"] + spindlespeed = tools_dict[tool_uid_key]['data']["tools_mill_spindlespeed"] + dwell = tools_dict[tool_uid_key]['data']["tools_mill_dwell"] + dwelltime = tools_dict[tool_uid_key]['data']["tools_mill_dwelltime"] + laser_min_power = tools_dict[tool_uid_key]['data']["tools_mill_min_power"] + laser_on_code = tools_dict[tool_uid_key]['data']["tools_mill_laser_on"] + pp_geometry_name = tools_dict[tool_uid_key]['data']["tools_mill_ppname_g"] - spindledir = self.app.options['tools_mill_spindledir'] + spindle_dir = self.app.options['tools_mill_spindledir'] tool_solid_geometry = geo_obj.solid_geometry new_cncjob_obj.coords_decimals = self.app.options["cncjob_coords_decimals"] @@ -3180,7 +3180,7 @@ class ToolMilling(AppTool, Excellon): new_cncjob_obj.obj_options['tool_dia'] = tooldia_val tool_lst = list(tools_dict.keys()) - is_first = True if tooluid_key == tool_lst[0] else False + is_first = True if tool_uid_key == tool_lst[0] else False # it seems that the tolerance needs to be a lot lower value than 0.01, and it was hardcoded initially # to a value of 0.0005 which is 20 times less than 0.01 @@ -3191,7 +3191,7 @@ class ToolMilling(AppTool, Excellon): geo_obj, tooldia=tooldia_val, offset=tool_offset, tolerance=tol, z_cut=z_cut, z_move=z_move, feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, - spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime, + spindlespeed=spindlespeed, spindle_dir=spindle_dir, dwell=dwell, dwelltime=dwelltime, laser_min_power=laser_min_power, laser_on_code=laser_on_code, multidepth=multidepth, depthpercut=depthpercut, @@ -3211,7 +3211,7 @@ class ToolMilling(AppTool, Excellon): total_gcode += res self.app.inform.emit('[success] %s' % _("G-Code parsing in progress...")) - dia_cnc_dict['gcode_parsed'] = new_cncjob_obj.gcode_parse(tool_data=tools_dict[tooluid_key]['data']) + dia_cnc_dict['gcode_parsed'] = new_cncjob_obj.gcode_parse(tool_data=tools_dict[tool_uid_key]['data']) app_obj.inform.emit('[success] %s' % _("G-Code parsing finished...")) # commented this; there is no need for the actual GCode geometry - the original one will serve as well @@ -3224,7 +3224,7 @@ class ToolMilling(AppTool, Excellon): app_obj.inform.emit('[ERROR] %s: %s' % (_("G-Code processing failed with error"), str(er))) new_cncjob_obj.tools.update({ - tooluid_key: deepcopy(dia_cnc_dict) + tool_uid_key: deepcopy(dia_cnc_dict) }) dia_cnc_dict.clear() @@ -3260,8 +3260,8 @@ class ToolMilling(AppTool, Excellon): # make sure that trying to make a CNCJob from an empty file is not creating an app crash if not geo_obj.solid_geometry: a = 0 - for tooluid_key in geo_obj.tools: - if geo_obj.tools[tooluid_key]['solid_geometry'] is None: + for tool_uid_key in geo_obj.tools: + if geo_obj.tools[tool_uid_key]['solid_geometry'] is None: a += 1 if a == len(geo_obj.tools): app_obj.inform.emit('[ERROR_NOTCL] %s...' % _('Cancelled. Empty file, it has no geometry')) @@ -3272,28 +3272,30 @@ class ToolMilling(AppTool, Excellon): used_tools = list(tools_dict.keys()) new_cncjob_obj.used_tools = used_tools total_gcode = '' - for tooluid_key in used_tools: + for tool_uid_key in used_tools: tool_cnt += 1 - dia_cnc_dict = deepcopy(tools_dict[tooluid_key]) + dia_cnc_dict = deepcopy(tools_dict[tool_uid_key]) # Tooldia update tooldia_val = app_obj.dec_format( - float(tools_dict[tooluid_key]['data']['tools_mill_tooldia']), self.decimals) + float(tools_dict[tool_uid_key]['data']['tools_mill_tooldia']), self.decimals) dia_cnc_dict['data']['tools_mill_tooldia'] = deepcopy(tooldia_val) # Path optimizations - if "optimization_type" not in tools_dict[tooluid_key]['data']: + if "optimization_type" not in tools_dict[tool_uid_key]['data']: def_optimization_type = geo_obj.obj_options["tools_mill_optimization_type"] - tools_dict[tooluid_key]['data']["tools_mill_optimization_type"] = def_optimization_type + tools_dict[tool_uid_key]['data']["tools_mill_optimization_type"] = def_optimization_type + # ----------------------------------------------------------------------------------------------------- # Polishing - job_type = tools_dict[tooluid_key]['data']['tools_mill_job_type'] + # ----------------------------------------------------------------------------------------------------- + job_type = tools_dict[tool_uid_key]['data']['tools_mill_job_type'] if job_type == 3: # Polishing self.app.log.debug("Painting the polished area ...") - margin = tools_dict[tooluid_key]['data']['tools_mill_polish_margin'] - overlap = tools_dict[tooluid_key]['data']['tools_mill_polish_overlap'] / 100 - paint_method = tools_dict[tooluid_key]['data']['tools_mill_polish_method'] + margin = tools_dict[tool_uid_key]['data']['tools_mill_polish_margin'] + overlap = tools_dict[tool_uid_key]['data']['tools_mill_polish_overlap'] / 100 + paint_method = tools_dict[tool_uid_key]['data']['tools_mill_polish_method'] # create the Paint geometry for this tool bbox = box(xmin-margin, ymin-margin, xmax+margin, ymax+margin) @@ -3307,9 +3309,9 @@ class ToolMilling(AppTool, Excellon): raise grace # Type(cpoly) == AppRTreeStorage | None - cpoly = None + c_poly = None if paint_method == 0: # Standard - cpoly = self.clear_polygon_shrink(bbox, + c_poly = self.clear_polygon_shrink(bbox, tooldia=tooldia_val, steps_per_circle=self.circle_steps, overlap=overlap, @@ -3317,7 +3319,7 @@ class ToolMilling(AppTool, Excellon): connect=True, prog_plot=False) elif paint_method == 1: # Seed - cpoly = self.clear_polygon_seed(bbox, + c_poly = self.clear_polygon_seed(bbox, tooldia=tooldia_val, steps_per_circle=self.circle_steps, overlap=overlap, @@ -3325,7 +3327,7 @@ class ToolMilling(AppTool, Excellon): connect=True, prog_plot=False) elif paint_method == 2: # Lines - cpoly = self.clear_polygon_lines(bbox, + c_poly = self.clear_polygon_lines(bbox, tooldia=tooldia_val, steps_per_circle=self.circle_steps, overlap=overlap, @@ -3333,11 +3335,11 @@ class ToolMilling(AppTool, Excellon): connect=True, prog_plot=False) - if not cpoly or not cpoly.objects: + if not c_poly or not c_poly.objects: self.app.inform.emit('[ERROR_NOTCL] %s' % _('Geometry could not be painted completely')) return - paint_geo = [g for g in cpoly.get_objects() if g and not g.is_empty] + paint_geo = [g for g in c_poly.get_objects() if g and not g.is_empty] except grace: return "fail" except Exception as ero: @@ -3347,8 +3349,9 @@ class ToolMilling(AppTool, Excellon): self.app.inform.emit(mssg) return - tools_dict[tooluid_key]['solid_geometry'] = paint_geo + tools_dict[tool_uid_key]['solid_geometry'] = paint_geo self.app.log.debug("Finished painting the polished area ...") + # ----------------------------------------------------------------------------------------------------- # ##################################################################################################### # ############################ COMMON Parameters ###################################################### @@ -3357,80 +3360,80 @@ class ToolMilling(AppTool, Excellon): # Toolchange Z try: if not from_tcl: - tools_dict[tooluid_key]['data']['tools_mill_toolchangez'] = \ + tools_dict[tool_uid_key]['data']['tools_mill_toolchangez'] = \ self.ui.toolchangez_entry.get_value() except AttributeError: - tools_dict[tooluid_key]['data']['tools_mill_toolchangez'] = \ + tools_dict[tool_uid_key]['data']['tools_mill_toolchangez'] = \ self.app.options['tools_mill_toolchangez'] # Toolchange X-Y try: if not from_tcl: - tools_dict[tooluid_key]['data']['tools_mill_toolchangexy'] = \ + tools_dict[tool_uid_key]['data']['tools_mill_toolchangexy'] = \ self.ui.toolchangexy_entry.get_value() except AttributeError: - tools_dict[tooluid_key]['data']['tools_mill_toolchangexy'] = \ + tools_dict[tool_uid_key]['data']['tools_mill_toolchangexy'] = \ self.app.options['tools_mill_toolchangexy'] # End Move Z try: if not from_tcl: - tools_dict[tooluid_key]['data']['tools_mill_endz'] = self.ui.endz_entry.get_value() + tools_dict[tool_uid_key]['data']['tools_mill_endz'] = self.ui.endz_entry.get_value() except AttributeError: - tools_dict[tooluid_key]['data']['tools_mill_endz'] = self.app.options['tools_mill_endz'] + tools_dict[tool_uid_key]['data']['tools_mill_endz'] = self.app.options['tools_mill_endz'] # End Move XY try: if not from_tcl: - tools_dict[tooluid_key]['data']['tools_mill_endxy'] = self.ui.endxy_entry.get_value() + tools_dict[tool_uid_key]['data']['tools_mill_endxy'] = self.ui.endxy_entry.get_value() except AttributeError: - tools_dict[tooluid_key]['data']['tools_mill_endxy'] = self.app.options['tools_mill_endxy'] + tools_dict[tool_uid_key]['data']['tools_mill_endxy'] = self.app.options['tools_mill_endxy'] # Probe Z try: if not from_tcl: - tools_dict[tooluid_key]['data']['tools_mill_z_p_depth'] = self.ui.pdepth_entry.get_value() + tools_dict[tool_uid_key]['data']['tools_mill_z_p_depth'] = self.ui.pdepth_entry.get_value() except AttributeError: - tools_dict[tooluid_key]['data']['tools_mill_z_p_depth'] = self.app.options['tools_mill_z_p_depth'] + tools_dict[tool_uid_key]['data']['tools_mill_z_p_depth'] = self.app.options['tools_mill_z_p_depth'] # Probe FR try: if not from_tcl: - tools_dict[tooluid_key]['data']['tools_mill_feedrate_probe'] = \ + tools_dict[tool_uid_key]['data']['tools_mill_feedrate_probe'] = \ self.ui.feedrate_probe_entry.get_value() except AttributeError: - tools_dict[tooluid_key]['data'][ + tools_dict[tool_uid_key]['data'][ 'tools_mill_feedrate_probe'] = self.app.options['tools_mill_feedrate_probe'] # Exclusion Areas Enable try: if not from_tcl: - tools_dict[tooluid_key]['data']['tools_mill_area_exclusion'] = self.ui.exclusion_cb.get_value() + tools_dict[tool_uid_key]['data']['tools_mill_area_exclusion'] = self.ui.exclusion_cb.get_value() except AttributeError: - tools_dict[tooluid_key]['data']['tools_mill_area_exclusion'] = False # Tcl Command most likely + tools_dict[tool_uid_key]['data']['tools_mill_area_exclusion'] = False # Tcl Command most likely # Exclusion Areas Shape try: if not from_tcl: - tools_dict[tooluid_key]['data']['tools_mill_area_shape'] = self.ui.area_shape_radio.get_value() + tools_dict[tool_uid_key]['data']['tools_mill_area_shape'] = self.ui.area_shape_radio.get_value() except AttributeError: - tools_dict[tooluid_key]['data']['tools_mill_area_shape'] = \ + tools_dict[tool_uid_key]['data']['tools_mill_area_shape'] = \ self.app.options['tools_mill_area_shape'] # Exclusion Areas Strategy try: if not from_tcl: - tools_dict[tooluid_key]['data']['tools_mill_area_strategy'] = self.ui.strategy_radio.get_value() + tools_dict[tool_uid_key]['data']['tools_mill_area_strategy'] = self.ui.strategy_radio.get_value() except AttributeError: - tools_dict[tooluid_key]['data']['tools_mill_area_strategy'] = \ + tools_dict[tool_uid_key]['data']['tools_mill_area_strategy'] = \ self.app.options['tools_mill_area_strategy'] # Exclusion Areas Overz try: if not from_tcl: - tools_dict[tooluid_key]['data']['tools_mill_area_overz'] = self.ui.over_z_entry.get_value() + tools_dict[tool_uid_key]['data']['tools_mill_area_overz'] = self.ui.over_z_entry.get_value() except AttributeError: - tools_dict[tooluid_key]['data']['tools_mill_area_overz'] = \ + tools_dict[tool_uid_key]['data']['tools_mill_area_overz'] = \ self.app.options['tools_mill_area_overz'] # Preprocessor try: if not from_tcl: - tools_dict[tooluid_key]['data']['tools_mill_ppname_g'] = self.ui.pp_geo_name_cb.get_value() + tools_dict[tool_uid_key]['data']['tools_mill_ppname_g'] = self.ui.pp_geo_name_cb.get_value() except AttributeError: - tools_dict[tooluid_key]['data']['tools_mill_ppname_g'] = self.app.options['tools_mill_ppname_g'] + tools_dict[tool_uid_key]['data']['tools_mill_ppname_g'] = self.app.options['tools_mill_ppname_g'] # Offset calculation offset_type = dia_cnc_dict['data']['tools_mill_offset_type'] @@ -3443,7 +3446,7 @@ class ToolMilling(AppTool, Excellon): if not from_tcl: offset_value = self.ui.offset_entry.get_value() else: - offset_value = tools_dict[tooluid_key]['data']['tools_mill_offset_value'] + offset_value = tools_dict[tool_uid_key]['data']['tools_mill_offset_value'] except AttributeError: offset_value = self.app.options['tools_mill_offset_value'] if offset_value: @@ -3461,10 +3464,10 @@ class ToolMilling(AppTool, Excellon): tool_offset = 0.0 dia_cnc_dict['data']['tools_mill_offset_value'] = tool_offset - tools_dict[tooluid_key]['data']['tools_mill_offset_value'] = tool_offset + tools_dict[tool_uid_key]['data']['tools_mill_offset_value'] = tool_offset # Solid Geometry - tool_solid_geometry = geo_obj.tools[tooluid_key]['solid_geometry'] + tool_solid_geometry = geo_obj.tools[tool_uid_key]['solid_geometry'] # Coordinates new_cncjob_obj.coords_decimals = self.app.options["cncjob_coords_decimals"] @@ -3481,19 +3484,21 @@ class ToolMilling(AppTool, Excellon): tol = glob_tol / 20 if self.units.lower() == 'in' else glob_tol tool_lst = list(tools_dict.keys()) - is_first = True if tooluid_key == tool_lst[0] else False + is_first = True if tool_uid_key == tool_lst[0] else False first_pt = (0, 0) - is_last = True if tooluid_key == tool_lst[-1] else False - last_pt = tools_dict[tooluid_key]['data']['tools_mill_endxy'] + is_last = True if tool_uid_key == tool_lst[-1] else False + last_pt = tools_dict[tool_uid_key]['data']['tools_mill_endxy'] - res, start_gcode = new_cncjob_obj.geometry_tool_gcode_gen(tooluid_key, tools_dict, first_pt=first_pt, + res, start_gcode = new_cncjob_obj.geometry_tool_gcode_gen(tool_uid_key, tools_dict, first_pt=first_pt, last_pt=last_pt, tolerance=tol, is_first=is_first, is_last=is_last, - toolchange=is_toolchange, + toolchange=is_tool_change, use_ui=not from_tcl) if res == 'fail': - self.app.log.debug("ToolMilling.mtool_gen_cncjob() --> geometry_tool_gcode_gen() failed") + self.app.log.debug( + "ToolMilling.generate_cnc_job_handler() --> " + "Failed to generate GCode for tool: %s" % tool_uid_key) return 'fail' # Store the GCode @@ -3504,7 +3509,7 @@ class ToolMilling(AppTool, Excellon): new_cncjob_obj.gc_start = start_gcode app_obj.inform.emit('[success] %s' % _("G-Code parsing in progress...")) - dia_cnc_dict['gcode_parsed'] = new_cncjob_obj.gcode_parse(tool_data=tools_dict[tooluid_key]['data']) + dia_cnc_dict['gcode_parsed'] = new_cncjob_obj.gcode_parse(tool_data=tools_dict[tool_uid_key]['data']) app_obj.inform.emit('[success] %s' % _("G-Code parsing finished...")) # commented this; there is no need for the actual GCode geometry - the original one will serve as well @@ -3523,7 +3528,7 @@ class ToolMilling(AppTool, Excellon): # Update the CNCJob tools dictionary new_cncjob_obj.tools.update({ - tooluid_key: deepcopy(dia_cnc_dict) + tool_uid_key: deepcopy(dia_cnc_dict) }) dia_cnc_dict.clear() diff --git a/camlib.py b/camlib.py index 39a4f405..1caa49a5 100644 --- a/camlib.py +++ b/camlib.py @@ -98,7 +98,7 @@ class ApertureMacro: """ # ## Regular expressions - am1_re = re.compile(r'^%AM([^\*]+)\*(.+)?(%)?$') + am1_re = re.compile(r'^%AM([^*]+)\*(.+)?(%)?$') am2_re = re.compile(r'(.*)%$') am_comm_re = re.compile(r'^0(.*)') am_prim_re = re.compile(r'^[1-9].*') @@ -964,7 +964,7 @@ class Geometry(object): Results are placed in self.flat_geometry - :param geometry: Shapely type or list or list of list of such. + :param geometry: Shapely type, or list, or a list of lists of such. :param reset: Clears the contents of self.flat_geometry. :param pathonly: Expands polygons into linear elements. """ @@ -1004,7 +1004,7 @@ class Geometry(object): Polygons are expanded into its exterior and interiors. - :param geometry: Shapely type or list or list of list of such. + :param geometry: Shapely type, or a list, or a list of lists of such. """ flat_geo_ext = [] @@ -1063,7 +1063,7 @@ class Geometry(object): # indexes them in rtree. # # :param geometry: Iterable geometry - # :param reset: Wether to clear (True) or append (False) to self.flat_geometry + # :param reset: Either to clear (True) or append (False) to self.flat_geometry # :return: self.flat_geometry, self.flat_geometry_rtree # """ # @@ -2346,7 +2346,7 @@ class Geometry(object): skew_reference='center', scale_reference='center', mirror_reference='center', mirror=None): """ - Exports the Geometry Object as a SVG Element + Exports the Geometry Object as an SVG Element :return: SVG Element """ @@ -2428,7 +2428,7 @@ class Geometry(object): if scale_stroke_factor <= 0: scale_stroke_factor = 0.01 - # Convert to a SVG + # Convert to an SVG element svg_elem = geom.svg(scale_factor=scale_stroke_factor) return svg_elem @@ -3150,9 +3150,9 @@ class CNCjob(Geometry): :param points: List of tuples with x, y coordinates :type points: list - :param start: a tuple with a x,y coordinates of the start point + :param start: a tuple with the x,y coordinates of the start point :type start: tuple - :return: List of points ordered in a optimized way + :return: List of points ordered in an optimized way :rtype: list """ @@ -3598,7 +3598,7 @@ class CNCjob(Geometry): # t_gcode += self.doformat(p.rapid_code, x=locx, y=locy) - # test if the self.z_cut >= 0, in that case we don not use the up_to_zero feature + # test if the self.z_cut >= 0, in that case we do not use the up_to_zero feature cancel_up2zero = False if self.z_cut >= 0: cancel_up2zero = True @@ -3735,7 +3735,7 @@ class CNCjob(Geometry): if not HAS_ORTOOLS: opt_type = 'R' - opt_time = tool_dict['tools_mill_search_time'] if 'tools_mill_search_time' in tool_dict else 'R' + opt_time = tool_dict['tools_mill_search_time'] if 'tools_mill_search_time' in tool_dict else 1.0 if opt_type == 'M': self.app.log.debug("Using OR-Tools Metaheuristic Guided Local Search path optimization.") @@ -3840,7 +3840,7 @@ class CNCjob(Geometry): self.obj_options['name'])) return 'fail' - # made sure that depth_per_cut is no more then the z_cut + # made sure that depth_per_cut is no more than the z_cut if abs(self.z_cut) < self.z_depthpercut: self.z_depthpercut = abs(self.z_cut) @@ -3951,6 +3951,7 @@ class CNCjob(Geometry): # if there are no locations then go to the next tool if not locations: return 'fail' + optimized_locations = self.optimized_ortools_meta(locations=locations, opt_time=opt_time) optimized_path = [(locations[loc], geo_storage[locations[loc]]) for loc in optimized_locations] elif opt_type == 'B': @@ -3969,9 +3970,13 @@ class CNCjob(Geometry): optimized_path = self.geo_optimized_rtree(temp_solid_geometry) if optimized_path == 'fail': return 'fail' + elif opt_type == 'N': + optimized_path = [(k , v) for k, v in geo_storage.items()] + if not optimized_path: + return 'fail' else: # it's actually not optimized path but here we build a list of (x,y) coordinates - # out of the tool's drills + # out of the tool for geo in temp_solid_geometry: optimized_path.append(geo.coords[0]) # ######################################################################################################### @@ -4144,7 +4149,6 @@ class CNCjob(Geometry): which is the one doing it) :type is_first: bool :return: None - :rtype: None """ # ############################################################################################################# @@ -4213,7 +4217,7 @@ class CNCjob(Geometry): sorted_tools = all_tools if tools == "all": - selected_tools = [i[0] for i in all_tools] # we get a array of ordered tools + selected_tools = [i[0] for i in all_tools] # we get an array of ordered tools else: selected_tools = eval(tools) if not isinstance(selected_tools, list): @@ -5061,7 +5065,7 @@ class CNCjob(Geometry): self.obj_options['name'])) return 'fail' - # made sure that depth_per_cut is no more then the z_cut + # made sure that depth_per_cut is no more than the z_cut if abs(self.z_cut) < self.z_depthpercut: self.z_depthpercut = abs(self.z_cut) @@ -5229,7 +5233,7 @@ class CNCjob(Geometry): def generate_from_geometry_2(self, geo_obj, append=True, tooldia=None, offset=0.0, tolerance=0, z_cut=None, z_move=None, feedrate=None, feedrate_z=None, feedrate_rapid=None, spindlespeed=None, - spindledir='CW', dwell=False, dwelltime=None, + spindle_dir='CW', dwell=False, dwelltime=None, laser_min_power=0.0, laser_on_code="M03", multidepth=False, depthpercut=None, @@ -5254,7 +5258,7 @@ class CNCjob(Geometry): :param feedrate_z: :param feedrate_rapid: :param spindlespeed: - :param spindledir: + :param spindle_dir: :param dwell: :param dwelltime: :param laser_min_power: Float value. Used when the preprocessor cotanins 'laser' in its name. Control @@ -5307,7 +5311,7 @@ class CNCjob(Geometry): maxy = max(maxy, maxy_) return minx, miny, maxx, maxy else: - # it's a Shapely object, return it's bounds + # it's a Shapely object, return its bounds return obj.bounds # ############################################################################################################# @@ -5395,7 +5399,7 @@ class CNCjob(Geometry): self.app.options["tools_mill_feedrate_rapid"] self.spindlespeed = int(spindlespeed) if spindlespeed != 0 and spindlespeed is not None else None - self.spindledir = spindledir + self.spindledir = spindle_dir self.dwell = dwell self.dwelltime = float(dwelltime) if dwelltime is not None else self.app.options["tools_mill_dwelltime"] @@ -5497,26 +5501,70 @@ class CNCjob(Geometry): except TypeError: self.z_depthpercut = abs(self.z_cut) - # ## Index first and last points in paths - # What points to index. - def get_pts(o): - return [o.coords[0], o.coords[-1]] + # ######################################################################################################### + # ############ Create the data. ########################################################################### + # ######################################################################################################### + opt_type = self.app.options["tools_mill_optimization_type"] + if not HAS_ORTOOLS: + opt_type = 'R' - # Create the indexed storage. - storage = AppRTreeStorage() - storage.get_points = get_pts + opt_time = int(self.app.options['tools_mill_search_time']) - # Store the geometry - self.app.log.debug("Indexing geometry before generating G-Code...") - self.app.inform.emit(_("Indexing geometry before generating G-Code...")) + if opt_type == 'M': + self.app.log.debug("Using OR-Tools Metaheuristic Guided Local Search path optimization.") + elif opt_type == 'B': + self.app.log.debug("Using OR-Tools Basic path optimization.") + elif opt_type == 'T': + self.app.log.debug("Using Travelling Salesman path optimization.") + elif opt_type == 'R': + self.app.log.debug("Using RTree path optimization.") + else: + self.app.log.debug("Using no path optimization.") - for geo_shape in temp_solid_geometry: - if self.app.abort_flag: - # graceful abort requested by the user - raise grace + optimized_path = [] - if geo_shape is not None: - storage.insert(geo_shape) + geo_storage = {} + for geo in temp_solid_geometry: + if geo is not None and isinstance(geo, (LineString, LinearRing)): + try: + geo_storage[geo.coords[0]] = geo + except Exception: + pass + locations = list(geo_storage.keys()) + + if opt_type == 'M': + # if there are no locations then go to the next tool + if not locations: + return 'fail' + + optimized_locations = self.optimized_ortools_meta(locations=locations, opt_time=opt_time) + optimized_path = [(locations[loc], geo_storage[locations[loc]]) for loc in optimized_locations] + elif opt_type == 'B': + # if there are no locations then go to the next tool + if not locations: + return 'fail' + optimized_locations = self.optimized_ortools_basic(locations=locations) + optimized_path = [(locations[loc], geo_storage[locations[loc]]) for loc in optimized_locations] + elif opt_type == 'T': + # if there are no locations then go to the next tool + if not locations: + return 'fail' + optimized_locations = self.optimized_travelling_salesman(locations) + optimized_path = [(loc, geo_storage[loc]) for loc in optimized_locations] + elif opt_type == 'R': + optimized_path = self.geo_optimized_rtree(temp_solid_geometry) + if optimized_path == 'fail': + return 'fail' + elif opt_type == 'N': + optimized_path = [(k, v) for k, v in geo_storage.items()] + if not optimized_path: + return 'fail' + else: + # it's actually not optimized path but here we build a list of (x,y) coordinates + # out of the tool + for geo in temp_solid_geometry: + optimized_path.append(geo.coords[0]) + # ######################################################################################################### if not append: self.gcode = "" @@ -5595,64 +5643,53 @@ class CNCjob(Geometry): path_count = 0 current_pt = (0, 0) - pt, geo = storage.nearest(current_pt) - # when nothing is left in the storage a StopIteration exception will be raised therefore stopping - # the whole process including the infinite loop while True below. - try: - while True: - if self.app.abort_flag: - # graceful abort requested by the user - raise grace + for pt, geo in optimized_path: + if self.app.abort_flag: + # graceful abort requested by the user + raise grace - path_count += 1 + path_count += 1 - # Remove before modifying, otherwise deletion will fail. - storage.remove(geo) + # If last point in geometry is the nearest but prefer the first one if last point == first point + # then reverse coordinates. + if pt != geo.coords[0] and pt == geo.coords[-1]: + # geo.coords = list(geo.coords)[::-1] # Shapely 2.0 + geo = LineString(list(geo.coords)[::-1]) - # If last point in geometry is the nearest but prefer the first one if last point == first point - # then reverse coordinates. - if pt != geo.coords[0] and pt == geo.coords[-1]: - # geo.coords = list(geo.coords)[::-1] # Shapely 2.0 - geo = LineString(list(geo.coords)[::-1]) + # ---------- Single depth/pass -------- + if not multidepth: + # calculate the cut distance + total_cut += geo.length + self.gcode += self.create_gcode_single_pass(geo, current_tooldia, extracut, self.extracut_length, + tolerance, z_move=z_move, old_point=current_pt) - # ---------- Single depth/pass -------- - if not multidepth: - # calculate the cut distance - total_cut += geo.length - self.gcode += self.create_gcode_single_pass(geo, current_tooldia, extracut, self.extracut_length, - tolerance, z_move=z_move, old_point=current_pt) + # --------- Multi-pass --------- + else: + # calculate the cut distance + # due of the number of cuts (multi depth) it has to multiplied by the number of cuts + nr_cuts = 0 + depth = abs(self.z_cut) + while depth > 0: + nr_cuts += 1 + depth -= float(self.z_depthpercut) - # --------- Multi-pass --------- - else: - # calculate the cut distance - # due of the number of cuts (multi depth) it has to multiplied by the number of cuts - nr_cuts = 0 - depth = abs(self.z_cut) - while depth > 0: - nr_cuts += 1 - depth -= float(self.z_depthpercut) + total_cut += (geo.length * nr_cuts) - total_cut += (geo.length * nr_cuts) + gc, geo = self.create_gcode_multi_pass(geo, current_tooldia, extracut, self.extracut_length, + tolerance, z_move=z_move, postproc=p, + old_point=current_pt) + self.gcode += gc - gc, geo = self.create_gcode_multi_pass(geo, current_tooldia, extracut, self.extracut_length, - tolerance, z_move=z_move, postproc=p, - old_point=current_pt) - self.gcode += gc + # calculate the travel distance + total_travel += abs(distance(pt1=current_pt, pt2=pt)) + current_pt = pt - # calculate the travel distance - total_travel += abs(distance(pt1=current_pt, pt2=pt)) - current_pt = geo.coords[-1] - - pt, geo = storage.nearest(current_pt) # Next - - # update the activity counter (lower left side of the app, status bar) - disp_number = int(np.interp(path_count, [0, geo_len], [0, 100])) - if old_disp_number < disp_number <= 100: - self.app.proc_container.update_view_text(' %d%%' % disp_number) - old_disp_number = disp_number - except StopIteration: # Nothing found in storage. - pass + # update the activity counter (lower left side of the app, status bar) + disp_number = int(np.interp(path_count, [0, geo_len], [0, 100])) + if old_disp_number < disp_number <= 100: + self.app.proc_container.update_view_text(' %d%%' % disp_number) + old_disp_number = disp_number self.app.log.debug("Finishing G-Code... %s paths traced." % path_count) @@ -5926,7 +5963,7 @@ class CNCjob(Geometry): :param tolerance: Tolerance used to simplify the paths (making them mre rough) :type tolerance: float :param postproc: Preprocessor class - :type postproc: class + :type postproc: Callable :param z_move: Travel Z :type z_move: float :param old_point: Previous point @@ -6275,7 +6312,7 @@ class CNCjob(Geometry): def excellon_tool_gcode_parse(self, dia, gcode, start_pt=(0, 0), force_parsing=None): """ - G-Code parser (from self.tools['tool_id']['gcode']). For Excellon. Generates dictionary with + G-Code parser (from "self.tools['tool_id']['gcode']"). For Excellon. Generates dictionary with single-segment LineString's and "kind" indicating cut or travel, fast or feedrate speed. @@ -7115,8 +7152,8 @@ class CNCjob(Geometry): def point2gcode(self, point, dia, z_move=None, old_point=(0, 0)): """ - :param point: A Shapely Point - :type point: Point + :param point: A Shapely point geometry element + :type point: Shapely Point :param dia: The tool diameter that is going on the path :type dia: float :param z_move: Travel Z @@ -7203,7 +7240,7 @@ class CNCjob(Geometry): mirror=None): """ - Exports the CNC Job as a SVG Element + Exports the CNC Job as an SVG Element :param scale_stroke_factor: A factor to scale the SVG geometry element outline :param scale_factor_x: x factor for scale @@ -7391,7 +7428,6 @@ class CNCjob(Geometry): :param point: the (x,y) coords for the point of origin of scale :type point: tuple :return: None - :rtype: None """ self.app.log.debug("camlib.CNCJob.scale()") @@ -7678,7 +7714,7 @@ class CNCjob(Geometry): def mirror(self, axis, point): """ - Mirror the geometry of an object by an given axis around the coordinates of the 'point' + Mirror the geometry of an object by a given axis around the coordinates of the 'point' :param axis: Axis for Mirror :param point: tuple of coordinates (x,y). Point of origin for Mirror @@ -7758,7 +7794,7 @@ class CNCjob(Geometry): def rotate(self, angle, point): """ - Rotate the geometry of an object by an given angle around the coordinates of the 'point' + Rotate the geometry of an object by a given angle around the coordinates of the 'point' :param angle: Angle of Rotation :param point: tuple of coordinates (x,y). Origin point for Rotation @@ -7924,7 +7960,7 @@ def arc_angle(start, stop, direction): # if poly.contains(Point(point)): # return poly # except AttributeError: -# return None +# pass # # return None @@ -8336,7 +8372,7 @@ def distance_euclidian(x1, y1, x2, y2): class AppRTree(object): """ - Indexes geometry (Any object with "cooords" property containing + Indexes geometry (Any object with "coords" property containing a list of tuples with x, y values). Objects are indexed by all their points by default. To index by arbitrary points, override self.points2obj. @@ -8428,11 +8464,11 @@ class AppRTreeStorage(AppRTree): self.objects.append(obj) idx = len(self.objects) - 1 - # Note: Shapely objects are not hashable any more, although + # Note: Shapely objects are not hashable anymore, although # there seem to be plans to re-introduce the feature in # version 2.0. For now, we will index using the object's id, # but it's important to remember that shapely geometry is - # mutable, ie. it can be modified to a totally different shape + # mutable, i.e. it can be modified to a totally different shape # and continue to have the same id. # self.indexes[obj] = idx self.indexes[id(obj)] = idx