FreeCAD-macros

Форк
0
/
SketcherOffset.FCMacro 
458 строк · 17.8 Кб
1
"""
2
This macro creates a line chain parallel to an existing one.
3
The 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.
4
Existing and created line chain can be draged using the mouse holding the parallelism.
5
Workflow:
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

22
import re
23
import math
24

25
from PySide import QtGui  # FreeCAD's PySide!!
26

27
from pivy.coin import *
28

29
major, minor = [int(n) for n in App.Version()[:2]]
30
if [major, minor] == [0, 18]:
31
    import DraftTrackers as gui_trackers
32
elif ((major > 0)
33
      or ((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.
36
    from draftguitools import gui_trackers
37

38

39
class OffsetDialog:
40
    """
41
    read selected items and show the offset distance
42
    """
43

44
    def __init__(self):
45
        self.view = FreeCADGui.ActiveDocument.ActiveView
46
        self.OffsetLine = []
47
        self.tracker = gui_trackers.lineTracker(scolor=(1,1,1),swidth=3,dotted=True)
48
        self.tracker.raiseTracker()
49
        self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.getPoint)
50
        self.callmouse=self.view.addEventCallbackPivy(SoLocation2Event.getClassTypeId(),self.getMousePoint)
51
        self.SelGeoId = self.getElements()
52

53
    def getMousePoint(self, event_cb):
54
        """
55
        slot: the mouse has been moved
56
        """
57
        event = event_cb.getEvent()
58
        if len(self.OffsetLine)==1:
59
            pos = event.getPosition()
60
            point = self.view.getPoint(pos[0],pos[1])
61
            self.tracker.p2(point)
62

63
    def getPoint(self, event_callback):
64
        """
65
        slot: a mouse button has been pressed
66
        """
67
        event = event_callback.getEvent()
68
        if event.getState() == SoMouseButtonEvent.DOWN:
69
            pos = event.getPosition()
70
            point = self.view.getPoint(pos[0], pos[1])
71
            self.OffsetLine.append(point)
72
            self.tracker.p2(point)
73
            self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.callback)
74
            self.view.removeEventCallbackPivy(SoLocation2Event.getClassTypeId(),self.callmouse)
75
            self.tracker.off()
76
            self.tracker.finalize()
77
            OffsetInSketcher.addOffset(self.SelGeoId,self.OffsetLine)
78

79
    def selectedGeoId(self):
80
        """
81
        returns a list containing GeoId's of selected lines and arcs
82
        """
83
        if not Gui.Selection.getSelectionEx():
84
            OffsetDialog.message('Please select elements')
85
        SelEx=Gui.Selection.getSelectionEx()[0].SubElementNames
86
        SelGeoId = []
87
        for El in SelEx:
88
            if El.find('Edge') == 0:
89
                Number = int(re.findall(r'\d+',El)[0])-1
90
                SelGeoId.append(Number)
91
        return SelGeoId
92

93
    def getElements(self):
94
        """
95
        get selected elements and offset value
96
        """
97
        SelGeoId = self.selectedGeoId()
98
        point = Sketch.getSketchPoint(SelGeoId[0])
99
        self.OffsetLine.append(point)
100
        self.tracker.on()
101
        self.tracker.p1(point)
102
        return SelGeoId
103

104
    def message(MessageText):
105
        msgBox = QtGui.QMessageBox()
106
        msgBox.setText(MessageText)
107
        msgBox.exec_()
108

109
class Edge:
110
    """
111
    manage the edge-element
