소스 검색

- Panelize Tool - add a new option for the panels of type Geometry named Path Optimiztion. If the checkbox is checked then all the LineStrings that are overlapped in the resulting multigeo Geometry panel object will keep only one of the paths thus minimizing the tool cuts.

Marius Stanciu 5 년 전
부모
커밋
8dc4eecbf4
5개의 변경된 파일92개의 추가작업 그리고 26개의 파일을 삭제
  1. 1 0
      CHANGELOG.md
  2. 1 0
      appGUI/preferences/PreferencesUIManager.py
  3. 15 5
      appGUI/preferences/tools/ToolsPanelizePrefGroupUI.py
  4. 74 21
      appTools/ToolPanelize.py
  5. 1 0
      defaults.py

+ 1 - 0
CHANGELOG.md

@@ -15,6 +15,7 @@ CHANGELOG for FlatCAM beta
 - Cutout Tool - in manual gap adding there is now an option to automatically turn on the big cursor which could help
 - Cutout Tool - fixed errors when trying to add a manual gap without having a geometry object selected in the combobox
 - Cutout Tool - made sure that all the paths generated by this tool are contiguous which means that two lines that meet at one end will become onle line therefore reducing unnecessary Z moves
+- Panelize Tool - add a new option for the panels of type Geometry named Path Optimiztion. If the checkbox is checked then all the LineStrings that are overlapped in the resulting multigeo Geometry panel object will keep only one of the paths thus minimizing the tool cuts.
 
 17.06.2020
 

+ 1 - 0
appGUI/preferences/PreferencesUIManager.py

@@ -437,6 +437,7 @@ class PreferencesUIManager:
             "tools_panelize_spacing_rows": self.ui.tools_defaults_form.tools_panelize_group.pspacing_rows,
             "tools_panelize_columns": self.ui.tools_defaults_form.tools_panelize_group.pcolumns,
             "tools_panelize_rows": self.ui.tools_defaults_form.tools_panelize_group.prows,
+            "tools_panelize_optimization": self.ui.tools_defaults_form.tools_panelize_group.poptimization_cb,
             "tools_panelize_constrain": self.ui.tools_defaults_form.tools_panelize_group.pconstrain_cb,
             "tools_panelize_constrainx": self.ui.tools_defaults_form.tools_panelize_group.px_width_entry,
             "tools_panelize_constrainy": self.ui.tools_defaults_form.tools_panelize_group.py_height_entry,

+ 15 - 5
appGUI/preferences/tools/ToolsPanelizePrefGroupUI.py

@@ -106,6 +106,16 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.panel_type_label, 4, 0)
         grid0.addWidget(self.panel_type_radio, 4, 1)
 
+        # Path optimization
+        self.poptimization_cb = FCCheckBox('%s' % _("Path Optimization"))
+        self.poptimization_cb.setToolTip(
+            _("Active only for Geometry panel type.\n"
+              "When checked the application will find\n"
+              "any two overlapping Line elements in the panel\n"
+              "and remove the overlapping parts, keeping only one of them.")
+        )
+        grid0.addWidget(self.poptimization_cb, 5, 0, 1, 2)
+
         # ## Constrains
         self.pconstrain_cb = FCCheckBox('%s:' % _("Constrain within"))
         self.pconstrain_cb.setToolTip(
@@ -115,7 +125,7 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
               "the final panel will have as many columns and rows as\n"
               "they fit completely within selected area.")
         )
-        grid0.addWidget(self.pconstrain_cb, 5, 0, 1, 2)
+        grid0.addWidget(self.pconstrain_cb, 10, 0, 1, 2)
 
         self.px_width_entry = FCDoubleSpinner()
         self.px_width_entry.set_range(0.000001, 9999.9999)
@@ -127,8 +137,8 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
             _("The width (DX) within which the panel must fit.\n"
               "In current units.")
         )
-        grid0.addWidget(self.x_width_lbl, 6, 0)
-        grid0.addWidget(self.px_width_entry, 6, 1)
+        grid0.addWidget(self.x_width_lbl, 12, 0)
+        grid0.addWidget(self.px_width_entry, 12, 1)
 
         self.py_height_entry = FCDoubleSpinner()
         self.py_height_entry.set_range(0.000001, 9999.9999)
