FreeCAD

Форк
0
/
gui_trackers.py 
1496 строк · 56.0 Кб
1
# ***************************************************************************
2
# *   Copyright (c) 2011 Yorik van Havre <yorik@uncreated.net>              *
3
# *                                                                         *
4
# *   This program is free software; you can redistribute it and/or modify  *
5
# *   it under the terms of the GNU Lesser General Public License (LGPL)    *
6
# *   as published by the Free Software Foundation; either version 2 of     *
7
# *   the License, or (at your option) any later version.                   *
8
# *   for detail see the LICENCE text file.                                 *
9
# *                                                                         *
10
# *   This program is distributed in the hope that it will be useful,       *
11
# *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13
# *   GNU Library General Public License for more details.                  *
14
# *                                                                         *
15
# *   You should have received a copy of the GNU Library General Public     *
16
# *   License along with this program; if not, write to the Free Software   *
17
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
18
# *   USA                                                                   *
19
# *                                                                         *
20
# ***************************************************************************
21
"""Provides Coin based objects used to preview objects being built.
22

23
This module provides Coin (pivy) based objects
24
that are used by the Draft Workbench to draw temporary geometry,
25
that is, previews, of the real objects that will be created on the 3D view.
26
"""
27
## @package gui_trackers
28
# \ingroup draftguitools
29
# \brief Provides Coin based objects used to preview objects being built.
30
#
31
# This module provides Coin (pivy) based objects
32
# that are used by the Draft Workbench to draw temporary geometry,
33
# that is, previews, of the real objects that will be created on the 3D view.
34

35
## \addtogroup draftguitools
36
# @{
37
import math
38
import re
39
import pivy.coin as coin
40

41
import FreeCAD
42
import FreeCADGui
43
import Draft
44
import DraftVecUtils
45
from FreeCAD import Vector
46
from draftutils import params
47
from draftutils import utils
48
from draftutils.messages import _msg
49
from draftutils.todo import ToDo
50

51
__title__ = "FreeCAD Draft Trackers"
52
__author__ = "Yorik van Havre"
53
__url__ = "https://www.freecad.org"
54

55

56
class Tracker:
57
    """A generic Draft Tracker, to be used by other specific trackers."""
58

59
    def __init__(self, dotted=False, scolor=None, swidth=None,
60
                 children=[], ontop=False, name=None):
61
        global Part, DraftGeomUtils
62
        import Part
63
        import DraftGeomUtils
64
        self.ontop = ontop
65
        self.color = coin.SoBaseColor()
66
        drawstyle = coin.SoDrawStyle()
67
        if swidth:
68
            drawstyle.lineWidth = swidth
69
        if dotted:
70
            drawstyle.style = coin.SoDrawStyle.LINES
71
            drawstyle.lineWeight = 3
72
            drawstyle.linePattern = 0x0f0f  # 0xaa
73
        node = coin.SoSeparator()
74
        for c in [drawstyle, self.color] + children:
75
            node.addChild(c)
76
        self.switch = coin.SoSwitch()  # this is the on/off switch
77
        if name:
78
            self.switch.setName(name)
79
        self.switch.addChild(node)
80
        self.switch.whichChild = -1
81
        self.setColor(scolor)
82
        self.Visible = False
83
        ToDo.delay(self._insertSwitch, self.switch)
84

85
    def finalize(self):
86
        """Finish the command by removing the switch.
87
        Also called by ghostTracker.remove.
88
        """
89
        ToDo.delay(self._removeSwitch, self.switch)
90
        self.switch = None
91

92
    def _insertSwitch(self, switch):
93
        """Insert self.switch into the scene graph.
94

95
        Must not be called
96
        from an event handler (or other scene graph traversal).
97
        """
98
        sg = Draft.get3DView().getSceneGraph()
99
        if self.ontop:
100
            sg.insertChild(switch, 0)
101
        else:
102
            sg.addChild(switch)
103

104
    def _removeSwitch(self, switch):
105
        """Remove self.switch from the scene graph.
106

107
        As with _insertSwitch,
108
        must not be called during scene graph traversal).
109
        """
110
        sg = Draft.get3DView().getSceneGraph()
111
        if sg.findChild(switch) >= 0:
112
            sg.removeChild(switch)
113

114
    def on(self):
115
        """Set the visibility to True."""
116
        self.switch.whichChild = 0
117
        self.Visible = True
118

119
    def off(self):
120
        """Set the visibility to False."""
121
        self.switch.whichChild = -1
122
        self.Visible = False
123

124
    def lowerTracker(self):
125
        """Lower the tracker to the bottom of the scenegraph.
126

127
        So it doesn't obscure the other objects.
128
        """
129
        if self.switch:
130
            sg = Draft.get3DView().getSceneGraph()
131
            sg.removeChild(self.switch)
132
            sg.addChild(self.switch)
133

134
    def raiseTracker(self):
135
        """Raise the tracker to the top of the scenegraph.
136

137
        So it obscures the other objects.
138
        """
139
        if self.switch:
140
            sg = Draft.get3DView().getSceneGraph()
141
            sg.removeChild(self.switch)
142
            sg.insertChild(self.switch, 0)
143

144
    def setColor(self, color=None):
145
        """Set the color."""
146
        if color is not None:
147
            self.color.rgb = color
148
        elif hasattr(FreeCAD, "activeDraftCommand") \
149
                and FreeCAD.activeDraftCommand is not None \
150
                and hasattr(FreeCAD.activeDraftCommand, "featureName") \
151
                and FreeCAD.activeDraftCommand.featureName in ("Dimension", "Label", "Text"):
152
            self.color.rgb = utils.get_rgba_tuple(params.get_param("DefaultAnnoLineColor"))[:3]
153
        else:
154
            self.color.rgb = utils.get_rgba_tuple(params.get_param_view("DefaultShapeLineColor"))[:3]
155

156
    def _get_wp(self):
157
        return FreeCAD.DraftWorkingPlane
158

159

160
class snapTracker(Tracker):
161
    """Define Snap Mark tracker, used by tools that support snapping."""
162

163
    def __init__(self):
164
        self.marker = coin.SoMarkerSet()  # this is the marker symbol
165
        self.marker.markerIndex = FreeCADGui.getMarkerIndex("CIRCLE_FILLED", params.get_param_view("MarkerSize"))
166
        self.coords = coin.SoCoordinate3()  # this is the coordinate
167
        self.coords.point.setValue((0, 0, 0))
168
        node = coin.SoAnnotation()
169
        node.addChild(self.coords)
170
        node.addChild(self.marker)
171
        super().__init__(children=[node], name="snapTracker")
172
        self.setColor()
173

174
    def setMarker(self, style):
175
        """Set the marker index."""
176
        self.marker.markerIndex = FreeCADGui.getMarkerIndex(style, params.get_param_view("MarkerSize"))
177

178
    def setColor(self, color=None):
179
        """Set the color."""
180
        if color is None:
181
            self.color.rgb = utils.get_rgba_tuple(params.get_param("snapcolor"))[:3]
182
        else:
183
            self.color.rgb = color
184

185
    def setCoords(self, point):
186
        """Set the coordinates to the point."""
187
        self.coords.point.setValue((point.x, point.y, point.z))
188

189
    def addCoords(self, point):
190
        """Add the point to the current point."""
191
        l = self.coords.point.getValues()
192
        l.append(coin.SbVec3f(point.x, point.y, point.z))
193
        self.coords.point.setValues(l)
194

195
    def clear(self):
196
        """Delete the values of the point."""
197
        self.coords.point.deleteValues(0)
198

199

200
class lineTracker(Tracker):
201
    """A Line tracker, used by the tools that need to draw temporary lines"""
202

203
    def __init__(self, dotted=False, scolor=None, swidth=None, ontop=False):
204
        line = coin.SoLineSet()
205
        line.numVertices.setValue(2)
206
        self.coords = coin.SoCoordinate3()  # this is the coordinate
