Просмотр исходного кода

- made confirmation messages for the values that are modified not to be printed in the Shell
- Isolation Tool: working on the Rest machining: almost there, perhaps I will use multiprocessing

Marius Stanciu 5 лет назад
Родитель
Сommit
15d94404a7
5 измененных файлов с 250 добавлено и 125 удалено
  1. 9 6
      AppGUI/ObjectUI.py
  2. 9 5
      AppTool.py
  3. 221 108
      AppTools/ToolIsolation.py
  4. 6 6
      App_Main.py
  5. 5 0
      CHANGELOG.md

+ 9 - 6
AppGUI/ObjectUI.py

@@ -153,17 +153,20 @@ class ObjectUI(QtWidgets.QWidget):
     
     def confirmation_message(self, accepted, minval, maxval):
         if accepted is False:
-            self.app.inform.emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' %
-                                 (_("Edited value is out of range"), self.decimals, minval, self.decimals, maxval))
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
+                                                                                  self.decimals,
+                                                                                  minval,
+                                                                                  self.decimals,
+                                                                                  maxval), False)
         else:
-            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
 
     def confirmation_message_int(self, accepted, minval, maxval):
         if accepted is False:
-            self.app.inform.emit('[WARNING_NOTCL] %s: [%d, %d]' %
-                                 (_("Edited value is out of range"), minval, maxval))
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                 (_("Edited value is out of range"), minval, maxval), False)
         else:
-            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
 
 
 class GerberObjectUI(ObjectUI):

+ 9 - 5
AppTool.py

@@ -277,16 +277,20 @@ class AppTool(QtWidgets.QWidget):
 
     def confirmation_message(self, accepted, minval, maxval):
         if accepted is False:
-            self.app.inform.emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' %
-                                 (_("Edited value is out of range"), self.decimals, minval, self.decimals, maxval))
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
+                                                                                  self.decimals,
+                                                                                  minval,
+                                                                                  self.decimals,
+                                                                                  maxval), False)
         else:
-            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
 
     def confirmation_message_int(self, accepted, minval, maxval):
         if accepted is False:
-            self.app.inform.emit('[WARNING_NOTCL] %s: [%d, %d]' % (_("Edited value is out of range"), minval, maxval))
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                            (_("Edited value is out of range"), minval, maxval), False)
         else:
-            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
 
     def sizeHint(self):
         """

+ 221 - 108
AppTools/ToolIsolation.py

@@ -136,7 +136,7 @@ class ToolIsolation(AppTool, Gerber):
             _("This is the Tool Number.\n"
               "Non copper clearing will start with the tool with the biggest \n"
               "diameter, continuing until there are no more tools.\n"
-              "Only tools that create NCC clearing geometry will still be present\n"
+              "Only tools that create Isolation geometry will still be present\n"
               "in the resulting geometry. This is because with some tools\n"
               "this function will not be able to create painting geometry.")
         )
@@ -163,19 +163,19 @@ class ToolIsolation(AppTool, Gerber):
         self.tools_box.addLayout(grid1)
 
         # Tool order
-        self.ncc_order_label = QtWidgets.QLabel('%s:' % _('Tool order'))
-        self.ncc_order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
-                                          "'No' --> means that the used order is the one in the tool table\n"
-                                          "'Forward' --> means that the tools will be ordered from small to big\n"
-                                          "'Reverse' --> means that the tools will ordered from big to small\n\n"
-                                          "WARNING: using rest machining will automatically set the order\n"
-                                          "in reverse and disable this control."))
+        self.order_label = QtWidgets.QLabel('%s:' % _('Tool order'))
+        self.order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
+                                      "'No' --> means that the used order is the one in the tool table\n"
+                                      "'Forward' --> means that the tools will be ordered from small to big\n"
+                                      "'Reverse' --> means that the tools will ordered from big to small\n\n"
+                                      "WARNING: using rest machining will automatically set the order\n"
+                                      "in reverse and disable this control."))
 
         self.order_radio = RadioSet([{'label': _('No'), 'value': 'no'},
                                      {'label': _('Forward'), 'value': 'fwd'},
                                      {'label': _('Reverse'), 'value': 'rev'}])
 
-        grid1.addWidget(self.ncc_order_label, 1, 0)
+        grid1.addWidget(self.order_label, 1, 0)
         grid1.addWidget(self.order_radio, 1, 1)
 
         separator_line = QtWidgets.QFrame()
@@ -1082,10 +1082,7 @@ class ToolIsolation(AppTool, Gerber):
 
         sorted_tools = []
         for k, v in self.iso_tools.items():
-            if self.units == "IN":
-                sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
-            else:
-                sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
+            sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
 
         order = self.order_radio.get_value()
         if order == 'fwd':
@@ -1323,14 +1320,14 @@ class ToolIsolation(AppTool, Gerber):
     def on_rest_machining_check(self, state):
         if state:
             self.order_radio.set_value('rev')
-            self.ncc_order_label.setDisabled(True)
+            self.order_label.setDisabled(True)
             self.order_radio.setDisabled(True)
 
             self.old_combine_state = self.combine_passes_cb.get_value()
             self.combine_passes_cb.set_value(True)
             self.combine_passes_cb.setDisabled(True)
         else:
-            self.ncc_order_label.setDisabled(False)
+            self.order_label.setDisabled(False)
             self.order_radio.setDisabled(False)
 
             self.combine_passes_cb.set_value(self.old_combine_state)
@@ -1883,101 +1880,102 @@ class ToolIsolation(AppTool, Gerber):
         total_solid_geometry = []
 
         iso_name = iso_obj.options["name"]
-        geometry = iso2geo
+        work_geo = iso_obj.solid_geometry if iso2geo is None else iso2geo
 
-        for tool in tools_storage:
-            tool_dia = tools_storage[tool]['tooldia']
-            tool_type = tools_storage[tool]['tool_type']
-            tool_data = tools_storage[tool]['data']
-
-            to_follow = tool_data['tools_iso_follow']
+        sorted_tools = []
+        for k, v in self.iso_tools.items():
+            sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
 
-            work_geo = geometry
-            if work_geo is None:
-                work_geo = iso_obj.follow_geometry if to_follow else iso_obj.solid_geometry
+        order = self.order_radio.get_value()
+        if order == 'fwd':
+            sorted_tools.sort(reverse=False)
+        elif order == 'rev':
+            sorted_tools.sort(reverse=True)
+        else:
+            pass
 
-            iso_t = {
-                'ext': 0,
-                'int': 1,
-                'full': 2
-            }[tool_data['tools_iso_isotype']]
+        for sorted_tool in sorted_tools:
+            for tool in tools_storage:
+                if float('%.*f' % (self.decimals, tools_storage[tool]['tooldia'])) == sorted_tool:
 
-            passes = tool_data['tools_iso_passes']
-            overlap = tool_data['tools_iso_overlap']
-            overlap /= 100.0
+                    tool_dia = tools_storage[tool]['tooldia']
+                    tool_type = tools_storage[tool]['tool_type']
+                    tool_data = tools_storage[tool]['data']
 
-            milling_type = tool_data['tools_iso_milling_type']
+                    iso_t = {
+                        'ext': 0,
+                        'int': 1,
+                        'full': 2
+                    }[tool_data['tools_iso_isotype']]
 
-            iso_except = tool_data['tools_iso_isoexcept']
+                    passes = tool_data['tools_iso_passes']
+                    overlap = tool_data['tools_iso_overlap']
+                    overlap /= 100.0
 
-            outname = "%s_%.*f" % (iso_obj.options["name"], self.decimals, float(tool_dia))
+                    milling_type = tool_data['tools_iso_milling_type']
+                    # if milling type is climb then the move is counter-clockwise around features
+                    mill_dir = True if milling_type == 'cl' else False
 
-            iso_name = outname + "_iso"
-            if iso_t == 0:
-                iso_name = outname + "_ext_iso"
-            elif iso_t == 1:
-                iso_name = outname + "_int_iso"
+                    iso_except = tool_data['tools_iso_isoexcept']
 
-            # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
-            if tool_type.lower() == 'v':
-                new_cutz = self.ui.cutz_spinner.get_value()
-                new_vtipdia = self.ui.tipdia_spinner.get_value()
-                new_vtipangle = self.ui.tipangle_spinner.get_value()
-                tool_type = 'V'
-                tool_data.update({
-                    "name": iso_name,
-                    "cutz": new_cutz,
-                    "vtipdia": new_vtipdia,
-                    "vtipangle": new_vtipangle,
-                })
-            else:
-                tool_data.update({
-                    "name": iso_name,
-                })
-                tool_type = 'C1'
+                    outname = "%s_%.*f" % (iso_obj.options["name"], self.decimals, float(tool_dia))
 
-            solid_geo = []
-            for nr_pass in range(passes):
-                iso_offset = tool_dia * ((2 * nr_pass + 1) / 2.0000001) - (nr_pass * overlap * tool_dia)
+                    iso_name = outname + "_iso"
+                    if iso_t == 0:
+                        iso_name = outname + "_ext_iso"
+                    elif iso_t == 1:
+                        iso_name = outname + "_int_iso"
 
-                # if milling type is climb then the move is counter-clockwise around features
-                mill_dir = 1 if milling_type == 'cl' else 0
+                    # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
+                    if tool_type.lower() == 'v':
+                        new_cutz = self.ui.cutz_spinner.get_value()
+                        new_vtipdia = self.ui.tipdia_spinner.get_value()
+                        new_vtipangle = self.ui.tipangle_spinner.get_value()
+                        tool_type = 'V'
+                        tool_data.update({
+                            "name": iso_name,
+                            "cutz": new_cutz,
+                            "vtipdia": new_vtipdia,
+                            "vtipangle": new_vtipangle,
+                        })
+                    else:
+                        tool_data.update({
+                            "name": iso_name,
+                        })
+                        tool_type = 'C1'
 
-                iso_geo = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
-                                                 follow=to_follow, nr_passes=nr_pass)
-                if iso_geo == 'fail':
-                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
-                    continue
-                try:
-                    for geo in iso_geo:
-                        solid_geo.append(geo)
-                except TypeError:
-                    solid_geo.append(iso_geo)
+                    solid_geo, work_geo = self.generate_rest_geometry(geometry=work_geo, tooldia=tool_dia,
+                                                                      passes=passes, overlap=overlap, invert=mill_dir,
+                                                                      env_iso_type=iso_t)
 
-                # ############################################################
-                # ########## AREA SUBTRACTION ################################
-                # ############################################################
-                if iso_except:
-                    self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
-                    solid_geo = self.area_subtraction(solid_geo)
+                    # ############################################################
+                    # ########## AREA SUBTRACTION ################################
+                    # ############################################################
+                    if iso_except:
+                        self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
+                        solid_geo = self.area_subtraction(solid_geo)
 
-                if lim_area:
-                    self.app.proc_container.update_view_text(' %s' % _("Intersecting Geo"))
-                    solid_geo = self.area_intersection(solid_geo, intersection_geo=lim_area)
+                    if lim_area:
+                        self.app.proc_container.update_view_text(' %s' % _("Intersecting Geo"))
+                        solid_geo = self.area_intersection(solid_geo, intersection_geo=lim_area)
+
+                    tools_storage.update({
+                        tool: {
+                            'tooldia': float(tool_dia),
+                            'offset': 'Path',
+                            'offset_value': 0.0,
+                            'type': _('Rough'),
+                            'tool_type': tool_type,
+                            'data': tool_data,
+                            'solid_geometry': deepcopy(solid_geo)
+                        }
+                    })
 
-            tools_storage.update({
-                tool: {
-                    'tooldia': float(tool_dia),
-                    'offset': 'Path',
-                    'offset_value': 0.0,
-                    'type': _('Rough'),
-                    'tool_type': tool_type,
-                    'data': tool_data,
-                    'solid_geometry': deepcopy(solid_geo)
-                }
-            })
+                    total_solid_geometry += solid_geo
 
-            total_solid_geometry += solid_geo
+                    # if the geometry is all isolated
+                    if not work_geo:
+                        break
 
         def iso_init(geo_obj, app_obj):
             geo_obj.options["cnctooldia"] = str(tool_dia)
@@ -1989,11 +1987,12 @@ class ToolIsolation(AppTool, Gerber):
             if len(tools_storage) > 1:
                 geo_obj.multigeo = True
             else:
-                if to_follow:
-                    passes_no = 1
-                else:
+                try:
                     passes_no = float(tools_storage[0]['data']['tools_iso_passes'])
-                geo_obj.multigeo = True if passes_no > 1 else False
+                    geo_obj.multigeo = True if passes_no > 1 else False
+                except KeyError as e:
+                    log.debug("ToolIsolation.combined_rest.iso_init() --> KeyError: %s" % str(e))
+                    geo_obj.multogeo = False
 
             # detect if solid_geometry is empty and this require list flattening which is "heavy"
             # or just looking in the lists (they are one level depth) and if any is not empty
@@ -2018,6 +2017,20 @@ class ToolIsolation(AppTool, Gerber):
 
         self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
 
+        # the tools are finished but the isolation is not finished therefore it failed
+        if work_geo:
+            self.app.inform.emit("[WARNING] %s" % _("Partial failure. The geometry was processed with all tools.\n"
+                                                    "But there are still un-isolated geometry elements. "
+                                                    "Try to include a tool with smaller diameter."))
+            self.app.shell_message(msg=_("The following are coordinates for the copper features "
+                                         "that could not be isolated:"))
+            msg = ''
+            for geo in work_geo:
+                pt = geo.representative_point()
+                coords = '(%s, %s), ' % (str(pt.x), str(pt.y))
+                msg += coords
+            self.app.shell_message(msg=msg)
+
     def combined_normal(self, iso_obj, iso2geo, tools_storage, lim_area, plot=True):
         """
 
