Browse Source

- remade the Tool Cutout to work on panels
- remade the Tool Cutour such that on multiple applications on the same object it will yield the same result

Marius Stanciu 6 years ago
parent
commit
3713a5d78f
2 changed files with 216 additions and 177 deletions
  1. 5 0
      README.md
  2. 211 177
      flatcamTools/ToolCutOut.py

+ 5 - 0
README.md

@@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing.
 
 
 =================================================
 =================================================
 
 
+17.05.2019
+
+- remade the Tool Cutout to work on panels
+- remade the Tool Cutour such that on multiple applications on the same object it will yield the same result
+
 16.05.2019
 16.05.2019
 
 
 - Gerber Export: made sure that if some of the coordinates in a Gerber object geometry are repeating then the resulting Gerber code include only one copy
 - Gerber Export: made sure that if some of the coordinates in a Gerber object geometry are repeating then the resulting Gerber code include only one copy

+ 211 - 177
flatcamTools/ToolCutOut.py

@@ -114,7 +114,8 @@ class CutOut(FlatCAMTool):
         self.convex_box = FCCheckBox()
         self.convex_box = FCCheckBox()
         self.convex_box_label = QtWidgets.QLabel(_("Convex Sh.:"))
         self.convex_box_label = QtWidgets.QLabel(_("Convex Sh.:"))
         self.convex_box_label.setToolTip(
         self.convex_box_label.setToolTip(
-            _("Create a convex shape surrounding the entire PCB.")
+            _("Create a convex shape surrounding the entire PCB.\n"
+              "Used only if the source object type is Gerber.")
         )
         )
         form_layout.addRow(self.convex_box_label, self.convex_box)
         form_layout.addRow(self.convex_box_label, self.convex_box)
 
 
@@ -327,9 +328,9 @@ class CutOut(FlatCAMTool):
 
 
     def on_freeform_cutout(self):
     def on_freeform_cutout(self):
 
 
-        def subtract_rectangle(obj_, x0, y0, x1, y1):
-            pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
-            obj_.subtract_polygon(pts)
+        # def subtract_rectangle(obj_, x0, y0, x1, y1):
+        #     pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
+        #     obj_.subtract_polygon(pts)
 
 
         name = self.obj_combo.currentText()
         name = self.obj_combo.currentText()
 
 
@@ -400,77 +401,83 @@ class CutOut(FlatCAMTool):
 
 
         convex_box = self.convex_box.get_value()
         convex_box = self.convex_box.get_value()
 
 
-        # Get min and max data for each object as we just cut rectangles across X or Y
-        xmin, ymin, xmax, ymax = cutout_obj.bounds()
-        px = 0.5 * (xmin + xmax) + margin
-        py = 0.5 * (ymin + ymax) + margin
-        lenghtx = (xmax - xmin) + (margin * 2)
-        lenghty = (ymax - ymin) + (margin * 2)
-
         gapsize = gapsize / 2 + (dia / 2)
         gapsize = gapsize / 2 + (dia / 2)
 
 
-        if isinstance(cutout_obj, FlatCAMGeometry):
-            # rename the obj name so it can be identified as cutout
-            cutout_obj.options["name"] += "_cutout"
-        else:
-            def geo_init(geo_obj, app_obj):
+        def geo_init(geo_obj, app_obj):
+            solid_geo = []
+
+            if isinstance(cutout_obj, FlatCAMGerber):
                 if convex_box:
                 if convex_box:
-                    geo = cutout_obj.solid_geometry.convex_hull
-                    geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
+                    object_geo = cutout_obj.solid_geometry.convex_hull
                 else:
                 else:
