فهرست منبع

Added feature: Select all polygons for painting and shell support with "paint" command.

Juan Pablo Caram 9 سال پیش
والد
کامیت
7474609776
7فایلهای تغییر یافته به همراه242 افزوده شده و 35 حذف شده
  1. 21 20
      FlatCAMApp.py
  2. 15 1
      FlatCAMGUI.py
  3. 97 14
      FlatCAMObj.py
  4. 14 0
      ObjectUI.py
  5. 10 0
      camlib.py
  6. 84 0
      tclCommands/TclCommandPaint.py
  7. 1 0
      tclCommands/__init__.py

+ 21 - 20
FlatCAMApp.py

@@ -259,6 +259,7 @@ class App(QtCore.QObject):
             "geometry_spindlespeed": self.defaults_form.geometry_group.cncspindlespeed_entry,
             "geometry_paintoverlap": self.defaults_form.geometry_group.paintoverlap_entry,
             "geometry_paintmargin": self.defaults_form.geometry_group.paintmargin_entry,
+            "geometry_selectmethod": self.defaults_form.geometry_group.selectmethod_combo,
             "cncjob_plot": self.defaults_form.cncjob_group.plot_cb,
             "cncjob_tooldia": self.defaults_form.cncjob_group.tooldia_entry,
             "cncjob_prepend": self.defaults_form.cncjob_group.prepend_text,
@@ -304,6 +305,7 @@ class App(QtCore.QObject):
             "geometry_painttooldia": 0.07,
             "geometry_paintoverlap": 0.15,
             "geometry_paintmargin": 0.0,
+            "geometry_selectmethod": "single",
             "cncjob_plot": True,
             "cncjob_tooldia": 0.016,
             "cncjob_prepend": "",
@@ -397,6 +399,7 @@ class App(QtCore.QObject):
             "geometry_painttooldia": self.options_form.geometry_group.painttooldia_entry,
             "geometry_paintoverlap": self.options_form.geometry_group.paintoverlap_entry,
             "geometry_paintmargin": self.options_form.geometry_group.paintmargin_entry,
+            "geometry_selectmethod": self.options_form.geometry_group.selectmethod_combo,
             "cncjob_plot": self.options_form.cncjob_group.plot_cb,
             "cncjob_tooldia": self.options_form.cncjob_group.tooldia_entry,
             "cncjob_prepend": self.options_form.cncjob_group.prepend_text,
@@ -439,12 +442,15 @@ class App(QtCore.QObject):
             "geometry_painttooldia": 0.07,
             "geometry_paintoverlap": 0.15,
             "geometry_paintmargin": 0.0,
+            "geometry_selectmethod": "single",
             "cncjob_plot": True,
             "cncjob_tooldia": 0.016,
             "cncjob_prepend": "",
             "cncjob_append": "",
-            "background_timeout": 300000, #default value is 5 minutes
-            "verbose_error_level": 0, # shell verbosity 0 = default(python trace only for unknown errors), 1 = show trace(show trace allways), 2 = (For the future).
+            "background_timeout": 300000,  # Default value is 5 minutes
+            "verbose_error_level": 0,  # Shell verbosity:
+                                       # 0 = default(python trace only for unknown errors),
+                                       # 1 = show trace(show trace allways), 2 = (For the future).
         })
         self.options.update(self.defaults)  # Copy app defaults to project options
         #self.options_write_form()
@@ -877,13 +883,13 @@ class App(QtCore.QObject):
             #self.shell.append_error("?\n")
             self.shell.append_error(str(e) + "\n")
 
-    def info(self, msg):
+    def info(self, msg, toshell=True):
         """
         Informs the user. Normally on the status bar, optionally
         also on the shell.
 
         :param msg: Text to write.
-        :param toshell: Forward the
+        :param toshell: Forward the meesage to the shell.
         :return: None
         """
 
@@ -894,12 +900,15 @@ class App(QtCore.QObject):
             msg_ = match.group(2)
             self.ui.fcinfo.set_status(QtCore.QString(msg_), level=level)
 