112
    """
113
    def __init__(self,Geometry,Number=None):
114
        self.Geo = Geometry
115
        self.GeoId = Number
116
        self.StartPoint = Geometry.StartPoint
117
        self.EndPoint = Geometry.EndPoint
118
        self.StartPointId = 1
119
        self.EndPointId = 2
120
        self.Type = 'line'
121
        if hasattr(Geometry,'Radius'):
122
            self.Type = 'arc'
123
            self.Axis = Geometry.Axis
124
            if self.Axis.z < 0.0:
125
                self.StartPointId = 2
126
                self.EndPointId = 1
127
            self.Center = Geometry.Center
128
            self.Radius = Geometry.Radius
129

130
    def addGeometry(self,Mode=False):
131
        self.GeoId = Sketch.getGeoNumber()
132
        Sketch.addGeometry(self.Geo,Mode)
133

134
    def reverse(self):
135
        self.Geo.reverse()
136
        self.StartPoint = self.Geo.StartPoint
137
        self.EndPoint = self.Geo.EndPoint
138
        self.StartPointId = 3-self.StartPointId
139
        self.EndPointId = 3-self.EndPointId
140
        if self.Type == 'arc':
141
            self.Axis = self.Geo.Axis
142

143
    def isNegative(self):
144
        if self.Type == 'arc':
145
            if self.Axis.z < 0.0:
146
                return True
147

148
class Sketch:
149
    """
150
    manage all connections to the sketch
151
    """
152
    def addGeometry(Element,Mode):
153
        ActiveSketch.addGeometry(Element,Mode)
154

155
    def getGeometry():
156
        return ActiveSketch.Geometry
157

158
    def getGeoNumber():
159
        return ActiveSketch.GeometryCount
160

161
    def getSketchPoint(Element):
162
        return ActiveSketch.getPoint(Element,1)
163

164
    def coincident(Element1,Point1,Element2,Point2):
165
        ActiveSketch.addConstraint(Sketcher.Constraint('Coincident',Element1.GeoId,Point1,Element2.GeoId,Point2))
166

167
    def equal(Element1,Element2):
168
        ActiveSketch.addConstraint(Sketcher.Constraint('Equal',Element1.GeoId,Element2.GeoId))
169

170
    def perpendicular(Element1,Element2):
171
        ActiveSketch.addConstraint(Sketcher.Constraint('Perpendicular',Element1.GeoId,Element2.GeoId))
172

173
    def pointOnObject(Element1,Point,Element2):
174
        ActiveSketch.addConstraint(Sketcher.Constraint('PointOnObject',Element1.GeoId,Point,Element2.GeoId))
175

176
    def symmetric(Element,MidVertexNum):
177
        ActiveSketch.addConstraint(Sketcher.Constraint('Symmetric',Element.GeoId,1,Element.GeoId,2,MidVertexNum,1))
178

179
    def tangent(Element1,Point1,Element2,Point2):
180
        ActiveSketch.addConstraint(Sketcher.Constraint('Tangent',Element1.GeoId,Point1,Element2.GeoId,Point2))
181

182
class Tools:
183
    """
184
    diverse geometric tools
185
    """
186
    def createLine(Point1,Point2):
187
        return Edge(Part.LineSegment(Point1,Point2))
188

189
    def concentricArcOfCircle(Element,Distance,Reverse=False):
190
        Arc = Element.Geo.clone()
191
        Circle = Arc.Circle
192
        ConcentricEdge = Edge(Part.ArcOfCircle(Part.Circle(Circle,Distance),Arc.FirstParameter,Arc.LastParameter))
193
        if Reverse: ConcentricEdge.reverse()
194
        return ConcentricEdge
195

196
    def getMidPoint(Element):
197
        XPoint = (Element.EndPoint.x+Element.StartPoint.x)/2
198
        YPoint = (Element.EndPoint.y+Element.StartPoint.y)/2
199
        return App.Vector(XPoint,YPoint,0)
200

201
class OffsetInSketcher():
202
    """
203
    class containing the addOffset command
204
    """
205
    def addOffset(SelGeoId,OffsetLine):
206
        """
207
        this is the OffsetInSketcher command
208
        """
209
        def getSelectedEdges(SelGeoID):
210
            """
211
            returns a list of all selected lines and arcs
212
            """
213
            AllGeo = Sketch.getGeometry()
214
            Selected = []
215
            for Id in SelGeoID:
216
                Geometry = AllGeo[Id]
217
                if Geometry.TypeId == 'Part::GeomCircle':
218
                    # Unsupported.
219
                    continue
220
                Selected.append(Edge(Geometry,Id))
221
            return Selected
222

223
        def areEqual(V1,V2):
224
            """