207
        self.coords.point.setValues(0, 2, [[0, 0, 0], [1, 0, 0]])
208
        super().__init__(dotted, scolor, swidth,
209
                         [self.coords, line],
210
                         ontop, name="lineTracker")
211

212
    def p1(self, point=None):
213
        """Set or get the first point of the line."""
214
        if point:
215
            if self.coords.point.getValues()[0].getValue() != tuple(point):
216
                self.coords.point.set1Value(0, point.x, point.y, point.z)
217
        else:
218
            return Vector(self.coords.point.getValues()[0].getValue())
219

220
    def p2(self, point=None):
221
        """Set or get the second point of the line."""
222
        if point:
223
            if self.coords.point.getValues()[-1].getValue() != tuple(point):
224
                self.coords.point.set1Value(1, point.x, point.y, point.z)
225
        else:
226
            return Vector(self.coords.point.getValues()[-1].getValue())
227

228
    def getLength(self):
229
        """Return the length of the line."""
230
        p1 = Vector(self.coords.point.getValues()[0].getValue())
231
        p2 = Vector(self.coords.point.getValues()[-1].getValue())
232
        return (p2.sub(p1)).Length
233

234

235
class rectangleTracker(Tracker):
236
    """A Rectangle tracker, used by the rectangle tool."""
237

238
    def __init__(self, dotted=False, scolor=None, swidth=None, face=False):
239
        self.origin = Vector(0, 0, 0)
240
        line = coin.SoLineSet()
241
        line.numVertices.setValue(5)
242
        self.coords = coin.SoCoordinate3()  # this is the coordinate
243
        self.coords.point.setValues(0, 50, [[0, 0, 0],
244
                                            [2, 0, 0],
245
                                            [2, 2, 0],
246
                                            [0, 2, 0],
247
                                            [0, 0, 0]])
248
        if face:
249
            m1 = coin.SoMaterial()
250
            m1.transparency.setValue(0.5)
251
            m1.diffuseColor.setValue([0.5, 0.5, 1.0])
252
            f = coin.SoIndexedFaceSet()
253
            f.coordIndex.setValues([0, 1, 2, 3])
254
            super().__init__(dotted, scolor, swidth,
255
                             [self.coords, line, m1, f],
256
                             name="rectangleTracker")
257
        else:
258
            super().__init__(dotted, scolor, swidth,
259
                             [self.coords, line],
260
                             name="rectangleTracker")
261
        wp = self._get_wp()
262
        self.u = wp.u
263
        self.v = wp.v
264

265
    def setorigin(self, point):
266
        """Set the base point of the rectangle."""
267
        self.coords.point.set1Value(0, point.x, point.y, point.z)
268
        self.coords.point.set1Value(4, point.x, point.y, point.z)
269
        self.origin = point
270

271
    def update(self, point):
272
        """Set the opposite (diagonal) point of the rectangle."""
273
        diagonal = point.sub(self.origin)
274
        inpoint1 = self.origin.add(DraftVecUtils.project(diagonal, self.v))
275
        inpoint2 = self.origin.add(DraftVecUtils.project(diagonal, self.u))
276
        self.coords.point.set1Value(1, inpoint1.x, inpoint1.y, inpoint1.z)
277
        self.coords.point.set1Value(2, point.x, point.y, point.z)
278
        self.coords.point.set1Value(3, inpoint2.x, inpoint2.y, inpoint2.z)
279

280
    def setPlane(self, u, v=None):
281
        """Set given (u,v) vectors as working plane.
282

283
        You can give only `u` and `v` will be deduced automatically
284
        given the current working plane.
285
        """
286
        self.u = u
287
        if v:
288
            self.v = v
289
        else:
290
            norm = self._get_wp().axis
291
            self.v = self.u.cross(norm)
292

293
    def p1(self, point=None):
294
        """Set or get the base point of the rectangle."""
295
        if point:
296
            self.setorigin(point)
297
        else:
298
            return Vector(self.coords.point.getValues()[0].getValue())
299

300
    def p2(self):
301
        """Get the second point (on u axis) of the rectangle."""
302
        return Vector(self.coords.point.getValues()[3].getValue())
303

304
    def p3(self, point=None):
305
        """Set or get the opposite (diagonal) point of the rectangle."""
306
        if point:
307
            self.update(point)
308
        else:
309
            return Vector(self.coords.point.getValues()[2].getValue())
310

311
    def p4(self):
312
        """Get the fourth point (on v axis) of the rectangle."""
313
        return Vector(self.coords.point.getValues()[1].getValue())
314

315
    def getSize(self):
316
        """Return (length, width) of the rectangle."""
317
        p1 = Vector(self.coords.point.getValues()[0].getValue())
318
        p2 = Vector(self.coords.point.getValues()[2].getValue())
319
        diag = p2.sub(p1)
320
        return ((DraftVecUtils.project(diag, self.u)).Length,
321
                (DraftVecUtils.project(diag, self.v)).Length)
322

323
    def getNormal(self):
324
        """Return the normal of the rectangle."""
325
        return (self.u.cross(self.v)).normalize()
326

327
    def isInside(self, point):
328
        """Return True if the given point is inside the rectangle."""
329
        vp = point.sub(self.p1())
330
        uv = self.p2().sub(self.p1())
331
        vv = self.p4().sub(self.p1())
332
        uvp = DraftVecUtils.project(vp, uv)
333
        vvp = DraftVecUtils.project(vp, vv)
334
        if uvp.getAngle(uv) < 1:
335
            if vvp.getAngle(vv) < 1:
336
                if uvp.Length <= uv.Length:
337
                    if vvp.Length <= vv.Length:
338
                        return True
339
        return False
340

341

342
class dimTracker(Tracker):
343
    """A Dimension tracker, used by the dimension tool."""
344

345
    def __init__(self, dotted=False, scolor=None, swidth=None):
346
        line = coin.SoLineSet()
347
        line.numVertices.setValue(4)
348
        self.coords = coin.SoCoordinate3()  # this is the coordinate
349
        self.coords.point.setValues(0, 4,
350
                                    [[0, 0, 0],
351
                                     [0, 0, 0],
352
                                     [0, 0, 0],
353
                                     [0, 0, 0]])
354
        super().__init__(dotted, scolor, swidth,
355
                         [self.coords, line], name="dimTracker")
356
        self.p1 = self.p2 = self.p3 = None
357

358
    def update(self, pts):
359
        """Update the points and calculate."""
360
        if not pts:
361
            return
362
        elif len(pts) == 1:
363
            self.p3 = pts[0]
364
        else:
365
            self.p1 = pts[0]
366
            self.p2 = pts[1]
367
            if len(pts) > 2:
368
                self.p3 = pts[2]
369
        self.calc()
370

371
    def calc(self):
372
        """Calculate the new points from p1 and p2."""
373
        import Part
374
        if (self.p1 is not None) and (self.p2 is not None):
375
            points = [DraftVecUtils.tup(self.p1, True),
376
                      DraftVecUtils.tup(self.p2, True),
377
                      DraftVecUtils.tup(self.p1, True),
378
                      DraftVecUtils.tup(self.p2, True)]
379
            if self.p3 is not None:
380
                p1 = self.p1
381
                p4 = self.p2
382
                if DraftVecUtils.equals(p1, p4):
383
                    proj = None
384
                else:
385
                    base = Part.LineSegment(p1, p4).toShape()
386
                    proj = DraftGeomUtils.findDistance(self.p3, base)
387
                if not proj:
388
                    p2 = p1
389
                    p3 = p4
390
                else:
391
                    p2 = p1.add(proj.negative())
392
                    p3 = p4.add(proj.negative())
393
                points = [DraftVecUtils.tup(p1),
394
                          DraftVecUtils.tup(p2),
395
                          DraftVecUtils.tup(p3),
396
                          DraftVecUtils.tup(p4)]
397
            self.coords.point.setValues(0, 4, points)
398

399

400
class bsplineTracker(Tracker):
401
    """A bspline tracker."""
