# from curve workbench import FreeCAD import FreeCADGui import Part import PySide.QtCore as QtCore import PySide.QtGui as QtGui from pivy import coin from Utils import graphics def parameterization(points, a, closed): """Computes a knot Sequence for a set of points fac (0-1) : parameterization factor fac=0 -> Uniform / fac=0.5 -> Centripetal / fac=1.0 -> Chord-Length""" pts = points.copy() if closed and pts[0].distanceToPoint(pts[-1]) > 1e-7: # we need to add the first point as the end point pts.append(pts[0]) params = [0] for i in range(1, len(pts)): p = pts[i] - pts[i - 1] if isinstance(p, FreeCAD.Vector): le = p.Length else: le = p.length() pl = pow(le, a) params.append(params[-1] + pl) return params class ConnectionMarker(graphics.Marker): def __init__(self, points): super(ConnectionMarker, self).__init__(points, True) 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._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._translate) self._text_switch.addChild(self._text_font) 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] print(name, " selected") 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._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({})".format(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 ConnectionLine(graphics.Line): def __init__(self, markers): super(ConnectionLine, self).__init__( sum([m.points for m in markers], []), True) self.markers = markers self._linear = False for m in self.markers: m.on_drag.append(self.updateLine) def updateLine(self): self.points = sum([m.points for m in self.markers], []) if self._linear: p1 = self.markers[0].points[0] p2 = self.markers[-1].points[0] t = p2 - p1 tan = FreeCAD.Vector(t[0], t[1], t[2]) for m in self.markers: m.tangent = tan @property def linear(self): return self._linear @linear.setter def linear(self, b): self._linear = bool(b) @property def drag_objects(self): return self.markers def check_dependency(self): if any([m._delete for m in self.markers]): self.delete() class Edit(object): def __init__(self, points=[], obj=None): self.points = list() self.lines = list() self.obj = obj self.root_inserted = False self.root = None self.editing = None # event callbacks self.selection_callback = None self._keyPressedCB = None self._mouseMovedCB = None self._mousePressedCB = None for p in points: if isinstance(p, FreeCAD.Vector): self.points.append(MarkerOnShape([p])) elif isinstance(p, (tuple, list)): self.points.append(MarkerOnShape([p[0]], p[1])) elif isinstance(p, (MarkerOnShape, ConnectionMarker)): self.points.append(p) else: FreeCAD.Console.PrintError("InterpoCurveEditor : bad input") # Setup coin objects if self.obj: self.guidoc = self.obj.ViewObject.Document else: if not FreeCADGui.ActiveDocument: FreeCAD.newDocument("New") self.guidoc = FreeCADGui.ActiveDocument self.view = self.guidoc.ActiveView self.rm = self.view.getViewer().getSoRenderManager() self.sg = self.view.getSceneGraph() self.setupInteractionSeparator() # Callbacks #self.unregister_editing_callbacks() #self.register_editing_callbacks() def setupInteractionSeparator(self): if self.root_inserted: self.sg.removeChild(self.root) self.root = graphics.InteractionSeparator(self.rm) self.root.setName("InteractionSeparator") self.root.pick_radius = 40 # Populate root node self.root += self.points self.build_lines() self.root += self.lines # set FreeCAD color scheme for o in self.points + self.lines: o.ovr_col = "yellow" o.sel_col = "green" self.root.register() self.sg.addChild(self.root) self.root_inserted = True self.root.selected_objects = list() def build_lines(self): for i in range(len(self.points) - 1): line = ConnectionLine([self.points[i], self.points[i + 1]]) line.set_color("blue") self.lines.append(line) # ------------------------------------------------------------------------- # SCENE EVENTS CALLBACKS # ------------------------------------------------------------------------- def register_editing_callbacks(self): """ Register editing callbacks (former action function) """ if self._keyPressedCB is None: self._keyPressedCB = self.root.events.addEventCallback(coin.SoKeyboardEvent.getClassTypeId(), self.keyPressed) if self._mousePressedCB is None: self._mousePressedCB = self.root.events.addEventCallback(coin.SoMouseButtonEvent.getClassTypeId(), self.mousePressed) if self._mouseMovedCB is None: self._mouseMovedCB = self.root.events.addEventCallback(coin.SoLocation2Event.getClassTypeId(), self.mouseMoved) def unregister_editing_callbacks(self): """ Remove callbacks used during editing if they exist """ if self._keyPressedCB: self.root.events.removeEventCallback(coin.SoKeyboardEvent.getClassTypeId(), self._keyPressedCB) self._keyPressedCB = None if self._mousePressedCB: self.root.events.removeEventCallback(coin.SoMouseButtonEvent.getClassTypeId(), self._mousePressedCB) self._mousePressedCB = None if self._mouseMovedCB: self.root.events.removeEventCallback(coin.SoLocation2Event.getClassTypeId(), self._mouseMovedCB) self._mouseMovedCB = None # ------------------------------------------------------------------------- # SCENE EVENT HANDLERS # ------------------------------------------------------------------------- def keyPressed(self, attr, event_callback): event = event_callback.getEvent() if event.getState() == event.UP: #FreeCAD.Console.PrintMessage("Key pressed : %s\n"%event.getKey()) if event.getKey() == ord("i"): self.subdivide() elif event.getKey() == ord("q"):# or event.getKey() == ord(65307): if self.obj: self.obj.ViewObject.Proxy.doubleClicked(self.obj.ViewObject) else: self.quit() elif event.getKey() == ord("s"): sel = FreeCADGui.Selection.getSelectionEx() tup = None if len(sel) == 1: tup = (sel[0].Object, sel[0].SubElementNames) for i in range(len(self.root.selected_objects)): if isinstance(self.root.selected_objects[i], MarkerOnShape): self.root.selected_objects[i].sublink = tup #FreeCAD.Console.PrintMessage("Snapped to {}\n".format(str(self.root.selected_objects[i].sublink))) self.root.selected_objects[i].drag_start() self.root.selected_objects[i].drag((0, 0, 0.)) self.root.selected_objects[i].drag_release() elif (event.getKey() == 65535) or (event.getKey() == 65288): # Suppr or Backspace # FreeCAD.Console.PrintMessage("Some objects have been deleted\n") pts = list() for o in self.root.dynamic_objects: if isinstance(o, MarkerOnShape): pts.append(o) self.points = pts self.setupInteractionSeparator() def mousePressed(self, attr, event_callback): """ Mouse button event handler, calls: startEditing, endEditing, addPoint, delPoint """ event = event_callback.getEvent() if (event.getState() == coin.SoMouseButtonEvent.DOWN) and (event.getButton() == event.BUTTON1): # left click if not event.wasAltDown(): ''' do something ''' if self.editing is None: ''' do something''' else: self.endEditing(self.obj, self.editing) elif event.wasAltDown(): # left click with ctrl down self.display_tracker_menu(event) elif (event.getState() == coin.SoMouseButtonEvent.DOWN) and (event.getButton() == event.BUTTON2): # right click self.display_tracker_menu(event) def mouseMoved(self, attr, event_callback): """ Execute as callback for mouse movement. Update tracker position and update preview ghost. """ event = event_callback.getEvent() pos = event.getPosition() ''' if self.editing is not None: self.updateTrackerAndGhost(event) else: # look for a node in mouse position and highlight it pos = event.getPosition() node = self.getEditNode(pos) ep = self.getEditNodeIndex(node) if ep is not None: if self.overNode is not None: self.overNode.setColor(COLORS["default"]) self.trackers[str(node.objectName.getValue())][ep].setColor(COLORS["red"]) self.overNode = self.trackers[str(node.objectName.getValue())][ep] print("show menu") # self.display_tracker_menu(event) else: if self.overNode is not None: self.overNode.setColor(COLORS["default"]) self.overNode = None ''' def endEditing(self, obj, nodeIndex=None, v=None): self.editing = None # ------------------------------------------------------------------------ # DRAFT EDIT Context menu # ------------------------------------------------------------------------ def display_tracker_menu(self, event): self.tracker_menu = QtGui.QMenu() self.event = event actions = None actions = ["add point"] ''' if self.overNode: # if user is over a node doc = self.overNode.get_doc_name() obj = App.getDocument(doc).getObject(self.overNode.get_obj_name()) ep = self.overNode.get_subelement_index() obj_gui_tools = self.get_obj_gui_tools(obj) if obj_gui_tools: actions = obj_gui_tools.get_edit_point_context_menu(obj, ep) else: # try if user is over an edited object pos = self.event.getPosition() obj = self.get_selected_obj_at_position(pos) if utils.get_type(obj) in ["Line", "Wire", "BSpline", "BezCurve"]: actions = ["add point"] elif utils.get_type(obj) in ["Circle"] and obj.FirstAngle != obj.LastAngle: actions = ["invert arc"] if actions is None: return ''' for a in actions: self.tracker_menu.addAction(a) self.tracker_menu.popup(FreeCADGui.getMainWindow().cursor().pos()) QtCore.QObject.connect(self.tracker_menu, QtCore.SIGNAL("triggered(QAction *)"), self.evaluate_menu_action) def evaluate_menu_action(self, labelname): action_label = str(labelname.text()) doc = None obj = None idx = None if action_label == "add point": self.addPoint(self.event) del self.event # ------------------------------------------------------------------------- # EDIT functions # ------------------------------------------------------------------------- def addPoint(self, event): ''' add point to the end ''' pos = event.getPosition() pts = self.points.copy() new_select = list() point = FreeCAD.Vector(pos) mark = MarkerOnShape([point]) pts.append(mark) new_select.append(mark) self.points = pts self.setupInteractionSeparator() self.root.selected_objects = new_select def subdivide(self): # get selected lines and subdivide them pts = list() new_select = list() for o in self.lines: #FreeCAD.Console.PrintMessage("object %s\n"%str(o)) if isinstance(o, ConnectionLine): pts.append(o.markers[0]) if o in self.root.selected_objects: #idx = self.lines.index(o) #FreeCAD.Console.PrintMessage("Subdividing line #{}\n".format(idx)) p1 = o.markers[0].points[0] p2 = o.markers[1].points[0] midpar = (FreeCAD.Vector(p1) + FreeCAD.Vector(p2)) / 2.0 mark = MarkerOnShape([midpar]) pts.append(mark) new_select.append(mark) pts.append(self.points[-1]) self.points = pts self.setupInteractionSeparator() self.root.selected_objects = new_select return True def quit(self): self.unregister_editing_callbacks() self.root.unregister() self.sg.removeChild(self.root) self.root_inserted = False