-                    geo = cutout_obj.solid_geometry
-                    geo = geo.buffer(margin + abs(dia / 2))
+                    object_geo = cutout_obj.solid_geometry
+            else:
+                object_geo = cutout_obj.solid_geometry
 
 
-                    if isinstance(geo, Polygon):
-                        geo_obj.solid_geometry = deepcopy(geo.exterior)
-                    elif isinstance(geo, MultiPolygon):
-                        solid_geo = []
-                        for poly in geo:
-                            solid_geo.append(poly.exterior)
-                        geo_obj.solid_geometry = deepcopy(solid_geo)
-
-            outname = cutout_obj.options["name"] + "_cutout"
-            self.app.new_object('geometry', outname, geo_init)
-
-            cutout_obj = self.app.collection.get_by_name(outname)
-
-        if gaps == '8' or gaps == '2LR':
-            subtract_rectangle(cutout_obj,
-                               xmin - gapsize,  # botleft_x
-                               py - gapsize + lenghty / 4,  # botleft_y
-                               xmax + gapsize,  # topright_x
-                               py + gapsize + lenghty / 4)  # topright_y
-            subtract_rectangle(cutout_obj,
-                               xmin - gapsize,
-                               py - gapsize - lenghty / 4,
-                               xmax + gapsize,
-                               py + gapsize - lenghty / 4)
-
-        if gaps == '8' or gaps == '2TB':
-            subtract_rectangle(cutout_obj,
-                               px - gapsize + lenghtx / 4,
-                               ymin - gapsize,
-                               px + gapsize + lenghtx / 4,
-                               ymax + gapsize)
-            subtract_rectangle(cutout_obj,
-                               px - gapsize - lenghtx / 4,
-                               ymin - gapsize,
-                               px + gapsize - lenghtx / 4,
-                               ymax + gapsize)
-
-        if gaps == '4' or gaps == 'LR':
-            subtract_rectangle(cutout_obj,
-                               xmin - gapsize,
-                               py - gapsize,
-                               xmax + gapsize,
-                               py + gapsize)
-
-        if gaps == '4' or gaps == 'TB':
-            subtract_rectangle(cutout_obj,
-                               px - gapsize,
-                               ymin - gapsize,
-                               px + gapsize,
-                               ymax + gapsize)
+            try:
+                _ = iter(object_geo)
+            except TypeError:
+                object_geo = [object_geo]
+
+            for geo in object_geo:
+                if isinstance(cutout_obj, FlatCAMGerber):
+                    geo = (geo.buffer(margin + abs(dia / 2))).exterior
+
+                # Get min and max data for each object as we just cut rectangles across X or Y
+                xmin, ymin, xmax, ymax = geo.bounds
+                px = 0.5 * (xmin + xmax) + margin
+                py = 0.5 * (ymin + ymax) + margin
+                lenx = (xmax - xmin) + (margin * 2)
+                leny = (ymax - ymin) + (margin * 2)
+
+                if gaps == '8' or gaps == '2LR':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      xmin - gapsize,  # botleft_x
+                                                      py - gapsize + leny / 4,  # botleft_y
+                                                      xmax + gapsize,  # topright_x
+                                                      py + gapsize + leny / 4)  # topright_y
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      xmin - gapsize,
+                                                      py - gapsize - leny / 4,
+                                                      xmax + gapsize,
+                                                      py + gapsize - leny / 4)
+
+                if gaps == '8' or gaps == '2TB':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      px - gapsize + lenx / 4,
+                                                      ymin - gapsize,
+                                                      px + gapsize + lenx / 4,
+                                                      ymax + gapsize)
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      px - gapsize - lenx / 4,
+                                                      ymin - gapsize,
+                                                      px + gapsize - lenx / 4,
+                                                      ymax + gapsize)
+
+                if gaps == '4' or gaps == 'LR':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      xmin - gapsize,
+                                                      py - gapsize,
+                                                      xmax + gapsize,
+                                                      py + gapsize)
+
+                if gaps == '4' or gaps == 'TB':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      px - gapsize,
+                                                      ymin - gapsize,
+                                                      px + gapsize,
+                                                      ymax + gapsize)
+
+                try:
+                    for g in geo:
+                        solid_geo.append(g)
+                except TypeError:
+                    solid_geo.append(geo)
+
+            geo_obj.solid_geometry = deepcopy(solid_geo)
+
+        outname = cutout_obj.options["name"] + "_cutout"
+        self.app.new_object('geometry', outname, geo_init)
 
 
         cutout_obj.plot()
         cutout_obj.plot()
         self.app.inform.emit(_("[success] Any form CutOut operation finished."))
         self.app.inform.emit(_("[success] Any form CutOut operation finished."))