402

403
    def __init__(self, dotted=False, scolor=None, swidth=None, points=[]):
404
        self.bspline = None
405
        self.points = points
406
        self.trans = coin.SoTransform()
407
        self.sep = coin.SoSeparator()
408
        self.recompute()
409
        super().__init__(dotted, scolor, swidth,
410
                         [self.trans, self.sep], name="bsplineTracker")
411

412
    def update(self, points):
413
        """Update the points and recompute."""
414
        self.points = points
415
        self.recompute()
416

417
    def recompute(self):
418
        """Recompute the tracker."""
419
        if len(self.points) >= 2:
420
            if self.bspline:
421
                self.sep.removeChild(self.bspline)
422
            self.bspline = None
423
            c =  Part.BSplineCurve()
424
            # DNC: allows to close the curve by placing ends close to each other
425
            if len(self.points) >= 3 and ( (self.points[0] - self.points[-1]).Length < Draft.tolerance() ):
426
                # YVH: Added a try to bypass some hazardous situations
427
                try:
428
                    c.interpolate(self.points[:-1], True)
429
                except Part.OCCError:
430
                    pass
431
            elif self.points:
432
                try:
433
                    c.interpolate(self.points, False)
434
                except Part.OCCError:
435
                    pass
436
            c = c.toShape()
437
            buf = c.writeInventor(2, 0.01)
438
            # fp = open("spline.iv", "w")
439
            # fp.write(buf)
440
            # fp.close()
441
            try:
442
                ivin = coin.SoInput()
443
                ivin.setBuffer(buf)
444
                ivob = coin.SoDB.readAll(ivin)
445
            except Exception:
446
                # workaround for pivy SoInput.setBuffer() bug
447
                buf = buf.replace("\n", "")
448
                pts = re.findall("point \\[(.*?)\\]", buf)[0]
449
                pts = pts.split(",")
450
                pc = []
451
                for p in pts:
452
                    v = p.strip().split()
453
                    pc.append([float(v[0]), float(v[1]), float(v[2])])
454
                coords = coin.SoCoordinate3()
455
                coords.point.setValues(0, len(pc), pc)
456
                line = coin.SoLineSet()
457
                line.numVertices.setValue(-1)
458
                self.bspline = coin.SoSeparator()
459
                self.bspline.addChild(coords)
460
                self.bspline.addChild(line)
461
                self.sep.addChild(self.bspline)
462
            else:
463
                if ivob and ivob.getNumChildren() > 1:
464
                    self.bspline = ivob.getChild(1).getChild(0)
465
                    self.bspline.removeChild(self.bspline.getChild(0))
466
                    self.bspline.removeChild(self.bspline.getChild(0))
467
                    self.sep.addChild(self.bspline)
468
                else:
469
                    FreeCAD.Console.PrintWarning("bsplineTracker.recompute() failed to read-in Inventor string\n")
470

471

472
class bezcurveTracker(Tracker):
473
    """A bezcurve tracker."""
474

475
    def __init__(self, dotted=False, scolor=None, swidth=None, points=[]):
476
        self.bezcurve = None
477
        self.points = points
478
        self.degree = None
479
        self.trans = coin.SoTransform()
480
        self.sep = coin.SoSeparator()
481
        self.recompute()
482
        super().__init__(dotted, scolor, swidth,
483
                         [self.trans, self.sep], name="bezcurveTracker")
484

485
    def update(self, points, degree=None):
486
        """Update the points and recompute."""
487
        self.points = points
488
        if degree:
489
            self.degree = degree
490
        self.recompute()
491

492
    def recompute(self):
493
        """Recompute the tracker."""
494
        if self.bezcurve:
495
            for seg in self.bezcurve:
496
                self.sep.removeChild(seg)
497
                seg = None
498

499
        self.bezcurve = []
500

501
        if (len(self.points) >= 2):
502
            if self.degree:
503
                poles = self.points[1:]
504
                segpoleslst = [poles[x:x+self.degree] for x in range(0, len(poles), (self.degree or 1))]
505
            else:
506
                segpoleslst = [self.points]
507
            startpoint = self.points[0]
508

509
            for segpoles in segpoleslst:
510
                c = Part.BezierCurve()  # last segment may have lower degree
511
                c.increase(len(segpoles))
512
                c.setPoles([startpoint] + segpoles)
513
                c = c.toShape()
514
                startpoint = segpoles[-1]
515
                buf = c.writeInventor(2, 0.01)
516
            # fp=open("spline.iv", "w")
517
            # fp.write(buf)
518
            # fp.close()
519
                try:
520
                    ivin = coin.SoInput()
521
                    ivin.setBuffer(buf)
522
                    ivob = coin.SoDB.readAll(ivin)
523
                except Exception:
524
                    # workaround for pivy SoInput.setBuffer() bug
525
                    buf = buf.replace("\n","")
526
                    pts = re.findall("point \\[(.*?)\\]", buf)[0]
527
                    pts = pts.split(",")
528
                    pc = []
529
                    for p in pts:
530
                        v = p.strip().split()
531
                        pc.append([float(v[0]), float(v[1]), float(v[2])])
532
                    coords = coin.SoCoordinate3()
533
                    coords.point.setValues(0, len(pc), pc)
534
                    line = coin.SoLineSet()
535
                    line.numVertices.setValue(-1)
536
                    bezcurveseg = coin.SoSeparator()
537
                    bezcurveseg.addChild(coords)
538
                    bezcurveseg.addChild(line)
539
                    self.sep.addChild(bezcurveseg)
540
                else:
541
                    if ivob and ivob.getNumChildren() > 1:
542
                        bezcurveseg = ivob.getChild(1).getChild(0)
543
                        bezcurveseg.removeChild(bezcurveseg.getChild(0))
544
                        bezcurveseg.removeChild(bezcurveseg.getChild(0))
545
                        self.sep.addChild(bezcurveseg)
546
                    else:
547
                        FreeCAD.Console.PrintWarning("bezcurveTracker.recompute() failed to read-in Inventor string\n")
548
                self.bezcurve.append(bezcurveseg)
549

550

551
class arcTracker(Tracker):
552
    """An arc tracker."""
553
    # Note: used by the Arc command but also for angular dimensions.
554
    def __init__(self, dotted=False, scolor=None, swidth=None,
555
                 start=0, end=math.pi*2):
556
        self.circle = None
557
        self.startangle = math.degrees(start)
558
        self.endangle = math.degrees(end)
559
        self.trans = coin.SoTransform()
560
        self.trans.translation.setValue([0, 0, 0])
561
        self.sep = coin.SoSeparator()
562
        self.autoinvert = True
563
        self.normal = self._get_wp().axis
564
        self.recompute()
565
        super().__init__(dotted, scolor, swidth,
566
                         [self.trans, self.sep], name="arcTracker")
567

568
    def getDeviation(self):
569
        """Return a deviation vector that represents the base of the circle."""
570
        import Part
571
        c = Part.makeCircle(1, Vector(0, 0, 0), self.normal)
572
        return c.Vertexes[0].Point
573

574
    def setCenter(self, cen):
575
        """Set the center point."""
576
        self.trans.translation.setValue([cen.x, cen.y, cen.z])
577

578
    def setRadius(self, rad):
579
        """Set the radius."""
580
        self.trans.scaleFactor.setValue([rad, rad, rad])
581

582
    def getRadius(self):
583
        """Return the current radius."""
584
        return self.trans.scaleFactor.getValue()[0]
585

586
    def setStartAngle(self, ang):
587
        """Set the start angle."""
588
        self.startangle = math.degrees(ang)
589
        self.recompute()
590

591
    def setEndAngle(self, ang):
592
        """Set the end angle."""
593
        self.endangle = math.degrees(ang)
594
        self.recompute()
595

596
    def getAngle(self, pt):
597
        """Return the angle of a given vector in radians."""
598
        c = self.trans.translation.getValue()
