ソースを参照

Added Feed Method for clearing polygon. Some minor correction to Geometry.plot()

Juan Pablo Caram 11 年 前
コミット
fe2b4c7478
5 ファイル変更272 行追加73 行削除
  1. 1 0
      FlatCAMCommon.py
  2. 12 5
      FlatCAMDraw.py
  3. 108 36
      FlatCAMObj.py
  4. 45 2
      ObjectUI.py
  5. 106 30
      camlib.py

+ 1 - 0
FlatCAMCommon.py

@@ -37,3 +37,4 @@ class LoudDict(dict):
         """
 
         self.callback = callback
+

+ 12 - 5
FlatCAMDraw.py

@@ -383,11 +383,15 @@ class FlatCAMDraw(QtCore.QObject):
         :param fcgeometry: FlatCAMGeometry
         :return: None
         """
-        try:
-            _ = iter(fcgeometry.solid_geometry)
-            geometry = fcgeometry.solid_geometry
-        except TypeError:
-            geometry = [fcgeometry.solid_geometry]
+
+        if fcgeometry.solid_geometry is None:
+            geometry = []
+        else:
+            try:
+                _ = iter(fcgeometry.solid_geometry)
+                geometry = fcgeometry.solid_geometry
+            except TypeError:
+                geometry = [fcgeometry.solid_geometry]
 
         # Delete contents of editor.
         self.shape_buffer = []
@@ -650,6 +654,9 @@ class FlatCAMDraw(QtCore.QObject):
         self.app.log.debug("plot_all()")
         self.axes.cla()
         for shape in self.shape_buffer:
+            if shape['geometry'] is None:  # TODO: This shouldn't have happened
+                continue
+
             if shape['utility']:
                 self.plot_shape(geometry=shape['geometry'], linespec='k--', linewidth=1)
                 continue

+ 108 - 36
FlatCAMObj.py

@@ -567,6 +567,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             "travelz": 0.1,
             "feedrate": 5.0,
             # "toolselection": ""
+            "tooldia": 0.1
         })
 
         # TODO: Document this.
@@ -613,6 +614,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             "travelz": self.ui.travelz_entry,
             "feedrate": self.ui.feedrate_entry,
             # "toolselection": self.ui.tools_entry
+            "tooldia": self.ui.tooldia_entry
         })
 
         assert isinstance(self.ui, ExcellonObjectUI)
@@ -620,13 +622,54 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
         self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
         # self.ui.choose_tools_button.clicked.connect(self.show_tool_chooser)
         self.ui.generate_cnc_button.clicked.connect(self.on_create_cncjob_button_click)
+        self.ui.generate_milling_button.clicked.connect(self.on_generate_milling_button_click)
+
+    def get_selected_tools_list(self):
+        """
+        Returns the keys to the self.tools dictionary corresponding
+        to the selections on the tool list in the GUI.
+        """
+        return [str(x.text()) for x in self.ui.tools_table.selectedItems()]
+
+    def on_generate_milling_button_click(self, *args):
+        self.app.report_usage("excellon_on_create_milling_button")
+        self.read_form()
+
+        # Get the tools from the list
+        tools = self.get_selected_tools_list()
+
+        if len(tools) == 0:
+            self.app.inform.emit("Please select one or more tools from the list and try again.")
+            return
+
+        geo_name = self.options["name"] + "_mill"
+
+        def geo_init(geo_obj, app_obj):
+            assert isinstance(geo_obj, FlatCAMGeometry)
+            app_obj.progress.emit(20)
+
+            geo_obj.solid_geometry = []
+
+            for hole in self.drills:
+                if hole['tool'] in tools:
+                    geo_obj.solid_geometry.append(
+                        Point(hole['point']).buffer(self.tools[hole['tool']]["C"]/2 - self.options["tooldia"]/2).exterior
+                    )
+
+        def geo_thread(app_obj):
+            app_obj.new_object("geometry", geo_name, geo_init)
+            app_obj.progress.emit(100)
+
+        # Send to worker
+        # self.app.worker.add_task(job_thread, [self.app])
+        self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]})
 
     def on_create_cncjob_button_click(self, *args):
         self.app.report_usage("excellon_on_create_cncjob_button")
         self.read_form()
 
         # Get the tools from the list
-        tools = [str(x.text()) for x in self.ui.tools_table.selectedItems()]
+        tools = self.get_selected_tools_list()
 
         if len(tools) == 0:
             self.app.inform.emit("Please select one or more tools from the list and try again.")
