FreeCAD-macros
458 строк · 17.8 Кб
1"""
2This macro creates a line chain parallel to an existing one.
3The existing line chain may be composed of line segments and arcs of cirles. All arcs have to be tangential to their neighbours. The line chain may be open or closed.
4Existing and created line chain can be draged using the mouse holding the parallelism.
5Workflow:
6- Select lines in an opened sketch.
7- Start the macro
8- Show value of distance and side (left/right) of the created lines using the mouse.
9"""
10
11__Name__ = "SketcherOffset"
12__Author__ = "edi"
13__Version__ = "0.0.4"
14__License__ = 'License identifier from https://spdx.org/licenses/, e.g. LGPL-2.0-or-later as FreeCAD, MIT, CC0-1.0'
15__Web__ = 'https://forum.freecadweb.org/viewtopic.php?f=19&t=49886'
16__Files__ = 'SketcherOffset.svg'
17__Help__ = 'Select some lines in a sketch. Start the macro. Show offset value using the rubber band.'
18__Status__ = 'Stable'
19__Requires__ = 'FreeCAD >= v0.18'
20__Date__ = "2021-04-30"
21
22import re
23import math
24
25from PySide import QtGui # FreeCAD's PySide!!
26
27from pivy.coin import *
28
29major, minor = [int(n) for n in App.Version()[:2]]
30if [major, minor] == [0, 18]:
31import DraftTrackers as gui_trackers
32elif ((major > 0)
33or ((major == 0) and (minor >= 19))):
34# The major is the year in the LinkDaily branch so that the condition
35# major > 0 is true for it.
36from draftguitools import gui_trackers
37
38
39class OffsetDialog:
40"""
41read selected items and show the offset distance
42"""
43
44def __init__(self):
45self.view = FreeCADGui.ActiveDocument.ActiveView
46self.OffsetLine = []
47self.tracker = gui_trackers.lineTracker(scolor=(1,1,1),swidth=3,dotted=True)
48self.tracker.raiseTracker()
49self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.getPoint)
50self.callmouse=self.view.addEventCallbackPivy(SoLocation2Event.getClassTypeId(),self.getMousePoint)
51self.SelGeoId = self.getElements()
52
53def getMousePoint(self, event_cb):
54"""
55slot: the mouse has been moved
56"""
57event = event_cb.getEvent()
58if len(self.OffsetLine)==1:
59pos = event.getPosition()
60point = self.view.getPoint(pos[0],pos[1])
61self.tracker.p2(point)
62
63def getPoint(self, event_callback):
64"""
65slot: a mouse button has been pressed
66"""
67event = event_callback.getEvent()
68if event.getState() == SoMouseButtonEvent.DOWN:
69pos = event.getPosition()
70point = self.view.getPoint(pos[0], pos[1])
71self.OffsetLine.append(point)
72self.tracker.p2(point)
73self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.callback)
74self.view.removeEventCallbackPivy(SoLocation2Event.getClassTypeId(),self.callmouse)
75self.tracker.off()
76self.tracker.finalize()
77OffsetInSketcher.addOffset(self.SelGeoId,self.OffsetLine)
78
79def selectedGeoId(self):
80"""
81returns a list containing GeoId's of selected lines and arcs
82"""
83if not Gui.Selection.getSelectionEx():
84OffsetDialog.message('Please select elements')
85SelEx=Gui.Selection.getSelectionEx()[0].SubElementNames
86SelGeoId = []
87for El in SelEx:
88if El.find('Edge') == 0:
89Number = int(re.findall(r'\d+',El)[0])-1
90SelGeoId.append(Number)
91return SelGeoId
92
93def getElements(self):
94"""
95get selected elements and offset value
96"""
97SelGeoId = self.selectedGeoId()
98point = Sketch.getSketchPoint(SelGeoId[0])
99self.OffsetLine.append(point)
100self.tracker.on()
101self.tracker.p1(point)
102return SelGeoId
103
104def message(MessageText):
105msgBox = QtGui.QMessageBox()
106msgBox.setText(MessageText)
107msgBox.exec_()
108
109class Edge:
110"""
111manage the edge-element
112"""
113def __init__(self,Geometry,Number=None):
114self.Geo = Geometry
115self.GeoId = Number
116self.StartPoint = Geometry.StartPoint
117self.EndPoint = Geometry.EndPoint
118self.StartPointId = 1
119self.EndPointId = 2
120self.Type = 'line'
121if hasattr(Geometry,'Radius'):
122self.Type = 'arc'
123self.Axis = Geometry.Axis
124if self.Axis.z < 0.0:
125self.StartPointId = 2
126self.EndPointId = 1
127self.Center = Geometry.Center
128self.Radius = Geometry.Radius
129
130def addGeometry(self,Mode=False):
131self.GeoId = Sketch.getGeoNumber()
132Sketch.addGeometry(self.Geo,Mode)
133
134def reverse(self):
135self.Geo.reverse()
136self.StartPoint = self.Geo.StartPoint
137self.EndPoint = self.Geo.EndPoint
138self.StartPointId = 3-self.StartPointId
139self.EndPointId = 3-self.EndPointId
140if self.Type == 'arc':
141self.Axis = self.Geo.Axis
142
143def isNegative(self):
144if self.Type == 'arc':
145if self.Axis.z < 0.0:
146return True
147
148class Sketch:
149"""
150manage all connections to the sketch
151"""
152def addGeometry(Element,Mode):
153ActiveSketch.addGeometry(Element,Mode)
154
155def getGeometry():
156return ActiveSketch.Geometry
157
158def getGeoNumber():
159return ActiveSketch.GeometryCount
160
161def getSketchPoint(Element):
162return ActiveSketch.getPoint(Element,1)
163
164def coincident(Element1,Point1,Element2,Point2):
165ActiveSketch.addConstraint(Sketcher.Constraint('Coincident',Element1.GeoId,Point1,Element2.GeoId,Point2))
166
167def equal(Element1,Element2):
168ActiveSketch.addConstraint(Sketcher.Constraint('Equal',Element1.GeoId,Element2.GeoId))
169
170def perpendicular(Element1,Element2):
171ActiveSketch.addConstraint(Sketcher.Constraint('Perpendicular',Element1.GeoId,Element2.GeoId))
172
173def pointOnObject(Element1,Point,Element2):
174ActiveSketch.addConstraint(Sketcher.Constraint('PointOnObject',Element1.GeoId,Point,Element2.GeoId))
175
176def symmetric(Element,MidVertexNum):
177ActiveSketch.addConstraint(Sketcher.Constraint('Symmetric',Element.GeoId,1,Element.GeoId,2,MidVertexNum,1))
178
179def tangent(Element1,Point1,Element2,Point2):
180ActiveSketch.addConstraint(Sketcher.Constraint('Tangent',Element1.GeoId,Point1,Element2.GeoId,Point2))
181
182class Tools:
183"""
184diverse geometric tools
185"""
186def createLine(Point1,Point2):
187return Edge(Part.LineSegment(Point1,Point2))
188
189def concentricArcOfCircle(Element,Distance,Reverse=False):
190Arc = Element.Geo.clone()
191Circle = Arc.Circle
192ConcentricEdge = Edge(Part.ArcOfCircle(Part.Circle(Circle,Distance),Arc.FirstParameter,Arc.LastParameter))
193if Reverse: ConcentricEdge.reverse()
194return ConcentricEdge
195
196def getMidPoint(Element):
197XPoint = (Element.EndPoint.x+Element.StartPoint.x)/2
198YPoint = (Element.EndPoint.y+Element.StartPoint.y)/2
199return App.Vector(XPoint,YPoint,0)
200
201class OffsetInSketcher():
202"""
203class containing the addOffset command
204"""
205def addOffset(SelGeoId,OffsetLine):
206"""
207this is the OffsetInSketcher command
208"""
209def getSelectedEdges(SelGeoID):
210"""
211returns a list of all selected lines and arcs
212"""
213AllGeo = Sketch.getGeometry()
214Selected = []
215for Id in SelGeoID:
216Geometry = AllGeo[Id]
217if Geometry.TypeId == 'Part::GeomCircle':
218# Unsupported.
219continue
220Selected.append(Edge(Geometry,Id))
221return Selected
222
223def areEqual(V1,V2):
224"""
225check the identity of two vectors
226"""
227if (V1-V2).Length < 0.001: return True
228
229def interlinkedEdges(UnsortedEdges):
230"""
231return an interlinked list of lines and arcs
232"""
233def getBackwardElem(Element,UnsortedEdges):
234"""
235get next edge in backward direction
236"""
237if not UnsortedEdges: return
238
239StartPt = Element.StartPoint
240Num = 0
241while Num <= len(UnsortedEdges)-1:
242El=UnsortedEdges[Num]
243if (areEqual(El.StartPoint,StartPt)) or (areEqual(El.EndPoint ,StartPt)):
244if areEqual(El.StartPoint,StartPt):
245El.reverse()
246SortedEdges.append(El)
247UnsortedEdges.pop(Num)
248getBackwardElem(El,UnsortedEdges)
249break
250Num = Num+1
251return
252
253def getForwardElem(Element,UnsortedEdges):
254"""
255get next edge in forward direction
256"""
257if not UnsortedEdges: return
258
259EndPt = Element.EndPoint
260Num = 0
261while Num <= len(UnsortedEdges)-1:
262El=UnsortedEdges[Num]
263if (areEqual(El.StartPoint,EndPt)) or (areEqual(El.EndPoint ,EndPt)):
264if areEqual(El.EndPoint,EndPt):
265El.reverse()
266SortedEdges.append(El)
267UnsortedEdges.pop(Num)
268getForwardElem(El,UnsortedEdges)
269break
270Num = Num+1
271return
272
273SortedEdges = []
274SortedEdges.append(UnsortedEdges.pop(0))
275getBackwardElem(SortedEdges[0],UnsortedEdges)
276SortedEdges.reverse()
277getForwardElem(SortedEdges[-1],UnsortedEdges)
278if len(UnsortedEdges) > 0 :
279OffsetDialog.message('Selected lines are split')
280return SortedEdges
281
282def flection(A,B,C):
283"""
284returns True if flection of polyline is negative
285"""
286M = App.Matrix( A.x, A.y, 1, 0,
287B.x, B.y, 1, 0,
288C.x, C.y, 1, 0,
2890, 0, 0, 1)
290if M.determinant() < 0.0: return True
291
292def pointBetween(Edge0,Edge1):
293"""
294calculate vertex between two offset edges
295"""
296if Edge0.Type == 'arc':
297delta = Edge0.EndPoint.sub(Edge0.Center).normalize()
298if Edge0.isNegative(): delta = delta.negative()
299elif Edge1.Type == 'arc':
300delta = Edge1.StartPoint.sub(Edge1.Center).normalize()
301if Edge1.isNegative(): delta = delta.negative()
302else:
303e0=(Edge0.EndPoint.sub(Edge0.StartPoint)).normalize()
304e1=(Edge1.EndPoint.sub(Edge1.StartPoint)).normalize()
305Angle = e0.getAngle(e1)
306if Angle == 0.0:
307delta =e0.cross(App.Vector(0,0,1))
308else:
309Factor = 1.0/math.cos(Angle/2)
310delta=e0.sub(e1).normalize().multiply(Factor)
311if flection(Edge0.StartPoint,Edge0.EndPoint,Edge1.EndPoint):
312delta=delta.negative()
313delta = delta.multiply(OffsetValue)
314pt=Edge0.EndPoint.add(delta)
315return pt
316
317def pointAtOpenEnd(Edge,Start):
318"""
319calculate offset vertex at start or end of open edge
320"""
321e = (Edge.EndPoint.sub(Edge.StartPoint)).normalize()
322delta =e.cross(App.Vector(0,0,1)).multiply(OffsetValue)
323if Start:
324pt = Edge.StartPoint+delta
325else:
326pt = Edge.EndPoint+delta
327return pt
328
329def createOffsetVertexes(SortedEdges):
330"""
331create vertexes of offset line
332"""
333OffsetPoints = []
334NumOfEdges = len(SortedEdges)-1
335if areEqual(SortedEdges[0].StartPoint,SortedEdges[-1].EndPoint):
336SortedEdges.append(SortedEdges[0])
337for i in range(NumOfEdges+1):
338OffsetPoints.append(pointBetween(SortedEdges[i],SortedEdges[i+1]))
339OffsetPoints.insert(0,OffsetPoints[-1])
340else:
341OffsetPoints.append(pointAtOpenEnd(SortedEdges[0],True))
342for i in range(NumOfEdges):
343OffsetPoints.append(pointBetween(SortedEdges[i],SortedEdges[i+1]))
344OffsetPoints.append(pointAtOpenEnd(SortedEdges[-1],False))
345return OffsetPoints
346
347def drawOffsetEdges(SortedEdges,OffsetPoints):
348"""
349create and draw the offset lines
350"""
351OffsetEdges = []
352for i in range(len(OffsetPoints)-1):
353if SortedEdges[i].Type == 'arc':
354Arc = SortedEdges[i]
355Offset = OffsetValue
356if Arc.isNegative():
357Offset = -OffsetValue
358if Arc.Radius+Offset < 0.0:
359OffsetDialog.message('Inner radius becomes negative')
360else:
361OffsetEdges.append(Tools.concentricArcOfCircle(SortedEdges[i],Offset))
362else:
363OffsetEdges.append(Edge(Part.LineSegment(OffsetPoints[i],OffsetPoints[i+1])))
364OffsetEdges[-1 ].addGeometry()
365return OffsetEdges
366
367def createConstraints(OffsetEdges):
368"""
369create constraints at vertexes
370"""
371def constraintAtVertex(Edge1,Edge2):
372"""
373create constraint between two edges
374"""
375if Edge1.Type == 'arc' or Edge2.Type == 'arc':
376Sketch.tangent(Edge1,Edge1.EndPointId,Edge2,Edge2.StartPointId)
377else:
378Sketch.coincident(Edge1,Edge1.EndPointId,Edge2,Edge2.StartPointId)
379
380for i in range(len(OffsetEdges)-1):
381constraintAtVertex(OffsetEdges[i],OffsetEdges[i+1])
382if areEqual(OffsetEdges[-1].EndPoint,OffsetEdges[0].StartPoint):
383constraintAtVertex(OffsetEdges[-1],OffsetEdges[0])
384
385def createParametric(SortedEdges,OffsetEdges,OffsetValue):
386"""
387create constraints generating the parametric behavior
388"""
389def parametricAtOpenEdge(BaseEdge,OffsetEdge,PtNum,OffsetValue):
390"""
391constraints at first and last edge
392"""
393if PtNum == 2:
394DistanceLines.append(Tools.createLine(BaseEdge.EndPoint,OffsetEdge.EndPoint))
395DistanceLines[-1 ].addGeometry(True)
396Sketch.coincident(DistanceLines[-1 ],1,BaseEdge,BaseEdge.EndPointId)
397Sketch.coincident(DistanceLines[-1 ],2,OffsetEdge,OffsetEdge.EndPointId)
398Sketch.equal(DistanceLines[0 ],DistanceLines[-1])
399else:
400DistanceLines.append(Tools.createLine(BaseEdge.StartPoint,OffsetEdge.StartPoint))
401DistanceLines[-1 ].addGeometry(True)
402Sketch.coincident(DistanceLines[-1 ],1,BaseEdge,BaseEdge.StartPointId)
403Sketch.coincident(DistanceLines[-1 ],2,OffsetEdge,OffsetEdge.StartPointId)
404Sketch.perpendicular(OffsetEdge,DistanceLines[-1])
405Sketch.perpendicular(BaseEdge,DistanceLines[-1])
406
407def parametricAtEdges(BaseEdge,OffsetEdge,OffsetValue):
408"""
409constraints at interior edges
410"""
411if OffsetEdge.Type == 'arc':
412Sketch.coincident(BaseEdge,3,OffsetEdge,3)
413else:
414e = (OffsetEdge.EndPoint.sub(OffsetEdge.StartPoint)).normalize()
415delta =e.cross(App.Vector(0,0,-1)).multiply(OffsetValue)
416MidPoint = Tools.getMidPoint(OffsetEdge)
417DistanceLines.append(Tools.createLine(MidPoint,MidPoint.add(delta)))
418DistanceLines[-1 ].addGeometry(True)
419Sketch.symmetric(OffsetEdge,Sketch.getGeoNumber()-1)
420if len(DistanceLines) > 1: Sketch.equal(DistanceLines[0 ],DistanceLines[-1])
421Sketch.perpendicular(OffsetEdge,DistanceLines[-1])
422Sketch.perpendicular(BaseEdge,DistanceLines[-1])
423# Sketch.pointOnObject(DistanceLines[-1],1,OffsetEdge)
424Sketch.pointOnObject(DistanceLines[-1],2,BaseEdge)
425
426DistanceLines = []
427if areEqual(OffsetEdges[-1].EndPoint,OffsetEdges[0].StartPoint):
428parametricAtEdges(SortedEdges[0],OffsetEdges[0],OffsetValue)
429for i in range(1,len(SortedEdges)-1):
430parametricAtEdges(SortedEdges[i],OffsetEdges[i],OffsetValue)
431else:
432parametricAtOpenEdge(SortedEdges[0],OffsetEdges[0],1,OffsetValue)
433for i in range(1,len(SortedEdges)-1):
434parametricAtEdges(SortedEdges[i],OffsetEdges[i],OffsetValue)
435parametricAtOpenEdge(SortedEdges[-1],OffsetEdges[-1],2,OffsetValue)
436
437def getOffsetValue(FirstEdge,OffsetLine):
438"""
439calculate offsetvalue using the offsetline
440"""
441OffsetValue = (OffsetLine[0].sub(OffsetLine[1])).Length
442if flection(OffsetLine[0],OffsetLine[1],FirstEdge.EndPoint):
443OffsetValue = -OffsetValue
444return OffsetValue
445
446SelectedEdges = getSelectedEdges(SelGeoId)
447if not SelectedEdges:
448App.Console.PrintWarning('No supported edge selected, doing nothing\n')
449return
450OffsetValue = getOffsetValue(SelectedEdges[0],OffsetLine)
451SortedEdges = interlinkedEdges(SelectedEdges)
452OffsetPoints = createOffsetVertexes(SortedEdges)
453OffsetEdges = drawOffsetEdges(SortedEdges,OffsetPoints)
454createConstraints(OffsetEdges)
455createParametric(SortedEdges,OffsetEdges,OffsetValue)
456App.ActiveDocument.recompute()
457
458ex = OffsetDialog()
459