@@ -140,7 +150,7 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
             _("The height (DY)within which the panel must fit.\n"
               "In current units.")
         )
-        grid0.addWidget(self.y_height_lbl, 7, 0)
-        grid0.addWidget(self.py_height_entry, 7, 1)
+        grid0.addWidget(self.y_height_lbl, 17, 0)
+        grid0.addWidget(self.py_height_entry, 17, 1)
 
         self.layout.addStretch()

+ 74 - 21
appTools/ToolPanelize.py

@@ -15,8 +15,8 @@ from copy import deepcopy
 import numpy as np
 
 import shapely.affinity as affinity
-from shapely.ops import unary_union
-from shapely.geometry import LineString
+from shapely.ops import unary_union, linemerge, snap
+from shapely.geometry import LineString, MultiLineString
 
 import gettext
 import appTranslation as fcTranslate
@@ -112,6 +112,10 @@ class Panelize(AppTool):
             self.app.defaults["tools_panelize_columns"] else 0.0
         self.ui.columns.set_value(int(cc))
 
+        optimized_path_cb = self.app.defaults["tools_panelize_optimization"] if \
+            self.app.defaults["tools_panelize_optimization"] else True
+        self.ui.optimization_cb.set_value(optimized_path_cb)
+
         c_cb = self.app.defaults["tools_panelize_constrain"] if \
             self.app.defaults["tools_panelize_constrain"] else False
         self.ui.constrain_cb.set_value(c_cb)
@@ -128,6 +132,8 @@ class Panelize(AppTool):
             self.app.defaults["tools_panelize_panel_type"] else 'gerber'
         self.ui.panel_type_radio.set_value(panel_type)
 
+        self.ui.on_panel_type(val=panel_type)
+
         # run once the following so the obj_type attribute is updated in the FCComboBoxes
         # such that the last loaded object is populated in the combo boxes
         self.on_type_obj_index_changed()
@@ -145,10 +151,12 @@ class Panelize(AppTool):
         if self.ui.type_obj_combo.currentText() != 'Excellon':
             self.ui.panel_type_label.setDisabled(False)
             self.ui.panel_type_radio.setDisabled(False)
+            self.ui.on_panel_type(val=self.ui.panel_type_radio.get_value())
         else:
             self.ui.panel_type_label.setDisabled(True)
             self.ui.panel_type_radio.setDisabled(True)
             self.ui.panel_type_radio.set_value('geometry')
+            self.ui.optimization_cb.setDisabled(True)
 
     def on_type_box_index_changed(self):
         obj_type = self.ui.type_box_combo.currentIndex()
@@ -257,6 +265,8 @@ class Panelize(AppTool):
             for tt, tt_val in list(panel_source_obj.apertures.items()):
                 copied_apertures[tt] = deepcopy(tt_val)
 
+        to_optimize = self.ui.optimization_cb.get_value()
+
         def panelize_worker():
             if panel_source_obj is not None:
                 self.app.inform.emit(_("Generating panel ... "))
@@ -370,7 +380,7 @@ class Panelize(AppTool):
                         obj_fin.tools = copied_tools
                         if panel_source_obj.multigeo is True:
                             for tool in panel_source_obj.tools:
-                                obj_fin.tools[tool]['solid_geometry'][:] = []
+                                obj_fin.tools[tool]['solid_geometry'] = []
                     elif panel_source_obj.kind == 'gerber':
                         obj_fin.apertures = copied_apertures
                         for ap in obj_fin.apertures:
@@ -385,11 +395,6 @@ class Panelize(AppTool):
                                     geo_len += len(panel_source_obj.tools[tool]['solid_geometry'])
                                 except TypeError:
                                     geo_len += 1
-                        # else:
-                        #     try:
-                        #         geo_len = len(panel_source_obj.solid_geometry)
-                        #     except TypeError:
-                        #         geo_len = 1
                     elif panel_source_obj.kind == 'gerber':
                         for ap in panel_source_obj.apertures:
                             if 'geometry' in panel_source_obj.apertures[ap]:
@@ -410,17 +415,20 @@ class Panelize(AppTool):
                             if panel_source_obj.kind == 'geometry':
                                 if panel_source_obj.multigeo is True:
                                     for tool in panel_source_obj.tools:
+                                        # graceful abort requested by the user
                                         if app_obj.abort_flag:
-                                            # graceful abort requested by the user
                                             raise grace
 
                                         # calculate the number of polygons
                                         geo_len = len(panel_source_obj.tools[tool]['solid_geometry'])
+
+                                        # panelization
                                         pol_nr = 0
                                         for geo_el in panel_source_obj.tools[tool]['solid_geometry']:
                                             trans_geo = translate_recursion(geo_el)
                                             obj_fin.tools[tool]['solid_geometry'].append(trans_geo)
 
+                                            # update progress
                                             pol_nr += 1
                                             disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
                                             if old_disp_number < disp_number <= 100:
@@ -428,15 +436,17 @@ class Panelize(AppTool):
                                                     ' %s: %d %d%%' % (_("Copy"), int(element), disp_number))
                                                 old_disp_number = disp_number
                                 else:
+                                    # graceful abort requested by the user
                                     if app_obj.abort_flag:
-                                        # graceful abort requested by the user
                                         raise grace
 
+                                    # calculate the number of polygons
                                     try:
-                                        # calculate the number of polygons
                                         geo_len = len(panel_source_obj.solid_geometry)
                                     except TypeError:
                                         geo_len = 1
+
+                                    # panelization
                                     pol_nr = 0
                                     try:
                                         for geo_el in panel_source_obj.solid_geometry:
@@ -447,9 +457,9 @@ class Panelize(AppTool):
                                             trans_geo = translate_recursion(geo_el)
                                             obj_fin.solid_geometry.append(trans_geo)
 
+                                            # update progress
                                             pol_nr += 1
                                             disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
-
                                             if old_disp_number < disp_number <= 100:
                                                 app_obj.proc_container.update_view_text(
                                                     ' %s: %d %d%%' % (_("Copy"), int(element), disp_number))
@@ -460,14 +470,15 @@ class Panelize(AppTool):
                                         obj_fin.solid_geometry.append(trans_geo)
                             # Will panelize a Gerber Object
                             else:
+                                # graceful abort requested by the user
                                 if self.app.abort_flag:
-                                    # graceful abort requested by the user
                                     raise grace
 
+                                # panelization solid_geometry
                                 try:
                                     for geo_el in panel_source_obj.solid_geometry:
+                                        # graceful abort requested by the user
                                         if app_obj.abort_flag:
-                                            # graceful abort requested by the user
                                             raise grace
 
                                         trans_geo = translate_recursion(geo_el)
@@ -477,15 +488,18 @@ class Panelize(AppTool):
                                     obj_fin.solid_geometry.append(trans_geo)
 
                                 for apid in panel_source_obj.apertures:
+                                    # graceful abort requested by the user
                                     if app_obj.abort_flag:
-                                        # graceful abort requested by the user
                                         raise grace
+
                                     if 'geometry' in panel_source_obj.apertures[apid]:
+                                        # calculate the number of polygons
                                         try:
-                                            # calculate the number of polygons
                                             geo_len = len(panel_source_obj.apertures[apid]['geometry'])
                                         except TypeError:
                                             geo_len = 1
+
+                                        # panelization -> tools
                                         pol_nr = 0
                                         for el in panel_source_obj.apertures[apid]['geometry']:
                                             if app_obj.abort_flag:
@@ -496,20 +510,17 @@ class Panelize(AppTool):
                                             if 'solid' in el:
                                                 geo_aper = translate_recursion(el['solid'])
                                                 new_el['solid'] = geo_aper
-
                                             if 'clear' in el:
                                                 geo_aper = translate_recursion(el['clear'])
                                                 new_el['clear'] = geo_aper
