diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index 26094aa8..8c000231 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -10,7 +10,7 @@ from PyQt4 import QtGui, QtCore, Qt import FlatCAMApp from camlib import * from FlatCAMTool import FlatCAMTool -from ObjectUI import LengthEntry +from ObjectUI import LengthEntry, RadioSet from shapely.geometry import Polygon, LineString, Point, LinearRing from shapely.geometry import MultiPoint, MultiPolygon @@ -70,6 +70,94 @@ class BufferSelectionTool(FlatCAMTool): self.fcdraw.buffer(buffer_distance) +class PaintOptionsTool(FlatCAMTool): + """ + Inputs to specify how to paint the selected polygons. + """ + + toolName = "Paint Options" + + def __init__(self, app, fcdraw): + FlatCAMTool.__init__(self, app) + + self.app = app + self.fcdraw = fcdraw + + ## Title + title_label = QtGui.QLabel("%s" % self.toolName) + self.layout.addWidget(title_label) + + ## Form Layout + form_layout = QtGui.QFormLayout() + self.layout.addLayout(form_layout) + + # Tool dia + ptdlabel = QtGui.QLabel('Tool dia:') + ptdlabel.setToolTip( + "Diameter of the tool to\n" + "be used in the operation." + ) + + self.painttooldia_entry = LengthEntry() + form_layout.addRow(ptdlabel, self.painttooldia_entry) + + # Overlap + ovlabel = QtGui.QLabel('Overlap:') + ovlabel.setToolTip( + "How much (fraction) of the tool\n" + "width to overlap each tool pass." + ) + + self.paintoverlap_entry = LengthEntry() + form_layout.addRow(ovlabel, self.paintoverlap_entry) + + # Margin + marginlabel = QtGui.QLabel('Margin:') + marginlabel.setToolTip( + "Distance by which to avoid\n" + "the edges of the polygon to\n" + "be painted." + ) + + self.paintmargin_entry = LengthEntry() + form_layout.addRow(marginlabel, self.paintmargin_entry) + + # Method + methodlabel = QtGui.QLabel('Method:') + methodlabel.setToolTip( + "Algorithm to paint the polygon:
" + "Standard: Fixed step inwards.
" + "Seed-based: Outwards from seed." + ) + + self.paintmethod_combo = RadioSet([ + {"label": "Standard", "value": "standard"}, + {"label": "Seed-based", "value": "seed"} + ]) + form_layout.addRow(methodlabel, self.paintmethod_combo) + + ## Buttons + hlay = QtGui.QHBoxLayout() + self.layout.addLayout(hlay) + hlay.addStretch() + self.paint_button = QtGui.QPushButton("Paint") + hlay.addWidget(self.paint_button) + + self.layout.addStretch() + + ## Signals + self.paint_button.clicked.connect(self.on_paint) + + def on_paint(self): + + tooldia = self.painttooldia_entry.get_value() + overlap = self.paintoverlap_entry.get_value() + margin = self.paintoverlap_entry.get_value() + method = self.paintmethod_combo.get_value() + + self.fcdraw.paint(tooldia, overlap, margin, method) + + class DrawToolShape(object): """ Encapsulates "shapes" under a common class. @@ -657,8 +745,10 @@ class FlatCAMDraw(QtCore.QObject): # self.copy_menuitem = self.menu.addAction(QtGui.QIcon('share/copy16.png'), "Copy Objects 'c'") self.delete_menuitem = self.menu.addAction(QtGui.QIcon('share/deleteshape16.png'), "Delete Shape '-'") self.buffer_menuitem = self.menu.addAction(QtGui.QIcon('share/buffer16.png'), "Buffer selection 'b'") + self.paint_menuitem = self.menu.addAction(QtGui.QIcon('share/paint16.png'), "Paint selection") self.menu.addSeparator() + self.paint_menuitem.triggered.connect(self.on_paint_tool) self.buffer_menuitem.triggered.connect(self.on_buffer_tool) self.delete_menuitem.triggered.connect(self.on_delete_btn) self.union_menuitem.triggered.connect(self.union) @@ -872,6 +962,10 @@ class FlatCAMDraw(QtCore.QObject): buff_tool = BufferSelectionTool(self.app, self) buff_tool.run() + def on_paint_tool(self): + paint_tool = PaintOptionsTool(self.app, self) + paint_tool.run() + def on_tool_select(self, tool): """ Behavior of the toolbar. Tool initialization. @@ -1370,6 +1464,61 @@ class FlatCAMDraw(QtCore.QObject): self.replot() + def paint(self, tooldia, overlap, margin, method): + selected = self.get_selected() + + if len(selected) == 0: + self.app.inform.emit("[warning] Nothing selected for painting.") + return + + for param in [tooldia, overlap, margin]: + if not isinstance(param, float): + param_name = [k for k, v in locals().iteritems() if v is param][0] + self.app.inform.emit("[warning] Invalid value for {}".format()) + + # Todo: Check for valid method. + + # Todo: This is the 3rd implementation on painting polys... try to consolidate + + results = [] + + 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 + + for geo in selected: + + local_results = [] + for poly in recurse(geo.geo): + + if method == "seed": + # Type(cp) == FlatCAMRTreeStorage | None + cp = Geometry.clear_polygon2(poly.buffer(-margin), + tooldia, overlap=overlap) + + else: + # Type(cp) == FlatCAMRTreeStorage | None + cp = Geometry.clear_polygon(poly.buffer(-margin), + tooldia, overlap=overlap) + + if cp is not None: + local_results += list(cp.get_objects()) + + results.append(cascaded_union(local_results)) + + # This is a dirty patch: + for r in results: + self.add_shape(DrawToolShape(r)) + + self.replot() + def distance(pt1, pt2): return sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)