@@ -2112,16 +2125,16 @@ class ToolIsolation(AppTool, Gerber):
                 except TypeError:
                     solid_geo.append(iso_geo)
 
-                # ############################################################
-                # ########## AREA SUBTRACTION ################################
-                # ############################################################
-                if iso_except:
-                    self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
-                    solid_geo = self.area_subtraction(solid_geo)
+            # ############################################################
+            # ########## AREA SUBTRACTION ################################
+            # ############################################################
+            if iso_except:
+                self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
+                solid_geo = self.area_subtraction(solid_geo)
 
-                if lim_area:
-                    self.app.proc_container.update_view_text(' %s' % _("Intersecting Geo"))
-                    solid_geo = self.area_intersection(solid_geo, intersection_geo=lim_area)
+            if lim_area:
+                self.app.proc_container.update_view_text(' %s' % _("Intersecting Geo"))
+                solid_geo = self.area_intersection(solid_geo, intersection_geo=lim_area)
 
             tools_storage.update({
                 tool: {
@@ -2675,6 +2688,14 @@ class ToolIsolation(AppTool, Gerber):
     def poly2rings(poly):
         return [poly.exterior] + [interior for interior in poly.interiors]
 
+    @staticmethod
+    def poly2ext(poly):
+        return [poly.exterior]
+
+    @staticmethod
+    def poly2ints(poly):
+        return [interior for interior in poly.interiors]
+
     def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0):
         """
         Isolation_geometry produces an envelope that is going on the left of the geometry
@@ -2734,6 +2755,98 @@ class ToolIsolation(AppTool, Gerber):
                 return 'fail'
         return geom
 
+    @staticmethod
+    def generate_rest_geometry(geometry, tooldia, passes, overlap, invert, env_iso_type=2):
+        """
+        Will try to isolate the geometry and return a tuple made of list of paths made through isolation
+        and a list of Shapely Polygons that could not be isolated
+
+        :param geometry:        A list of Shapely Polygons to be isolated
+        :type geometry:         list
+        :param tooldia:         The tool diameter used to do the isolation
+        :type tooldia:          float
+        :param passes:          Number of passes that will made the isolation
+        :type passes:           int
+        :param overlap:         How much to overlap the previous pass; in percentage [0.00, 99.99]%
+        :type overlap:          float
+        :param invert:          If to invert the direction of the resulting isolated geometries
+        :type invert:           bool
+        :param env_iso_type:    can be either 0 = keep exteriors or 1 = keep interiors or 2 = keep all paths
+        :type env_iso_type:     int
+        :return:                Tuple made from list of isolating paths and list of not isolated Polygons
+        :rtype:                 tuple
+        """
+
+        isolated_geo = []
+        not_isolated_geo = []
+
+        work_geo = []
+
+        for idx, geo in enumerate(geometry):
+            good_pass_iso = []
+            start_idx = idx + 1
+
+            for nr_pass in range(passes):
+                iso_offset = tooldia * ((2 * nr_pass + 1) / 2.0) - (nr_pass * overlap * tooldia)
+                buf_chek = iso_offset * 2
+                check_geo = geo.buffer(buf_chek)
+
+                intersect_flag = False
+                # find if current pass for current geo is valid (no intersection with other geos))
+                for geo_search_idx in range(idx):
+                    if check_geo.intersects(geometry[geo_search_idx]):
+                        intersect_flag = True
+                        break
+
+                if intersect_flag == False:
+                    for geo_search_idx in range(start_idx, len(geometry)):
+                        if check_geo.intersects(geometry[geo_search_idx]):
+                            intersect_flag = True
+                            break
+
+                # if we had an intersection do nothing, else add the geo to the good pass isolations
+                if intersect_flag is False:
+                    good_pass_iso.append(geo.buffer(iso_offset))
+
+            if good_pass_iso:
+                work_geo += good_pass_iso
+            else:
+                not_isolated_geo.append(geo)
+
+        if invert:
+            try:
+                pl = []
+                for p in work_geo:
+                    if p is not None:
+                        if isinstance(p, Polygon):
+                            pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
+                        elif isinstance(p, LinearRing):
+                            pl.append(Polygon(p.coords[::-1]))
+                work_geo = MultiPolygon(pl)
+            except TypeError:
+                if isinstance(work_geo, Polygon) and work_geo is not None:
+                    work_geo = [Polygon(work_geo.exterior.coords[::-1], work_geo.interiors)]
+                elif isinstance(work_geo, LinearRing) and work_geo is not None:
+                    work_geo = [Polygon(work_geo.coords[::-1])]
+                else:
+                    log.debug("ToolIsolation.generate_rest_geometry() Error --> Unexpected Geometry %s" %
+                              type(work_geo))
+            except Exception as e:
+                log.debug("ToolIsolation.generate_rest_geometry() Error --> %s" % str(e))
+                return 'fail', 'fail'
+
+        if env_iso_type == 0:  # exterior
+            for geo in work_geo:
+                isolated_geo.append(geo.exterior)
+        elif env_iso_type == 1:  # interiors
+            for geo in work_geo:
+                isolated_geo += [interior for interior in geo.interiors]
+        else:  # exterior + interiors
+            for geo in work_geo:
+                isolated_geo += [geo.exterior] + [interior for interior in geo.interiors]
+
+        return isolated_geo, not_isolated_geo
+
     def on_iso_tool_add_from_db_executed(self, tool):
         """
         Here add the tool from DB  in the selected geometry object
@@ -2809,7 +2922,7 @@ class ToolIsolation(AppTool, Gerber):
                 'solid_geometry': []
             }
         })