599
        center = Vector(c[0], c[1], c[2])
600
        return DraftVecUtils.angle(pt.sub(center), self.getDeviation(), self.normal)
601

602
    def getAngles(self):
603
        """Return the start and end angles in degrees."""
604
        return(self.startangle, self.endangle)
605

606
    def setStartPoint(self, pt):
607
        """Set the start angle from a point."""
608
        self.setStartAngle(-self.getAngle(pt))
609

610
    def setEndPoint(self, pt):
611
        """Set the end angle from a point."""
612
        self.setEndAngle(-self.getAngle(pt))
613

614
    def setApertureAngle(self, ang):
615
        """Set the end angle by giving the aperture angle."""
616
        ap = math.degrees(ang)
617
        self.endangle = self.startangle + ap
618
        self.recompute()
619

620
    def setBy3Points(self, p1, p2, p3):
621
        """Set the arc by three points."""
622
        import Part
623
        try:
624
            arc = Part.ArcOfCircle(p1, p2, p3)
625
        except Exception:
626
            return
627
        e = arc.toShape()
628
        self.autoinvert = False
629
        self.normal = e.Curve.Axis.negative()  # axis is always in wrong direction
630
        self.setCenter(e.Curve.Center)
631
        self.setRadius(e.Curve.Radius)
632
        self.setStartPoint(p1)
633
        self.setEndPoint(p3)
634

635
    def recompute(self):
636
        """Recompute the tracker."""
637
        import Part
638
        if self.circle:
639
            self.sep.removeChild(self.circle)
640
        self.circle = None
641
        if (self.endangle < self.startangle) or not self.autoinvert:
642
            c = Part.makeCircle(1, Vector(0, 0, 0),
643
                                self.normal, self.endangle, self.startangle)
644
        else:
645
            c = Part.makeCircle(1, Vector(0, 0, 0),
646
                                self.normal, self.startangle, self.endangle)
647
        buf = c.writeInventor(2, 0.01)
648
        try:
649
            ivin = coin.SoInput()
650
            ivin.setBuffer(buf)
651
            ivob = coin.SoDB.readAll(ivin)
652
        except Exception:
653
            # workaround for pivy SoInput.setBuffer() bug
654
            buf = buf.replace("\n", "")
655
            pts = re.findall("point \\[(.*?)\\]", buf)[0]
656
            pts = pts.split(",")
657
            pc = []
658
            for p in pts:
659
                v = p.strip().split()
660
                pc.append([float(v[0]), float(v[1]), float(v[2])])
661
            coords = coin.SoCoordinate3()
662
            coords.point.setValues(0, len(pc), pc)
663
            line = coin.SoLineSet()
664
            line.numVertices.setValue(-1)
665
            self.circle = coin.SoSeparator()
666
            self.circle.addChild(coords)
667
            self.circle.addChild(line)
668
            self.sep.addChild(self.circle)
669
        else:
670
            if ivob and ivob.getNumChildren() > 1:
671
                self.circle = ivob.getChild(1).getChild(0)
672
                self.circle.removeChild(self.circle.getChild(0))
673
                self.circle.removeChild(self.circle.getChild(0))
674
                self.sep.addChild(self.circle)
675
            else:
676
                FreeCAD.Console.PrintWarning("arcTracker.recompute() failed to read-in Inventor string\n")
677

678

679
class ghostTracker(Tracker):
680
    """A Ghost tracker, that allows to copy whole object representations.
681

682
    You can pass it an object or a list of objects, or a shape.
683
    """
684

685
    def __init__(self, sel, dotted=False, scolor=None, swidth=None, mirror=False):
686
        self.trans = coin.SoTransform()
687
        self.trans.translation.setValue([0, 0, 0])
688
        self.children = [self.trans]
689
        rootsep = coin.SoSeparator()
690
        if not isinstance(sel, list):
691
            sel = [sel]
692
        for obj in sel:
693
            import Part
694
            if not isinstance(obj, Part.Vertex):
695
                rootsep.addChild(self.getNode(obj))
696
            else:
697
                self.coords = coin.SoCoordinate3()
698
                self.coords.point.setValue((obj.X, obj.Y, obj.Z))
699
                self.marker = coin.SoMarkerSet()  # this is the marker symbol
700
                self.marker.markerIndex = FreeCADGui.getMarkerIndex("SQUARE_FILLED", params.get_param_view("MarkerSize"))
701
                node = coin.SoAnnotation()
702
                selnode = coin.SoSeparator()
703
                selnode.addChild(self.coords)
704
                selnode.addChild(self.marker)
705
                node.addChild(selnode)
706
                rootsep.addChild(node)
707
        if mirror is True:
708
            self._flip(rootsep)
709
        self.children.append(rootsep)
710
        super().__init__(dotted, scolor, swidth,
711
                         children=self.children, name="ghostTracker")
712
        self.setColor(scolor)
713

714
    def setColor(self, color=None):
715
        """Set the color."""
716
        if color is None:
717
            self.color.rgb = utils.get_rgba_tuple(params.get_param("snapcolor"))[:3]
718
        else:
719
            self.color.rgb = color
720

721
    def remove(self):
722
        """Remove the ghost when switching to and from subelement mode."""
723
        if self.switch:
724
            self.finalize()
725

726
    def move(self, delta):
727
        """Move the ghost to a given position.
728

729
        Relative from its start position.
730
        """
731
        self.trans.translation.setValue([delta.x, delta.y, delta.z])
732

733
    def rotate(self, axis, angle):
734
        """Rotate the ghost of a given angle."""
735
        self.trans.rotation.setValue(coin.SbVec3f(DraftVecUtils.tup(axis)), angle)
736

737
    def center(self, point):
738
        """Set the rotation/scale center of the ghost."""
739
        self.trans.center.setValue(point.x, point.y, point.z)
740

741
    def scale(self, delta):
742
        """Scale the ghost by the given factor."""
743
        self.trans.scaleFactor.setValue([delta.x, delta.y, delta.z])
744

745
    def getNode(self, obj):
746
        """Return a coin node representing the given object."""
747
        import Part
748
        if isinstance(obj, Part.Shape):
749
            return self.getNodeLight(obj)
750
        elif obj.isDerivedFrom("Part::Feature"):
751
            return self.getNodeFull(obj)
752
        else:
753
            return self.getNodeFull(obj)
754

755
    def getNodeFull(self, obj):
756
        """Get a coin node which is a copy of the current representation."""
757
        sep = coin.SoSeparator()
758
        try:
759
            sep.addChild(obj.ViewObject.RootNode.copy())
760
            # add Part container offset
761
            if hasattr(obj, "getGlobalPlacement"):
762
                if obj.Placement != obj.getGlobalPlacement():
763
                    if sep.getChild(0).getNumChildren() > 0:
764
                        if isinstance(sep.getChild(0).getChild(0),coin.SoTransform):
765
                            gpl = obj.getGlobalPlacement()
766
                            sep.getChild(0).getChild(0).translation.setValue(tuple(gpl.Base))
767
                            sep.getChild(0).getChild(0).rotation.setValue(gpl.Rotation.Q)
768
        except Exception:
769
            _msg("ghostTracker: Error retrieving coin node (full)")
770
        return sep
771

772
    def getNodeLight(self, shape):
773
        """Extract a lighter version directly from a shape."""
774
        # error-prone
775
        sep = coin.SoSeparator()
776
        try:
777
            inputstr = coin.SoInput()
778
            inputstr.setBuffer(shape.writeInventor())
779
            coinobj = coin.SoDB.readAll(inputstr)
780
            # only add wireframe or full node?
781
            sep.addChild(coinobj.getChildren()[1])
782
            # sep.addChild(coinobj)
783
        except Exception:
784
            _msg("ghostTracker: Error retrieving coin node (light)")
785
        return sep
786

787
    def getMatrix(self):
788
        """Get matrix of the active view."""
789
        r = FreeCADGui.ActiveDocument.ActiveView.getViewer().getSoRenderManager().getViewportRegion()