@@ -890,7 +933,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             "cnctooldia": 0.4 / 25.4,
             "painttooldia": 0.0625,
             "paintoverlap": 0.15,
-            "paintmargin": 0.01
+            "paintmargin": 0.01,
+            "paintmethod": "standard"
         })
 
         # Attributes to be included in serialization
@@ -918,7 +962,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             "cnctooldia": self.ui.cnctooldia_entry,
             "painttooldia": self.ui.painttooldia_entry,
             "paintoverlap": self.ui.paintoverlap_entry,
-            "paintmargin": self.ui.paintmargin_entry
+            "paintmargin": self.ui.paintmargin_entry,
+            "paintmethod": self.ui.paintmethod_combo
         })
 
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
@@ -945,13 +990,18 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         subscription = self.app.plotcanvas.mpl_connect('button_press_event', doit)
 
     def paint_poly(self, inside_pt, tooldia, overlap):
+
+        # Which polygon.
         poly = find_polygon(self.solid_geometry, inside_pt)
 
         # Initializes the new geometry object
         def gen_paintarea(geo_obj, app_obj):
             assert isinstance(geo_obj, FlatCAMGeometry)
             #assert isinstance(app_obj, App)
-            cp = clear_poly(poly.buffer(-self.options["paintmargin"]), tooldia, overlap)
+            #cp = clear_poly(poly.buffer(-self.options["paintmargin"]), tooldia, overlap)
+            cp = self.clear_polygon(poly.buffer(-self.options["paintmargin"]), tooldia, overlap=overlap)
+            if self.options["paintmethod"] == "seed":
+                cp = self.clear_polygon2(poly.buffer(-self.options["paintmargin"]), tooldia, overlap=overlap)
             geo_obj.solid_geometry = cp
             geo_obj.options["cnctooldia"] = tooldia
 
@@ -1067,6 +1117,26 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
         return factor
 