-            error = level == "error" or level == "warning"
-            self.shell_message(msg, error=error, show=True)
+            if toshell:
+                error = level == "error" or level == "warning"
+                self.shell_message(msg, error=error, show=True)
 
         else:
             self.ui.fcinfo.set_status(QtCore.QString(msg), level="info")
-            self.shell_message(msg)
+
+            if toshell:
+                self.shell_message(msg)
 
     def load_defaults(self):
         """
@@ -981,6 +990,11 @@ class App(QtCore.QObject):
         this is, updates the GUI accordingly, any other records and plots it.
         This method is thread-safe.
 
+        Notes:
+            * If the name is in use, the self.collection will modify it
+              when appending it to the collection. There is no need to handle
+              name conflicts here.
+
         :param kind: The kind of object to create. One of 'gerber',
          'excellon', 'cncjob' and 'geometry'.
         :type kind: str
@@ -998,19 +1012,6 @@ class App(QtCore.QObject):
 
         t0 = time.time()  # Debug
 
-        ### Check for existing name
-        # while name in self.collection.get_names():
-        #     ## Create a new name
-        #     # Ends with number?
-        #     App.log.debug("new_object(): Object name (%s) exists, changing." % name)
-        #     match = re.search(r'(.*[^\d])?(\d+)$', name)
-        #     if match:  # Yes: Increment the number!
-        #         base = match.group(1) or ''
-        #         num = int(match.group(2))
-        #         name = base + str(num + 1)
-        #     else:  # No: add a number!
-        #         name += "_1"
-
         ## Create object
         classdict = {
             "gerber": FlatCAMGerber,

+ 15 - 1
FlatCAMGUI.py

@@ -746,7 +746,21 @@ class GeometryOptionsGroupUI(OptionsGroupUI):
         )
         grid2.addWidget(marginlabel, 2, 0)
         self.paintmargin_entry = LengthEntry()
-        grid2.addWidget(self.paintmargin_entry)
+        grid2.addWidget(self.paintmargin_entry, 2, 1)
+
+        # Polygon selection
+        selectlabel = QtGui.QLabel('Selection:')
+        selectlabel.setToolTip(
+            "How to select the polygons to paint."
+        )
+        grid2.addWidget(selectlabel, 3, 0)
+        # grid3 = QtGui.QGridLayout()
+        self.selectmethod_combo = RadioSet([
+            {"label": "Single", "value": "single"},
+            {"label": "All", "value": "all"},
+            # {"label": "Rectangle", "value": "rectangle"}
+        ])
+        grid2.addWidget(self.selectmethod_combo, 3, 1)
 
 
 class CNCJobOptionsGroupUI(OptionsGroupUI):

+ 97 - 14
FlatCAMObj.py

@@ -1203,7 +1203,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             "paintmargin": 0.01,
             "paintmethod": "standard",
             "multidepth": False,
-            "depthperpass": 0.002
+            "depthperpass": 0.002,
+            "selectmethod": "single"
         })
 
         # Attributes to be included in serialization
@@ -1234,7 +1235,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             "paintmargin": self.ui.paintmargin_entry,
             "paintmethod": self.ui.paintmethod_combo,
             "multidepth": self.ui.mpass_cb,
-            "depthperpass": self.ui.maxdepth_entry
+            "depthperpass": self.ui.maxdepth_entry,
+            "selectmethod": self.ui.selectmethod_combo
         })
 
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
@@ -1244,24 +1246,38 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
     def on_paint_button_click(self, *args):
         self.app.report_usage("geometry_on_paint_button")
 
-        self.app.info("Click inside the desired polygon.")
         self.read_form()
         tooldia = self.options["painttooldia"]
         overlap = self.options["paintoverlap"]
 
-        # Connection ID for the click event
-        subscription = None
+        if self.options["selectmethod"] == "all":
+            self.paint_poly_all(tooldia, overlap)
+            return
+
+        if self.options["selectmethod"] == "single":
+            self.app.info("Click inside the desired polygon.")
 
-        # To be called after clicking on the plot.
-        def doit(event):
-            self.app.info("Painting polygon...")
-            self.app.plotcanvas.mpl_disconnect(subscription)
-            point = [event.xdata, event.ydata]
-            self.paint_poly(point, tooldia, overlap)
+            # To be called after clicking on the plot.
+            def doit(event):
+                self.app.info("Painting polygon...")
+                self.app.plotcanvas.mpl_disconnect(subscription)
+                point = [event.xdata, event.ydata]
+                self.paint_poly_single_click(point, tooldia, overlap)
 
-        subscription = self.app.plotcanvas.mpl_connect('button_press_event', doit)
+            subscription = self.app.plotcanvas.mpl_connect('button_press_event', doit)
+
+    def paint_poly_single_click(self, inside_pt, tooldia, overlap, outname=None):
+        """
+        Paints a polygon selected by clicking on its interior.
 