790
        v = coin.SoGetMatrixAction(r)
791
        m = self.trans.getMatrix(v)
792
        if m:
793
            m = m.getValue()
794
            return FreeCAD.Matrix(m[0][0], m[0][1], m[0][2], m[0][3],
795
                                  m[1][0], m[1][1], m[1][2], m[1][3],
796
                                  m[2][0], m[2][1], m[2][2], m[2][3],
797
                                  m[3][0], m[3][1], m[3][2], m[3][3])
798
        else:
799
            return FreeCAD.Matrix()
800

801
    def setMatrix(self, matrix):
802
        """Set the transformation matrix.
803

804
        The 4th column of the matrix (the position) is ignored.
805
        """
806
        m = coin.SbMatrix(matrix.A11, matrix.A12, matrix.A13, matrix.A14,
807
                          matrix.A21, matrix.A22, matrix.A23, matrix.A24,
808
                          matrix.A31, matrix.A32, matrix.A33, matrix.A34,
809
                          matrix.A41, matrix.A42, matrix.A43, matrix.A44)
810
        self.trans.setMatrix(m)
811

812
    def _flip(self, root):
813
        """Flip the normals of the coin faces."""
814
        # Code by wmayer:
815
        # https://forum.freecad.org/viewtopic.php?p=702640#p702640
816
        search = coin.SoSearchAction()
817
        search.setType(coin.SoIndexedFaceSet.getClassTypeId())
818
        search.apply(root)
819
        path = search.getPath()
820
        if path:
821
            node = path.getTail()
822
            index = node.coordIndex.getValues()
823
            if len(index) % 4 == 0:
824
                for i in range(0, len(index), 4):
825
                    tmp = index[i]
826
                    index[i] = index[i+1]
827
                    index[i+1] = tmp
828

829
                node.coordIndex.setValues(index)
830

831

832
class editTracker(Tracker):
833
    """A node edit tracker."""
834

835
    def __init__(self, pos=Vector(0, 0, 0), name=None, idx=0, objcol=None,
836
                 marker=None, inactive=False):
837
        self.marker = coin.SoMarkerSet()  # this is the marker symbol
838
        if marker is None:
839
            self.marker.markerIndex = FreeCADGui.getMarkerIndex("SQUARE_FILLED", params.get_param_view("MarkerSize"))
840
        else:
841
            self.marker.markerIndex = marker
842
        self.coords = coin.SoCoordinate3()  # this is the coordinate
843
        self.coords.point.setValue((pos.x, pos.y, pos.z))
844
        self.position = pos
845
        if inactive:
846
            self.selnode = coin.SoSeparator()
847
        else:
848
            self.selnode = coin.SoType.fromName("SoFCSelection").createInstance()
849
            if name:
850
                self.selnode.useNewSelection = False
851
                self.selnode.documentName.setValue(FreeCAD.ActiveDocument.Name)
852
                self.selnode.objectName.setValue(name)
853
                self.selnode.subElementName.setValue("EditNode" + str(idx))
854
        node = coin.SoAnnotation()
855
        self.selnode.addChild(self.coords)
856
        self.selnode.addChild(self.marker)
857
        node.addChild(self.selnode)
858
        ontop = not inactive
859
        super().__init__(children=[node],
860
                         ontop=ontop, name="editTracker")
861
        if objcol is None:
862
            self.setColor()
863
        else:
864
            self.setColor(objcol[:3])
865
        self.on()
866

867
    def set(self, pos):
868
        """Set the point to the position."""
869
        self.coords.point.setValue((pos.x, pos.y, pos.z))
870
        self.position = pos
871

872
    def get(self):
873
        """Get a vector from the point."""
874
        return self.position
875

876
    def get_doc_name(self):
877
        """Get the document name."""
878
        return str(self.selnode.documentName.getValue())
879

880
    def get_obj_name(self):
881
        """Get the object name."""
882
        return str(self.selnode.objectName.getValue())
883

884
    def get_subelement_name(self):
885
        """Get the subelement name."""
886
        return str(self.selnode.subElementName.getValue())
887

888
    def get_subelement_index(self):
889
        """Get the subelement index."""
890
        subElement = self.get_subelement_name()
891
        idx = int(subElement[8:])
892
        return idx
893

894
    def move(self, delta):
895
        """Get the point and add a delta, and set the new point."""
896
        self.set(self.get().add(delta))
897

898
    def setColor(self, color=None):
899
        """Set the color."""
900
        if color is None:
901
            self.color.rgb = utils.get_rgba_tuple(params.get_param("snapcolor"))[:3]
902
        else:
903
            self.color.rgb = color
904

905

906
class PlaneTracker(Tracker):
907
    """A working plane tracker."""
908

909
    def __init__(self):
910
        # getting screen distance
911
        p1 = Draft.get3DView().getPoint((100, 100))
912
        p2 = Draft.get3DView().getPoint((110, 100))
913
        bl = (p2.sub(p1)).Length * (params.get_param("snapRange")/2.0)
914
        pick = coin.SoPickStyle()
915
        pick.style.setValue(coin.SoPickStyle.UNPICKABLE)
916
        self.trans = coin.SoTransform()
917
        self.trans.translation.setValue([0, 0, 0])
918
        m1 = coin.SoMaterial()
919
        m1.transparency.setValue(0.8)
920
        m1.diffuseColor.setValue([0.4, 0.4, 0.6])
921
        c1 = coin.SoCoordinate3()
922
        c1.point.setValues([[-bl, -bl, 0],
923
                            [bl, -bl, 0],
924
                            [bl, bl, 0],
925
                            [-bl, bl, 0]])
926
        f = coin.SoIndexedFaceSet()
927
        f.coordIndex.setValues([0, 1, 2, 3])
928
        m2 = coin.SoMaterial()
929
        m2.transparency.setValue(0.7)
930
        m2.diffuseColor.setValue([0.2, 0.2, 0.3])
931
        c2 = coin.SoCoordinate3()
932
        c2.point.setValues([[0, bl, 0], [0, 0, 0],
933
                            [bl, 0, 0], [-0.05*bl, 0.95*bl, 0],
934
                            [0, bl, 0], [0.05*bl, 0.95*bl, 0],
935
                            [0.95*bl, 0.05*bl, 0], [bl, 0, 0],
936
                            [0.95*bl, -0.05*bl, 0]])
937
        l = coin.SoLineSet()
938
        l.numVertices.setValues([3, 3, 3])
939
        s = coin.SoSeparator()
940
        s.addChild(pick)
941
        s.addChild(self.trans)
942
        s.addChild(m1)
943
        s.addChild(c1)
944
        s.addChild(f)
945
        s.addChild(m2)
946
        s.addChild(c2)
947
        s.addChild(l)
948
        super().__init__(children=[s], name="planeTracker")
949

950
    def set(self, pos=None):
951
        """Set the translation to the position."""
952
        plm = self._get_wp().get_placement()
953
        Q = plm.Rotation.Q
954
        if pos is None:
955
            pos = plm.Base
956
        self.trans.translation.setValue([pos.x, pos.y, pos.z])
957
        self.trans.rotation.setValue([Q[0], Q[1], Q[2], Q[3]])
958
        self.on()
959

960

961
class wireTracker(Tracker):
962
    """A wire tracker."""
963

964
    def __init__(self, wire):
965
        self.line = coin.SoLineSet()
966
        self.closed = DraftGeomUtils.isReallyClosed(wire)
967
        if self.closed:
968
            self.line.numVertices.setValue(len(wire.Vertexes)+1)
969
        else:
970
            self.line.numVertices.setValue(len(wire.Vertexes))
971
        self.coords = coin.SoCoordinate3()
972
        self.update(wire)
973
        super().__init__(children=[self.coords, self.line],
974
                         name="wireTracker")
975

976
    def update(self, wire, forceclosed=False):
977
        """Update the tracker."""
978
        if wire:
979
            if self.closed or forceclosed:
980
                self.line.numVertices.setValue(len(wire.Vertexes) + 1)
981
            else:
982
                self.line.numVertices.setValue(len(wire.Vertexes))
983
            for i in range(len(wire.Vertexes)):
984
                p = wire.Vertexes[i].Point
985
                self.coords.point.set1Value(i, [p.x, p.y, p.z])
986
            if self.closed or forceclosed:
987
                t = len(wire.Vertexes)
988
                p = wire.Vertexes[0].Point
989
                self.coords.point.set1Value(t, [p.x, p.y, p.z])
990

991
    def updateFromPointlist(self, points, forceclosed=False):
992
        """Update the tracker from points."""
993
        if points:
994
            for i in range(len(points)):
995
                p = points[i]
996
                self.coords.point.set1Value(i, [p.x, p.y, p.z])
997

998

999
class gridTracker(Tracker):
1000
    """A grid tracker."""
1001

1002
    def __init__(self):
1003

1004
        col, red, green, blue, gtrans = self.getGridColors()
1005
        pick = coin.SoPickStyle()
1006
        pick.style.setValue(coin.SoPickStyle.UNPICKABLE)
1007
        self.trans = coin.SoTransform()
1008
        self.trans.translation.setValue([0, 0, 0])
1009

1010
        # small squares
1011
        self.mat1 = coin.SoMaterial()
1012
        self.mat1.transparency.setValue(0.8*(1-gtrans))
1013
        self.mat1.diffuseColor.setValue(col)
1014
        self.font = coin.SoFont()
1015
        self.coords1 = coin.SoCoordinate3()
1016
        self.lines1 = coin.SoLineSet() # small squares
1017

1018
        # texts
1019
        texts = coin.SoSeparator()
1020
        t1 = coin.SoSeparator()
1021
        self.textpos1 = coin.SoTransform()
1022
        self.text1 = coin.SoAsciiText()
1023
        self.text1.string = " "
1024
        t2 = coin.SoSeparator()
1025
        self.textpos2 = coin.SoTransform()
1026
        self.textpos2.rotation.setValue((0.0, 0.0, 0.7071067811865475, 0.7071067811865476))
1027
        self.text2 = coin.SoAsciiText()
1028
        self.text2.string = " "
1029
        t1.addChild(self.textpos1)
1030
        t1.addChild(self.text1)
1031
        t2.addChild(self.textpos2)
1032
        t2.addChild(self.text2)
1033
        texts.addChild(self.font)
1034
        texts.addChild(t1)
1035
        texts.addChild(t2)
1036

1037
        # big squares
1038
        self.mat2 = coin.SoMaterial()
1039
        self.mat2.transparency.setValue(0.2*(1-gtrans))
1040
        self.mat2.diffuseColor.setValue(col)
1041
        self.coords2 = coin.SoCoordinate3()
1042
        self.lines2 = coin.SoLineSet() # big squares
1043

1044
        # human figure
1045
        mat_human = coin.SoMaterial()
1046
        mat_human.transparency.setValue(0.3*(1-gtrans))
1047
        mat_human.diffuseColor.setValue(col)
1048
        self.coords_human = coin.SoCoordinate3()
1049
        self.human = coin.SoLineSet()
1050

1051
        # axes
1052
        self.mat3 = coin.SoMaterial()
1053
        self.mat3.transparency.setValue(gtrans)
1054
        self.mat3.diffuseColor.setValues([col,red,green,blue])
1055
        self.coords3 = coin.SoCoordinate3()
1056
        self.lines3 = coin.SoIndexedLineSet() # axes
1057
        self.lines3.coordIndex.setValues(0,5,[0,1,-1,2,3])
1058
        self.lines3.materialIndex.setValues(0,2,[0,0])
1059
        mbind3 = coin.SoMaterialBinding()
1060
        mbind3.value = coin.SoMaterialBinding.PER_PART_INDEXED
1061

1062
        self.pts = []
1063
        s = coin.SoType.fromName("SoSkipBoundingGroup").createInstance()
1064
        s.addChild(pick)
1065
        s.addChild(self.trans)
1066
        s.addChild(self.mat1)
1067
        s.addChild(self.coords1)
1068
        s.addChild(self.lines1)
1069
        s.addChild(self.mat2)
1070
        s.addChild(self.coords2)
1071
        s.addChild(self.lines2)
1072
        s.addChild(mat_human)
1073
        s.addChild(self.coords_human)
1074
        s.addChild(self.human)
1075
        s.addChild(mbind3)
1076
        s.addChild(self.mat3)
1077
        s.addChild(self.coords3)
1078
        s.addChild(self.lines3)
1079
        s.addChild(texts)
1080

1081
        super().__init__(children=[s], name="gridTracker")
1082
        self.show_during_command = False
1083
        self.show_always = False
1084
        self.reset()
1085

1086
    def update(self):
1087
        """Redraw the grid."""
1088
        # Resize the grid to make sure it fits
1089
        # an exact pair number of main lines
1090
        if self.space == 0:
1091
            self.lines1.numVertices.deleteValues(0)
1092
            self.lines2.numVertices.deleteValues(0)
1093
            self.pts = []
1094
            FreeCAD.Console.PrintWarning("Draft Grid: Spacing value is zero\n")
1095
            return
1096
        if self.mainlines == 0:
1097
            self.lines1.numVertices.deleteValues(0)
1098
            self.lines2.numVertices.deleteValues(0)
1099
            self.pts = []
1100
            return
1101
        if self.numlines == 0:
1102
            self.lines1.numVertices.deleteValues(0)
1103
            self.lines2.numVertices.deleteValues(0)
1104
            self.pts = []
1105
            return
1106
        numlines = self.numlines // self.mainlines // 2 * 2 * self.mainlines
