FreeCAD
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
23This module provides Coin (pivy) based objects
24that are used by the Draft Workbench to draw temporary geometry,
25that 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# @{
37import math
38import re
39import pivy.coin as coin
40
41import FreeCAD
42import FreeCADGui
43import Draft
44import DraftVecUtils
45from FreeCAD import Vector
46from draftutils import params
47from draftutils import utils
48from draftutils.messages import _msg
49from draftutils.todo import ToDo
50
51__title__ = "FreeCAD Draft Trackers"
52__author__ = "Yorik van Havre"
53__url__ = "https://www.freecad.org"
54
55
56class Tracker:
57"""A generic Draft Tracker, to be used by other specific trackers."""
58
59def __init__(self, dotted=False, scolor=None, swidth=None,
60children=[], ontop=False, name=None):
61global Part, DraftGeomUtils
62import Part
63import DraftGeomUtils
64self.ontop = ontop
65self.color = coin.SoBaseColor()
66drawstyle = coin.SoDrawStyle()
67if swidth:
68drawstyle.lineWidth = swidth
69if dotted:
70drawstyle.style = coin.SoDrawStyle.LINES
71drawstyle.lineWeight = 3
72drawstyle.linePattern = 0x0f0f # 0xaa
73node = coin.SoSeparator()
74for c in [drawstyle, self.color] + children:
75node.addChild(c)
76self.switch = coin.SoSwitch() # this is the on/off switch
77if name:
78self.switch.setName(name)
79self.switch.addChild(node)
80self.switch.whichChild = -1
81self.setColor(scolor)
82self.Visible = False
83ToDo.delay(self._insertSwitch, self.switch)
84
85def finalize(self):
86"""Finish the command by removing the switch.
87Also called by ghostTracker.remove.
88"""
89ToDo.delay(self._removeSwitch, self.switch)
90self.switch = None
91
92def _insertSwitch(self, switch):
93"""Insert self.switch into the scene graph.
94
95Must not be called
96from an event handler (or other scene graph traversal).
97"""
98sg = Draft.get3DView().getSceneGraph()
99if self.ontop:
100sg.insertChild(switch, 0)
101else:
102sg.addChild(switch)
103
104def _removeSwitch(self, switch):
105"""Remove self.switch from the scene graph.
106
107As with _insertSwitch,
108must not be called during scene graph traversal).
109"""
110sg = Draft.get3DView().getSceneGraph()
111if sg.findChild(switch) >= 0:
112sg.removeChild(switch)
113
114def on(self):
115"""Set the visibility to True."""
116self.switch.whichChild = 0
117self.Visible = True
118
119def off(self):
120"""Set the visibility to False."""
121self.switch.whichChild = -1
122self.Visible = False
123
124def lowerTracker(self):
125"""Lower the tracker to the bottom of the scenegraph.
126
127So it doesn't obscure the other objects.
128"""
129if self.switch:
130sg = Draft.get3DView().getSceneGraph()
131sg.removeChild(self.switch)
132sg.addChild(self.switch)
133
134def raiseTracker(self):
135"""Raise the tracker to the top of the scenegraph.
136
137So it obscures the other objects.
138"""
139if self.switch:
140sg = Draft.get3DView().getSceneGraph()
141sg.removeChild(self.switch)
142sg.insertChild(self.switch, 0)
143
144def setColor(self, color=None):
145"""Set the color."""
146if color is not None:
147self.color.rgb = color
148elif hasattr(FreeCAD, "activeDraftCommand") \
149and FreeCAD.activeDraftCommand is not None \
150and hasattr(FreeCAD.activeDraftCommand, "featureName") \
151and FreeCAD.activeDraftCommand.featureName in ("Dimension", "Label", "Text"):
152self.color.rgb = utils.get_rgba_tuple(params.get_param("DefaultAnnoLineColor"))[:3]
153else:
154self.color.rgb = utils.get_rgba_tuple(params.get_param_view("DefaultShapeLineColor"))[:3]
155
156def _get_wp(self):
157return FreeCAD.DraftWorkingPlane
158
159
160class snapTracker(Tracker):
161"""Define Snap Mark tracker, used by tools that support snapping."""
162
163def __init__(self):
164self.marker = coin.SoMarkerSet() # this is the marker symbol
165self.marker.markerIndex = FreeCADGui.getMarkerIndex("CIRCLE_FILLED", params.get_param_view("MarkerSize"))
166self.coords = coin.SoCoordinate3() # this is the coordinate
167self.coords.point.setValue((0, 0, 0))
168node = coin.SoAnnotation()
169node.addChild(self.coords)
170node.addChild(self.marker)
171super().__init__(children=[node], name="snapTracker")
172self.setColor()
173
174def setMarker(self, style):
175"""Set the marker index."""
176self.marker.markerIndex = FreeCADGui.getMarkerIndex(style, params.get_param_view("MarkerSize"))
177
178def setColor(self, color=None):
179"""Set the color."""
180if color is None:
181self.color.rgb = utils.get_rgba_tuple(params.get_param("snapcolor"))[:3]
182else:
183self.color.rgb = color
184
185def setCoords(self, point):
186"""Set the coordinates to the point."""
187self.coords.point.setValue((point.x, point.y, point.z))
188
189def addCoords(self, point):
190"""Add the point to the current point."""
191l = self.coords.point.getValues()
192l.append(coin.SbVec3f(point.x, point.y, point.z))
193self.coords.point.setValues(l)
194
195def clear(self):
196"""Delete the values of the point."""
197self.coords.point.deleteValues(0)
198
199
200class lineTracker(Tracker):
201"""A Line tracker, used by the tools that need to draw temporary lines"""
202
203def __init__(self, dotted=False, scolor=None, swidth=None, ontop=False):
204line = coin.SoLineSet()
205line.numVertices.setValue(2)
206self.coords = coin.SoCoordinate3() # this is the coordinate
207self.coords.point.setValues(0, 2, [[0, 0, 0], [1, 0, 0]])
208super().__init__(dotted, scolor, swidth,
209[self.coords, line],
210ontop, name="lineTracker")
211
212def p1(self, point=None):
213"""Set or get the first point of the line."""
214if point:
215if self.coords.point.getValues()[0].getValue() != tuple(point):
216self.coords.point.set1Value(0, point.x, point.y, point.z)
217else:
218return Vector(self.coords.point.getValues()[0].getValue())
219
220def p2(self, point=None):
221"""Set or get the second point of the line."""
222if point:
223if self.coords.point.getValues()[-1].getValue() != tuple(point):
224self.coords.point.set1Value(1, point.x, point.y, point.z)
225else:
226return Vector(self.coords.point.getValues()[-1].getValue())
227
228def getLength(self):
229"""Return the length of the line."""
230p1 = Vector(self.coords.point.getValues()[0].getValue())
231p2 = Vector(self.coords.point.getValues()[-1].getValue())
232return (p2.sub(p1)).Length
233
234
235class rectangleTracker(Tracker):
236"""A Rectangle tracker, used by the rectangle tool."""
237
238def __init__(self, dotted=False, scolor=None, swidth=None, face=False):
239self.origin = Vector(0, 0, 0)
240line = coin.SoLineSet()
241line.numVertices.setValue(5)
242self.coords = coin.SoCoordinate3() # this is the coordinate
243self.coords.point.setValues(0, 50, [[0, 0, 0],
244[2, 0, 0],
245[2, 2, 0],
246[0, 2, 0],
247[0, 0, 0]])
248if face:
249m1 = coin.SoMaterial()
250m1.transparency.setValue(0.5)
251m1.diffuseColor.setValue([0.5, 0.5, 1.0])
252f = coin.SoIndexedFaceSet()
253f.coordIndex.setValues([0, 1, 2, 3])
254super().__init__(dotted, scolor, swidth,
255[self.coords, line, m1, f],
256name="rectangleTracker")
257else:
258super().__init__(dotted, scolor, swidth,
259[self.coords, line],
260name="rectangleTracker")
261wp = self._get_wp()
262self.u = wp.u
263self.v = wp.v
264
265def setorigin(self, point):
266"""Set the base point of the rectangle."""
267self.coords.point.set1Value(0, point.x, point.y, point.z)
268self.coords.point.set1Value(4, point.x, point.y, point.z)
269self.origin = point
270
271def update(self, point):
272"""Set the opposite (diagonal) point of the rectangle."""
273diagonal = point.sub(self.origin)
274inpoint1 = self.origin.add(DraftVecUtils.project(diagonal, self.v))
275inpoint2 = self.origin.add(DraftVecUtils.project(diagonal, self.u))
276self.coords.point.set1Value(1, inpoint1.x, inpoint1.y, inpoint1.z)
277self.coords.point.set1Value(2, point.x, point.y, point.z)
278self.coords.point.set1Value(3, inpoint2.x, inpoint2.y, inpoint2.z)
279
280def setPlane(self, u, v=None):
281"""Set given (u,v) vectors as working plane.
282
283You can give only `u` and `v` will be deduced automatically
284given the current working plane.
285"""
286self.u = u
287if v:
288self.v = v
289else:
290norm = self._get_wp().axis
291self.v = self.u.cross(norm)
292
293def p1(self, point=None):
294"""Set or get the base point of the rectangle."""
295if point:
296self.setorigin(point)
297else:
298return Vector(self.coords.point.getValues()[0].getValue())
299
300def p2(self):
301"""Get the second point (on u axis) of the rectangle."""
302return Vector(self.coords.point.getValues()[3].getValue())
303
304def p3(self, point=None):
305"""Set or get the opposite (diagonal) point of the rectangle."""
306if point:
307self.update(point)
308else:
309return Vector(self.coords.point.getValues()[2].getValue())
310
311def p4(self):
312"""Get the fourth point (on v axis) of the rectangle."""
313return Vector(self.coords.point.getValues()[1].getValue())
314
315def getSize(self):
316"""Return (length, width) of the rectangle."""
317p1 = Vector(self.coords.point.getValues()[0].getValue())
318p2 = Vector(self.coords.point.getValues()[2].getValue())
319diag = p2.sub(p1)
320return ((DraftVecUtils.project(diag, self.u)).Length,
321(DraftVecUtils.project(diag, self.v)).Length)
322
323def getNormal(self):
324"""Return the normal of the rectangle."""
325return (self.u.cross(self.v)).normalize()
326
327def isInside(self, point):
328"""Return True if the given point is inside the rectangle."""
329vp = point.sub(self.p1())
330uv = self.p2().sub(self.p1())
331vv = self.p4().sub(self.p1())
332uvp = DraftVecUtils.project(vp, uv)
333vvp = DraftVecUtils.project(vp, vv)
334if uvp.getAngle(uv) < 1:
335if vvp.getAngle(vv) < 1:
336if uvp.Length <= uv.Length:
337if vvp.Length <= vv.Length:
338return True
339return False
340
341
342class dimTracker(Tracker):
343"""A Dimension tracker, used by the dimension tool."""
344
345def __init__(self, dotted=False, scolor=None, swidth=None):
346line = coin.SoLineSet()
347line.numVertices.setValue(4)
348self.coords = coin.SoCoordinate3() # this is the coordinate
349self.coords.point.setValues(0, 4,
350[[0, 0, 0],
351[0, 0, 0],
352[0, 0, 0],
353[0, 0, 0]])
354super().__init__(dotted, scolor, swidth,
355[self.coords, line], name="dimTracker")
356self.p1 = self.p2 = self.p3 = None
357
358def update(self, pts):
359"""Update the points and calculate."""
360if not pts:
361return
362elif len(pts) == 1:
363self.p3 = pts[0]
364else:
365self.p1 = pts[0]
366self.p2 = pts[1]
367if len(pts) > 2:
368self.p3 = pts[2]
369self.calc()
370
371def calc(self):
372"""Calculate the new points from p1 and p2."""
373import Part
374if (self.p1 is not None) and (self.p2 is not None):
375points = [DraftVecUtils.tup(self.p1, True),
376DraftVecUtils.tup(self.p2, True),
377DraftVecUtils.tup(self.p1, True),
378DraftVecUtils.tup(self.p2, True)]
379if self.p3 is not None:
380p1 = self.p1
381p4 = self.p2
382if DraftVecUtils.equals(p1, p4):
383proj = None
384else:
385base = Part.LineSegment(p1, p4).toShape()
386proj = DraftGeomUtils.findDistance(self.p3, base)
387if not proj:
388p2 = p1
389p3 = p4
390else:
391p2 = p1.add(proj.negative())
392p3 = p4.add(proj.negative())
393points = [DraftVecUtils.tup(p1),
394DraftVecUtils.tup(p2),
395DraftVecUtils.tup(p3),
396DraftVecUtils.tup(p4)]
397self.coords.point.setValues(0, 4, points)
398
399
400class bsplineTracker(Tracker):
401"""A bspline tracker."""
402
403def __init__(self, dotted=False, scolor=None, swidth=None, points=[]):
404self.bspline = None
405self.points = points
406self.trans = coin.SoTransform()
407self.sep = coin.SoSeparator()
408self.recompute()
409super().__init__(dotted, scolor, swidth,
410[self.trans, self.sep], name="bsplineTracker")
411
412def update(self, points):
413"""Update the points and recompute."""
414self.points = points
415self.recompute()
416
417def recompute(self):
418"""Recompute the tracker."""
419if len(self.points) >= 2:
420if self.bspline:
421self.sep.removeChild(self.bspline)
422self.bspline = None
423c = Part.BSplineCurve()
424# DNC: allows to close the curve by placing ends close to each other
425if len(self.points) >= 3 and ( (self.points[0] - self.points[-1]).Length < Draft.tolerance() ):
426# YVH: Added a try to bypass some hazardous situations
427try:
428c.interpolate(self.points[:-1], True)
429except Part.OCCError:
430pass
431elif self.points:
432try:
433c.interpolate(self.points, False)
434except Part.OCCError:
435pass
436c = c.toShape()
437buf = c.writeInventor(2, 0.01)
438# fp = open("spline.iv", "w")
439# fp.write(buf)
440# fp.close()
441try:
442ivin = coin.SoInput()
443ivin.setBuffer(buf)
444ivob = coin.SoDB.readAll(ivin)
445except Exception:
446# workaround for pivy SoInput.setBuffer() bug
447buf = buf.replace("\n", "")
448pts = re.findall("point \\[(.*?)\\]", buf)[0]
449pts = pts.split(",")
450pc = []
451for p in pts:
452v = p.strip().split()
453pc.append([float(v[0]), float(v[1]), float(v[2])])
454coords = coin.SoCoordinate3()
455coords.point.setValues(0, len(pc), pc)
456line = coin.SoLineSet()
457line.numVertices.setValue(-1)
458self.bspline = coin.SoSeparator()
459self.bspline.addChild(coords)
460self.bspline.addChild(line)
461self.sep.addChild(self.bspline)
462else:
463if ivob and ivob.getNumChildren() > 1:
464self.bspline = ivob.getChild(1).getChild(0)
465self.bspline.removeChild(self.bspline.getChild(0))
466self.bspline.removeChild(self.bspline.getChild(0))
467self.sep.addChild(self.bspline)
468else:
469FreeCAD.Console.PrintWarning("bsplineTracker.recompute() failed to read-in Inventor string\n")
470
471
472class bezcurveTracker(Tracker):
473"""A bezcurve tracker."""
474
475def __init__(self, dotted=False, scolor=None, swidth=None, points=[]):
476self.bezcurve = None
477self.points = points
478self.degree = None
479self.trans = coin.SoTransform()
480self.sep = coin.SoSeparator()
481self.recompute()
482super().__init__(dotted, scolor, swidth,
483[self.trans, self.sep], name="bezcurveTracker")
484
485def update(self, points, degree=None):
486"""Update the points and recompute."""
487self.points = points
488if degree:
489self.degree = degree
490self.recompute()
491
492def recompute(self):
493"""Recompute the tracker."""
494if self.bezcurve:
495for seg in self.bezcurve:
496self.sep.removeChild(seg)
497seg = None
498
499self.bezcurve = []
500
501if (len(self.points) >= 2):
502if self.degree:
503poles = self.points[1:]
504segpoleslst = [poles[x:x+self.degree] for x in range(0, len(poles), (self.degree or 1))]
505else:
506segpoleslst = [self.points]
507startpoint = self.points[0]
508
509for segpoles in segpoleslst:
510c = Part.BezierCurve() # last segment may have lower degree
511c.increase(len(segpoles))
512c.setPoles([startpoint] + segpoles)
513c = c.toShape()
514startpoint = segpoles[-1]
515buf = c.writeInventor(2, 0.01)
516# fp=open("spline.iv", "w")
517# fp.write(buf)
518# fp.close()
519try:
520ivin = coin.SoInput()
521ivin.setBuffer(buf)
522ivob = coin.SoDB.readAll(ivin)
523except Exception:
524# workaround for pivy SoInput.setBuffer() bug
525buf = buf.replace("\n","")
526pts = re.findall("point \\[(.*?)\\]", buf)[0]
527pts = pts.split(",")
528pc = []
529for p in pts:
530v = p.strip().split()
531pc.append([float(v[0]), float(v[1]), float(v[2])])
532coords = coin.SoCoordinate3()
533coords.point.setValues(0, len(pc), pc)
534line = coin.SoLineSet()
535line.numVertices.setValue(-1)
536bezcurveseg = coin.SoSeparator()
537bezcurveseg.addChild(coords)
538bezcurveseg.addChild(line)
539self.sep.addChild(bezcurveseg)
540else:
541if ivob and ivob.getNumChildren() > 1:
542bezcurveseg = ivob.getChild(1).getChild(0)
543bezcurveseg.removeChild(bezcurveseg.getChild(0))
544bezcurveseg.removeChild(bezcurveseg.getChild(0))
545self.sep.addChild(bezcurveseg)
546else:
547FreeCAD.Console.PrintWarning("bezcurveTracker.recompute() failed to read-in Inventor string\n")
548self.bezcurve.append(bezcurveseg)
549
550
551class arcTracker(Tracker):
552"""An arc tracker."""
553# Note: used by the Arc command but also for angular dimensions.
554def __init__(self, dotted=False, scolor=None, swidth=None,
555start=0, end=math.pi*2):
556self.circle = None
557self.startangle = math.degrees(start)
558self.endangle = math.degrees(end)
559self.trans = coin.SoTransform()
560self.trans.translation.setValue([0, 0, 0])
561self.sep = coin.SoSeparator()
562self.autoinvert = True
563self.normal = self._get_wp().axis
564self.recompute()
565super().__init__(dotted, scolor, swidth,
566[self.trans, self.sep], name="arcTracker")
567
568def getDeviation(self):
569"""Return a deviation vector that represents the base of the circle."""
570import Part
571c = Part.makeCircle(1, Vector(0, 0, 0), self.normal)
572return c.Vertexes[0].Point
573
574def setCenter(self, cen):
575"""Set the center point."""
576self.trans.translation.setValue([cen.x, cen.y, cen.z])
577
578def setRadius(self, rad):
579"""Set the radius."""
580self.trans.scaleFactor.setValue([rad, rad, rad])
581
582def getRadius(self):
583"""Return the current radius."""
584return self.trans.scaleFactor.getValue()[0]
585
586def setStartAngle(self, ang):
587"""Set the start angle."""
588self.startangle = math.degrees(ang)
589self.recompute()
590
591def setEndAngle(self, ang):
592"""Set the end angle."""
593self.endangle = math.degrees(ang)
594self.recompute()
595
596def getAngle(self, pt):
597"""Return the angle of a given vector in radians."""
598c = self.trans.translation.getValue()
599center = Vector(c[0], c[1], c[2])
600return DraftVecUtils.angle(pt.sub(center), self.getDeviation(), self.normal)
601
602def getAngles(self):
603"""Return the start and end angles in degrees."""
604return(self.startangle, self.endangle)
605
606def setStartPoint(self, pt):
607"""Set the start angle from a point."""
608self.setStartAngle(-self.getAngle(pt))
609
610def setEndPoint(self, pt):
611"""Set the end angle from a point."""
612self.setEndAngle(-self.getAngle(pt))
613
614def setApertureAngle(self, ang):
615"""Set the end angle by giving the aperture angle."""
616ap = math.degrees(ang)
617self.endangle = self.startangle + ap
618self.recompute()
619
620def setBy3Points(self, p1, p2, p3):
621"""Set the arc by three points."""
622import Part
623try:
624arc = Part.ArcOfCircle(p1, p2, p3)
625except Exception:
626return
627e = arc.toShape()
628self.autoinvert = False
629self.normal = e.Curve.Axis.negative() # axis is always in wrong direction
630self.setCenter(e.Curve.Center)
631self.setRadius(e.Curve.Radius)
632self.setStartPoint(p1)
633self.setEndPoint(p3)
634
635def recompute(self):
636"""Recompute the tracker."""
637import Part
638if self.circle:
639self.sep.removeChild(self.circle)
640self.circle = None
641if (self.endangle < self.startangle) or not self.autoinvert:
642c = Part.makeCircle(1, Vector(0, 0, 0),
643self.normal, self.endangle, self.startangle)
644else:
645c = Part.makeCircle(1, Vector(0, 0, 0),
646self.normal, self.startangle, self.endangle)
647buf = c.writeInventor(2, 0.01)
648try:
649ivin = coin.SoInput()
650ivin.setBuffer(buf)
651ivob = coin.SoDB.readAll(ivin)
652except Exception:
653# workaround for pivy SoInput.setBuffer() bug
654buf = buf.replace("\n", "")
655pts = re.findall("point \\[(.*?)\\]", buf)[0]
656pts = pts.split(",")
657pc = []
658for p in pts:
659v = p.strip().split()
660pc.append([float(v[0]), float(v[1]), float(v[2])])
661coords = coin.SoCoordinate3()
662coords.point.setValues(0, len(pc), pc)
663line = coin.SoLineSet()
664line.numVertices.setValue(-1)
665self.circle = coin.SoSeparator()
666self.circle.addChild(coords)
667self.circle.addChild(line)
668self.sep.addChild(self.circle)
669else:
670if ivob and ivob.getNumChildren() > 1:
671self.circle = ivob.getChild(1).getChild(0)
672self.circle.removeChild(self.circle.getChild(0))
673self.circle.removeChild(self.circle.getChild(0))
674self.sep.addChild(self.circle)
675else:
676FreeCAD.Console.PrintWarning("arcTracker.recompute() failed to read-in Inventor string\n")
677
678
679class ghostTracker(Tracker):
680"""A Ghost tracker, that allows to copy whole object representations.
681
682You can pass it an object or a list of objects, or a shape.
683"""
684
685def __init__(self, sel, dotted=False, scolor=None, swidth=None, mirror=False):
686self.trans = coin.SoTransform()
687self.trans.translation.setValue([0, 0, 0])
688self.children = [self.trans]
689rootsep = coin.SoSeparator()
690if not isinstance(sel, list):
691sel = [sel]
692for obj in sel:
693import Part
694if not isinstance(obj, Part.Vertex):
695rootsep.addChild(self.getNode(obj))
696else:
697self.coords = coin.SoCoordinate3()
698self.coords.point.setValue((obj.X, obj.Y, obj.Z))
699self.marker = coin.SoMarkerSet() # this is the marker symbol
700self.marker.markerIndex = FreeCADGui.getMarkerIndex("SQUARE_FILLED", params.get_param_view("MarkerSize"))
701node = coin.SoAnnotation()
702selnode = coin.SoSeparator()
703selnode.addChild(self.coords)
704selnode.addChild(self.marker)
705node.addChild(selnode)
706rootsep.addChild(node)
707if mirror is True:
708self._flip(rootsep)
709self.children.append(rootsep)
710super().__init__(dotted, scolor, swidth,
711children=self.children, name="ghostTracker")
712self.setColor(scolor)
713
714def setColor(self, color=None):
715"""Set the color."""
716if color is None:
717self.color.rgb = utils.get_rgba_tuple(params.get_param("snapcolor"))[:3]
718else:
719self.color.rgb = color
720
721def remove(self):
722"""Remove the ghost when switching to and from subelement mode."""
723if self.switch:
724self.finalize()
725
726def move(self, delta):
727"""Move the ghost to a given position.
728
729Relative from its start position.
730"""
731self.trans.translation.setValue([delta.x, delta.y, delta.z])
732
733def rotate(self, axis, angle):
734"""Rotate the ghost of a given angle."""
735self.trans.rotation.setValue(coin.SbVec3f(DraftVecUtils.tup(axis)), angle)
736
737def center(self, point):
738"""Set the rotation/scale center of the ghost."""
739self.trans.center.setValue(point.x, point.y, point.z)
740
741def scale(self, delta):
742"""Scale the ghost by the given factor."""
743self.trans.scaleFactor.setValue([delta.x, delta.y, delta.z])
744
745def getNode(self, obj):
746"""Return a coin node representing the given object."""
747import Part
748if isinstance(obj, Part.Shape):
749return self.getNodeLight(obj)
750elif obj.isDerivedFrom("Part::Feature"):
751return self.getNodeFull(obj)
752else:
753return self.getNodeFull(obj)
754
755def getNodeFull(self, obj):
756"""Get a coin node which is a copy of the current representation."""
757sep = coin.SoSeparator()
758try:
759sep.addChild(obj.ViewObject.RootNode.copy())
760# add Part container offset
761if hasattr(obj, "getGlobalPlacement"):
762if obj.Placement != obj.getGlobalPlacement():
763if sep.getChild(0).getNumChildren() > 0:
764if isinstance(sep.getChild(0).getChild(0),coin.SoTransform):
765gpl = obj.getGlobalPlacement()
766sep.getChild(0).getChild(0).translation.setValue(tuple(gpl.Base))
767sep.getChild(0).getChild(0).rotation.setValue(gpl.Rotation.Q)
768except Exception:
769_msg("ghostTracker: Error retrieving coin node (full)")
770return sep
771
772def getNodeLight(self, shape):
773"""Extract a lighter version directly from a shape."""
774# error-prone
775sep = coin.SoSeparator()
776try:
777inputstr = coin.SoInput()
778inputstr.setBuffer(shape.writeInventor())
779coinobj = coin.SoDB.readAll(inputstr)
780# only add wireframe or full node?
781sep.addChild(coinobj.getChildren()[1])
782# sep.addChild(coinobj)
783except Exception:
784_msg("ghostTracker: Error retrieving coin node (light)")
785return sep
786
787def getMatrix(self):
788"""Get matrix of the active view."""
789r = FreeCADGui.ActiveDocument.ActiveView.getViewer().getSoRenderManager().getViewportRegion()
790v = coin.SoGetMatrixAction(r)
791m = self.trans.getMatrix(v)
792if m:
793m = m.getValue()
794return FreeCAD.Matrix(m[0][0], m[0][1], m[0][2], m[0][3],
795m[1][0], m[1][1], m[1][2], m[1][3],
796m[2][0], m[2][1], m[2][2], m[2][3],
797m[3][0], m[3][1], m[3][2], m[3][3])
798else:
799return FreeCAD.Matrix()
800
801def setMatrix(self, matrix):
802"""Set the transformation matrix.
803
804The 4th column of the matrix (the position) is ignored.
805"""
806m = coin.SbMatrix(matrix.A11, matrix.A12, matrix.A13, matrix.A14,
807matrix.A21, matrix.A22, matrix.A23, matrix.A24,
808matrix.A31, matrix.A32, matrix.A33, matrix.A34,
809matrix.A41, matrix.A42, matrix.A43, matrix.A44)
810self.trans.setMatrix(m)
811
812def _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
816search = coin.SoSearchAction()
817search.setType(coin.SoIndexedFaceSet.getClassTypeId())
818search.apply(root)
819path = search.getPath()
820if path:
821node = path.getTail()
822index = node.coordIndex.getValues()
823if len(index) % 4 == 0:
824for i in range(0, len(index), 4):
825tmp = index[i]
826index[i] = index[i+1]
827index[i+1] = tmp
828
829node.coordIndex.setValues(index)
830
831
832class editTracker(Tracker):
833"""A node edit tracker."""
834
835def __init__(self, pos=Vector(0, 0, 0), name=None, idx=0, objcol=None,
836marker=None, inactive=False):
837self.marker = coin.SoMarkerSet() # this is the marker symbol
838if marker is None:
839self.marker.markerIndex = FreeCADGui.getMarkerIndex("SQUARE_FILLED", params.get_param_view("MarkerSize"))
840else:
841self.marker.markerIndex = marker
842self.coords = coin.SoCoordinate3() # this is the coordinate
843self.coords.point.setValue((pos.x, pos.y, pos.z))
844self.position = pos
845if inactive:
846self.selnode = coin.SoSeparator()
847else:
848self.selnode = coin.SoType.fromName("SoFCSelection").createInstance()
849if name:
850self.selnode.useNewSelection = False
851self.selnode.documentName.setValue(FreeCAD.ActiveDocument.Name)
852self.selnode.objectName.setValue(name)
853self.selnode.subElementName.setValue("EditNode" + str(idx))
854node = coin.SoAnnotation()
855self.selnode.addChild(self.coords)
856self.selnode.addChild(self.marker)
857node.addChild(self.selnode)
858ontop = not inactive
859super().__init__(children=[node],
860ontop=ontop, name="editTracker")
861if objcol is None:
862self.setColor()
863else:
864self.setColor(objcol[:3])
865self.on()
866
867def set(self, pos):
868"""Set the point to the position."""
869self.coords.point.setValue((pos.x, pos.y, pos.z))
870self.position = pos
871
872def get(self):
873"""Get a vector from the point."""
874return self.position
875
876def get_doc_name(self):
877"""Get the document name."""
878return str(self.selnode.documentName.getValue())
879
880def get_obj_name(self):
881"""Get the object name."""
882return str(self.selnode.objectName.getValue())
883
884def get_subelement_name(self):
885"""Get the subelement name."""
886return str(self.selnode.subElementName.getValue())
887
888def get_subelement_index(self):
889"""Get the subelement index."""
890subElement = self.get_subelement_name()
891idx = int(subElement[8:])
892return idx
893
894def move(self, delta):
895"""Get the point and add a delta, and set the new point."""
896self.set(self.get().add(delta))
897
898def setColor(self, color=None):
899"""Set the color."""
900if color is None:
901self.color.rgb = utils.get_rgba_tuple(params.get_param("snapcolor"))[:3]
902else:
903self.color.rgb = color
904
905
906class PlaneTracker(Tracker):
907"""A working plane tracker."""
908
909def __init__(self):
910# getting screen distance
911p1 = Draft.get3DView().getPoint((100, 100))
912p2 = Draft.get3DView().getPoint((110, 100))
913bl = (p2.sub(p1)).Length * (params.get_param("snapRange")/2.0)
914pick = coin.SoPickStyle()
915pick.style.setValue(coin.SoPickStyle.UNPICKABLE)
916self.trans = coin.SoTransform()
917self.trans.translation.setValue([0, 0, 0])
918m1 = coin.SoMaterial()
919m1.transparency.setValue(0.8)
920m1.diffuseColor.setValue([0.4, 0.4, 0.6])
921c1 = coin.SoCoordinate3()
922c1.point.setValues([[-bl, -bl, 0],
923[bl, -bl, 0],
924[bl, bl, 0],
925[-bl, bl, 0]])
926f = coin.SoIndexedFaceSet()
927f.coordIndex.setValues([0, 1, 2, 3])
928m2 = coin.SoMaterial()
929m2.transparency.setValue(0.7)
930m2.diffuseColor.setValue([0.2, 0.2, 0.3])
931c2 = coin.SoCoordinate3()
932c2.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]])
937l = coin.SoLineSet()
938l.numVertices.setValues([3, 3, 3])
939s = coin.SoSeparator()
940s.addChild(pick)
941s.addChild(self.trans)
942s.addChild(m1)
943s.addChild(c1)
944s.addChild(f)
945s.addChild(m2)
946s.addChild(c2)
947s.addChild(l)
948super().__init__(children=[s], name="planeTracker")
949
950def set(self, pos=None):
951"""Set the translation to the position."""
952plm = self._get_wp().get_placement()
953Q = plm.Rotation.Q
954if pos is None:
955pos = plm.Base
956self.trans.translation.setValue([pos.x, pos.y, pos.z])
957self.trans.rotation.setValue([Q[0], Q[1], Q[2], Q[3]])
958self.on()
959
960
961class wireTracker(Tracker):
962"""A wire tracker."""
963
964def __init__(self, wire):
965self.line = coin.SoLineSet()
966self.closed = DraftGeomUtils.isReallyClosed(wire)
967if self.closed:
968self.line.numVertices.setValue(len(wire.Vertexes)+1)
969else:
970self.line.numVertices.setValue(len(wire.Vertexes))
971self.coords = coin.SoCoordinate3()
972self.update(wire)
973super().__init__(children=[self.coords, self.line],
974name="wireTracker")
975
976def update(self, wire, forceclosed=False):
977"""Update the tracker."""
978if wire:
979if self.closed or forceclosed:
980self.line.numVertices.setValue(len(wire.Vertexes) + 1)
981else:
982self.line.numVertices.setValue(len(wire.Vertexes))
983for i in range(len(wire.Vertexes)):
984p = wire.Vertexes[i].Point
985self.coords.point.set1Value(i, [p.x, p.y, p.z])
986if self.closed or forceclosed:
987t = len(wire.Vertexes)
988p = wire.Vertexes[0].Point
989self.coords.point.set1Value(t, [p.x, p.y, p.z])
990
991def updateFromPointlist(self, points, forceclosed=False):
992"""Update the tracker from points."""
993if points:
994for i in range(len(points)):
995p = points[i]
996self.coords.point.set1Value(i, [p.x, p.y, p.z])
997
998
999class gridTracker(Tracker):
1000"""A grid tracker."""
1001
1002def __init__(self):
1003
1004col, red, green, blue, gtrans = self.getGridColors()
1005pick = coin.SoPickStyle()
1006pick.style.setValue(coin.SoPickStyle.UNPICKABLE)
1007self.trans = coin.SoTransform()
1008self.trans.translation.setValue([0, 0, 0])
1009
1010# small squares
1011self.mat1 = coin.SoMaterial()
1012self.mat1.transparency.setValue(0.8*(1-gtrans))
1013self.mat1.diffuseColor.setValue(col)
1014self.font = coin.SoFont()
1015self.coords1 = coin.SoCoordinate3()
1016self.lines1 = coin.SoLineSet() # small squares
1017
1018# texts
1019texts = coin.SoSeparator()
1020t1 = coin.SoSeparator()
1021self.textpos1 = coin.SoTransform()
1022self.text1 = coin.SoAsciiText()
1023self.text1.string = " "
1024t2 = coin.SoSeparator()
1025self.textpos2 = coin.SoTransform()
1026self.textpos2.rotation.setValue((0.0, 0.0, 0.7071067811865475, 0.7071067811865476))
1027self.text2 = coin.SoAsciiText()
1028self.text2.string = " "
1029t1.addChild(self.textpos1)
1030t1.addChild(self.text1)
1031t2.addChild(self.textpos2)
1032t2.addChild(self.text2)
1033texts.addChild(self.font)
1034texts.addChild(t1)
1035texts.addChild(t2)
1036
1037# big squares
1038self.mat2 = coin.SoMaterial()
1039self.mat2.transparency.setValue(0.2*(1-gtrans))
1040self.mat2.diffuseColor.setValue(col)
1041self.coords2 = coin.SoCoordinate3()
1042self.lines2 = coin.SoLineSet() # big squares
1043
1044# human figure
1045mat_human = coin.SoMaterial()
1046mat_human.transparency.setValue(0.3*(1-gtrans))
1047mat_human.diffuseColor.setValue(col)
1048self.coords_human = coin.SoCoordinate3()
1049self.human = coin.SoLineSet()
1050
1051# axes
1052self.mat3 = coin.SoMaterial()
1053self.mat3.transparency.setValue(gtrans)
1054self.mat3.diffuseColor.setValues([col,red,green,blue])
1055self.coords3 = coin.SoCoordinate3()
1056self.lines3 = coin.SoIndexedLineSet() # axes
1057self.lines3.coordIndex.setValues(0,5,[0,1,-1,2,3])
1058self.lines3.materialIndex.setValues(0,2,[0,0])
1059mbind3 = coin.SoMaterialBinding()
1060mbind3.value = coin.SoMaterialBinding.PER_PART_INDEXED
1061
1062self.pts = []
1063s = coin.SoType.fromName("SoSkipBoundingGroup").createInstance()
1064s.addChild(pick)
1065s.addChild(self.trans)
1066s.addChild(self.mat1)
1067s.addChild(self.coords1)
1068s.addChild(self.lines1)
1069s.addChild(self.mat2)
1070s.addChild(self.coords2)
1071s.addChild(self.lines2)
1072s.addChild(mat_human)
1073s.addChild(self.coords_human)
1074s.addChild(self.human)
1075s.addChild(mbind3)
1076s.addChild(self.mat3)
1077s.addChild(self.coords3)
1078s.addChild(self.lines3)
1079s.addChild(texts)
1080
1081super().__init__(children=[s], name="gridTracker")
1082self.show_during_command = False
1083self.show_always = False
1084self.reset()
1085
1086def update(self):
1087"""Redraw the grid."""
1088# Resize the grid to make sure it fits
1089# an exact pair number of main lines
1090if self.space == 0:
1091self.lines1.numVertices.deleteValues(0)
1092self.lines2.numVertices.deleteValues(0)
1093self.pts = []
1094FreeCAD.Console.PrintWarning("Draft Grid: Spacing value is zero\n")
1095return
1096if self.mainlines == 0:
1097self.lines1.numVertices.deleteValues(0)
1098self.lines2.numVertices.deleteValues(0)
1099self.pts = []
1100return
1101if self.numlines == 0:
1102self.lines1.numVertices.deleteValues(0)
1103self.lines2.numVertices.deleteValues(0)
1104self.pts = []
1105return
1106numlines = self.numlines // self.mainlines // 2 * 2 * self.mainlines
1107bound = (numlines // 2) * self.space
1108border = (numlines//2 + self.mainlines/2) * self.space
1109cursor = self.mainlines//4 * self.space
1110pts = []
1111mpts = []
1112apts = []
1113cpts = []
1114for i in range(numlines + 1):
1115curr = -bound + i * self.space
1116z = 0
1117if i / float(self.mainlines) == i // self.mainlines:
1118if round(curr, 4) == 0:
1119apts.extend([[-bound, curr, z], [bound, curr, z]])
1120apts.extend([[curr, -bound, z], [curr, bound, z]])
1121else:
1122mpts.extend([[-bound, curr, z], [bound, curr, z]])
1123mpts.extend([[curr, -bound, z], [curr, bound, z]])
1124cpts.extend([[-border,curr,z], [-border+cursor,curr,z]])
1125cpts.extend([[border-cursor,curr,z], [border,curr,z]])
1126cpts.extend([[curr,-border,z], [curr,-border+cursor,z]])
1127cpts.extend([[curr,border-cursor,z], [curr,border,z]])
1128else:
1129pts.extend([[-bound, curr, z], [bound, curr, z]])
1130pts.extend([[curr, -bound, z], [curr, bound, z]])
1131if pts != self.pts:
1132idx = []
1133midx = []
1134#aidx = []
1135cidx = []
1136for p in range(0, len(pts), 2):
1137idx.append(2)
1138for mp in range(0, len(mpts), 2):
1139midx.append(2)
1140#for ap in range(0, len(apts), 2):
1141# aidx.append(2)
1142for cp in range(0, len(cpts),2):
1143cidx.append(2)
1144
1145if params.get_param("gridBorder"):
1146# extra border
1147border = (numlines//2 + self.mainlines/2) * self.space
1148mpts.extend([[-border, -border, z], [border, -border, z], [border, border, z], [-border, border, z], [-border, -border, z]])
1149midx.append(5)
1150# cursors
1151mpts.extend(cpts)
1152midx.extend(cidx)
1153# texts
1154self.font.size = self.space*(self.mainlines//4) or 1
1155self.font.name = params.get_param("textfont")
1156txt = FreeCAD.Units.Quantity(self.space*self.mainlines,FreeCAD.Units.Length).UserString
1157self.text1.string = txt
1158self.text2.string = txt
1159self.textpos1.translation.setValue((-bound+self.space,-border+self.space,z))
1160self.textpos2.translation.setValue((-bound-self.space,-bound+self.space,z))
1161else:
1162self.text1.string = " "
1163self.text2.string = " "
1164
1165self.lines1.numVertices.deleteValues(0)
1166self.lines2.numVertices.deleteValues(0)
1167#self.lines3.numVertices.deleteValues(0)
1168self.coords1.point.setValues(pts)
1169self.lines1.numVertices.setValues(idx)
1170self.coords2.point.setValues(mpts)
1171self.lines2.numVertices.setValues(midx)
1172self.coords3.point.setValues(apts)
1173#self.lines3.numVertices.setValues(aidx)
1174self.pts = pts
1175
1176# update the grid colors
1177col, red, green, blue, gtrans = self.getGridColors()
1178self.mat1.diffuseColor.setValue(col)
1179self.mat2.diffuseColor.setValue(col)
1180self.mat3.diffuseColor.setValues([col,red,green,blue])
1181
1182def getGridColors(self):
1183"""Returns grid colors stored in the preferences"""
1184gtrans = params.get_param("gridTransparency")/100.0
1185col = utils.get_rgba_tuple(params.get_param("gridColor"))[:3]
1186if params.get_param("coloredGridAxes"):
1187red = ((1.0+col[0])/2,0.0,0.0)
1188green = (0.0,(1.0+col[1])/2,0.0)
1189blue = (0.0,0.0,(1.0+col[2])/2)
1190else:
1191red = col
1192green = col
1193blue = col
1194return col, red, green, blue, gtrans
1195
1196def displayHumanFigure(self, wp):
1197""" Display the human figure at the grid corner.
1198The 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"""
1204numlines = self.numlines // self.mainlines // 2 * 2 * self.mainlines
1205bound = (numlines // 2) * self.space
1206pts = []
1207pidx = []
1208if params.get_param("gridBorder") \
1209and params.get_param("gridShowHuman") \
1210and wp.axis.getAngle(FreeCAD.Vector(0,0,1)) < 0.001:
1211try:
1212import BimProjectManager
1213loc = FreeCAD.Vector(-bound+self.space/2,-bound+self.space/2,0)
1214hpts = BimProjectManager.getHuman(loc)
1215pts.extend([tuple(p) for p in hpts])
1216pidx.append(len(hpts))
1217except Exception:
1218# BIM not installed
1219return
1220self.human.numVertices.deleteValues(0)
1221self.coords_human.point.setValues(pts)
1222self.human.numVertices.setValues(pidx)
1223
1224def setAxesColor(self, wp):
1225"""set axes color"""
1226cols = [0,0]
1227if params.get_param("coloredGridAxes"):
1228if round(wp.u.getAngle(FreeCAD.Vector(1,0,0)),2) in (0,3.14):
1229cols[0] = 1
1230elif round(wp.u.getAngle(FreeCAD.Vector(0,1,0)),2) in (0,3.14):
1231cols[0] = 2
1232elif round(wp.u.getAngle(FreeCAD.Vector(0,0,1)),2) in (0,3.14):
1233cols[0] = 3
1234if round(wp.v.getAngle(FreeCAD.Vector(1,0,0)),2) in (0,3.14):
1235cols[1] = 1
1236elif round(wp.v.getAngle(FreeCAD.Vector(0,1,0)),2) in (0,3.14):
1237cols[1] = 2
1238elif round(wp.v.getAngle(FreeCAD.Vector(0,0,1)),2) in (0,3.14):
1239cols[1] = 3
1240self.lines3.materialIndex.setValues(0,2,cols)
1241
1242def setSize(self, size):
1243"""Set size of the lines and update."""
1244self.numlines = size
1245self.update()
1246
1247def setSpacing(self, space):
1248"""Set spacing and update."""
1249self.space = space
1250self.update()
1251
1252def setMainlines(self, ml):
1253"""Set mainlines and update."""
1254self.mainlines = ml
1255self.update()
1256
1257def reset(self):
1258"""Reset the grid according to preferences settings."""
1259try:
1260self.space = FreeCAD.Units.Quantity(params.get_param("gridSpacing")).Value
1261except ValueError:
1262self.space = 1
1263self.mainlines = params.get_param("gridEvery")
1264self.numlines = params.get_param("gridSize")
1265self.update()
1266
1267def set(self):
1268"""Move and rotate the grid according to the current working plane."""
1269self.reset()
1270wp = self._get_wp()
1271Q = wp.get_placement().Rotation.Q
1272P = wp.position
1273self.trans.rotation.setValue([Q[0], Q[1], Q[2], Q[3]])
1274self.trans.translation.setValue([P.x, P.y, P.z])
1275self.displayHumanFigure(wp)
1276self.setAxesColor(wp)
1277self.on()
1278
1279def getClosestNode(self, point):
1280"""Return the closest node from the given point."""
1281wp = self._get_wp()
1282pt = wp.get_local_coords(point)
1283pu = round(pt.x / self.space, 0) * self.space
1284pv = round(pt.y / self.space, 0) * self.space
1285pt = wp.get_global_coords(Vector(pu, pv, 0))
1286return pt
1287
1288
1289class boxTracker(Tracker):
1290"""A box tracker, can be based on a line object."""
1291
1292def __init__(self, line=None, width=0.1, height=1, shaded=False):
1293self.trans = coin.SoTransform()
1294m = coin.SoMaterial()
1295m.transparency.setValue(0.8)
1296m.diffuseColor.setValue([0.4, 0.4, 0.6])
1297w = coin.SoDrawStyle()
1298w.style = coin.SoDrawStyle.LINES
1299self.cube = coin.SoCube()
1300self.cube.height.setValue(width)
1301self.cube.depth.setValue(height)
1302self.baseline = None
1303if line:
1304self.baseline = line
1305self.update()
1306if shaded:
1307super().__init__(children=[self.trans, m, self.cube],
1308name="boxTracker")
1309else:
1310super().__init__(children=[self.trans, w, self.cube],
1311name="boxTracker")
1312
1313def update(self, line=None, normal=None):
1314"""Update the tracker."""
1315import DraftGeomUtils
1316if not normal:
1317normal = self._get_wp().axis
1318if line:
1319if isinstance(line, list):
1320bp = line[0]
1321lvec = line[1].sub(line[0])
1322else:
1323lvec = DraftGeomUtils.vec(line.Shape.Edges[0])
1324bp = line.Shape.Edges[0].Vertexes[0].Point
1325elif self.baseline:
1326lvec = DraftGeomUtils.vec(self.baseline.Shape.Edges[0])
1327bp = self.baseline.Shape.Edges[0].Vertexes[0].Point
1328else:
1329return
1330self.cube.width.setValue(lvec.Length)
1331bp = bp.add(lvec.multiply(0.5))
1332bp = bp.add(DraftVecUtils.scaleTo(normal, self.cube.depth.getValue()/2.0))
1333self.pos(bp)
1334tol = 1e-6
1335if lvec.Length > tol and normal.Length > tol:
1336lvec.normalize()
1337normal.normalize()
1338if not lvec.isEqual(normal, tol) \
1339and not lvec.isEqual(normal.negative(), tol):
1340rot = FreeCAD.Rotation(lvec, FreeCAD.Vector(), normal, "XZY")
1341self.trans.rotation.setValue(rot.Q)
1342
1343def setRotation(self, rot):
1344"""Set the rotation."""
1345self.trans.rotation.setValue(rot.Q)
1346
1347def pos(self, p):
1348"""Set the translation."""
1349self.trans.translation.setValue(DraftVecUtils.tup(p))
1350
1351def width(self, w=None):
1352"""Set the width."""
1353if w:
1354self.cube.height.setValue(w)
1355else:
1356return self.cube.height.getValue()
1357
1358def length(self, l=None):
1359"""Set the length."""
1360if l:
1361self.cube.width.setValue(l)
1362else:
1363return self.cube.width.getValue()
1364
1365def height(self, h=None):
1366"""Set the height."""
1367if h:
1368self.cube.depth.setValue(h)
1369self.update()
1370else:
1371return self.cube.depth.getValue()
1372
1373
1374class radiusTracker(Tracker):
1375"""A tracker that displays a transparent sphere to inicate a radius."""
1376
1377def __init__(self, position=FreeCAD.Vector(0, 0, 0), radius=1):
1378self.trans = coin.SoTransform()
1379self.trans.translation.setValue([position.x, position.y, position.z])
1380m = coin.SoMaterial()
1381m.transparency.setValue(0.9)
1382m.diffuseColor.setValue([0, 1, 0])
1383self.sphere = coin.SoSphere()
1384self.sphere.radius.setValue(radius)
1385self.baseline = None
1386super().__init__(children=[self.trans, m, self.sphere],
1387name="radiusTracker")
1388
1389def update(self, arg1, arg2=None):
1390"""Update the tracker."""
1391if isinstance(arg1, FreeCAD.Vector):
1392self.trans.translation.setValue([arg1.x, arg1.y, arg1.z])
1393else:
1394self.sphere.radius.setValue(arg1)
1395if arg2 is not None:
1396if isinstance(arg2, FreeCAD.Vector):
1397self.trans.translation.setValue([arg2.x, arg2.y, arg2.z])
1398else:
1399self.sphere.radius.setValue(arg2)
1400
1401
1402class archDimTracker(Tracker):
1403"""A wrapper around a Sketcher dim."""
1404
1405def __init__(self, p1=FreeCAD.Vector(0, 0, 0), p2=FreeCAD.Vector(1, 0, 0), mode=1):
1406import SketcherGui
1407self.transform = coin.SoMatrixTransform()
1408self.dimnode = coin.SoType.fromName("SoDatumLabel").createInstance()
1409p1node = coin.SbVec3f([p1.x, p1.y, p1.z])
1410p2node = coin.SbVec3f([p2.x, p2.y, p2.z])
1411self.dimnode.pnts.setValues([p1node, p2node])
1412self.dimnode.lineWidth = 1
1413color = utils.get_rgba_tuple(params.get_param("snapcolor"))[:3]
1414self.dimnode.textColor.setValue(coin.SbVec3f(color))
1415self.dimnode.size = 11
1416self.size_pixel = self.dimnode.size.getValue()*96/72
1417self.offset = 0.5
1418self.mode = mode
1419self.matrix = self.transform.matrix
1420self.norm = self.dimnode.norm
1421self.param1 = self.dimnode.param1
1422self.param2 = self.dimnode.param2
1423self.pnts = self.dimnode.pnts
1424self.string = self.dimnode.string
1425self.view = Draft.get3DView()
1426self.camera = self.view.getCameraNode()
1427self.setMode(mode)
1428self.setString()
1429super().__init__(children=[self.transform, self.dimnode], name="archDimTracker")
1430
1431def setString(self, text=None):
1432"""Set the dim string to the given value or auto value."""
1433plane = self._get_wp()
1434p1 = Vector(self.pnts.getValues()[0].getValue())
1435p2 = Vector(self.pnts.getValues()[-1].getValue())
1436self.norm.setValue(plane.axis)
1437# set the offset sign to prevent the dim line from intersecting the curve near the cursor
1438sign_dx = math.copysign(1, (p2.sub(p1)).x)
1439sign_dy = math.copysign(1, (p2.sub(p1)).y)
1440sign = sign_dx*sign_dy
1441if self.mode == 2:
1442self.Distance = abs((p2.sub(p1)).x)
1443self.param1.setValue(sign*self.offset)
1444elif self.mode == 3:
1445self.Distance = abs((p2.sub(p1)).y)
1446self.param1.setValue(-1*sign*self.offset)
1447else:
1448self.Distance = (p2.sub(p1)).Length
1449
1450text = FreeCAD.Units.Quantity(self.Distance, FreeCAD.Units.Length).UserString
1451self.matrix.setValue(*plane.get_placement().Matrix.transposed().A)
1452self.string.setValue(text.encode('utf8'))
1453# change the text position to external depending on the distance and scale values
1454volume = self.camera.getViewVolume()
1455scale = self.view.getSize()[1]/volume.getHeight()
1456if scale*self.Distance > self.size_pixel*len(text):
1457self.param2.setValue(0)
1458else:
1459self.param2.setValue(1/2*self.Distance + 3/5*self.size_pixel*len(text)/scale)
1460
1461
1462def setMode(self, mode=1):
1463"""Set the mode.
1464
14650 = without lines (a simple mark)
14661 = aligned (default)
14672 = horizontal
14683 = vertical.
1469"""
1470self.dimnode.datumtype.setValue(mode)
1471
1472def p1(self, point=None):
1473"""Set or get the first point of the dim."""
1474plane = self._get_wp()
1475if point:
1476p1_proj = plane.project_point(point)
1477p1_proj_u = (p1_proj - plane.position).dot(plane.u.normalize())
1478p1_proj_v = (p1_proj - plane.position).dot(plane.v.normalize())
1479self.pnts.set1Value(0, p1_proj_u, p1_proj_v, 0)
1480self.setString()
1481else:
1482return Vector(self.pnts.getValues()[0].getValue())
1483
1484def p2(self, point=None):
1485"""Set or get the second point of the dim."""
1486plane = self._get_wp()
1487if point:
1488p2_proj = plane.project_point(point)
1489p2_proj_u = (p2_proj - plane.position).dot(plane.u.normalize())
1490p2_proj_v = (p2_proj - plane.position).dot(plane.v.normalize())
1491self.pnts.set1Value(1, p2_proj_u, p2_proj_v, 0)
1492self.setString()
1493else:
1494return Vector(self.pnts.getValues()[-1].getValue())
1495
1496## @}
1497