-    def paint_poly(self, inside_pt, tooldia, overlap):
+        Note:
+            * The margin is taken directly from the form.
+
+        :param inside_pt: [x, y]
+        :param tooldia: Diameter of the painting tool
+        :param overlap: Overlap of the tool between passes.
+        :return: None
+        """
 
         # Which polygon.
         #poly = find_polygon(self.solid_geometry, inside_pt)
@@ -1275,7 +1291,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
         proc = self.app.proc_container.new("Painting polygon.")
 
-        name = self.options["name"] + "_paint"
+        name = outname or self.options["name"] + "_paint"
 
         # Initializes the new geometry object
         def gen_paintarea(geo_obj, app_obj):
@@ -1293,6 +1309,73 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
             geo_obj.solid_geometry = list(cp.get_objects())
             geo_obj.options["cnctooldia"] = tooldia
+
+            # Experimental...
+            print "Indexing...",
+            geo_obj.make_index()
+            print "Done"
+
+            self.app.inform.emit("Done.")
+
+        def job_thread(app_obj):
+            try:
+                app_obj.new_object("geometry", name, gen_paintarea)
+            except Exception as e:
+                proc.done()
+                raise e
+            proc.done()
+
+        self.app.inform.emit("Polygon Paint started ...")
+
+        # Promise object with the new name
+        self.app.collection.promise(name)
+
+        # Background
+        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+
+    def paint_poly_all(self, tooldia, overlap, outname=None):
+
+        proc = self.app.proc_container.new("Painting polygon.")
+
+        name = outname or self.options["name"] + "_paint"
+
+        def recurse(geo):
+            try:
+                for subg in geo:
+                    for subsubg in recurse(subg):
+                        yield subsubg
+            except TypeError:
+                if isinstance(geo, Polygon):
+                    yield geo
+
+            raise StopIteration
+
+        # Initializes the new geometry object
+        def gen_paintarea(geo_obj, app_obj):
+            assert isinstance(geo_obj, FlatCAMGeometry), \
+                "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
+
+            geo_obj.solid_geometry = []
+
+            for poly in recurse(self.solid_geometry):
+
+                if self.options["paintmethod"] == "seed":
+                    cp = self.clear_polygon2(poly.buffer(-self.options["paintmargin"]),
+                                             tooldia, overlap=overlap)
+
+                else:
+                    cp = self.clear_polygon(poly.buffer(-self.options["paintmargin"]),
+                                            tooldia, overlap=overlap)
+
+                geo_obj.solid_geometry += list(cp.get_objects())
+
+            geo_obj.options["cnctooldia"] = tooldia
+
+            # Experimental...
+            print "Indexing...",
+            geo_obj.make_index()
+            print "Done"
+
             self.app.inform.emit("Done.")
 
         def job_thread(app_obj):

+ 14 - 0
ObjectUI.py

@@ -397,6 +397,20 @@ class GeometryObjectUI(ObjectUI):
         ])
         grid2.addWidget(self.paintmethod_combo, 3, 1)
 
+        # Polygon selection
+        selectlabel = QtGui.QLabel('Selection:')
+        selectlabel.setToolTip(
+            "How to select the polygons to paint."
+        )
+        grid2.addWidget(selectlabel, 4, 0)
+        #grid3 = QtGui.QGridLayout()
+        self.selectmethod_combo = RadioSet([
+            {"label": "Single", "value": "single"},
+            {"label": "All", "value": "all"},
+            #{"label": "Rectangle", "value": "rectangle"}
+        ])
+        grid2.addWidget(self.selectmethod_combo, 4, 1)
+
         # GO Button
         self.generate_paint_button = QtGui.QPushButton('Generate')
         self.generate_paint_button.setToolTip(

+ 10 - 0
camlib.py

@@ -92,6 +92,16 @@ class Geometry(object):
         # Flattened geometry (list of paths only)
         self.flat_geometry = []
 
+        # Index
+        self.index = None
+
+    def make_index(self):
+        self.flatten()
+        self.index = FlatCAMRTree()
+
+        for i, g in enumerate(self.flat_geometry):
+            self.index.insert(i, g)
+
     def add_circle(self, origin, radius):
         """
         Adds a circle to the object.