225
            check the  identity of two vectors
226
            """
227
            if (V1-V2).Length < 0.001: return True
228

229
        def interlinkedEdges(UnsortedEdges):
230
            """
231
            return an interlinked list of lines and arcs
232
            """
233
            def getBackwardElem(Element,UnsortedEdges):
234
                """
235
                get next edge in backward direction
236
                """
237
                if not UnsortedEdges: return
238

239
                StartPt = Element.StartPoint
240
                Num = 0
241
                while Num <= len(UnsortedEdges)-1:
242
                    El=UnsortedEdges[Num]
243
                    if (areEqual(El.StartPoint,StartPt)) or (areEqual(El.EndPoint ,StartPt)):
244
                        if areEqual(El.StartPoint,StartPt):
245
                            El.reverse()
246
                        SortedEdges.append(El)
247
                        UnsortedEdges.pop(Num)
248
                        getBackwardElem(El,UnsortedEdges)
249
                        break
250
                    Num = Num+1
251
                return
252

253
            def getForwardElem(Element,UnsortedEdges):
254
                """
255
                get next edge in forward direction
256
                """
257
                if not UnsortedEdges: return
258

259
                EndPt = Element.EndPoint
260
                Num = 0
261
                while Num <= len(UnsortedEdges)-1:
262
                    El=UnsortedEdges[Num]
263
                    if (areEqual(El.StartPoint,EndPt)) or (areEqual(El.EndPoint ,EndPt)):
264
                        if areEqual(El.EndPoint,EndPt):
265
                            El.reverse()
266
                        SortedEdges.append(El)
267
                        UnsortedEdges.pop(Num)
268
                        getForwardElem(El,UnsortedEdges)
269
                        break
270
                    Num = Num+1
271
                return
272

273
            SortedEdges = []
274
            SortedEdges.append(UnsortedEdges.pop(0))
275
            getBackwardElem(SortedEdges[0],UnsortedEdges)
276
            SortedEdges.reverse()
277
            getForwardElem(SortedEdges[-1],UnsortedEdges)
278
            if len(UnsortedEdges) > 0 :
279
                OffsetDialog.message('Selected lines are split')
280
            return SortedEdges
281

282
        def flection(A,B,C):
283
            """
284
            returns True if flection of polyline is negative
285
            """
286
            M = App.Matrix(	A.x,	A.y,	1,	0,
287
					B.x,	B.y,	1,	0,
288
					C.x,	C.y,	1,	0,
289
					0,	0,	0,	1)
290
            if M.determinant() < 0.0: return True
291

292
        def pointBetween(Edge0,Edge1):
293
            """
294
            calculate  vertex between two offset edges
295
            """
296
            if  Edge0.Type == 'arc':
297
                delta = Edge0.EndPoint.sub(Edge0.Center).normalize()
298
                if Edge0.isNegative(): delta = delta.negative()
299
            elif  Edge1.Type == 'arc':
300
                delta = Edge1.StartPoint.sub(Edge1.Center).normalize()
301
                if Edge1.isNegative(): delta = delta.negative()
302
            else:
303
                e0=(Edge0.EndPoint.sub(Edge0.StartPoint)).normalize()
304
                e1=(Edge1.EndPoint.sub(Edge1.StartPoint)).normalize()
305
                Angle = e0.getAngle(e1)
306
                if Angle == 0.0:
307
                    delta =e0.cross(App.Vector(0,0,1))
308
                else:
309
                    Factor = 1.0/math.cos(Angle/2)
310
                    delta=e0.sub(e1).normalize().multiply(Factor)
311
                if flection(Edge0.StartPoint,Edge0.EndPoint,Edge1.EndPoint):
312
                    delta=delta.negative()
313
            delta = delta.multiply(OffsetValue)
314
            pt=Edge0.EndPoint.add(delta)
315
            return pt
316

317
        def pointAtOpenEnd(Edge,Start):
318
            """