@@ -479,9 +486,9 @@ class CutOut(FlatCAMTool):
 
 
     def on_rectangular_cutout(self):
     def on_rectangular_cutout(self):
 
 
-        def subtract_rectangle(obj_, x0, y0, x1, y1):
-            pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
-            obj_.subtract_polygon(pts)
+        # def subtract_rectangle(obj_, x0, y0, x1, y1):
+        #     pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
+        #     obj_.subtract_polygon(pts)
 
 
         name = self.obj_combo.currentText()
         name = self.obj_combo.currentText()
 
 
@@ -550,75 +557,75 @@ class CutOut(FlatCAMTool):
             return
             return
 
 
         # Get min and max data for each object as we just cut rectangles across X or Y
         # Get min and max data for each object as we just cut rectangles across X or Y
-        if isinstance(cutout_obj.solid_geometry, Polygon):
-            xmin, ymin, xmax, ymax = cutout_obj.bounds()
-            geo = box(xmin, ymin, xmax, ymax)
-        elif isinstance(cutout_obj.solid_geometry, MultiPolygon):
-            geo = []
-            for poly in cutout_obj.solid_geometry:
-                xmin, ymin, xmax, ymax = poly.bounds
-                poly_geo = box(xmin, ymin, xmax, ymax)
-                geo.append(poly_geo)
 
 
-        px = 0.5 * (xmin + xmax) + margin
-        py = 0.5 * (ymin + ymax) + margin
-        lenghtx = (xmax - xmin) + (margin * 2)
-        lenghty = (ymax - ymin) + (margin * 2)
         gapsize = gapsize / 2 + (dia / 2)
         gapsize = gapsize / 2 + (dia / 2)
 
 
         def geo_init(geo_obj, app_obj):
         def geo_init(geo_obj, app_obj):
-            if isinstance(geo, list):
-                solid_geo = []
-                for subgeo in geo:
-                    solid_geo.append(subgeo.buffer(margin + abs(dia / 2)))
-                geo_obj.solid_geometry = deepcopy(solid_geo)
-            else:
-                geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
+            solid_geo = []
+
+            for poly in cutout_obj.solid_geometry:
+                xmin, ymin, xmax, ymax = poly.bounds
+                geo = box(xmin, ymin, xmax, ymax)
+
+                # if Gerber create a buffer at a distance
+                # if Geometry then cut through the geometry
+                if isinstance(cutout_obj, FlatCAMGerber):
+                    geo = geo.buffer(margin + abs(dia / 2))
+
+                px = 0.5 * (xmin + xmax) + margin
+                py = 0.5 * (ymin + ymax) + margin
+                lenx = (xmax - xmin) + (margin * 2)
+                leny = (ymax - ymin) + (margin * 2)
+
+                if gaps == '8' or gaps == '2LR':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      xmin - gapsize,  # botleft_x
+                                                      py - gapsize + leny / 4,  # botleft_y
+                                                      xmax + gapsize,  # topright_x
+                                                      py + gapsize + leny / 4)  # topright_y
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      xmin - gapsize,
+                                                      py - gapsize - leny / 4,
+                                                      xmax + gapsize,
+                                                      py + gapsize - leny / 4)
+
+                if gaps == '8' or gaps == '2TB':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      px - gapsize + lenx / 4,
+                                                      ymin - gapsize,
+                                                      px + gapsize + lenx / 4,
+                                                      ymax + gapsize)
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      px - gapsize - lenx / 4,
+                                                      ymin - gapsize,
+                                                      px + gapsize - lenx / 4,
+                                                      ymax + gapsize)
+
+                if gaps == '4' or gaps == 'LR':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      xmin - gapsize,
+                                                      py - gapsize,
+                                                      xmax + gapsize,
+                                                      py + gapsize)
+
+                if gaps == '4' or gaps == 'TB':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      px - gapsize,
+                                                      ymin - gapsize,
+                                                      px + gapsize,
+                                                      ymax + gapsize)
+                try:
+                    for g in geo:
+                        solid_geo.append(g)
+                except TypeError:
+                    solid_geo.append(geo)
+
+            geo_obj.solid_geometry = deepcopy(solid_geo)
 
 
         outname = cutout_obj.options["name"] + "_cutout"
         outname = cutout_obj.options["name"] + "_cutout"
         self.app.new_object('geometry', outname, geo_init)
         self.app.new_object('geometry', outname, geo_init)
 
 