+ 84 - 0
tclCommands/TclCommandPaint.py

@@ -0,0 +1,84 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandPaint(TclCommand.TclCommandSignaled):
+    """
+    Paint the interior of polygons
+    """
+
+    # Array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+    aliases = ['paint']
+
+    # dictionary of types from Tcl command, needs to be ordered
+    arg_names = collections.OrderedDict([
+        ('name', str),
+        ('tooldia', float),
+        ('overlap', float)
+    ])
+
+    # dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
+    option_types = collections.OrderedDict([
+        ('outname', str),
+        ('all', bool),
+        ('x', float),
+        ('y', float)
+    ])
+
+    # array of mandatory options for current Tcl command: required = {'name','outname'}
+    required = ['name', 'tooldia', 'overlap']
+
+    # structured help for current command, args needs to be ordered
+    help = {
+        'main': "Paint polygons",
+        'args': collections.OrderedDict([
+            ('name', 'Name of the source Geometry object.'),
+            ('tooldia', 'Diameter of the tool to be used.'),
+            ('overlap', 'Fraction of the tool diameter to overlap cuts.'),
+            ('outname', 'Name of the resulting Geometry object.'),
+            ('all', 'Paint all polygons in the object.'),
+            ('x', 'X value of coordinate for the selection of a single polygon.'),
+            ('y', 'Y value of coordinate for the selection of a single polygon.')
+        ]),
+        'examples': []
+    }
+
+    def execute(self, args, unnamed_args):
+        """
+        execute current TCL shell command
+
+        :param args: array of known named arguments and options
+        :param unnamed_args: array of other values which were passed into command
+            without -somename and  we do not have them in known arg_names
+        :return: None or exception
+        """
+
+        name = args['name']
+        tooldia = args['tooldia']
+        overlap = args['overlap']
+
+        if 'outname' in args:
+            outname = args['outname']
+        else:
+            outname = name + "_paint"
+
+        obj = self.app.collection.get_by_name(name)
+        if obj is None:
+            self.raise_tcl_error("Object not found: %s" % name)
+
+        if not isinstance(obj, Geometry):
+            self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
+
+        if 'all' in args and args['all']:
+            obj.paint_poly_all(tooldia, overlap, outname)
+            return
+
+        if 'x' not in args or 'y' not in args:
+            self.raise_tcl_error('Expected -all 1 or -x <value> and -y <value>.')
+
+        x = args['x']
+        y = args['y']
+
+        obj.paint_poly_single_click([x, y], tooldia, overlap, outname)
+
+

+ 1 - 0
tclCommands/__init__.py

@@ -13,6 +13,7 @@ import tclCommands.TclCommandInteriors
 import tclCommands.TclCommandIsolate
 import tclCommands.TclCommandNew
 import tclCommands.TclCommandOpenGerber
+import tclCommands.TclCommandPaint
 
 
 __all__ = []