-
                                             if 'follow' in el:
                                                 geo_aper = translate_recursion(el['follow'])
                                                 new_el['follow'] = geo_aper
-
                                             obj_fin.apertures[apid]['geometry'].append(deepcopy(new_el))
 
+                                            # update progress
                                             pol_nr += 1
                                             disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
-
                                             if old_disp_number < disp_number <= 100:
                                                 app_obj.proc_container.update_view_text(
                                                     ' %s: %d %d%%' % (_("Copy"), int(element), disp_number))
@@ -522,17 +533,42 @@ class Panelize(AppTool):
                         # I'm going to do this only here as a fix for panelizing cutouts
                         # I'm going to separate linestrings out of the solid geometry from other
                         # possible type of elements and apply unary_union on them to fuse them
+
+                        if to_optimize is True:
+                            app_obj.inform.emit('%s' % _("Optimizing the overlapping paths."))
+
                         for tool in obj_fin.tools:
                             lines = []
                             other_geo = []
                             for geo in obj_fin.tools[tool]['solid_geometry']:
                                 if isinstance(geo, LineString):
                                     lines.append(geo)
+                                elif isinstance(geo, MultiLineString):
+                                    for line in geo:
+                                        lines.append(line)
                                 else:
                                     other_geo.append(geo)
-                            fused_lines = list(unary_union(lines))
+
+                            if to_optimize is True:
+                                for idx, line in enumerate(lines):
+                                    for idx_s in range(idx+1, len(lines)):
+                                        line_mod = lines[idx_s]
+                                        dist = line.distance(line_mod)
+                                        if dist < 1e-8:
+                                            print("Disjoint %d: %d -> %s" % (idx, idx_s, str(dist)))
+                                            print("Distance %f" % dist)
+                                        res = snap(line_mod, line, tolerance=1e-7)
+                                        if res and not res.is_empty:
+                                            lines[idx_s] = res
+
+                            fused_lines = linemerge(lines)
+                            fused_lines = [unary_union(fused_lines)]
+
                             obj_fin.tools[tool]['solid_geometry'] = fused_lines + other_geo
 
+                        if to_optimize is True:
+                            app_obj.inform.emit('%s' % _("Optimization complete."))
+
                     if panel_type == 'gerber':
                         app_obj.inform.emit('%s' % _("Generating panel ... Adding the Gerber code."))
                         obj_fin.source_file = self.app.export_gerber(obj_name=self.outname, filename=None,
@@ -766,6 +802,16 @@ class PanelizeUI:
         form_layout.addRow(self.panel_type_label)
         form_layout.addRow(self.panel_type_radio)
 
+        # Path optimization
+        self.optimization_cb = FCCheckBox('%s' % _("Path Optimization"))
+        self.optimization_cb.setToolTip(
+            _("Active only for Geometry panel type.\n"
+              "When checked the application will find\n"
+              "any two overlapping Line elements in the panel\n"
+              "and remove the overlapping parts, keeping only one of them.")
+        )
+        form_layout.addRow(self.optimization_cb)
+
         # Constrains
         self.constrain_cb = FCCheckBox('%s:' % _("Constrain panel within"))
         self.constrain_cb.setToolTip(
@@ -839,6 +885,13 @@ class PanelizeUI:
 
         # #################################### FINSIHED GUI ###########################
         # #############################################################################
+        self.panel_type_radio.activated_custom.connect(self.on_panel_type)
+
+    def on_panel_type(self, val):
+        if val == 'geometry':
+            self.optimization_cb.setDisabled(False)
+        else:
+            self.optimization_cb.setDisabled(True)
 
     def confirmation_message(self, accepted, minval, maxval):
         if accepted is False:

+ 1 - 0
defaults.py

@@ -506,6 +506,7 @@ class FlatCAMDefaults:
         "tools_panelize_spacing_rows": 0.0,
         "tools_panelize_columns": 1,
         "tools_panelize_rows": 1,
+        "tools_panelize_optimization": True,
         "tools_panelize_constrain": False,
         "tools_panelize_constrainx": 200.0,
         "tools_panelize_constrainy": 290.0,