319
            calculate  offset vertex at start or end of open edge
320
            """
321
            e = (Edge.EndPoint.sub(Edge.StartPoint)).normalize()
322
            delta =e.cross(App.Vector(0,0,1)).multiply(OffsetValue)
323
            if Start:
324
                pt = Edge.StartPoint+delta
325
            else:
326
                pt = Edge.EndPoint+delta
327
            return pt
328

329
        def createOffsetVertexes(SortedEdges):
330
            """
331
            create vertexes of offset line
332
            """
333
            OffsetPoints = []
334
            NumOfEdges = len(SortedEdges)-1
335
            if areEqual(SortedEdges[0].StartPoint,SortedEdges[-1].EndPoint):
336
                SortedEdges.append(SortedEdges[0])
337
                for i in range(NumOfEdges+1):
338
                    OffsetPoints.append(pointBetween(SortedEdges[i],SortedEdges[i+1]))
339
                OffsetPoints.insert(0,OffsetPoints[-1])
340
            else:
341
                OffsetPoints.append(pointAtOpenEnd(SortedEdges[0],True))
342
                for i in range(NumOfEdges):
343
                    OffsetPoints.append(pointBetween(SortedEdges[i],SortedEdges[i+1]))
344
                OffsetPoints.append(pointAtOpenEnd(SortedEdges[-1],False))
345
            return OffsetPoints
346

347
        def drawOffsetEdges(SortedEdges,OffsetPoints):
348
            """
349
            create and draw the offset lines
350
            """
351
            OffsetEdges = []
352
            for i in range(len(OffsetPoints)-1):
353
                if SortedEdges[i].Type == 'arc':
354
                    Arc = SortedEdges[i]
355
                    Offset = OffsetValue
356
                    if Arc.isNegative():
357
                        Offset = -OffsetValue
358
                    if Arc.Radius+Offset < 0.0:
359
                        OffsetDialog.message('Inner radius becomes negative')
360
                    else:
361
                        OffsetEdges.append(Tools.concentricArcOfCircle(SortedEdges[i],Offset))
362
                else:
363
                    OffsetEdges.append(Edge(Part.LineSegment(OffsetPoints[i],OffsetPoints[i+1])))
364
                OffsetEdges[-1 ].addGeometry()
365
            return OffsetEdges
366

367
        def createConstraints(OffsetEdges):
368
            """
369
            create constraints at vertexes
370
            """
371
            def constraintAtVertex(Edge1,Edge2):
372
                """
373
                create constraint between two edges
374
                """
375
                if Edge1.Type == 'arc' or Edge2.Type == 'arc':
376
                    Sketch.tangent(Edge1,Edge1.EndPointId,Edge2,Edge2.StartPointId)
377
                else:
378
                    Sketch.coincident(Edge1,Edge1.EndPointId,Edge2,Edge2.StartPointId)
379

380
            for i in range(len(OffsetEdges)-1):
381
                constraintAtVertex(OffsetEdges[i],OffsetEdges[i+1])
382
            if  areEqual(OffsetEdges[-1].EndPoint,OffsetEdges[0].StartPoint):
383
                constraintAtVertex(OffsetEdges[-1],OffsetEdges[0])
384

385
        def createParametric(SortedEdges,OffsetEdges,OffsetValue):
386
            """
387
            create constraints generating the parametric behavior
388
            """
389
            def parametricAtOpenEdge(BaseEdge,OffsetEdge,PtNum,OffsetValue):
390
                """
391
                constraints at first and last edge