-        cutout_obj = self.app.collection.get_by_name(outname)
-
-        if gaps == '8' or gaps == '2LR':
-            subtract_rectangle(cutout_obj,
-                               xmin - gapsize,  # botleft_x
-                               py - gapsize + lenghty / 4,  # botleft_y
-                               xmax + gapsize,  # topright_x
-                               py + gapsize + lenghty / 4)  # topright_y
-            subtract_rectangle(cutout_obj,
-                               xmin - gapsize,
-                               py - gapsize - lenghty / 4,
-                               xmax + gapsize,
-                               py + gapsize - lenghty / 4)
-
-        if gaps == '8' or gaps == '2TB':
-            subtract_rectangle(cutout_obj,
-                               px - gapsize + lenghtx / 4,
-                               ymin - gapsize,
-                               px + gapsize + lenghtx / 4,
-                               ymax + gapsize)
-            subtract_rectangle(cutout_obj,
-                               px - gapsize - lenghtx / 4,
-                               ymin - gapsize,
-                               px + gapsize - lenghtx / 4,
-                               ymax + gapsize)
-
-        if gaps == '4' or gaps == 'LR':
-            subtract_rectangle(cutout_obj,
-                               xmin - gapsize,
-                               py - gapsize,
-                               xmax + gapsize,
-                               py + gapsize)
-
-        if gaps == '4' or gaps == 'TB':
-            subtract_rectangle(cutout_obj,
-                               px - gapsize,
-                               ymin - gapsize,
-                               px + gapsize,
-                               ymax + gapsize)
-
-        cutout_obj.plot()
+        # cutout_obj.plot()
         self.app.inform.emit(_("[success] Any form CutOut operation finished."))
         self.app.inform.emit(_("[success] Any form CutOut operation finished."))
         self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
         self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
         self.app.should_we_save = True
         self.app.should_we_save = True
@@ -847,58 +854,27 @@ class CutOut(FlatCAMTool):
             self.app.geo_editor.tool_shape.clear(update=True)
             self.app.geo_editor.tool_shape.clear(update=True)
             self.app.geo_editor.tool_shape.enabled = False
             self.app.geo_editor.tool_shape.enabled = False
 
 
-    def flatten(self, geometry=None, reset=True, pathonly=False):
+    def subtract_poly_from_geo(self, solid_geo, x0, y0, x1, y1):
         """
         """