-        self.iso_tools[tooluid]['data']['name'] = '_ncc'
+        self.iso_tools[tooluid]['data']['name'] = '_iso'
 
         self.app.inform.emit('[success] %s' % _("New tool added to Tool Table."))
 

+ 6 - 6
App_Main.py

@@ -9845,12 +9845,12 @@ class App(QtCore.QObject):
         """
         Shows a message on the FlatCAM Shell
 
-        :param msg: Message to display.
-        :param show: Opens the shell.
-        :param error: Shows the message as an error.
-        :param warning: Shows the message as an warning.
-        :param success: Shows the message as an success.
-        :param selected: Indicate that something was selected on canvas
+        :param msg:         Message to display.
+        :param show:        Opens the shell.
+        :param error:       Shows the message as an error.
+        :param warning:     Shows the message as an warning.
+        :param success:     Shows the message as an success.
+        :param selected:    Indicate that something was selected on canvas
         :return: None
         """
         if show:

+ 5 - 0
CHANGELOG.md

@@ -7,6 +7,11 @@ CHANGELOG for FlatCAM beta
 
 =================================================
 
+30.05.2020
+
+- made confirmation messages for the values that are modified not to be printed in the Shell
+- Isolation Tool: working on the Rest machining: almost there, perhaps I will use multiprocessing
+
 29.05.2020
 
 - fixed the Tool Isolation when using the 'follow' parameter