1107
        bound = (numlines // 2) * self.space
1108
        border = (numlines//2 + self.mainlines/2) * self.space
1109
        cursor = self.mainlines//4 * self.space
1110
        pts = []
1111
        mpts = []
1112
        apts = []
1113
        cpts = []
1114
        for i in range(numlines + 1):
1115
            curr = -bound + i * self.space
1116
            z = 0
1117
            if i / float(self.mainlines) == i // self.mainlines:
1118
                if round(curr, 4) == 0:
1119
                    apts.extend([[-bound, curr, z], [bound, curr, z]])
1120
                    apts.extend([[curr, -bound, z], [curr, bound, z]])
1121
                else:
1122
                    mpts.extend([[-bound, curr, z], [bound, curr, z]])
1123
                    mpts.extend([[curr, -bound, z], [curr, bound, z]])
1124
                cpts.extend([[-border,curr,z], [-border+cursor,curr,z]])
1125
                cpts.extend([[border-cursor,curr,z], [border,curr,z]])
1126
                cpts.extend([[curr,-border,z], [curr,-border+cursor,z]])
1127
                cpts.extend([[curr,border-cursor,z], [curr,border,z]])
1128
            else:
1129
                pts.extend([[-bound, curr, z], [bound, curr, z]])
1130
                pts.extend([[curr, -bound, z], [curr, bound, z]])
1131
        if pts != self.pts:
1132
            idx = []
1133
            midx = []
1134
            #aidx = []
1135
            cidx = []
1136
            for p in range(0, len(pts), 2):
1137
                idx.append(2)
1138
            for mp in range(0, len(mpts), 2):
1139
                midx.append(2)
1140
            #for ap in range(0, len(apts), 2):
1141
            #    aidx.append(2)
1142
            for cp in range(0, len(cpts),2):
1143
                cidx.append(2)
1144

1145
            if params.get_param("gridBorder"):
1146
                # extra border
1147
                border = (numlines//2 + self.mainlines/2) * self.space
1148
                mpts.extend([[-border, -border, z], [border, -border, z], [border, border, z], [-border, border, z], [-border, -border, z]])
1149
                midx.append(5)
1150
                # cursors
1151
                mpts.extend(cpts)
1152
                midx.extend(cidx)
1153
                # texts
1154
                self.font.size = self.space*(self.mainlines//4) or 1
1155
                self.font.name = params.get_param("textfont")
1156
                txt = FreeCAD.Units.Quantity(self.space*self.mainlines,FreeCAD.Units.Length).UserString
1157
                self.text1.string = txt
1158
                self.text2.string = txt
1159
                self.textpos1.translation.setValue((-bound+self.space,-border+self.space,z))
1160
                self.textpos2.translation.setValue((-bound-self.space,-bound+self.space,z))
1161
            else:
1162
                self.text1.string = " "
1163
                self.text2.string = " "
1164

1165
            self.lines1.numVertices.deleteValues(0)
1166
            self.lines2.numVertices.deleteValues(0)
1167
            #self.lines3.numVertices.deleteValues(0)
1168
            self.coords1.point.setValues(pts)
1169
            self.lines1.numVertices.setValues(idx)
1170
            self.coords2.point.setValues(mpts)
1171
            self.lines2.numVertices.setValues(midx)
1172
            self.coords3.point.setValues(apts)
1173
            #self.lines3.numVertices.setValues(aidx)
1174
            self.pts = pts
1175

1176
        # update the grid colors
1177
        col, red, green, blue, gtrans = self.getGridColors()
1178
        self.mat1.diffuseColor.setValue(col)
1179
        self.mat2.diffuseColor.setValue(col)
1180
        self.mat3.diffuseColor.setValues([col,red,green,blue])
1181

1182
    def getGridColors(self):
1183
        """Returns grid colors stored in the preferences"""
1184
        gtrans = params.get_param("gridTransparency")/100.0
1185
        col = utils.get_rgba_tuple(params.get_param("gridColor"))[:3]
1186
        if params.get_param("coloredGridAxes"):
1187
            red = ((1.0+col[0])/2,0.0,0.0)
1188
            green = (0.0,(1.0+col[1])/2,0.0)
1189
            blue = (0.0,0.0,(1.0+col[2])/2)
1190
        else:
1191
            red = col
1192
            green = col
1193
            blue = col
1194
        return col, red, green, blue, gtrans
1195

1196
    def displayHumanFigure(self, wp):
1197
        """ Display the human figure at the grid corner.
1198
        The silhouette is displayed only if:
1199
        - BIM Workbench is available;
1200
        - preference BaseApp/Preferences/Mod/Draft/gridBorder is True;
1201
        - preference BaseApp/Preferences/Mod/Draft/gridShowHuman is True;
1202
        - the working plane normal is vertical.
1203
        """
1204
        numlines = self.numlines // self.mainlines // 2 * 2 * self.mainlines
1205
        bound = (numlines // 2) * self.space
1206
        pts = []
1207
        pidx = []
1208
        if params.get_param("gridBorder") \
1209
                and params.get_param("gridShowHuman") \
1210
                and wp.axis.getAngle(FreeCAD.Vector(0,0,1)) < 0.001:
1211
            try:
1212
                import BimProjectManager
1213
                loc = FreeCAD.Vector(-bound+self.space/2,-bound+self.space/2,0)
1214
                hpts = BimProjectManager.getHuman(loc)
1215
                pts.extend([tuple(p) for p in hpts])
1216
                pidx.append(len(hpts))
1217
            except Exception:
1218
                # BIM not installed
1219
                return
1220
        self.human.numVertices.deleteValues(0)
1221
        self.coords_human.point.setValues(pts)
1222
        self.human.numVertices.setValues(pidx)
1223

1224
    def setAxesColor(self, wp):
1225
        """set axes color"""
1226
        cols = [0,0]
1227
        if params.get_param("coloredGridAxes"):
1228
            if round(wp.u.getAngle(FreeCAD.Vector(1,0,0)),2) in (0,3.14):
1229
                cols[0] = 1
1230
            elif round(wp.u.getAngle(FreeCAD.Vector(0,1,0)),2) in (0,3.14):
1231
                cols[0] = 2
1232
            elif round(wp.u.getAngle(FreeCAD.Vector(0,0,1)),2) in (0,3.14):
1233
                cols[0] = 3
1234
            if round(wp.v.getAngle(FreeCAD.Vector(1,0,0)),2) in (0,3.14):
1235
                cols[1] = 1
1236
            elif round(wp.v.getAngle(FreeCAD.Vector(0,1,0)),2) in (0,3.14):
1237
                cols[1] = 2
1238
            elif round(wp.v.getAngle(FreeCAD.Vector(0,0,1)),2) in (0,3.14):
1239
                cols[1] = 3
1240
        self.lines3.materialIndex.setValues(0,2,cols)
1241

1242
    def setSize(self, size):
1243
        """Set size of the lines and update."""
1244
        self.numlines = size
1245
        self.update()
1246

1247
    def setSpacing(self, space):
1248
        """Set spacing and update."""
1249
        self.space = space
1250
        self.update()
1251

1252
    def setMainlines(self, ml):
1253
        """Set mainlines and update."""
1254
        self.mainlines = ml
1255
        self.update()
1256

1257
    def reset(self):
1258
        """Reset the grid according to preferences settings."""
1259
        try:
1260
            self.space = FreeCAD.Units.Quantity(params.get_param("gridSpacing")).Value
1261
        except ValueError:
1262
            self.space = 1
1263
        self.mainlines = params.get_param("gridEvery")
1264
        self.numlines = params.get_param("gridSize")
1265
        self.update()
1266

1267
    def set(self):
1268
        """Move and rotate the grid according to the current working plane."""
1269
        self.reset()
1270
        wp = self._get_wp()
1271
        Q = wp.get_placement().Rotation.Q
1272
        P = wp.position
1273
        self.trans.rotation.setValue([Q[0], Q[1], Q[2], Q[3]])
1274
        self.trans.translation.setValue([P.x, P.y, P.z])
1275
        self.displayHumanFigure(wp)
1276
        self.setAxesColor(wp)
1277
        self.on()
1278

1279
    def getClosestNode(self, point):
1280
        """Return the closest node from the given point."""
1281
        wp = self._get_wp()
1282
        pt = wp.get_local_coords(point)
1283
        pu = round(pt.x / self.space, 0) * self.space
1284
        pv = round(pt.y / self.space, 0) * self.space
1285
        pt = wp.get_global_coords(Vector(pu, pv, 0))
1286
        return pt
1287

1288

1289
class boxTracker(Tracker):
1290
    """A box tracker, can be based on a line object."""
1291

1292
    def __init__(self, line=None, width=0.1, height=1, shaded=False):
1293
        self.trans = coin.SoTransform()
1294
        m = coin.SoMaterial()
1295
        m.transparency.setValue(0.8)
1296
        m.diffuseColor.setValue([0.4, 0.4, 0.6])
1297
        w = coin.SoDrawStyle()
1298
        w.style = coin.SoDrawStyle.LINES
1299
        self.cube = coin.SoCube()
1300
        self.cube.height.setValue(width)
1301
        self.cube.depth.setValue(height)
1302
        self.baseline = None
1303
        if line:
1304
            self.baseline = line
1305
            self.update()
1306
        if shaded:
1307
            super().__init__(children=[self.trans, m, self.cube],
1308
                             name="boxTracker")
1309
        else:
1310
            super().__init__(children=[self.trans, w, self.cube],
1311
                             name="boxTracker")
1312

1313
    def update(self, line=None, normal=None):
1314
        """Update the tracker."""
1315
        import DraftGeomUtils
1316
        if not normal:
1317
            normal = self._get_wp().axis
1318
        if line:
1319
            if isinstance(line, list):
1320
                bp = line[0]
1321
                lvec = line[1].sub(line[0])
1322
            else:
1323
                lvec = DraftGeomUtils.vec(line.Shape.Edges[0])
1324
                bp = line.Shape.Edges[0].Vertexes[0].Point
1325
        elif self.baseline:
1326
            lvec = DraftGeomUtils.vec(self.baseline.Shape.Edges[0])
1327
            bp = self.baseline.Shape.Edges[0].Vertexes[0].Point
1328
        else:
1329
            return
1330
        self.cube.width.setValue(lvec.Length)
1331
        bp = bp.add(lvec.multiply(0.5))
1332
        bp = bp.add(DraftVecUtils.scaleTo(normal, self.cube.depth.getValue()/2.0))
1333
        self.pos(bp)
1334
        tol = 1e-6
1335
        if lvec.Length > tol and normal.Length > tol:
1336
            lvec.normalize()
1337
            normal.normalize()
1338
            if not lvec.isEqual(normal, tol) \
1339
                    and not lvec.isEqual(normal.negative(), tol):
1340
                rot = FreeCAD.Rotation(lvec, FreeCAD.Vector(), normal, "XZY")
1341
                self.trans.rotation.setValue(rot.Q)
1342

1343
    def setRotation(self, rot):
1344
        """Set the rotation."""
1345
        self.trans.rotation.setValue(rot.Q)
1346

1347
    def pos(self, p):
1348
        """Set the translation."""
1349
        self.trans.translation.setValue(DraftVecUtils.tup(p))
1350

1351
    def width(self, w=None):
1352
        """Set the width."""
1353
        if w:
1354
            self.cube.height.setValue(w)
1355
        else:
1356
            return self.cube.height.getValue()
1357

1358
    def length(self, l=None):
1359
        """Set the length."""
1360
        if l:
1361
            self.cube.width.setValue(l)
1362
        else:
1363
            return self.cube.width.getValue()
1364

1365
    def height(self, h=None):
1366
        """Set the height."""
1367
        if h:
1368
            self.cube.depth.setValue(h)
1369
            self.update()
1370
        else:
1371
            return self.cube.depth.getValue()
1372

1373

1374
class radiusTracker(Tracker):
1375
    """A tracker that displays a transparent sphere to inicate a radius."""
1376

1377
    def __init__(self, position=FreeCAD.Vector(0, 0, 0), radius=1):
1378
        self.trans = coin.SoTransform()
1379
        self.trans.translation.setValue([position.x, position.y, position.z])
1380
        m = coin.SoMaterial()
1381
        m.transparency.setValue(0.9)
1382
        m.diffuseColor.setValue([0, 1, 0])
1383
        self.sphere = coin.SoSphere()
1384
        self.sphere.radius.setValue(radius)
1385
        self.baseline = None
1386
        super().__init__(children=[self.trans, m, self.sphere],
1387
                         name="radiusTracker")
1388

1389
    def update(self, arg1, arg2=None):
1390
        """Update the tracker."""
1391
        if isinstance(arg1, FreeCAD.Vector):
1392
            self.trans.translation.setValue([arg1.x, arg1.y, arg1.z])
1393
        else:
1394
            self.sphere.radius.setValue(arg1)
1395
        if arg2 is not None:
1396
            if isinstance(arg2, FreeCAD.Vector):
1397
                self.trans.translation.setValue([arg2.x, arg2.y, arg2.z])
1398
            else:
1399
                self.sphere.radius.setValue(arg2)
1400

1401

1402
class archDimTracker(Tracker):
1403
    """A wrapper around a Sketcher dim."""
1404

1405
    def __init__(self, p1=FreeCAD.Vector(0, 0, 0), p2=FreeCAD.Vector(1, 0, 0), mode=1):
1406
        import SketcherGui
1407
        self.transform = coin.SoMatrixTransform()
1408
        self.dimnode = coin.SoType.fromName("SoDatumLabel").createInstance()
1409
        p1node = coin.SbVec3f([p1.x, p1.y, p1.z])
1410
        p2node = coin.SbVec3f([p2.x, p2.y, p2.z])
1411
        self.dimnode.pnts.setValues([p1node, p2node])
1412
        self.dimnode.lineWidth = 1
1413
        color = utils.get_rgba_tuple(params.get_param("snapcolor"))[:3]
1414
        self.dimnode.textColor.setValue(coin.SbVec3f(color))
1415
        self.dimnode.size = 11
1416
        self.size_pixel = self.dimnode.size.getValue()*96/72
1417
        self.offset = 0.5
1418
        self.mode = mode
1419
        self.matrix = self.transform.matrix
1420
        self.norm = self.dimnode.norm
1421
        self.param1 = self.dimnode.param1
1422
        self.param2 = self.dimnode.param2
1423
        self.pnts = self.dimnode.pnts
1424
        self.string = self.dimnode.string
1425
        self.view = Draft.get3DView()
1426
        self.camera = self.view.getCameraNode()
1427
        self.setMode(mode)
1428
        self.setString()
1429
        super().__init__(children=[self.transform, self.dimnode], name="archDimTracker")
1430

1431
    def setString(self, text=None):
1432
        """Set the dim string to the given value or auto value."""
1433
        plane = self._get_wp()
1434
        p1 = Vector(self.pnts.getValues()[0].getValue())
1435
        p2 = Vector(self.pnts.getValues()[-1].getValue())
1436
        self.norm.setValue(plane.axis)
1437
        # set the offset sign to prevent the dim line from intersecting the curve near the cursor
1438
        sign_dx = math.copysign(1, (p2.sub(p1)).x)
1439
        sign_dy = math.copysign(1, (p2.sub(p1)).y)
1440
        sign = sign_dx*sign_dy
1441
        if self.mode == 2:
1442
            self.Distance = abs((p2.sub(p1)).x)
1443
            self.param1.setValue(sign*self.offset)
1444
        elif self.mode == 3:
1445
            self.Distance = abs((p2.sub(p1)).y)
1446
            self.param1.setValue(-1*sign*self.offset)
1447
        else:
1448
            self.Distance = (p2.sub(p1)).Length
1449

1450
        text = FreeCAD.Units.Quantity(self.Distance, FreeCAD.Units.Length).UserString
1451
        self.matrix.setValue(*plane.get_placement().Matrix.transposed().A)
1452
        self.string.setValue(text.encode('utf8'))
1453
        # change the text position to external depending on the distance and scale values
1454
        volume = self.camera.getViewVolume()
1455
        scale = self.view.getSize()[1]/volume.getHeight()
1456
        if scale*self.Distance > self.size_pixel*len(text):
1457
            self.param2.setValue(0)
1458
        else:
1459
            self.param2.setValue(1/2*self.Distance + 3/5*self.size_pixel*len(text)/scale)
1460

1461

1462
    def setMode(self, mode=1):
1463
        """Set the mode.
1464

1465
        0 = without lines (a simple mark)
1466
        1 = aligned (default)
1467
        2 = horizontal
1468
        3 = vertical.
1469
        """
1470
        self.dimnode.datumtype.setValue(mode)
1471

1472
    def p1(self, point=None):
1473
        """Set or get the first point of the dim."""
1474
        plane = self._get_wp()
1475
        if point:
1476
            p1_proj = plane.project_point(point)
1477
            p1_proj_u = (p1_proj - plane.position).dot(plane.u.normalize())
1478
            p1_proj_v = (p1_proj - plane.position).dot(plane.v.normalize())
1479
            self.pnts.set1Value(0, p1_proj_u, p1_proj_v, 0)
1480
            self.setString()
1481
        else:
1482
            return Vector(self.pnts.getValues()[0].getValue())
1483

1484
    def p2(self, point=None):
1485
        """Set or get the second point of the dim."""
1486
        plane = self._get_wp()
1487
        if point:
1488
            p2_proj = plane.project_point(point)
1489
            p2_proj_u = (p2_proj - plane.position).dot(plane.u.normalize())
1490
            p2_proj_v = (p2_proj - plane.position).dot(plane.v.normalize())
1491
            self.pnts.set1Value(1, p2_proj_u, p2_proj_v, 0)
1492
            self.setString()
1493
        else:
1494
            return Vector(self.pnts.getValues()[-1].getValue())
1495

1496
## @}
1497

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

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

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

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