+    def plot_element(self, element):
+        try:
+            for sub_el in element:
+                self.plot_element(sub_el)
+        except TypeError:
+            if type(element) == Polygon:
+                x, y = element.exterior.coords.xy
+                self.axes.plot(x, y, 'r-')
+                for ints in element.interiors:
+                    x, y = ints.coords.xy
+                    self.axes.plot(x, y, 'r-')
+                return
+
+            if type(element) == LineString or type(element) == LinearRing:
+                x, y = element.coords.xy
+                self.axes.plot(x, y, 'r-')
+                return
+
+        FlatCAMApp.App.log.warning("Did not plot:", str(type(element)))
+
     def plot(self):
         """
         Plots the object into its axes. If None, of if the axes
@@ -1082,39 +1152,41 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
         # Make sure solid_geometry is iterable.
         # TODO: This method should not modify the object !!!
-        try:
-            _ = iter(self.solid_geometry)
-        except TypeError:
-            if self.solid_geometry is None:
-                self.solid_geometry = []
-            else:
-                self.solid_geometry = [self.solid_geometry]
-
-        for geo in self.solid_geometry:
-
-            if type(geo) == Polygon:
-                x, y = geo.exterior.coords.xy
-                self.axes.plot(x, y, 'r-')
-                for ints in geo.interiors:
-                    x, y = ints.coords.xy
-                    self.axes.plot(x, y, 'r-')
-                continue
-
-            if type(geo) == LineString or type(geo) == LinearRing:
-                x, y = geo.coords.xy
-                self.axes.plot(x, y, 'r-')
-                continue
-
-            if type(geo) == MultiPolygon:
-                for poly in geo:
-                    x, y = poly.exterior.coords.xy
-                    self.axes.plot(x, y, 'r-')
-                    for ints in poly.interiors:
-                        x, y = ints.coords.xy
-                        self.axes.plot(x, y, 'r-')
-                continue
+        # try:
+        #     _ = iter(self.solid_geometry)
+        # except TypeError:
+        #     if self.solid_geometry is None:
+        #         self.solid_geometry = []
+        #     else:
+        #         self.solid_geometry = [self.solid_geometry]
+        #
+        # for geo in self.solid_geometry:
+        #
+        #     if type(geo) == Polygon:
+        #         x, y = geo.exterior.coords.xy
+        #         self.axes.plot(x, y, 'r-')
+        #         for ints in geo.interiors:
+        #             x, y = ints.coords.xy
+        #             self.axes.plot(x, y, 'r-')
+        #         continue
+        #
+        #     if type(geo) == LineString or type(geo) == LinearRing:
+        #         x, y = geo.coords.xy
+        #         self.axes.plot(x, y, 'r-')
+        #         continue
+        #
+        #     if type(geo) == MultiPolygon:
+        #         for poly in geo:
+        #             x, y = poly.exterior.coords.xy
+        #             self.axes.plot(x, y, 'r-')
+        #             for ints in poly.interiors:
+        #                 x, y = ints.coords.xy
+        #                 self.axes.plot(x, y, 'r-')
+        #         continue
+        #
+        #     FlatCAMApp.App.log.warning("Did not plot:", str(type(geo)))
 
-            FlatCAMApp.App.log.warning("Did not plot:", str(type(geo)))
+        self.plot_element(self.solid_geometry)
 
         self.app.plotcanvas.auto_adjust_axes()
         # GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)

+ 45 - 2
ObjectUI.py

@@ -245,7 +245,9 @@ class GeometryObjectUI(ObjectUI):
         )
         self.custom_box.addWidget(self.generate_cnc_button)
 
-        ## Paint area
+        ################
+        ## Paint area ##
+        ################
         self.paint_label = QtGui.QLabel('<b>Paint Area:</b>')
         self.paint_label.setToolTip(
             "Creates tool paths to cover the\n"
@@ -288,7 +290,19 @@ class GeometryObjectUI(ObjectUI):
         )
         grid2.addWidget(marginlabel, 2, 0)
         self.paintmargin_entry = LengthEntry()
-        grid2.addWidget(self.paintmargin_entry)
+        grid2.addWidget(self.paintmargin_entry, 2, 1)
+
+        # Method
+        methodlabel = QtGui.QLabel('Method:')
+        methodlabel.setToolTip(
+            "Algorithm to paint the polygon."
+        )
+        grid2.addWidget(methodlabel, 3, 0)
+        self.paintmethod_combo = RadioSet([
+            {"label": "Standard", "value": "standard"},
+            {"label": "Seed-based", "value": "seed"}
+        ])
+        grid2.addWidget(self.paintmethod_combo, 3, 1)
 
         # GO Button
         self.generate_paint_button = QtGui.QPushButton('Generate')
@@ -386,6 +400,35 @@ class ExcellonObjectUI(ObjectUI):
         )
         self.custom_box.addWidget(self.generate_cnc_button)
 
+        ## Milling Holes
+        self.mill_hole_label = QtGui.QLabel('<b>Mill Holes</b>')
+        self.mill_hole_label.setToolTip(
+            "Create Geometry for milling holes."
+        )
+        self.custom_box.addWidget(self.mill_hole_label)
+
+        grid1 = QtGui.QGridLayout()
+        self.custom_box.addLayout(grid1)
+        tdlabel = QtGui.QLabel('Tool dia:')
+        tdlabel.setToolTip(
+            "Diameter of the cutting tool."
+        )
+        grid1.addWidget(tdlabel, 0, 0)
+        self.tooldia_entry = LengthEntry()
+        grid1.addWidget(self.tooldia_entry, 0, 1)
+
+        choose_tools_label2 = QtGui.QLabel(
+            "Select from the tools section above\n"
+            "the tools you want to include."
+        )
+        self.custom_box.addWidget(choose_tools_label2)
+
+        self.generate_milling_button = QtGui.QPushButton('Generate Geometry')
+        self.generate_milling_button.setToolTip(
+            "Create the Geometry Object\n"
+            "for milling toolpaths."
+        )
+        self.custom_box.addWidget(self.generate_milling_button)
 
 class GerberObjectUI(ObjectUI):
     """

+ 106 - 30
camlib.py

@@ -216,6 +216,14 @@ class Geometry(object):
         """
         Creates geometry inside a polygon for a tool to cover
         the whole area.
+
+        This algorithm shrinks the edges of the polygon and takes
+        the resulting edges as toolpaths.
+
+        :param polygon: Polygon to clear.
+        :param tooldia: Diameter of the tool.
+        :param overlap: Overlap of toolpasses.
+        :return:
         """
         poly_cuts = [polygon.buffer(-tooldia/2.0)]
         while True:
@@ -226,6 +234,58 @@ class Geometry(object):
                 break
         return poly_cuts
 