-        Creates a list of non-iterable linear geometry objects.
-        Polygons are expanded into its exterior and interiors if specified.
-
-        Results are placed in self.flat_geometry
-
-        :param geometry: Shapely type or list or list of list of such.
-        :param reset: Clears the contents of self.flat_geometry.
-        :param pathonly: Expands polygons into linear elements.
-        """
-
-        if geometry is None:
-            geometry = self.solid_geometry
-
-        if reset:
-            self.flat_geometry = []
-
-        # If iterable, expand recursively.
-        try:
-            for geo in geometry:
-                if geo is not None:
-                    self.flatten(geometry=geo,
-                                 reset=False,
-                                 pathonly=pathonly)
-
-        # Not iterable, do the actual indexing and add.
-        except TypeError:
-            if pathonly and type(geometry) == Polygon:
-                self.flat_geometry.append(geometry.exterior)
-                self.flatten(geometry=geometry.interiors,
-                             reset=False,
-                             pathonly=True)
-            else:
-                self.flat_geometry.append(geometry)
-
-        return self.flat_geometry
-
-    def subtract_poly_from_geo(self, solid_geo, points):
-        """
-        Subtract polygon from the given object. This only operates on the paths in the original geometry,
+        Subtract polygon made from points from the given object.
+        This only operates on the paths in the original geometry,
         i.e. it converts polygons into paths.
         i.e. it converts polygons into paths.
 
 
-        :param points: The vertices of the polygon.
+        :param x0: x coord for lower left vertice of the polygon.
+        :param y0: y coord for lower left vertice of the polygon.
+        :param x1: x coord for upper right vertice of the polygon.
+        :param y1: y coord for upper right vertice of the polygon.
+
         :param solid_geo: Geometry from which to substract. If none, use the solid_geomety property of the object
         :param solid_geo: Geometry from which to substract. If none, use the solid_geomety property of the object
         :return: none
         :return: none
         """
         """
+        points = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
 
 
         # pathonly should be allways True, otherwise polygons are not subtracted
         # pathonly should be allways True, otherwise polygons are not subtracted
-        flat_geometry = self.flatten(geometry=solid_geo, pathonly=True)
+        flat_geometry = flatten(geometry=solid_geo)
 
 
         log.debug("%d paths" % len(flat_geometry))
         log.debug("%d paths" % len(flat_geometry))
+
         polygon = Polygon(points)
         polygon = Polygon(points)
         toolgeo = cascaded_union(polygon)
         toolgeo = cascaded_union(polygon)
         diffs = []
         diffs = []
@@ -908,7 +884,65 @@ class CutOut(FlatCAMTool):
             else:
             else:
                 log.warning("Not implemented.")
                 log.warning("Not implemented.")
 
 
-        return cascaded_union(diffs)
+        return unary_union(diffs)
 
 
     def reset_fields(self):
     def reset_fields(self):
         self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+
+
+def flatten(geometry):
+    """
+    Creates a list of non-iterable linear geometry objects.
+    Polygons are expanded into its exterior and interiors.
+
+    Results are placed in self.flat_geometry
+
+    :param geometry: Shapely type or list or list of list of such.
+    """
+    flat_geo = []
+    try:
+        for geo in geometry:
+            if type(geo) == Polygon:
+                flat_geo.append(geo.exterior)
+                for subgeo in geo.interiors:
+                    flat_geo.append(subgeo)
+            else:
+                flat_geo.append(geo)
+    except TypeError:
+        if type(geometry) == Polygon:
+            flat_geo.append(geometry.exterior)
+            for subgeo in geometry.interiors:
+                flat_geo.append(subgeo)
+        else:
+            flat_geo.append(geometry)
+
+    return flat_geo
+
+
+def recursive_bounds(geometry):
+    """
+    Returns coordinates of rectangular bounds
+    of geometry: (xmin, ymin, xmax, ymax).
+    """
+
+    # now it can get bounds for nested lists of objects
+
+    def bounds_rec(obj):
+        try:
+            minx = Inf
+            miny = Inf
+            maxx = -Inf
+            maxy = -Inf
+
+            for k in obj:
+                minx_, miny_, maxx_, maxy_ = bounds_rec(k)
+                minx = min(minx, minx_)
+                miny = min(miny, miny_)
+                maxx = max(maxx, maxx_)
+                maxy = max(maxy, maxy_)
+            return minx, miny, maxx, maxy
+        except TypeError:
+            # it's a Shapely object, return it's bounds
+            return obj.bounds
+
+    return bounds_rec(geometry)