392
                """
393
                if PtNum == 2:
394
                    DistanceLines.append(Tools.createLine(BaseEdge.EndPoint,OffsetEdge.EndPoint))
395
                    DistanceLines[-1 ].addGeometry(True)
396
                    Sketch.coincident(DistanceLines[-1 ],1,BaseEdge,BaseEdge.EndPointId)
397
                    Sketch.coincident(DistanceLines[-1 ],2,OffsetEdge,OffsetEdge.EndPointId)
398
                    Sketch.equal(DistanceLines[0 ],DistanceLines[-1])
399
                else:
400
                    DistanceLines.append(Tools.createLine(BaseEdge.StartPoint,OffsetEdge.StartPoint))
401
                    DistanceLines[-1 ].addGeometry(True)
402
                    Sketch.coincident(DistanceLines[-1 ],1,BaseEdge,BaseEdge.StartPointId)
403
                    Sketch.coincident(DistanceLines[-1 ],2,OffsetEdge,OffsetEdge.StartPointId)
404
                Sketch.perpendicular(OffsetEdge,DistanceLines[-1])
405
                Sketch.perpendicular(BaseEdge,DistanceLines[-1])
406

407
            def parametricAtEdges(BaseEdge,OffsetEdge,OffsetValue):
408
                """
409
                constraints at interior edges
410
                """
411
                if OffsetEdge.Type == 'arc':
412
                    Sketch.coincident(BaseEdge,3,OffsetEdge,3)
413
                else:
414
                    e = (OffsetEdge.EndPoint.sub(OffsetEdge.StartPoint)).normalize()
415
                    delta =e.cross(App.Vector(0,0,-1)).multiply(OffsetValue)
416
                    MidPoint = Tools.getMidPoint(OffsetEdge)
417
                    DistanceLines.append(Tools.createLine(MidPoint,MidPoint.add(delta)))
418
                    DistanceLines[-1 ].addGeometry(True)
419
                    Sketch.symmetric(OffsetEdge,Sketch.getGeoNumber()-1)
420
                    if len(DistanceLines) > 1: Sketch.equal(DistanceLines[0 ],DistanceLines[-1])
421
                    Sketch.perpendicular(OffsetEdge,DistanceLines[-1])
422
                    Sketch.perpendicular(BaseEdge,DistanceLines[-1])
423
                    # Sketch.pointOnObject(DistanceLines[-1],1,OffsetEdge)
424
                    Sketch.pointOnObject(DistanceLines[-1],2,BaseEdge)
425

426
            DistanceLines = []
427
            if  areEqual(OffsetEdges[-1].EndPoint,OffsetEdges[0].StartPoint):
428
                parametricAtEdges(SortedEdges[0],OffsetEdges[0],OffsetValue)
429
                for i in range(1,len(SortedEdges)-1):
430
                    parametricAtEdges(SortedEdges[i],OffsetEdges[i],OffsetValue)
431
            else:
432
                parametricAtOpenEdge(SortedEdges[0],OffsetEdges[0],1,OffsetValue)
433
                for i in range(1,len(SortedEdges)-1):
434
                    parametricAtEdges(SortedEdges[i],OffsetEdges[i],OffsetValue)
435
                parametricAtOpenEdge(SortedEdges[-1],OffsetEdges[-1],2,OffsetValue)
436

437
        def getOffsetValue(FirstEdge,OffsetLine):
438
            """
439
            calculate offsetvalue using the offsetline
440
            """
441
            OffsetValue = (OffsetLine[0].sub(OffsetLine[1])).Length
442
            if flection(OffsetLine[0],OffsetLine[1],FirstEdge.EndPoint):
443
                OffsetValue = -OffsetValue
444
            return OffsetValue
445

446
        SelectedEdges = getSelectedEdges(SelGeoId)
447
        if not SelectedEdges:
448
            App.Console.PrintWarning('No supported edge selected, doing nothing\n')
449
            return
450
        OffsetValue = getOffsetValue(SelectedEdges[0],OffsetLine)
451
        SortedEdges = interlinkedEdges(SelectedEdges)
452
        OffsetPoints = createOffsetVertexes(SortedEdges)
453
        OffsetEdges = drawOffsetEdges(SortedEdges,OffsetPoints)
454
        createConstraints(OffsetEdges)
455
        createParametric(SortedEdges,OffsetEdges,OffsetValue)
456
        App.ActiveDocument.recompute()
457

458
ex = OffsetDialog()
459

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.