| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538 |
- ############################################################
- # FlatCAM: 2D Post-processing for Manufacturing #
- # http://flatcam.org #
- # Author: Juan Pablo Caram (c) #
- # Date: 2/5/2014 #
- # MIT Licence #
- ############################################################
- from PyQt4 import QtGui, QtCore, Qt
- import FlatCAMApp
- from camlib import *
- from FlatCAMTool import FlatCAMTool
- from ObjectUI import LengthEntry, RadioSet
- from shapely.geometry import Polygon, LineString, Point, LinearRing
- from shapely.geometry import MultiPoint, MultiPolygon
- from shapely.geometry import box as shply_box
- from shapely.ops import cascaded_union, unary_union
- import shapely.affinity as affinity
- from shapely.wkt import loads as sloads
- from shapely.wkt import dumps as sdumps
- from shapely.geometry.base import BaseGeometry
- from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos, sign, dot
- from numpy.linalg import solve
- #from mpl_toolkits.axes_grid.anchored_artists import AnchoredDrawingArea
- from rtree import index as rtindex
- from GUIElements import FCEntry
- class BufferSelectionTool(FlatCAMTool):
- """
- Simple input for buffer distance.
- """
- toolName = "Buffer Selection"
- def __init__(self, app, fcdraw):
- FlatCAMTool.__init__(self, app)
- self.fcdraw = fcdraw
- ## Title
- title_label = QtGui.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
- self.layout.addWidget(title_label)
- ## Form Layout
- form_layout = QtGui.QFormLayout()
- self.layout.addLayout(form_layout)
- ## Buffer distance
- self.buffer_distance_entry = LengthEntry()
- form_layout.addRow("Buffer distance:", self.buffer_distance_entry)
- ## Buttons
- hlay = QtGui.QHBoxLayout()
- self.layout.addLayout(hlay)
- hlay.addStretch()
- self.buffer_button = QtGui.QPushButton("Buffer")
- hlay.addWidget(self.buffer_button)
- self.layout.addStretch()
- ## Signals
- self.buffer_button.clicked.connect(self.on_buffer)
- def on_buffer(self):
- buffer_distance = self.buffer_distance_entry.get_value()
- 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("<font size=4><b>%s</b></font>" % 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:<BR>"
- "<B>Standard</B>: Fixed step inwards.<BR>"
- "<B>Seed-based</B>: 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.
- """
- @staticmethod
- def get_pts(o):
- """
- Returns a list of all points in the object, where
- the object can be a Polygon, Not a polygon, or a list
- of such. Search is done recursively.
- :param: geometric object
- :return: List of points
- :rtype: list
- """
- pts = []
- ## Iterable: descend into each item.
- try:
- for subo in o:
- pts += DrawToolShape.get_pts(subo)
- ## Non-iterable
- except TypeError:
- ## DrawToolShape: descend into .geo.
- if isinstance(o, DrawToolShape):
- pts += DrawToolShape.get_pts(o.geo)
- ## Descend into .exerior and .interiors
- elif type(o) == Polygon:
- pts += DrawToolShape.get_pts(o.exterior)
- for i in o.interiors:
- pts += DrawToolShape.get_pts(i)
- ## Has .coords: list them.
- else:
- pts += list(o.coords)
- return pts
- def __init__(self, geo=[]):
- # Shapely type or list of such
- self.geo = geo
- self.utility = False
- def get_all_points(self):
- return DrawToolShape.get_pts(self)
- class DrawToolUtilityShape(DrawToolShape):
- """
- Utility shapes are temporary geometry in the editor
- to assist in the creation of shapes. For example it
- will show the outline of a rectangle from the first
- point to the current mouse pointer before the second
- point is clicked and the final geometry is created.
- """
- def __init__(self, geo=[]):
- super(DrawToolUtilityShape, self).__init__(geo=geo)
- self.utility = True
- class DrawTool(object):
- """
- Abstract Class representing a tool in the drawing
- program. Can generate geometry, including temporary
- utility geometry that is updated on user clicks
- and mouse motion.
- """
- def __init__(self, draw_app):
- self.draw_app = draw_app
- self.complete = False
- self.start_msg = "Click on 1st point..."
- self.points = []
- self.geometry = None # DrawToolShape or None
- def click(self, point):
- """
- :param point: [x, y] Coordinate pair.
- """
- return ""
- def on_key(self, key):
- return None
- def utility_geometry(self, data=None):
- return None
- class FCShapeTool(DrawTool):
- """
- Abstarct class for tools that create a shape.
- """
- def __init__(self, draw_app):
- DrawTool.__init__(self, draw_app)
- def make(self):
- pass
- class FCCircle(FCShapeTool):
- """
- Resulting type: Polygon
- """
- def __init__(self, draw_app):
- DrawTool.__init__(self, draw_app)
- self.start_msg = "Click on CENTER ..."
- def click(self, point):
- self.points.append(point)
- if len(self.points) == 1:
- return "Click on perimeter to complete ..."
- if len(self.points) == 2:
- self.make()
- return "Done."
- return ""
- def utility_geometry(self, data=None):
- if len(self.points) == 1:
- p1 = self.points[0]
- p2 = data
- radius = sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
- return DrawToolUtilityShape(Point(p1).buffer(radius))
- return None
- def make(self):
- p1 = self.points[0]
- p2 = self.points[1]
- radius = distance(p1, p2)
- self.geometry = DrawToolShape(Point(p1).buffer(radius))
- self.complete = True
- class FCArc(FCShapeTool):
- def __init__(self, draw_app):
- DrawTool.__init__(self, draw_app)
- self.start_msg = "Click on CENTER ..."
- # Direction of rotation between point 1 and 2.
- # 'cw' or 'ccw'. Switch direction by hitting the
- # 'o' key.
- self.direction = "cw"
- # Mode
- # C12 = Center, p1, p2
- # 12C = p1, p2, Center
- # 132 = p1, p3, p2
- self.mode = "c12" # Center, p1, p2
- self.steps_per_circ = 55
- def click(self, point):
- self.points.append(point)
- if len(self.points) == 1:
- return "Click on 1st point ..."
- if len(self.points) == 2:
- return "Click on 2nd point to complete ..."
- if len(self.points) == 3:
- self.make()
- return "Done."
- return ""
- def on_key(self, key):
- if key == 'o':
- self.direction = 'cw' if self.direction == 'ccw' else 'ccw'
- return 'Direction: ' + self.direction.upper()
- if key == 'p':
- if self.mode == 'c12':
- self.mode = '12c'
- elif self.mode == '12c':
- self.mode = '132'
- else:
- self.mode = 'c12'
- return 'Mode: ' + self.mode
- def utility_geometry(self, data=None):
- if len(self.points) == 1: # Show the radius
- center = self.points[0]
- p1 = data
- return DrawToolUtilityShape(LineString([center, p1]))
- if len(self.points) == 2: # Show the arc
- if self.mode == 'c12':
- center = self.points[0]
- p1 = self.points[1]
- p2 = data
- radius = sqrt((center[0] - p1[0]) ** 2 + (center[1] - p1[1]) ** 2)
- startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
- stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
- return DrawToolUtilityShape([LineString(arc(center, radius, startangle, stopangle,
- self.direction, self.steps_per_circ)),
- Point(center)])
- elif self.mode == '132':
- p1 = array(self.points[0])
- p3 = array(self.points[1])
- p2 = array(data)
- center, radius, t = three_point_circle(p1, p2, p3)
- direction = 'cw' if sign(t) > 0 else 'ccw'
- startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
- stopangle = arctan2(p3[1] - center[1], p3[0] - center[0])
- return DrawToolUtilityShape([LineString(arc(center, radius, startangle, stopangle,
- direction, self.steps_per_circ)),
- Point(center), Point(p1), Point(p3)])
- else: # '12c'
- p1 = array(self.points[0])
- p2 = array(self.points[1])
- # Midpoint
- a = (p1 + p2) / 2.0
- # Parallel vector
- c = p2 - p1
- # Perpendicular vector
- b = dot(c, array([[0, -1], [1, 0]], dtype=float32))
- b /= norm(b)
- # Distance
- t = distance(data, a)
- # Which side? Cross product with c.
- # cross(M-A, B-A), where line is AB and M is test point.
- side = (data[0] - p1[0]) * c[1] - (data[1] - p1[1]) * c[0]
- t *= sign(side)
- # Center = a + bt
- center = a + b * t
- radius = norm(center - p1)
- startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
- stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
- return DrawToolUtilityShape([LineString(arc(center, radius, startangle, stopangle,
- self.direction, self.steps_per_circ)),
- Point(center)])
- return None
- def make(self):
- if self.mode == 'c12':
- center = self.points[0]
- p1 = self.points[1]
- p2 = self.points[2]
- radius = distance(center, p1)
- startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
- stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
- self.geometry = DrawToolShape(LineString(arc(center, radius, startangle, stopangle,
- self.direction, self.steps_per_circ)))
- elif self.mode == '132':
- p1 = array(self.points[0])
- p3 = array(self.points[1])
- p2 = array(self.points[2])
- center, radius, t = three_point_circle(p1, p2, p3)
- direction = 'cw' if sign(t) > 0 else 'ccw'
- startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
- stopangle = arctan2(p3[1] - center[1], p3[0] - center[0])
- self.geometry = DrawToolShape(LineString(arc(center, radius, startangle, stopangle,
- direction, self.steps_per_circ)))
- else: # self.mode == '12c'
- p1 = array(self.points[0])
- p2 = array(self.points[1])
- pc = array(self.points[2])
- # Midpoint
- a = (p1 + p2) / 2.0
- # Parallel vector
- c = p2 - p1
- # Perpendicular vector
- b = dot(c, array([[0, -1], [1, 0]], dtype=float32))
- b /= norm(b)
- # Distance
- t = distance(pc, a)
- # Which side? Cross product with c.
- # cross(M-A, B-A), where line is AB and M is test point.
- side = (pc[0] - p1[0]) * c[1] - (pc[1] - p1[1]) * c[0]
- t *= sign(side)
- # Center = a + bt
- center = a + b * t
- radius = norm(center - p1)
- startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
- stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
- self.geometry = DrawToolShape(LineString(arc(center, radius, startangle, stopangle,
- self.direction, self.steps_per_circ)))
- self.complete = True
- class FCRectangle(FCShapeTool):
- """
- Resulting type: Polygon
- """
- def __init__(self, draw_app):
- DrawTool.__init__(self, draw_app)
- self.start_msg = "Click on 1st corner ..."
- def click(self, point):
- self.points.append(point)
- if len(self.points) == 1:
- return "Click on opposite corner to complete ..."
- if len(self.points) == 2:
- self.make()
- return "Done."
- return ""
- def utility_geometry(self, data=None):
- if len(self.points) == 1:
- p1 = self.points[0]
- p2 = data
- return DrawToolUtilityShape(LinearRing([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])]))
- return None
- def make(self):
- p1 = self.points[0]
- p2 = self.points[1]
- #self.geometry = LinearRing([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])])
- self.geometry = DrawToolShape(Polygon([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])]))
- self.complete = True
- class FCPolygon(FCShapeTool):
- """
- Resulting type: Polygon
- """
- def __init__(self, draw_app):
- DrawTool.__init__(self, draw_app)
- self.start_msg = "Click on 1st point ..."
- def click(self, point):
- self.points.append(point)
- if len(self.points) > 0:
- return "Click on next point or hit SPACE to complete ..."
- return ""
- def utility_geometry(self, data=None):
- if len(self.points) == 1:
- temp_points = [x for x in self.points]
- temp_points.append(data)
- return DrawToolUtilityShape(LineString(temp_points))
- if len(self.points) > 1:
- temp_points = [x for x in self.points]
- temp_points.append(data)
- return DrawToolUtilityShape(LinearRing(temp_points))
- return None
- def make(self):
- # self.geometry = LinearRing(self.points)
- self.geometry = DrawToolShape(Polygon(self.points))
- self.complete = True
- def on_key(self, key):
- if key == 'backspace':
- if len(self.points) > 0:
- self.points = self.points[0:-1]
- class FCPath(FCPolygon):
- """
- Resulting type: LineString
- """
- def make(self):
- self.geometry = DrawToolShape(LineString(self.points))
- self.complete = True
- def utility_geometry(self, data=None):
- if len(self.points) > 0:
- temp_points = [x for x in self.points]
- temp_points.append(data)
- return DrawToolUtilityShape(LineString(temp_points))
- return None
- def on_key(self, key):
- if key == 'backspace':
- if len(self.points) > 0:
- self.points = self.points[0:-1]
- class FCSelect(DrawTool):
- def __init__(self, draw_app):
- DrawTool.__init__(self, draw_app)
- self.storage = self.draw_app.storage
- #self.shape_buffer = self.draw_app.shape_buffer
- self.selected = self.draw_app.selected
- self.start_msg = "Click on geometry to select"
- def click(self, point):
- try:
- _, closest_shape = self.storage.nearest(point)
- except StopIteration:
- return ""
- if self.draw_app.key != 'control':
- self.draw_app.selected = []
- self.draw_app.set_selected(closest_shape)
- self.draw_app.app.log.debug("Selected shape containing: " + str(closest_shape.geo))
- return ""
- class FCMove(FCShapeTool):
- def __init__(self, draw_app):
- FCShapeTool.__init__(self, draw_app)
- #self.shape_buffer = self.draw_app.shape_buffer
- self.origin = None
- self.destination = None
- self.start_msg = "Click on reference point."
- def set_origin(self, origin):
- self.origin = origin
- def click(self, point):
- if len(self.draw_app.get_selected()) == 0:
- return "Nothing to move."
- if self.origin is None:
- self.set_origin(point)
- return "Click on final location."
- else:
- self.destination = point
- self.make()
- return "Done."
- def make(self):
- # Create new geometry
- dx = self.destination[0] - self.origin[0]
- dy = self.destination[1] - self.origin[1]
- self.geometry = [DrawToolShape(affinity.translate(geom.geo, xoff=dx, yoff=dy))
- for geom in self.draw_app.get_selected()]
- # Delete old
- self.draw_app.delete_selected()
- # # Select the new
- # for g in self.geometry:
- # # Note that g is not in the app's buffer yet!
- # self.draw_app.set_selected(g)
- self.complete = True
- def utility_geometry(self, data=None):
- """
- Temporary geometry on screen while using this tool.
- :param data:
- :return:
- """
- if self.origin is None:
- return None
- if len(self.draw_app.get_selected()) == 0:
- return None
- dx = data[0] - self.origin[0]
- dy = data[1] - self.origin[1]
- return DrawToolUtilityShape([affinity.translate(geom.geo, xoff=dx, yoff=dy)
- for geom in self.draw_app.get_selected()])
- class FCCopy(FCMove):
- def make(self):
- # Create new geometry
- dx = self.destination[0] - self.origin[0]
- dy = self.destination[1] - self.origin[1]
- self.geometry = [DrawToolShape(affinity.translate(geom.geo, xoff=dx, yoff=dy))
- for geom in self.draw_app.get_selected()]
- self.complete = True
- ########################
- ### Main Application ###
- ########################
- class FlatCAMDraw(QtCore.QObject):
- def __init__(self, app, disabled=False):
- assert isinstance(app, FlatCAMApp.App), \
- "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
- super(FlatCAMDraw, self).__init__()
- self.app = app
- self.canvas = app.plotcanvas
- self.axes = self.canvas.new_axes("draw")
- ### Drawing Toolbar ###
- self.drawing_toolbar = QtGui.QToolBar()
- self.drawing_toolbar.setDisabled(disabled)
- self.app.ui.addToolBar(self.drawing_toolbar)
- self.select_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), "Select 'Esc'")
- self.add_circle_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/circle32.png'), 'Add Circle')
- self.add_arc_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/arc32.png'), 'Add Arc')
- self.add_rectangle_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/rectangle32.png'), 'Add Rectangle')
- self.add_polygon_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/polygon32.png'), 'Add Polygon')
- self.add_path_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/path32.png'), 'Add Path')
- self.union_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/union32.png'), 'Polygon Union')
- self.intersection_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/intersection32.png'), 'Polygon Intersection')
- self.subtract_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/subtract32.png'), 'Polygon Subtraction')
- self.cutpath_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/cutpath32.png'), 'Cut Path')
- self.move_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/move32.png'), "Move Objects 'm'")
- self.copy_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/copy32.png'), "Copy Objects 'c'")
- self.delete_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/deleteshape32.png'), "Delete Shape '-'")
- ### Snap Toolbar ###
- self.snap_toolbar = QtGui.QToolBar()
- self.grid_snap_btn = self.snap_toolbar.addAction(QtGui.QIcon('share:grid32.png'), 'Snap to grid')
- self.grid_gap_x_entry = FCEntry()
- self.grid_gap_x_entry.setMaximumWidth(70)
- self.grid_gap_x_entry.setToolTip("Grid X distance")
- self.snap_toolbar.addWidget(self.grid_gap_x_entry)
- self.grid_gap_y_entry = FCEntry()
- self.grid_gap_y_entry.setMaximumWidth(70)
- self.grid_gap_y_entry.setToolTip("Grid Y distante")
- self.snap_toolbar.addWidget(self.grid_gap_y_entry)
- self.corner_snap_btn = self.snap_toolbar.addAction(QtGui.QIcon('share:corner32.png'), 'Snap to corner')
- self.snap_max_dist_entry = FCEntry()
- self.snap_max_dist_entry.setMaximumWidth(70)
- self.snap_max_dist_entry.setToolTip("Max. magnet distance")
- self.snap_toolbar.addWidget(self.snap_max_dist_entry)
- self.snap_toolbar.setDisabled(disabled)
- self.app.ui.addToolBar(self.snap_toolbar)
- ### Application menu ###
- self.menu = QtGui.QMenu("Drawing")
- self.app.ui.menu.insertMenu(self.app.ui.menutoolaction, self.menu)
- # self.select_menuitem = self.menu.addAction(QtGui.QIcon('share/pointer16.png'), "Select 'Esc'")
- # self.add_circle_menuitem = self.menu.addAction(QtGui.QIcon('share/circle16.png'), 'Add Circle')
- # self.add_arc_menuitem = self.menu.addAction(QtGui.QIcon('share/arc16.png'), 'Add Arc')
- # self.add_rectangle_menuitem = self.menu.addAction(QtGui.QIcon('share/rectangle16.png'), 'Add Rectangle')
- # self.add_polygon_menuitem = self.menu.addAction(QtGui.QIcon('share/polygon16.png'), 'Add Polygon')
- # self.add_path_menuitem = self.menu.addAction(QtGui.QIcon('share/path16.png'), 'Add Path')
- self.union_menuitem = self.menu.addAction(QtGui.QIcon('share/union16.png'), 'Polygon Union')
- self.intersection_menuitem = self.menu.addAction(QtGui.QIcon('share/intersection16.png'), 'Polygon Intersection')
- # self.subtract_menuitem = self.menu.addAction(QtGui.QIcon('share/subtract16.png'), 'Polygon Subtraction')
- self.cutpath_menuitem = self.menu.addAction(QtGui.QIcon('share/cutpath16.png'), 'Cut Path')
- # self.move_menuitem = self.menu.addAction(QtGui.QIcon('share/move16.png'), "Move Objects 'm'")
- # 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)
- self.intersection_menuitem.triggered.connect(self.intersection)
- self.cutpath_menuitem.triggered.connect(self.cutpath)
- ### Event handlers ###
- # Connection ids for Matplotlib
- self.cid_canvas_click = None
- self.cid_canvas_move = None
- self.cid_canvas_key = None
- self.cid_canvas_key_release = None
- # Connect the canvas
- #self.connect_canvas_event_handlers()
- self.union_btn.triggered.connect(self.union)
- self.intersection_btn.triggered.connect(self.intersection)
- self.subtract_btn.triggered.connect(self.subtract)
- self.cutpath_btn.triggered.connect(self.cutpath)
- self.delete_btn.triggered.connect(self.on_delete_btn)
- ## Toolbar events and properties
- self.tools = {
- "select": {"button": self.select_btn,
- "constructor": FCSelect},
- "circle": {"button": self.add_circle_btn,
- "constructor": FCCircle},
- "arc": {"button": self.add_arc_btn,
- "constructor": FCArc},
- "rectangle": {"button": self.add_rectangle_btn,
- "constructor": FCRectangle},
- "polygon": {"button": self.add_polygon_btn,
- "constructor": FCPolygon},
- "path": {"button": self.add_path_btn,
- "constructor": FCPath},
- "move": {"button": self.move_btn,
- "constructor": FCMove},
- "copy": {"button": self.copy_btn,
- "constructor": FCCopy}
- }
- ### Data
- self.active_tool = None
- self.storage = FlatCAMDraw.make_storage()
- self.utility = []
- ## List of selected shapes.
- self.selected = []
- self.move_timer = QtCore.QTimer()
- self.move_timer.setSingleShot(True)
- self.key = None # Currently pressed key
- def make_callback(thetool):
- def f():
- self.on_tool_select(thetool)
- return f
- for tool in self.tools:
- self.tools[tool]["button"].triggered.connect(make_callback(tool)) # Events
- self.tools[tool]["button"].setCheckable(True) # Checkable
- # for snap_tool in [self.grid_snap_btn, self.corner_snap_btn]:
- # snap_tool.triggered.connect(lambda: self.toolbar_tool_toggle("grid_snap"))
- # snap_tool.setCheckable(True)
- self.grid_snap_btn.setCheckable(True)
- self.grid_snap_btn.triggered.connect(lambda: self.toolbar_tool_toggle("grid_snap"))
- self.corner_snap_btn.setCheckable(True)
- self.corner_snap_btn.triggered.connect(lambda: self.toolbar_tool_toggle("corner_snap"))
- self.options = {
- "snap-x": 0.1,
- "snap-y": 0.1,
- "snap_max": 0.05,
- "grid_snap": False,
- "corner_snap": False,
- }
- self.grid_gap_x_entry.setText(str(self.options["snap-x"]))
- self.grid_gap_y_entry.setText(str(self.options["snap-y"]))
- self.snap_max_dist_entry.setText(str(self.options["snap_max"]))
- self.rtree_index = rtindex.Index()
- def entry2option(option, entry):
- self.options[option] = float(entry.text())
- self.grid_gap_x_entry.setValidator(QtGui.QDoubleValidator())
- self.grid_gap_x_entry.editingFinished.connect(lambda: entry2option("snap-x", self.grid_gap_x_entry))
- self.grid_gap_y_entry.setValidator(QtGui.QDoubleValidator())
- self.grid_gap_y_entry.editingFinished.connect(lambda: entry2option("snap-y", self.grid_gap_y_entry))
- self.snap_max_dist_entry.setValidator(QtGui.QDoubleValidator())
- self.snap_max_dist_entry.editingFinished.connect(lambda: entry2option("snap_max", self.snap_max_dist_entry))
- def activate(self):
- pass
- def connect_canvas_event_handlers(self):
- ## Canvas events
- self.cid_canvas_click = self.canvas.mpl_connect('button_press_event', self.on_canvas_click)
- self.cid_canvas_move = self.canvas.mpl_connect('motion_notify_event', self.on_canvas_move)
- self.cid_canvas_key = self.canvas.mpl_connect('key_press_event', self.on_canvas_key)
- self.cid_canvas_key_release = self.canvas.mpl_connect('key_release_event', self.on_canvas_key_release)
- def disconnect_canvas_event_handlers(self):
- self.canvas.mpl_disconnect(self.cid_canvas_click)
- self.canvas.mpl_disconnect(self.cid_canvas_move)
- self.canvas.mpl_disconnect(self.cid_canvas_key)
- self.canvas.mpl_disconnect(self.cid_canvas_key_release)
- def add_shape(self, shape):
- """
- Adds a shape to the shape storage.
- :param shape: Shape to be added.
- :type shape: DrawToolShape
- :return: None
- """
- # List of DrawToolShape?
- if isinstance(shape, list):
- for subshape in shape:
- self.add_shape(subshape)
- return
- assert isinstance(shape, DrawToolShape), \
- "Expected a DrawToolShape, got %s" % type(shape)
- assert shape.geo is not None, \
- "Shape object has empty geometry (None)"
- assert (isinstance(shape.geo, list) and len(shape.geo) > 0) or \
- not isinstance(shape.geo, list), \
- "Shape objects has empty geometry ([])"
- if isinstance(shape, DrawToolUtilityShape):
- self.utility.append(shape)
- else:
- self.storage.insert(shape)
- def deactivate(self):
- self.disconnect_canvas_event_handlers()
- self.clear()
- self.drawing_toolbar.setDisabled(True)
- self.snap_toolbar.setDisabled(True) # TODO: Combine and move into tool
- def delete_utility_geometry(self):
- #for_deletion = [shape for shape in self.shape_buffer if shape.utility]
- #for_deletion = [shape for shape in self.storage.get_objects() if shape.utility]
- for_deletion = [shape for shape in self.utility]
- for shape in for_deletion:
- self.delete_shape(shape)
- def cutpath(self):
- selected = self.get_selected()
- tools = selected[1:]
- toolgeo = cascaded_union([shp.geo for shp in tools])
- target = selected[0]
- if type(target.geo) == Polygon:
- for ring in poly2rings(target.geo):
- self.add_shape(DrawToolShape(ring.difference(toolgeo)))
- self.delete_shape(target)
- elif type(target.geo) == LineString or type(target.geo) == LinearRing:
- self.add_shape(DrawToolShape(target.geo.difference(toolgeo)))
- self.delete_shape(target)
- else:
- self.app.log.warning("Not implemented.")
- self.replot()
- def toolbar_tool_toggle(self, key):
- self.options[key] = self.sender().isChecked()
- def clear(self):
- self.active_tool = None
- #self.shape_buffer = []
- self.selected = []
- self.storage = FlatCAMDraw.make_storage()
- self.replot()
- def edit_fcgeometry(self, fcgeometry):
- """
- Imports the geometry from the given FlatCAM Geometry object
- into the editor.
- :param fcgeometry: FlatCAMGeometry
- :return: None
- """
- assert isinstance(fcgeometry, Geometry), \
- "Expected a Geometry, got %s" % type(fcgeometry)
- self.deactivate()
- self.connect_canvas_event_handlers()
- self.select_tool("select")
- # Link shapes into editor.
- for shape in fcgeometry.flatten():
- if shape is not None: # TODO: Make flatten never create a None
- self.add_shape(DrawToolShape(shape))
- self.replot()
- self.drawing_toolbar.setDisabled(False)
- self.snap_toolbar.setDisabled(False)
- def on_buffer_tool(self):
- 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.
- :rtype : None
- """
- self.app.log.debug("on_tool_select('%s')" % tool)
- # This is to make the group behave as radio group
- if tool in self.tools:
- if self.tools[tool]["button"].isChecked():
- self.app.log.debug("%s is checked." % tool)
- for t in self.tools:
- if t != tool:
- self.tools[t]["button"].setChecked(False)
- self.active_tool = self.tools[tool]["constructor"](self)
- self.app.inform.emit(self.active_tool.start_msg)
- else:
- self.app.log.debug("%s is NOT checked." % tool)
- for t in self.tools:
- self.tools[t]["button"].setChecked(False)
- self.active_tool = None
- def on_canvas_click(self, event):
- """
- event.x and .y have canvas coordinates
- event.xdaya and .ydata have plot coordinates
- :param event: Event object dispatched by Matplotlib
- :return: None
- """
- # Selection with left mouse button
- if self.active_tool is not None and event.button is 1:
- # Dispatch event to active_tool
- msg = self.active_tool.click(self.snap(event.xdata, event.ydata))
- self.app.inform.emit(msg)
- # If it is a shape generating tool
- if isinstance(self.active_tool, FCShapeTool) and self.active_tool.complete:
- self.on_shape_complete()
- return
- if isinstance(self.active_tool, FCSelect):
- self.app.log.debug("Replotting after click.")
- self.replot()
- else:
- self.app.log.debug("No active tool to respond to click!")
- def on_canvas_move(self, event):
- """
- event.x and .y have canvas coordinates
- event.xdaya and .ydata have plot coordinates
- :param event: Event object dispatched by Matplotlib
- :return:
- """
- self.on_canvas_move_effective(event)
- return None
- # self.move_timer.stop()
- #
- # if self.active_tool is None:
- # return
- #
- # # Make a function to avoid late evaluation
- # def make_callback():
- # def f():
- # self.on_canvas_move_effective(event)
- # return f
- # callback = make_callback()
- #
- # self.move_timer.timeout.connect(callback)
- # self.move_timer.start(500) # Stops if aready running
- def on_canvas_move_effective(self, event):
- """
- Is called after timeout on timer set in on_canvas_move.
- For details on animating on MPL see:
- http://wiki.scipy.org/Cookbook/Matplotlib/Animations
- event.x and .y have canvas coordinates
- event.xdaya and .ydata have plot coordinates
- :param event: Event object dispatched by Matplotlib
- :return: None
- """
- try:
- x = float(event.xdata)
- y = float(event.ydata)
- except TypeError:
- return
- if self.active_tool is None:
- return
- ### Snap coordinates
- x, y = self.snap(x, y)
- ### Utility geometry (animated)
- self.canvas.canvas.restore_region(self.canvas.background)
- geo = self.active_tool.utility_geometry(data=(x, y))
- if isinstance(geo, DrawToolShape) and geo.geo is not None:
- # Remove any previous utility shape
- self.delete_utility_geometry()
- # Add the new utility shape
- self.add_shape(geo)
- # Efficient plotting for fast animation
- #self.canvas.canvas.restore_region(self.canvas.background)
- elements = self.plot_shape(geometry=geo.geo,
- linespec="b--",
- linewidth=1,
- animated=True)
- for el in elements:
- self.axes.draw_artist(el)
- #self.canvas.canvas.blit(self.axes.bbox)
- # Pointer (snapped)
- elements = self.axes.plot(x, y, 'bo', animated=True)
- for el in elements:
- self.axes.draw_artist(el)
- self.canvas.canvas.blit(self.axes.bbox)
- def on_canvas_key(self, event):
- """
- event.key has the key.
- :param event:
- :return:
- """
- self.key = event.key
- ### Finish the current action. Use with tools that do not
- ### complete automatically, like a polygon or path.
- if event.key == ' ':
- if isinstance(self.active_tool, FCShapeTool):
- self.active_tool.click(self.snap(event.xdata, event.ydata))
- self.active_tool.make()
- if self.active_tool.complete:
- self.on_shape_complete()
- self.app.inform.emit("Done.")
- return
- ### Abort the current action
- if event.key == 'escape':
- # TODO: ...?
- #self.on_tool_select("select")
- self.app.inform.emit("Cancelled.")
- self.delete_utility_geometry()
- self.replot()
- # self.select_btn.setChecked(True)
- # self.on_tool_select('select')
- self.select_tool('select')
- return
- ### Delete selected object
- if event.key == '-':
- self.delete_selected()
- self.replot()
- ### Move
- if event.key == 'm':
- self.move_btn.setChecked(True)
- self.on_tool_select('move')
- self.active_tool.set_origin(self.snap(event.xdata, event.ydata))
- self.app.inform.emit("Click on target point.")
- ### Copy
- if event.key == 'c':
- self.copy_btn.setChecked(True)
- self.on_tool_select('copy')
- self.active_tool.set_origin(self.snap(event.xdata, event.ydata))
- self.app.inform.emit("Click on target point.")
- ### Snap
- if event.key == 'g':
- self.grid_snap_btn.trigger()
- if event.key == 'k':
- self.corner_snap_btn.trigger()
- ### Buffer
- if event.key == 'b':
- self.on_buffer_tool()
- ### Propagate to tool
- response = None
- if self.active_tool is not None:
- response = self.active_tool.on_key(event.key)
- if response is not None:
- self.app.inform.emit(response)
- def on_canvas_key_release(self, event):
- self.key = None
- def on_delete_btn(self):
- self.delete_selected()
- self.replot()
- def get_selected(self):
- """
- Returns list of shapes that are selected in the editor.
- :return: List of shapes.
- """
- #return [shape for shape in self.shape_buffer if shape["selected"]]
- return self.selected
- def delete_selected(self):
- tempref = [s for s in self.selected]
- for shape in tempref:
- self.delete_shape(shape)
- self.selected = []
- def plot_shape(self, geometry=None, linespec='b-', linewidth=1, animated=False):
- """
- Plots a geometric object or list of objects without rendering. Plotted objects
- are returned as a list. This allows for efficient/animated rendering.
- :param geometry: Geometry to be plotted (Any Shapely.geom kind or list of such)
- :param linespec: Matplotlib linespec string.
- :param linewidth: Width of lines in # of pixels.
- :param animated: If geometry is to be animated. (See MPL plot())
- :return: List of plotted elements.
- """
- plot_elements = []
- if geometry is None:
- geometry = self.active_tool.geometry
- try:
- for geo in geometry:
- plot_elements += self.plot_shape(geometry=geo,
- linespec=linespec,
- linewidth=linewidth,
- animated=animated)
- ## Non-iterable
- except TypeError:
- ## DrawToolShape
- if isinstance(geometry, DrawToolShape):
- plot_elements += self.plot_shape(geometry=geometry.geo,
- linespec=linespec,
- linewidth=linewidth,
- animated=animated)
- ## Polygon: Dscend into exterior and each interior.
- if type(geometry) == Polygon:
- plot_elements += self.plot_shape(geometry=geometry.exterior,
- linespec=linespec,
- linewidth=linewidth,
- animated=animated)
- plot_elements += self.plot_shape(geometry=geometry.interiors,
- linespec=linespec,
- linewidth=linewidth,
- animated=animated)
- if type(geometry) == LineString or type(geometry) == LinearRing:
- x, y = geometry.coords.xy
- element, = self.axes.plot(x, y, linespec, linewidth=linewidth, animated=animated)
- plot_elements.append(element)
- if type(geometry) == Point:
- x, y = geometry.coords.xy
- element, = self.axes.plot(x, y, 'bo', linewidth=linewidth, animated=animated)
- plot_elements.append(element)
- return plot_elements
- def plot_all(self):
- """
- Plots all shapes in the editor.
- Clears the axes, plots, and call self.canvas.auto_adjust_axes.
- :return: None
- :rtype: None
- """
- self.app.log.debug("plot_all()")
- self.axes.cla()
- for shape in self.storage.get_objects():
- if shape.geo is None: # TODO: This shouldn't have happened
- continue
- if shape in self.selected:
- self.plot_shape(geometry=shape.geo, linespec='k-', linewidth=2)
- continue
- self.plot_shape(geometry=shape.geo)
- for shape in self.utility:
- self.plot_shape(geometry=shape.geo, linespec='k--', linewidth=1)
- continue
- self.canvas.auto_adjust_axes()
- def on_shape_complete(self):
- self.app.log.debug("on_shape_complete()")
- # Add shape
- self.add_shape(self.active_tool.geometry)
- # Remove any utility shapes
- self.delete_utility_geometry()
- # Replot and reset tool.
- self.replot()
- self.active_tool = type(self.active_tool)(self)
- def delete_shape(self, shape):
- if shape in self.utility:
- self.utility.remove(shape)
- return
- self.storage.remove(shape)
- if shape in self.selected:
- self.selected.remove(shape)
- def replot(self):
- self.axes = self.canvas.new_axes("draw")
- self.plot_all()
- @staticmethod
- def make_storage():
- ## Shape storage.
- storage = FlatCAMRTreeStorage()
- storage.get_points = DrawToolShape.get_pts
- return storage
- def select_tool(self, toolname):
- """
- Selects a drawing tool. Impacts the object and GUI.
- :param toolname: Name of the tool.
- :return: None
- """
- self.tools[toolname]["button"].setChecked(True)
- self.on_tool_select(toolname)
- def set_selected(self, shape):
- # Remove and add to the end.
- if shape in self.selected:
- self.selected.remove(shape)
- self.selected.append(shape)
- def set_unselected(self, shape):
- if shape in self.selected:
- self.selected.remove(shape)
- def snap(self, x, y):
- """
- Adjusts coordinates to snap settings.
- :param x: Input coordinate X
- :param y: Input coordinate Y
- :return: Snapped (x, y)
- """
- snap_x, snap_y = (x, y)
- snap_distance = Inf
- ### Object (corner?) snap
- ### No need for the objects, just the coordinates
- ### in the index.
- if self.options["corner_snap"]:
- try:
- nearest_pt, shape = self.storage.nearest((x, y))
- nearest_pt_distance = distance((x, y), nearest_pt)
- if nearest_pt_distance <= self.options["snap_max"]:
- snap_distance = nearest_pt_distance
- snap_x, snap_y = nearest_pt
- except (StopIteration, AssertionError):
- pass
- ### Grid snap
- if self.options["grid_snap"]:
- if self.options["snap-x"] != 0:
- snap_x_ = round(x / self.options["snap-x"]) * self.options['snap-x']
- else:
- snap_x_ = x
- if self.options["snap-y"] != 0:
- snap_y_ = round(y / self.options["snap-y"]) * self.options['snap-y']
- else:
- snap_y_ = y
- nearest_grid_distance = distance((x, y), (snap_x_, snap_y_))
- if nearest_grid_distance < snap_distance:
- snap_x, snap_y = (snap_x_, snap_y_)
- return snap_x, snap_y
- def update_fcgeometry(self, fcgeometry):
- """
- Transfers the drawing tool shape buffer to the selected geometry
- object. The geometry already in the object are removed.
- :param fcgeometry: FlatCAMGeometry
- :return: None
- """
- fcgeometry.solid_geometry = []
- #for shape in self.shape_buffer:
- for shape in self.storage.get_objects():
- fcgeometry.solid_geometry.append(shape.geo)
- def union(self):
- """
- Makes union of selected polygons. Original polygons
- are deleted.
- :return: None.
- """
- results = cascaded_union([t.geo for t in self.get_selected()])
- # Delete originals.
- for_deletion = [s for s in self.get_selected()]
- for shape in for_deletion:
- self.delete_shape(shape)
- # Selected geometry is now gone!
- self.selected = []
- self.add_shape(DrawToolShape(results))
- self.replot()
- def intersection(self):
- """
- Makes intersectino of selected polygons. Original polygons are deleted.
- :return: None
- """
- shapes = self.get_selected()
- results = shapes[0].geo
- for shape in shapes[1:]:
- results = results.intersection(shape.geo)
- # Delete originals.
- for_deletion = [s for s in self.get_selected()]
- for shape in for_deletion:
- self.delete_shape(shape)
- # Selected geometry is now gone!
- self.selected = []
- self.add_shape(DrawToolShape(results))
- self.replot()
- def subtract(self):
- selected = self.get_selected()
- tools = selected[1:]
- toolgeo = cascaded_union([shp.geo for shp in tools])
- result = selected[0].geo.difference(toolgeo)
- self.delete_shape(selected[0])
- self.add_shape(DrawToolShape(result))
- self.replot()
- def buffer(self, buf_distance):
- selected = self.get_selected()
- if len(selected) == 0:
- self.app.inform.emit("[warning] Nothing selected for buffering.")
- return
- if not isinstance(buf_distance, float):
- self.app.inform.emit("[warning] Invalid distance for buffering.")
- return
- pre_buffer = cascaded_union([t.geo for t in selected])
- results = pre_buffer.buffer(buf_distance)
- self.add_shape(DrawToolShape(results))
- 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 list(locals().items()) 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)
- def mag(vec):
- return sqrt(vec[0] ** 2 + vec[1] ** 2)
- def poly2rings(poly):
- return [poly.exterior] + [interior for interior in poly.interiors]
|