+    def clear_polygon2(self, polygon, tooldia, seedpoint=None, overlap=0.15):
+        """
+        Creates geometry inside a polygon for a tool to cover
+        the whole area.
+
+        This algorithm starts with a seed point inside the polygon
+        and draws circles around it. Arcs inside the polygons are
+        valid cuts. Finalizes by cutting around the inside edge of
+        the polygon.
+
+        :param polygon:
+        :param tooldia:
+        :param seedpoint:
+        :param overlap:
+        :return:
+        """
+
+        if seedpoint is None:
+            seedpoint = polygon.representative_point()
+
+        # Current buffer radius
+        radius = tooldia/2*(1-overlap)
+
+        # The toolpaths
+        geoms = [Point(seedpoint).buffer(radius).exterior]
+
+        # Path margin
+        path_margin = polygon.buffer(-tooldia/2)
+
+        # Grow from seed until outside the box.
+        while 1:
+            path = Point(seedpoint).buffer(radius).exterior
+            path = path.intersection(path_margin)
+
+            # Touches polygon?
+            if path.is_empty:
+                break
+            else:
+                geoms.append(path)
+
+            radius += tooldia*(1-overlap)
+
+        # Clean edges
+        outer_edges = [x.exterior for x in autolist(polygon.buffer(-tooldia/2))]
+        inner_edges = []
+        for x in autolist(polygon.buffer(-tooldia/2)):  # Over resulting polygons
+            for y in x.interiors:  # Over interiors of each polygon
+                inner_edges.append(y)
+        geoms += outer_edges + inner_edges
+
+        return geoms
+
     def scale(self, factor):
         """
         Scales all of the object's geometry by a given factor. Override
@@ -2695,30 +2755,30 @@ def arc_angle(start, stop, direction):
     return angle
 
 
-def clear_poly(poly, tooldia, overlap=0.1):
-    """
-    Creates a list of Shapely geometry objects covering the inside
-    of a Shapely.Polygon. Use for removing all the copper in a region
-    or bed flattening.
-
-    :param poly: Target polygon
-    :type poly: Shapely.Polygon
-    :param tooldia: Diameter of the tool
-    :type tooldia: float
-    :param overlap: Fraction of the tool diameter to overlap
-        in each pass.
-    :type overlap: float
-    :return: list of Shapely.Polygon
-    :rtype: list
-    """
-    poly_cuts = [poly.buffer(-tooldia/2.0)]
-    while True:
-        poly = poly_cuts[-1].buffer(-tooldia*(1-overlap))
-        if poly.area > 0:
-            poly_cuts.append(poly)
-        else:
-            break
-    return poly_cuts
+# def clear_poly(poly, tooldia, overlap=0.1):
+#     """
+#     Creates a list of Shapely geometry objects covering the inside
+#     of a Shapely.Polygon. Use for removing all the copper in a region
+#     or bed flattening.
+#
+#     :param poly: Target polygon
+#     :type poly: Shapely.Polygon
+#     :param tooldia: Diameter of the tool
+#     :type tooldia: float
+#     :param overlap: Fraction of the tool diameter to overlap
+#         in each pass.
+#     :type overlap: float
+#     :return: list of Shapely.Polygon
+#     :rtype: list
+#     """
+#     poly_cuts = [poly.buffer(-tooldia/2.0)]
+#     while True:
+#         poly = poly_cuts[-1].buffer(-tooldia*(1-overlap))
+#         if poly.area > 0:
+#             poly_cuts.append(poly)
+#         else:
+#             break
+#     return poly_cuts
 
 
 def find_polygon(poly_set, point):
@@ -2775,7 +2835,7 @@ def dict2obj(d):
         return d
 
 
-def plotg(geo):
+def plotg(geo, solid_poly=False):
     try:
         _ = iter(geo)
     except:
@@ -2783,12 +2843,21 @@ def plotg(geo):
 
     for g in geo:
         if type(g) == Polygon:
-            x, y = g.exterior.coords.xy
-            plot(x, y)
-            for ints in g.interiors:
-                x, y = ints.coords.xy
+            if solid_poly:
+                patch = PolygonPatch(g,
+                                     facecolor="#BBF268",
+                                     edgecolor="#006E20",
+                                     alpha=0.75,
+                                     zorder=2)
+                ax = subplot(111)
+                ax.add_patch(patch)
+            else:
+                x, y = g.exterior.coords.xy
                 plot(x, y)
-            continue
+                for ints in g.interiors:
+                    x, y = ints.coords.xy
+                    plot(x, y)
+                continue
 
         if type(g) == LineString or type(g) == LinearRing:
             x, y = g.coords.xy
@@ -3025,3 +3094,10 @@ class Zprofile:
         return [{"path": path.intersection(self.polygons[i]),
                  "z": self.data[i][2]} for i in crossing]
 
+
+def autolist(obj):
+    try:
+        _ = iter(obj)
+        return obj
+    except TypeError:
+        return [obj]