# -*- coding: utf-8 -*- __title__ = "Manipulators" __author__ = "Christophe Grellier (Chris_G)" __license__ = "LGPL 2.1" __doc__ = "FreeCAD interactive editing library" import sys if sys.version_info.major >= 3: from importlib import reload import FreeCAD #import FreeCADGui import Part #import _utils from pivy import coin from freecad.Curves import graphics class Object3D(graphics.Object3D): """freeCAD manipulator base class""" def __init__(self, points, dynamic=True): if isinstance(points, (list, tuple)): pts = [self.vector(p) for p in points] else: pts = [self.vector(points)] super(Object3D, self).__init__(pts, dynamic) self.ctrl_keys = dict() def vector(self, p): if isinstance(p, FreeCAD.Vector): return p elif isinstance(p, Part.Vertex): return p.Point else: return FreeCAD.Vector(p[0],p[1],p[2]) @property def vectors(self): return [FreeCAD.Vector(p[0],p[1],p[2]) for p in self.points] @property def point(self): return self.vector(self.points[0]) class Point(Object3D): """Basic point manipulator""" def __init__(self, points, dynamic=True): super(Point, self).__init__(points, dynamic) self.marker = coin.SoMarkerSet() self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_5_5 self.addChild(self.marker) class ShapeSnap(Point): """Point manipulator that snaps to a shape""" def __init__(self, points, sh=None): super(ShapeSnap, self).__init__(points) self.snap_shape = sh @property def snap_shape(self): return self._shape @snap_shape.setter def snap_shape(self, sh): if isinstance(sh,Part.Shape): self._shape = sh if not self.snap_drag in self.on_drag: self.on_drag.append(self.snap_drag) else: self._shape = None if self.snap_drag in self.on_drag: self.on_drag.remove(self.snap_drag) def snap_drag(self): if self.enabled: pts = self.points for p in pts: v = Part.Vertex(p[0],p[1],p[2]) proj = v.distToShape(self.snap_shape)[1][0][1] # FreeCAD.Console.PrintMessage("%s -> %s\n"%(p.getValue(),proj)) p[0] = proj.x p[1] = proj.y p[2] = proj.z self.points = pts class EdgeSnapAndTangent(ShapeSnap): """Point manipulator that snaps to an edge, and generates a tangent edge that another manipulator can snap to""" def __init__(self, points, sh=None): super(EdgeSnapAndTangent, self).__init__(points, sh) #self.tangent = None self.tangent_update() #self.tangent_update_cb = [] self.on_drag.append(self.tangent_update) def tangent_update(self): v = Part.Vertex(self.point) p = v.distToShape(self.snap_shape)[1][0][1] try: par = self.snap_shape.Curve.parameter(p) except: print("Failed to get curve parameter") par = self.snap_shape.FirstParameter #print(par) tan = self.snap_shape.tangentAt(par) e = Part.makeLine(p, p+tan) self.tangent = e.Curve.toShape(-2e10, 2e10) class SubLinkSnap(ShapeSnap): """Point manipulator that snaps to a shape provided by a PropertySubLink""" def __init__(self, points, sl=None): super(SubLinkSnap, self).__init__(points, True) sublink = sl def subshape_from_sublink(self, o): name = o[1][0] if 'Vertex' in name: n = eval(name.lstrip('Vertex')) return(o[0].Shape.Vertexes[n-1]) elif 'Edge' in name: n = eval(name.lstrip('Edge')) return(o[0].Shape.Edges[n-1]) elif 'Face' in name: n = eval(name.lstrip('Face')) return(o[0].Shape.Faces[n-1]) @property def sublink(self): return self._sublink @sublink.setter def sublink(self, sl): sub = None if isinstance(sl,(tuple,list)) and not (sl == self._sublink): sub = self.subshape_from_sublink(sl) if sub: self._sublink = sl self.snap_shape = sub else: self._sublink = None self.snap_shape = None class TangentSnap(ShapeSnap): """Point manipulator that snaps to the tangent of a ShapeSnap manipulator""" def __init__(self, manip): super(TangentSnap, self).__init__(FreeCAD.Vector()) self.parent = manip self._par = 1.0 self._scale = 1.0 self.update_tangent() #p = self.snap_shape.valueAt(self.par) #self.points = [p] self.parent.on_drag.append(self.update_tangent) self.on_drag.append(self.update_parameter) def update_tangent(self): self.snap_shape = self.parent.tangent self.points = [self.snap_shape.valueAt(self._par * self._scale)] def update_parameter(self): p = self.vector(self.points[0]) self._par = self.snap_shape.Curve.parameter(p) / self._scale @property def parameter(self): return self._par @parameter.setter def parameter(self, t): self._par = t self.points = [self.snap_shape.valueAt(self._par * self._scale)] class WithCustomTangent(object): """Point Extension class that adds a custom tangent""" def __init__(self): self._tangent = None @property def tangent(self): return self._tangent @tangent.setter def tangent(self, t): if isinstance(t, FreeCAD.Vector) and t.Length > 1e-7: self._tangent = t #self._tangent.normalize() self.marker.markerIndex = coin.SoMarkerSet.DIAMOND_FILLED_9_9 else: self._tangent = None self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9 def set_tangent_toward_point(self, p): if self.vector(p): self.tangent = self.vector(p) - self.vector(self.points[0]) class CustomText(Object3D): """Text manipulator""" def __init__(self, parent, dynamic=False): super(CustomText, self).__init__(parent.points, dynamic) #self._text_offset = FreeCAD.Vector(0,0,0) self._text_translate = coin.SoTranslation() self._text_font = coin.SoFont() self._text_font.name = "Arial:Bold" self._text_font.size = 13.0 self._text = coin.SoText2() self._text_switch = coin.SoSwitch() self._text_switch.addChild(self._text_translate) self._text_switch.addChild(self._text_font) self._text_switch.addChild(self._text) self.addChild(self._text_switch) self.parent = parent self.parent.on_drag.append(self.translate) self.translate() def show(self): self._text_switch.whichChild = coin.SO_SWITCH_ALL def hide(self): self._text_switch.whichChild = coin.SO_SWITCH_NONE def translate(self): self._text_translate.translation = self.parent.point @property def text(self): return self._text.string.getValues() @text.setter def text(self, txt): strlist = [] if isinstance(txt, str): strlist = [txt] elif isinstance(txt, (list, tuple)): strlist = txt self._text.string.setValues(0, len(strlist), strlist) class CycleText(CustomText): """Text manipulator that cycles through a list of strings""" def __init__(self, parent, dynamic=False): super(CycleText, self).__init__(parent, dynamic) self.text_list = ["",] self.ctrl_keys = {"c" : [self.cycle]} def cycle(self): if self.text[0] in self.text_list: n = self.text_list.index(self.text[0]) if n < len(self.text_list)-1: self.text = self.text_list[n+1] else: self.text = self.text_list[0] else: self.text = self.text_list[0] class ParameterText(CustomText): """Text manipulator that displays the parameter value of its parent""" def __init__(self, parent, dynamic=False): super(ParameterText, self).__init__(parent, dynamic) self.parent.on_drag.append(self.update_text) if hasattr(self.parent, "parent"): self.parent.parent.on_drag.append(self.translate) def update_text(self): self.text = '{: 9.3f}'.format(self.parent.parameter) class MarkerOnShape(graphics.Marker): def __init__(self, points, sh=None): super(MarkerOnShape, self).__init__(points, True) self._shape = None self._sublink = None self._tangent = None self._text_translate = coin.SoTranslation() self._text = coin.SoText2() self._text_switch = coin.SoSwitch() self._text_switch.addChild(self._text_translate) self._text_switch.addChild(self._text) self.on_drag_start.append(self.add_text) self.on_drag_release.append(self.remove_text) self.addChild(self._text_switch) if isinstance(sh,Part.Shape): self.snap_shape = sh elif isinstance(sh,(tuple,list)): self.sublink = sh def subshape_from_sublink(self, o): name = o[1][0] if 'Vertex' in name: n = eval(name.lstrip('Vertex')) return(o[0].Shape.Vertexes[n-1]) elif 'Edge' in name: n = eval(name.lstrip('Edge')) return(o[0].Shape.Edges[n-1]) elif 'Face' in name: n = eval(name.lstrip('Face')) return(o[0].Shape.Faces[n-1]) def add_text(self): self._text_switch.whichChild = coin.SO_SWITCH_ALL self.on_drag.append(self.update_text) def remove_text(self): self._text_switch.whichChild = coin.SO_SWITCH_NONE self.on_drag.remove(self.update_text) def update_text(self): p = self.points[0] coords = ['{: 9.3f}'.format(p[0]),'{: 9.3f}'.format(p[1]),'{: 9.3f}'.format(p[2])] self._text_translate.translation = p self._text.string.setValues(0,3,coords) @property def tangent(self): return self._tangent @tangent.setter def tangent(self, t): if isinstance(t,FreeCAD.Vector): if t.Length > 1e-7: self._tangent = t self._tangent.normalize() self.marker.markerIndex = coin.SoMarkerSet.DIAMOND_FILLED_9_9 else: self._tangent = None self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9 else: self._tangent = None self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9 @property def snap_shape(self): return self._shape @snap_shape.setter def snap_shape(self, sh): if isinstance(sh,Part.Shape): self._shape = sh else: self._shape = None self.alter_color() @property def sublink(self): return self._sublink @sublink.setter def sublink(self, sl): if isinstance(sl,(tuple,list)) and not (sl == self._sublink): self._shape = self.subshape_from_sublink(sl) self._sublink = sl else: self._shape = None self._sublink = None self.alter_color() def alter_color(self): if isinstance(self._shape, Part.Vertex): self.set_color("white") elif isinstance(self._shape, Part.Edge): self.set_color("cyan") elif isinstance(self._shape, Part.Face): self.set_color("magenta") else: self.set_color("black") def __repr__(self): return("MarkerOnShape(%s)"%self._shape) def drag(self, mouse_coords, fact=1.): if self.enabled: pts = self.points for i, p in enumerate(pts): p[0] = mouse_coords[0] * fact + self._tmp_points[i][0] p[1] = mouse_coords[1] * fact + self._tmp_points[i][1] p[2] = mouse_coords[2] * fact + self._tmp_points[i][2] if self._shape: v = Part.Vertex(p[0],p[1],p[2]) proj = v.distToShape(self._shape)[1][0][1] # FreeCAD.Console.PrintMessage("%s -> %s\n"%(p.getValue(),proj)) p[0] = proj.x p[1] = proj.y p[2] = proj.z self.points = pts for foo in self.on_drag: foo() class ConnectionPolygon(graphics.Polygon): std_col = "green" def __init__(self, markers): super(ConnectionPolygon, self).__init__( sum([m.points for m in markers], []), True) self.markers = markers for m in self.markers: m.on_drag.append(self.updatePolygon) def updatePolygon(self): self.points = sum([m.points for m in self.markers], []) @property def drag_objects(self): return self.markers def check_dependency(self): if any([m._delete for m in self.markers]): self.delete() class Line(graphics.Line): def __init__(self, markers): super(Line, self).__init__( sum([m.points for m in markers], []), True) self.markers = markers for m in self.markers: # If some markers are moved ... m.on_drag.append(self.updateLine) #self.markers[0].on_drag.append(self.updateLine) def updateLine(self): # ... consecutively copy their points (SoCoordinate3) self.points = sum([m.points for m in self.markers], []) @property def drag_objects(self): return self.markers def check_dependency(self): if any([m._delete for m in self.markers]): self.delete()