# -*- coding: utf8 -*- #*************************************************************************** #* Copyright (c) 2009, 2010 Yorik van Havre * #* Copyright (c) 2009, 2010 Ken Cline * #* Copyright (c) 2019, 2020 Carlo Pavan * #* * #* This program is free software; you can redistribute it and/or modify * #* it under the terms of the GNU Lesser General Public License (LGPL) * #* as published by the Free Software Foundation; either version 2 of * #* the License, or (at your option) any later version. * #* for detail see the LICENCE text file. * #* * #* This program is distributed in the hope that it will be useful, * #* but WITHOUT ANY WARRANTY; without even the implied warranty of * #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * #* GNU Library General Public License for more details. * #* * #* You should have received a copy of the GNU Library General Public * #* License along with this program; if not, write to the Free Software * #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * #* USA * #* * #*************************************************************************** __title__= "FreeCAD Draft Edit Tool" __author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, \ Dmitry Chigrin, Carlo Pavan" __url__ = "https://www.freecadweb.org" import FreeCAD as App import math import Draft if App.GuiUp: # Do not import GUI-related modules if GUI is not there import FreeCADGui as Gui import DraftTools from DraftTrackers import editTracker, wireTracker, arcTracker, bsplineTracker, bezcurveTracker from pivy import coin from PySide import QtCore, QtGui from PySide.QtCore import QT_TRANSLATE_NOOP from DraftTools import translate COLORS = { "default": Gui.draftToolBar.getDefaultColor("snap"), "black": (0., 0., 0.), "white": (1., 1., 1.), "grey": (.5, .5, .5), "red": (1., 0., 0.), "green": (0., 1., 0.), "blue": (0., 0., 1.), "yellow": (1., 1., 0.), "cyan": (0., 1., 1.), "magenta":(1., 0., 1.) } class Edit(): """ The Draft_Edit FreeCAD command definition. A tool to graphically edit FreeCAD objects. Current implementation use many parts of pivy graphics code by user "looo". The tool collect editpoints from objects and display Trackers on them to allow editing their Shape and their parameters. Callbacks ---------- selection_callback registered when tool is launched, identify selected objects. editing_callbacks self._keyPressedCB -> self.keyPressed self._mouseMovedCB -> self._mouseMovedCB if self._mousePressedCB -> self.mousePressed when trackers are displayed for selected objects, these callbacks capture user events and forward them to related functions Task panel (Draft Toolbar) ---------- self.ui = Gui.draftToolBar TODO: since we introduced context menu for interacting with editTrackers, point 2 should become obsolete, because not consistent with multi-object editing. Draft_Edit uses taskpanel in 3 ways: 1 - when waiting for user to select an object calling self.ui.selectUi() 2 - when Trackers are displayed and user must click one, a custom task panel is displayed depending on edited object: self.ui.editUi() -> the default one self.ui.editUi("Wire") -> line and wire editing self.ui.editUi("BezCurve") -> BezCurve editing self.ui.editUi("Circle") -> circle editing self.ui.editUi("Arc") -> arc editing When Draft_Edit evaluate mouse click, depending if some ui button have been pressed (.isChecked()), decide if the action is a startEditing or AddPoint or DelPoint or change BezCurve Continuity, ecc. 3 - when in editing, lineUi support clicking destination point by self.startEditing self.ui.lineUi() self.ui.isRelative.show() Tracker selection ---------- If the tool recognize mouse click as an attempt to startEditing, using soRayPickAction, it identifies the selected editTracker and start editing it. Here is where "looo" code was very useful. Editing preview ---------- When object editing begins, self.ghost is initiated with the corresponding DraftTracker of the object type. The object Tracker is deleted when user clicks again and endEditing. Context Menu ---------- Activated with Alt+LeftClick or pressing key "e" It's a custom context menu, that depends on clicked tracker or on clicked object. display_tracker_menu populates the menu with custom actions evaluate_menu_action evaluate user chosen action and launch corresponding function. Preferences ---------- maxObjects : Int set by "DraftEditMaxObjects" in user preferences The max number of FreeCAD objects the tool is allowed to edit at the same time. pick_radius : Int set by "DraftEditPickRadius" in user preferences The pick radius during editing operation. Increase if you experience problems in clicking on a editTracker because of screen resolution. Attributes ---------- obj : Edited object I'm planning to discard this attribute. In old implementation every function was supposed to act on self.obj, self.editpoints, self.trackers, self.pl, self.invpl. Due to multiple object editing, i'm planning to keep just self.trackers. Any other object will be identified and processed starting from editTracker information. editing : Int Index of the editTracker that has been clicked by the user. Tracker selection mechanism is based on it. if self.editing == None : the user didn't click any node, and next click will be processed as an attempt to start editing operation if self.editing == o or 1 or 2 or 3 etc : the user is editing corresponding node, so next click will be processed as an attempt to end editing operation editpoints : List [FreeCAD::App.Vector] List of editpoints collected from the edited object, on whick editTrackers will be placed. trackers : Dictionary {object.Name : [editTrackers]} It records the list of DraftTrackers.editTracker. {object.Name as String : [editTrackers for the object]} Each tracker is created with (position,obj.Name,idx), so it's possible to recall it self.trackers[str(node.objectName.getValue())][ep] overNode : DraftTrackers.editTracker It represent the editTracker under the cursor position. It is used to preview the tracker selection action. ghost : DraftTrackers.* Handles the tracker to preview editing operations. it is initialized when user clicks on a editTracker by self.startEditing() function. alt_edit_mode : Int Allows alternative editing modes for objects. ATM supported for: - arcs: if 0 edit by 3 points, if 1 edit by center, radius, angles supportedObjs : List List of supported Draft Objects. The tool use Draft.getType(obj) to compare object type to the list. supportedPartObjs : List List of supported Part Objects. The tool use Draft.getType(obj) and obj.TypeId to compare object type to the list. """ def __init__(self): """ Initialize Draft_Edit Command. """ self.running = False self.trackers = {'object':[]} self.overNode = None # preselected node with mouseover self.obj = None self.editing = None # event callbacks self.selection_callback = None self._keyPressedCB = None self._mouseMovedCB = None self._mousePressedCB = None # this are used to edit structure objects, it's a bit buggy i think self.selectstate = None self.originalDisplayMode = None self.originalPoints = None self.originalNodes = None # settings param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") self.maxObjects = param.GetInt("DraftEditMaxObjects", 5) self.pick_radius = param.GetInt("DraftEditPickRadius", 20) self.alt_edit_mode = 0 # alternative edit mode for objects # preview self.ghost = None #list of supported Draft and Arch objects self.supportedObjs = ["BezCurve","Wire","BSpline","Circle","Rectangle", "Polygon","Dimension","Space","Structure","PanelCut", "PanelSheet","Wall", "Window"] #list of supported Part objects (they don't have a proxy) #TODO: Add support for "Part::Circle" "Part::RegularPolygon" "Part::Plane" "Part::Ellipse" "Part::Vertex" "Part::Spiral" self.supportedPartObjs = ["Sketch", "Sketcher::SketchObject", "Part", "Part::Line", "Part::Box"] def GetResources(self): return {'Pixmap': 'Draft_Edit', 'Accel': "D, E", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Edit", "Edit"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Edit", "Edits the active object")} #--------------------------------------------------------------------------- # MAIN FUNCTIONS #--------------------------------------------------------------------------- def Activated(self): """ Activated is run when user launch Edit command. If something is selected -> call self.proceed() If nothing is selected -> self.register_selection_callback() """ if self.running: self.finish() DraftTools.Modifier.Activated(self,"Edit") if not App.ActiveDocument: self.finish() self.ui = Gui.draftToolBar self.view = Draft.get3DView() if Gui.Selection.getSelection(): self.proceed() else: self.ui.selectUi() App.Console.PrintMessage(translate("draft", "Select a Draft object to edit") + "\n") self.register_selection_callback() def proceed(self): "this method defines editpoints and set the editTrackers" self.unregister_selection_callback() self.edited_objects = self.getObjsFromSelection() if not self.edited_objects: return self.finish() # Save selectstate and turn selectable false. # Object can remain selectable commenting following lines: # self.saveSelectState(self.obj) # self.setSelectState(self.obj, False) # start object editing Gui.Selection.clearSelection() Gui.Snapper.setSelectMode(True) self.ui.editUi() for obj in self.edited_objects: self.setEditPoints(obj) self.register_editing_callbacks() # TODO: align working plane when editing starts # App.DraftWorkingPlane.save() # self.alignWorkingPlane() def finish(self,closed=False): """ terminates Edit Tool """ self.unregister_selection_callback() self.unregister_editing_callbacks() self.editing = None self.finalizeGhost() Gui.Snapper.setSelectMode(False) if self.obj and closed: if "Closed" in self.obj.PropertiesList: if not self.obj.Closed: self.obj.Closed = True if self.ui: self.removeTrackers() self.restoreSelectState(self.obj) if Draft.getType(self.obj) == "Structure": if self.originalDisplayMode is not None: self.obj.ViewObject.DisplayMode = self.originalDisplayMode if self.originalPoints is not None: self.obj.ViewObject.NodeSize = self.originalPoints if self.originalNodes is not None: self.obj.ViewObject.ShowNodes = self.originalNodes self.selectstate = None self.originalDisplayMode = None self.originalPoints = None self.originalNodes = None DraftTools.Modifier.finish(self) App.DraftWorkingPlane.restore() if Gui.Snapper.grid: Gui.Snapper.grid.set() self.running = False # delay resetting edit mode otherwise it doesn't happen from PySide import QtCore QtCore.QTimer.singleShot(0,Gui.ActiveDocument.resetEdit) #--------------------------------------------------------------------------- # SCENE EVENTS CALLBACKS #--------------------------------------------------------------------------- def register_selection_callback(self): """ register callback for selection when command is launched """ self.unregister_selection_callback() self.selection_callback = self.view.addEventCallback("SoEvent",DraftTools.selectObject) def unregister_selection_callback(self): """ remove selection callback if it exists """ if self.selection_callback: self.view.removeEventCallback("SoEvent",self.selection_callback) self.selection_callback = None def register_editing_callbacks(self): """ register editing callbacks (former action function) """ viewer = Gui.ActiveDocument.ActiveView.getViewer() self.render_manager = viewer.getSoRenderManager() view = Gui.ActiveDocument.ActiveView if self._keyPressedCB is None: self._keyPressedCB = view.addEventCallbackPivy( coin.SoKeyboardEvent.getClassTypeId(), self.keyPressed) if self._mouseMovedCB is None: self._mouseMovedCB = view.addEventCallbackPivy( coin.SoLocation2Event.getClassTypeId(), self.mouseMoved) if self._mousePressedCB is None: self._mousePressedCB = view.addEventCallbackPivy( coin.SoMouseButtonEvent.getClassTypeId(), self.mousePressed) #App.Console.PrintMessage("Draft edit callbacks registered \n") def unregister_editing_callbacks(self): """ remove callbacks used during editing if they exist """ view = Gui.ActiveDocument.ActiveView if self._keyPressedCB: view.removeEventCallbackSWIG(coin.SoKeyboardEvent.getClassTypeId(), self._keyPressedCB) self._keyPressedCB = None #App.Console.PrintMessage("Draft edit keyboard callback unregistered \n") if self._mouseMovedCB: view.removeEventCallbackSWIG(coin.SoLocation2Event.getClassTypeId(), self._mouseMovedCB) self._mouseMovedCB = None #App.Console.PrintMessage("Draft edit location callback unregistered \n") if self._mousePressedCB: view.removeEventCallbackSWIG(coin.SoMouseButtonEvent.getClassTypeId(), self._mousePressedCB) self._mousePressedCB = None #App.Console.PrintMessage("Draft edit mouse button callback unregistered \n") #--------------------------------------------------------------------------- # SCENE EVENT HANDLERS #--------------------------------------------------------------------------- def keyPressed(self, event_callback): """ keyboard event handler """ #TODO: Get the keys from preferences event = event_callback.getEvent() if event.getState() == coin.SoKeyboardEvent.DOWN: key = event.getKey() #App.Console.PrintMessage("pressed key : "+str(key)+"\n") if key == 65307: # ESC self.finish() if key == 97: # "a" self.finish() if key == 111: # "o" self.finish(closed=True) if key == 101: # "e" self.display_tracker_menu(event) if key == 105: # "i" if Draft.getType(self.obj) == "Circle": self.arcInvert(self.obj) def mousePressed(self, 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(): if self.ui.addButton.isChecked(): self.addPoint(event) return if self.ui.delButton.isChecked(): self.delPoint(event) return if Draft.getType(self.obj) == "BezCurve" and (self.ui.sharpButton.isChecked() or self.ui.tangentButton.isChecked() or self.ui.symmetricButton.isChecked()): pos = event.getPosition() node = self.getEditNode(pos) ep = self.getEditNodeIndex(node) if ep is None: return doc = App.getDocument(str(node.documentName.getValue())) self.obj = doc.getObject(str(node.objectName.getValue())) if self.obj is None: return if self.ui.sharpButton.isChecked(): return self.smoothBezPoint(self.obj, ep, 'Sharp') elif self.ui.tangentButton.isChecked(): return self.smoothBezPoint(self.obj, ep, 'Tangent') elif self.ui.symmetricButton.isChecked(): return self.smoothBezPoint(self.obj, ep, 'Symmetric') if self.editing is None: self.startEditing(event) else: self.endEditing(self.obj,self.editing) elif event.wasAltDown(): #left click with ctrl down self.display_tracker_menu(event) def mouseMoved(self, event_callback): "mouse moved event handler, update tracker position and update preview ghost" event = event_callback.getEvent() if self.editing != 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] else: if self.overNode is not None: self.overNode.setColor(COLORS["default"]) self.overNode = None def startEditing(self, event): "start editing selected EditNode" pos = event.getPosition() node = self.getEditNode(pos) ep = self.getEditNodeIndex(node) if ep is None: return doc = App.getDocument(str(node.documentName.getValue())) self.obj = doc.getObject(str(node.objectName.getValue())) if self.obj is None: return self.setPlacement(self.obj) App.Console.PrintMessage(self.obj.Name + ": editing node number " + str(ep) + "\n") self.ui.lineUi() self.ui.isRelative.show() self.editing = ep self.trackers[self.obj.Name][self.editing].off() self.finalizeGhost() self.ghost = self.initGhost(self.obj) self.node.append(self.trackers[self.obj.Name][self.editing].get()) Gui.Snapper.setSelectMode(False) self.hideTrackers() def updateTrackerAndGhost(self, event): "updates tracker position when editing and update ghost" pos = event.getPosition().getValue() orthoConstrain = False if event.wasShiftDown() == 1: orthoConstrain = True snappedPos = Gui.Snapper.snap((pos[0],pos[1]),self.node[-1], constrain=orthoConstrain) self.trackers[self.obj.Name][self.editing].set(snappedPos) self.ui.displayPoint(snappedPos,self.node[-1]) if self.ghost: self.updateGhost(obj=self.obj,idx=self.editing,pt=snappedPos) def endEditing(self, obj, nodeIndex, v = None): "terminate editing and start object updating process" self.finalizeGhost() self.trackers[obj.Name][nodeIndex].on() Gui.Snapper.setSelectMode(True) if v is None: # endEditing is called by mousePressed v = self.trackers[obj.Name][nodeIndex].get() else: # endEditing is called by numericInput, so tracker # position should be updated manually self.trackers[obj.Name][nodeIndex].set(v) self.update(obj, nodeIndex, v) self.alt_edit_mode = 0 self.ui.editUi(self.ui.lastMode) self.node = [] self.editing = None self.showTrackers() DraftTools.redraw3DView() #--------------------------------------------------------------------------- # UTILS #--------------------------------------------------------------------------- def getObjsFromSelection(self): "evaluate selection and returns a valid object to edit" selection = Gui.Selection.getSelection() self.edited_objects = [] if len(selection) > self.maxObjects: App.Console.PrintMessage(translate("draft", "Too many objects selected, max number set to: ") + str(self.maxObjects) + "\n") return None for obj in selection: if Draft.getType(obj) in self.supportedObjs: self.edited_objects.append(obj) continue elif Draft.getType(obj) in self.supportedPartObjs: if obj.TypeId in self.supportedPartObjs: self.edited_objects.append(obj) continue App.Console.PrintWarning(obj.Name + translate("draft", ": this object is not editable") + "\n") return self.edited_objects def get_selected_obj_at_position(self, pos): """return object at given position if object is one of the edited objects (self.edited_objects) """ selobjs = Gui.ActiveDocument.ActiveView.getObjectsInfo((pos[0],pos[1])) if not selobjs: return for info in selobjs: if not info: return for obj in self.edited_objects: if obj.Name == info["Object"]: return obj def numericInput(self, v, numy=None, numz=None): """this function gets called by the toolbar or by the mouse click and activate the update function""" if (numy is not None): v = App.Vector(v,numy,numz) self.endEditing(self.obj, self.editing, v) App.ActiveDocument.recompute() def setSelectState(self, obj, selState = False): if hasattr(obj.ViewObject,"Selectable"): obj.ViewObject.Selectable = selState def saveSelectState(self, obj): if hasattr(obj.ViewObject,"Selectable"): self.selectstate = obj.ViewObject.Selectable def restoreSelectState(self,obj): if obj: if hasattr(obj.ViewObject,"Selectable") and (self.selectstate is not None): obj.ViewObject.Selectable = self.selectstate def setPlacement(self,obj): "set self.pl and self.invpl to self.obj placement and inverse placement" if not obj: return if "Placement" in obj.PropertiesList: self.pl = obj.getGlobalPlacement() self.invpl = self.pl.inverse() def alignWorkingPlane(self): "align working plane to self.obj" if "Shape" in self.obj.PropertiesList: if DraftTools.plane.weak: DraftTools.plane.alignToFace(self.obj.Shape) if self.planetrack: self.planetrack.set(self.editpoints[0]) def getEditNode(self, pos): "get edit node from given screen position" node = self.sendRay(pos) return node def sendRay(self, mouse_pos): "sends a ray through the scene and return the nearest entity" ray_pick = coin.SoRayPickAction(self.render_manager.getViewportRegion()) ray_pick.setPoint(coin.SbVec2s(*mouse_pos)) ray_pick.setRadius(self.pick_radius) ray_pick.setPickAll(True) ray_pick.apply(self.render_manager.getSceneGraph()) picked_point = ray_pick.getPickedPointList() return self.searchEditNode(picked_point) def searchEditNode(self, picked_point): "search edit node inside picked point list and retrurn node number" for point in picked_point: path = point.getPath() length = path.getLength() point = path.getNode(length - 2) #import DraftTrackers if hasattr(point,"subElementName") and 'EditNode' in str(point.subElementName.getValue()): return point return None def getEditNodeIndex(self, point): "get edit node index from given screen position" if point: subElement = str(point.subElementName.getValue()) ep = int(subElement[8:]) return ep else: return None #--------------------------------------------------------------------------- # EDIT TRACKERS functions #--------------------------------------------------------------------------- def setTrackers(self, obj, points=None): "set Edit Trackers for editpoints collected from self.obj" if points is None or len(points) == 0: App.Console.PrintWarning(translate("draft", "No edit point found for selected object") + "\n") # do not finish if some trackers are still present if self.trackers == {'object':[]}: self.finish() return self.trackers[obj.Name] = [] if Draft.getType(obj) == "BezCurve": self.resetTrackersBezier(obj) else: if obj.Name in self.trackers: self.removeTrackers(obj) for ep in range(len(points)): self.trackers[obj.Name].append(editTracker(pos=points[ep],name=obj.Name,idx=ep)) def resetTrackers(self, obj): "reset Edit Trackers and set them again" self.removeTrackers(obj) self.setTrackers(obj, self.getEditPoints(obj)) def removeTrackers(self, obj = None): "reset Edit Trackers and set them again" if obj: if obj.Name in self.trackers: for t in self.trackers[obj.Name]: t.finalize() self.trackers[obj.Name] = [] else: for key in self.trackers.keys(): for t in self.trackers[key]: t.finalize() self.trackers = {'object':[]} def hideTrackers(self, obj=None): """hide Edit Trackers Attributes ---------- obj : FreeCAD object hides trackers only for given object, if obj is None, hides all trackers """ if obj is None: for key in self.trackers: for t in self.trackers[key]: t.off() else: for t in self.trackers[obj.Name]: t.off() def showTrackers(self, obj=None): """show Edit Trackers Attributes ---------- obj : FreeCAD object shows trackers only for given object, if obj is None, shows all trackers """ if obj is None: for key in self.trackers: for t in self.trackers[key]: t.on() else: for t in self.trackers[obj.Name]: t.on() #--------------------------------------------------------------------------- # PREVIEW #--------------------------------------------------------------------------- def initGhost(self,obj): "initialize preview ghost" if Draft.getType(obj) == "Wire": return wireTracker(obj.Shape) elif Draft.getType(obj) == "BSpline": return bsplineTracker() elif Draft.getType(obj) == "BezCurve": return bezcurveTracker() elif Draft.getType(obj) == "Circle": return arcTracker() def updateGhost(self,obj,idx,pt): if Draft.getType(obj) in ["Wire"]: self.ghost.on() pointList = self.applyPlacement(obj.Points) pointList[idx] = pt if obj.Closed: pointList.append(pointList[0]) self.ghost.updateFromPointlist(pointList) elif Draft.getType(obj) == "BSpline": self.ghost.on() pointList = self.applyPlacement(obj.Points) pointList[idx] = pt if obj.Closed: pointList.append(pointList[0]) self.ghost.update(pointList) elif Draft.getType(obj) == "BezCurve": self.ghost.on() plist = self.applyPlacement(obj.Points) pointList = self.recomputePointsBezier(obj,plist,idx,pt,obj.Degree,moveTrackers=True) self.ghost.update(pointList,obj.Degree) elif Draft.getType(obj) == "Circle": self.ghost.on() self.ghost.setCenter(obj.getGlobalPlacement().Base) self.ghost.setRadius(obj.Radius) if self.obj.FirstAngle == self.obj.LastAngle: # self.obj is a circle self.ghost.circle = True if self.editing == 0: self.ghost.setCenter(pt) elif self.editing == 1: radius = pt.sub(obj.getGlobalPlacement().Base).Length self.ghost.setRadius(radius) else: if self.alt_edit_mode == 0: # edit by 3 points if self.editing == 0: # center point import DraftVecUtils p1 = self.invpl.multVec(self.obj.Shape.Vertexes[0].Point) p2 = self.invpl.multVec(self.obj.Shape.Vertexes[1].Point) p0 = DraftVecUtils.project(self.invpl.multVec(pt),self.invpl.multVec(self.getArcMid(obj, global_placement=True))) self.ghost.autoinvert=False self.ghost.setRadius(p1.sub(p0).Length) self.ghost.setStartPoint(self.obj.Shape.Vertexes[1].Point) self.ghost.setEndPoint(self.obj.Shape.Vertexes[0].Point) self.ghost.setCenter(self.pl.multVec(p0)) return else: p1=self.getArcStart(obj,global_placement=True) p2=self.getArcMid(obj,global_placement=True) p3=self.getArcEnd(obj,global_placement=True) if self.editing == 1: p1=pt elif self.editing == 3: p2=pt elif self.editing == 2: p3=pt self.ghost.setBy3Points(p1,p2,p3) elif self.alt_edit_mode == 1: # edit by center radius angles self.ghost.setStartAngle(math.radians(obj.FirstAngle)) self.ghost.setEndAngle(math.radians(obj.LastAngle)) if self.editing == 0: self.ghost.setCenter(pt) elif self.editing == 1: self.ghost.setStartPoint(pt) elif self.editing == 2: self.ghost.setEndPoint(pt) elif self.editing == 3: self.ghost.setRadius(self.invpl.multVec(pt).Length) DraftTools.redraw3DView() def applyPlacement(self,pointList): if self.pl: plist = [] for p in pointList: point = self.pl.multVec(p) plist.append(point) return plist else: return pointList def finalizeGhost(self): try: self.ghost.finalize() self.ghost = None except: return #--------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Add/Delete Vertexes #--------------------------------------------------------------------------- def addPoint(self,event): "called by action, add point to obj and reset trackers" pos = event.getPosition() #self.setSelectState(self.obj, True) selobjs = Gui.ActiveDocument.ActiveView.getObjectsInfo((pos[0],pos[1])) if not selobjs: return for info in selobjs: if not info: return for o in self.edited_objects: if o.Name != info["Object"]: continue self.obj = o break self.setPlacement(self.obj) if Draft.getType(self.obj) == "Wire" and 'Edge' in info["Component"]: pt = App.Vector(info["x"], info["y"], info["z"]) self.addPointToWire(self.obj, pt, int(info["Component"][4:])) elif Draft.getType(self.obj) in ["BSpline", "BezCurve"]: #to fix double vertex created #pt = self.point if "x" in info:# prefer "real" 3D location over working-plane-driven one if possible pt = App.Vector(info["x"], info["y"], info["z"]) else: continue self.addPointToCurve(pt,info) self.obj.recompute() self.removeTrackers(self.obj) self.setEditPoints(self.obj) #self.setSelectState(self.obj, False) return def addPointToWire(self, obj, newPoint, edgeIndex): newPoints = [] hasAddedPoint = False if hasattr(obj, "ChamferSize") and hasattr(obj, "FilletRadius"): if obj.ChamferSize > 0 and obj.FilletRadius > 0: edgeIndex = (edgeIndex +3) / 4 elif obj.ChamferSize > 0 or obj.FilletRadius > 0: edgeIndex = (edgeIndex +1) / 2 for index, point in enumerate(self.obj.Points): if index == edgeIndex: hasAddedPoint = True newPoints.append(self.invpl.multVec(newPoint)) newPoints.append(point) if obj.Closed and edgeIndex == len(obj.Points): # last segment when object is closed newPoints.append(self.invpl.multVec(newPoint)) obj.Points = newPoints def addPointToCurve(self,point,info=None): import Part if not (Draft.getType(self.obj) in ["BSpline","BezCurve"]): return pts = self.obj.Points if Draft.getType(self.obj) == "BezCurve": if not info['Component'].startswith('Edge'): return # clicked control point edgeindex = int(info['Component'].lstrip('Edge')) -1 wire = self.obj.Shape.Wires[0] bz = wire.Edges[edgeindex].Curve param = bz.parameter(point) seg1 = wire.Edges[edgeindex].copy().Curve seg2 = wire.Edges[edgeindex].copy().Curve seg1.segment(seg1.FirstParameter,param) seg2.segment(param,seg2.LastParameter) if edgeindex == len(wire.Edges): #we hit the last segment, we need to fix the degree degree=wire.Edges[0].Curve.Degree seg1.increase(degree) seg2.increase(degree) edges = wire.Edges[0:edgeindex] + [Part.Edge(seg1),Part.Edge(seg2)] \ + wire.Edges[edgeindex + 1:] pts = edges[0].Curve.getPoles()[0:1] for edge in edges: pts.extend(edge.Curve.getPoles()[1:]) if self.obj.Closed: pts.pop() c = self.obj.Continuity # assume we have a tangent continuity for an arbitrarily split # segment, unless it's linear cont = 1 if (self.obj.Degree >= 2) else 0 self.obj.Continuity = c[0:edgeindex] + [cont] + c[edgeindex:] else: if (Draft.getType(self.obj) in ["BSpline"]): if (self.obj.Closed == True): curve = self.obj.Shape.Edges[0].Curve else: curve = self.obj.Shape.Curve uNewPoint = curve.parameter(point) uPoints = [] for p in self.obj.Points: uPoints.append(curve.parameter(p)) for i in range(len(uPoints) -1): if ( uNewPoint > uPoints[i] ) and ( uNewPoint < uPoints[i+1] ): pts.insert(i + 1, self.invpl.multVec(point)) break # DNC: fix: add points to last segment if curve is closed if ( self.obj.Closed ) and ( uNewPoint > uPoints[-1] ) : pts.append(self.invpl.multVec(point)) self.obj.Points = pts def delPoint(self,event): pos = event.getPosition() node = self.getEditNode(pos) ep = self.getEditNodeIndex(node) if ep is None: return App.Console.PrintWarning(translate("draft", "Node not found") + "\n") doc = App.getDocument(str(node.documentName.getValue())) self.obj = doc.getObject(str(node.objectName.getValue())) if self.obj is None: return if not (Draft.getType(self.obj) in ["Wire","BSpline","BezCurve"]): return if len(self.obj.Points) <= 2: App.Console.PrintWarning(translate("draft", "Active object must have more than two points/nodes") + "\n") return pts = self.obj.Points pts.pop(ep) self.obj.Points = pts if Draft.getType(self.obj) =="BezCurve": self.obj.Proxy.resetcontinuity(self.obj) self.obj.recompute() # don't do tan/sym on DWire/BSpline! self.removeTrackers(self.obj) self.setEditPoints(self.obj) #--------------------------------------------------------------------------- # EDIT OBJECT TOOLS : GENERAL #--------------------------------------------------------------------------- def setEditPoints(self,obj): "append given object's editpoints to self.edipoints and set EditTrackers" self.setPlacement(obj) self.editpoints = self.getEditPoints(obj) if self.editpoints: # set trackers and align plane self.setTrackers(obj, self.editpoints) self.editpoints = [] def getEditPoints(self, obj): """ (object) return a list of App.Vectors relative to object edit nodes """ objectType = Draft.getType(obj) if objectType in ["Wire","BSpline"]: self.ui.editUi("Wire") return self.getWirePts(obj) elif objectType == "BezCurve": self.ui.editUi("BezCurve") self.resetTrackersBezier(obj) self.editpoints = [] return elif objectType == "Circle": return self.getCirclePts(obj) elif objectType == "Rectangle": return self.getRectanglePts(obj) elif objectType == "Polygon": return self.getPolygonPts(obj) elif objectType == "Dimension": return self.getDimensionPts(obj) elif objectType == "Wall": return self.getWallPts(obj) elif objectType == "Window": return self.getWindowPts(obj) elif objectType == "Space": return self.getSpacePts(obj) elif objectType == "Structure": return self.getStructurePts(obj) elif objectType == "PanelCut": return self.getPanelCutPts(obj) elif objectType == "PanelSheet": return self.getPanelSheetPts(obj) elif objectType == "Part" and obj.TypeId == "Part::Box": return self.getPartBoxPts(obj) elif objectType == "Part::Line" and obj.TypeId == "Part::Line": return self.getPartLinePts(obj) elif objectType == "Sketch": return self.getSketchPts(obj) else: return None def update(self,obj, nodeIndex, v): "apply the App.Vector to the modified point and update self.obj" objectType = Draft.getType(obj) App.ActiveDocument.openTransaction("Edit") if objectType in ["Wire","BSpline"]: self.updateWire(obj, nodeIndex, v) elif objectType == "BezCurve": self.updateWire(obj, nodeIndex, v) elif objectType == "Circle": self.updateCircle(obj, nodeIndex, v) elif objectType == "Rectangle": self.updateRectangle(obj, nodeIndex, v) elif objectType == "Polygon": self.updatePolygon(obj, nodeIndex, v) elif objectType == "Dimension": self.updateDimension(obj, nodeIndex, v) elif objectType == "Sketch": self.updateSketch(obj, nodeIndex, v) elif objectType == "Wall": self.updateWall(obj, nodeIndex, v) elif objectType == "Window": self.updateWindow(obj, nodeIndex, v) elif objectType == "Space": self.updateSpace(obj, nodeIndex, v) elif objectType == "Structure": self.updateStructure(obj, nodeIndex, v) elif objectType == "PanelCut": self.updatePanelCut(obj, nodeIndex, v) elif objectType == "PanelSheet": self.updatePanelSheet(obj, nodeIndex, v) elif objectType == "Part::Line" and self.obj.TypeId == "Part::Line": self.updatePartLine(obj, nodeIndex, v) elif objectType == "Part" and self.obj.TypeId == "Part::Box": self.updatePartBox(obj, nodeIndex, v) obj.recompute() App.ActiveDocument.commitTransaction() try: Gui.ActiveDocument.ActiveView.redraw() except AttributeError as err: pass #--------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Line/Wire/Bspline/Bezcurve #--------------------------------------------------------------------------- def getWirePts(self,obj): editpoints = [] for p in obj.Points: p = obj.getGlobalPlacement().multVec(p) editpoints.append(p) return editpoints def updateWire(self, obj, nodeIndex, v): pts = obj.Points editPnt = obj.getGlobalPlacement().inverse().multVec(v) # DNC: allows to close the curve by placing ends close to each other tol = 0.001 if ( ( nodeIndex == 0 ) and ( (editPnt - pts[-1]).Length < tol) ) or ( nodeIndex == len(pts) - 1 ) and ( (editPnt - pts[0]).Length < tol): obj.Closed = True # DNC: fix error message if edited point coincides with one of the existing points if ( editPnt in pts ) == True: # checks if point enter is equal to other, this could cause a OCC problem App.Console.PrintMessage(translate("draft", "This object does not support possible " "coincident points, please try again.") + "\n") if Draft.getType(obj) in ["BezCurve"]: self.resetTrackers(obj) else: self.trackers[obj.Name][nodeIndex].set(obj.getGlobalPlacement(). multVec(obj.Points[nodeIndex])) return if Draft.getType(obj) in ["BezCurve"]: pts = self.recomputePointsBezier(obj,pts,nodeIndex,v,obj.Degree,moveTrackers=False) if obj.Closed: # check that the new point lies on the plane of the wire if hasattr(obj.Shape,"normalAt"): normal = obj.Shape.normalAt(0,0) point_on_plane = obj.Shape.Vertexes[0].Point print(v) v.projectToPlane(point_on_plane, normal) print(v) editPnt = obj.getGlobalPlacement().inverse().multVec(v) pts[nodeIndex] = editPnt obj.Points = pts self.trackers[obj.Name][nodeIndex].set(v) def recomputePointsBezier(self,obj,pts,idx,v,degree,moveTrackers=True): """ (object, Points as list, nodeIndex as Int, App.Vector of new point, moveTrackers as Bool) return the new point list, applying the App.Vector to the given index point """ editPnt = v # DNC: allows to close the curve by placing ends close to each other tol = 0.001 if ( ( idx == 0 ) and ( (editPnt - pts[-1]).Length < tol) ) or ( idx == len(pts) - 1 ) and ( (editPnt - pts[0]).Length < tol): obj.Closed = True # DNC: fix error message if edited point coincides with one of the existing points #if ( editPnt in pts ) == False: knot = None ispole = idx % degree if ispole == 0: #knot if degree >= 3: if idx >= 1: #move left pole knotidx = idx if idx < len(pts) else 0 pts[idx-1] = pts[idx-1] + editPnt - pts[knotidx] if moveTrackers: self.trackers[obj.Name][idx-1].set(pts[idx-1]) if idx < len(pts)-1: #move right pole pts[idx+1] = pts[idx+1] + editPnt - pts[idx] if moveTrackers: self.trackers[obj.Name][idx+1].set(pts[idx+1]) if idx == 0 and obj.Closed: # move last pole pts[-1] = pts [-1] + editPnt -pts[idx] if moveTrackers: self.trackers[obj.Name][-1].set(pts[-1]) elif ispole == 1 and (idx >=2 or obj.Closed): #right pole knot = idx -1 changep = idx -2 # -1 in case of closed curve elif ispole == degree-1 and idx <= len(pts)-3: #left pole knot = idx +1 changep = idx +2 elif ispole == degree-1 and obj.Closed and idx == len(pts)-1: #last pole knot = 0 changep = 1 if knot is not None: # we need to modify the opposite pole segment = int(knot / degree) -1 cont = obj.Continuity[segment] if len(obj.Continuity) > segment else 0 if cont == 1: #tangent pts[changep] = obj.Proxy.modifytangentpole(pts[knot], editPnt,pts[changep]) if moveTrackers: self.trackers[obj.Name][changep].set(pts[changep]) elif cont == 2: #symmetric pts[changep] = obj.Proxy.modifysymmetricpole(pts[knot],editPnt) if moveTrackers: self.trackers[obj.Name][changep].set(pts[changep]) pts[idx]=v return pts #returns the list of new points, taking into account knot continuity def resetTrackersBezier(self, obj): #in future move tracker definition to DraftTrackers from pivy import coin knotmarkers = (coin.SoMarkerSet.DIAMOND_FILLED_9_9,#sharp coin.SoMarkerSet.SQUARE_FILLED_9_9, #tangent coin.SoMarkerSet.HOURGLASS_FILLED_9_9) #symmetric polemarker = coin.SoMarkerSet.CIRCLE_FILLED_9_9 #pole self.trackers[obj.Name] = [] cont = obj.Continuity firstknotcont = cont[-1] if (obj.Closed and cont) else 0 pointswithmarkers = [(obj.Shape.Edges[0].Curve. getPole(1),knotmarkers[firstknotcont])] for edgeindex, edge in enumerate(obj.Shape.Edges): poles = edge.Curve.getPoles() pointswithmarkers.extend([(point,polemarker) for point in poles[1:-1]]) if not obj.Closed or len(obj.Shape.Edges) > edgeindex +1: knotmarkeri = cont[edgeindex] if len(cont) > edgeindex else 0 pointswithmarkers.append((poles[-1],knotmarkers[knotmarkeri])) for index, pwm in enumerate(pointswithmarkers): p,marker = pwm #if self.pl: p = self.pl.multVec(p) self.trackers[obj.Name].append(editTracker(p,obj.Name, index,obj.ViewObject.LineColor,marker=marker)) def smoothBezPoint(self, obj, point, style='Symmetric'): "called when changing the continuity of a knot" style2cont = {'Sharp':0,'Tangent':1,'Symmetric':2} if point is None: return if not (Draft.getType(obj) == "BezCurve"): return pts = obj.Points deg = obj.Degree if deg < 2: return if point % deg != 0: #point is a pole if deg >=3: #allow to select poles if (point % deg == 1) and (point > 2 or obj.Closed): #right pole knot = point -1 keepp = point changep = point -2 elif point < len(pts) -3 and point % deg == deg -1: #left pole knot = point +1 keepp = point changep = point +2 elif point == len(pts)-1 and obj.Closed: #last pole # if the curve is closed the last pole has the last # index in the points lists knot = 0 keepp = point changep = 1 else: App.Console.PrintWarning(translate("draft", "Can't change Knot belonging to pole %d"%point) + "\n") return if knot: if style == 'Tangent': pts[changep] = obj.Proxy.modifytangentpole(pts[knot], pts[keepp],pts[changep]) elif style == 'Symmetric': pts[changep] = obj.Proxy.modifysymmetricpole(pts[knot], pts[keepp]) else: #sharp pass # else: App.Console.PrintWarning(translate("draft", "Selection is not a Knot") + "\n") return else: #point is a knot if style == 'Sharp': if obj.Closed and point == len(pts)-1: knot = 0 else: knot = point elif style == 'Tangent' and point > 0 and point < len(pts)-1: prev, next = obj.Proxy.tangentpoles(pts[point], pts[point-1], pts[point+1]) pts[point-1] = prev pts[point+1] = next knot = point #index for continuity elif style == 'Symmetric' and point > 0 and point < len(pts)-1: prev, next = obj.Proxy.symmetricpoles(pts[point], pts[point-1], pts[point+1]) pts[point-1] = prev pts[point+1] = next knot = point #index for continuity elif obj.Closed and (style == 'Symmetric' or style == 'Tangent'): if style == 'Tangent': pts[1], pts[-1] = obj.Proxy.tangentpoles(pts[0], pts[1], pts[-1]) elif style == 'Symmetric': pts[1], pts[-1] = obj.Proxy.symmetricpoles(pts[0], pts[1], pts[-1]) knot = 0 else: App.Console.PrintWarning(translate("draft", "Endpoint of BezCurve can't be smoothed") + "\n") return segment = knot // deg #segment index newcont = obj.Continuity[:] #don't edit a property inplace !!! if not obj.Closed and (len(obj.Continuity) == segment -1 or segment == 0) : pass # open curve elif (len(obj.Continuity) >= segment or obj.Closed and segment == 0 and len(obj.Continuity) >1): newcont[segment-1] = style2cont.get(style) else: #should not happen App.Console.PrintWarning('Continuity indexing error:' + 'point:%d deg:%d len(cont):%d' % (knot,deg, len(obj.Continuity))) obj.Points = pts obj.Continuity = newcont self.resetTrackers(obj) #--------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Rectangle #--------------------------------------------------------------------------- def getRectanglePts(self, obj): """ returns the list of edipoints for the given Draft Rectangle 0 : Placement.Base 1 : Length 2 : Height """ editpoints = [] editpoints.append(obj.getGlobalPlacement().Base) editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(obj.Length,0,0))) editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(0,obj.Height,0))) return editpoints def updateRectangleTrackers(self, obj): self.trackers[obj.Name][0].set(obj.getGlobalPlacement().Base) self.trackers[obj.Name][1].set(obj.getGlobalPlacement().multVec(App.Vector(obj.Length,0,0))) self.trackers[obj.Name][2].set(obj.getGlobalPlacement().multVec(App.Vector(0,obj.Height,0))) def updateRectangle(self, obj, nodeIndex, v): import DraftVecUtils delta = obj.getGlobalPlacement().inverse().multVec(v) if nodeIndex == 0: #p = obj.getGlobalPlacement() #p.move(delta) obj.Placement.move(delta) elif self.editing == 1: obj.Length = DraftVecUtils.project(delta,App.Vector(1,0,0)).Length elif self.editing == 2: obj.Height = DraftVecUtils.project(delta,App.Vector(0,1,0)).Length self.updateRectangleTrackers(obj) #--------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Ellipse (# TODO: yet to be implemented) #--------------------------------------------------------------------------- def setEllipsePts(self): return def updateEllipse(self,v): return #--------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Circle/Arc #--------------------------------------------------------------------------- def getCirclePts(self, obj): """ returns the list of edipoints for the given Draft Arc or Circle circle: 0 : Placement.Base or center 1 : radius arc: 0 : Placement.Base or center 1 : first endpoint 2 : second endpoint 3 : midpoint """ editpoints = [] editpoints.append(obj.getGlobalPlacement().Base) if obj.FirstAngle == obj.LastAngle: # obj is a circle self.ui.editUi("Circle") editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(obj.Radius,0,0))) else: # obj is an arc self.ui.editUi("Arc") editpoints.append(self.getArcStart(obj, global_placement=True))#First endpoint editpoints.append(self.getArcEnd(obj, global_placement=True))#Second endpoint editpoints.append(self.getArcMid(obj, global_placement=True))#Midpoint return editpoints def updateCircleTrackers(self, obj): self.trackers[obj.Name][0].set(obj.getGlobalPlacement().Base) self.trackers[obj.Name][1].set(self.getArcStart(obj, global_placement=True)) if len(self.trackers[obj.Name]) > 2: # object is an arc self.trackers[obj.Name][2].set(self.getArcEnd(obj, global_placement=True)) self.trackers[obj.Name][3].set(self.getArcMid(obj, global_placement=True)) def updateCircle(self, obj, nodeIndex, v): delta = obj.getGlobalPlacement().inverse().multVec(v) local_v = obj.Placement.multVec(delta) if obj.FirstAngle == obj.LastAngle: # object is a circle if nodeIndex == 0: obj.Placement.Base = local_v elif nodeIndex == 1: obj.Radius = delta.Length else: # obj is an arc if self.alt_edit_mode == 0: # edit arc by 3 points import Part if nodeIndex == 0: #center point import DraftVecUtils p1 = self.getArcStart(obj) p2 = self.getArcEnd(obj) p0 = DraftVecUtils.project(delta,self.getArcMid(obj)) obj.Radius = p1.sub(p0).Length obj.FirstAngle = -math.degrees(DraftVecUtils.angle(p1.sub(p0))) obj.LastAngle = -math.degrees(DraftVecUtils.angle(p2.sub(p0))) obj.Placement.Base = obj.Placement.multVec(p0) self.setPlacement(obj) else: if nodeIndex == 1:#first point p1=v p2=self.getArcMid(obj,global_placement=True) p3=self.getArcEnd(obj,global_placement=True) elif nodeIndex == 3:#midpoint p1=self.getArcStart(obj,global_placement=True) p2=v p3=self.getArcEnd(obj,global_placement=True) elif nodeIndex == 2:#second point p1=self.getArcStart(obj,global_placement=True) p2=self.getArcMid(obj,global_placement=True) p3=v arc=Part.ArcOfCircle(p1,p2,p3) obj.Placement.Base = obj.Placement.multVec(obj.getGlobalPlacement().inverse().multVec(arc.Location)) self.setPlacement(obj) obj.Radius = arc.Radius delta = self.invpl.multVec(p1) obj.FirstAngle = math.degrees(math.atan2(delta[1],delta[0])) delta = self.invpl.multVec(p3) obj.LastAngle = math.degrees(math.atan2(delta[1],delta[0])) elif self.alt_edit_mode == 1: # edit arc by center radius FirstAngle LastAngle if nodeIndex == 0: obj.Placement.Base = local_v self.setPlacement(obj) else: dangle = math.degrees(math.atan2(delta[1],delta[0])) if nodeIndex == 1: obj.FirstAngle = dangle elif nodeIndex == 2: obj.LastAngle = dangle elif nodeIndex == 3: obj.Radius = delta.Length obj.recompute() self.updateCircleTrackers(obj) def getArcStart(self, obj, global_placement=False):#Returns object midpoint if Draft.getType(obj) == "Circle": return self.pointOnCircle(obj, obj.FirstAngle, global_placement) def getArcEnd(self, obj, global_placement=False):#Returns object midpoint if Draft.getType(obj) == "Circle": return self.pointOnCircle(obj, obj.LastAngle, global_placement) def getArcMid(self, obj, global_placement=False):#Returns object midpoint if Draft.getType(obj) == "Circle": if obj.LastAngle > obj.FirstAngle: midAngle = obj.FirstAngle + (obj.LastAngle - obj.FirstAngle) / 2.0 else: midAngle = obj.FirstAngle + (obj.LastAngle - obj.FirstAngle) / 2.0 midAngle += App.Units.Quantity(180,App.Units.Angle) return self.pointOnCircle(obj, midAngle, global_placement) def pointOnCircle(self, obj, angle, global_placement=False): if Draft.getType(obj) == "Circle": px = obj.Radius * math.cos(math.radians(angle)) py = obj.Radius * math.sin(math.radians(angle)) p = App.Vector(px, py, 0.0) if global_placement == True: p = obj.getGlobalPlacement().multVec(p) return p return None def arcInvert(self, obj): obj.FirstAngle, obj.LastAngle = obj.LastAngle, obj.FirstAngle obj.recompute() self.updateCircleTrackers(obj) #--------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Polygon (maybe could also rotate the polygon) #--------------------------------------------------------------------------- def getPolygonPts(self, obj): editpoints = [] editpoints.append(obj.Placement.Base) editpoints.append(obj.Shape.Vertexes[0].Point) return editpoints def updatePolygon(self, obj, nodeIndex, v): delta = v.sub(self.obj.Placement.Base) if self.editing == 0: p = self.obj.Placement p.move(delta) self.obj.Placement = p self.trackers[self.obj.Name][0].set(self.obj.Placement.Base) elif self.editing == 1: if self.obj.DrawMode == 'inscribed': self.obj.Radius = delta.Length else: halfangle = ((math.pi*2)/self.obj.FacesNumber)/2 rad = math.cos(halfangle)*delta.Length self.obj.Radius = rad self.obj.recompute() self.trackers[self.obj.Name][1].set(self.obj.Shape.Vertexes[0].Point) #--------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Dimension (point on dimension line is not clickable) #--------------------------------------------------------------------------- def getDimensionPts(self, obj): editpoints = [] p = obj.ViewObject.Proxy.textpos.translation.getValue() editpoints.append(obj.Start) editpoints.append(obj.End) editpoints.append(obj.Dimline) editpoints.append(App.Vector(p[0],p[1],p[2])) return editpoints def updateDimension(self, obj, nodeIndex, v): if self.editing == 0: self.obj.Start = v elif self.editing == 1: self.obj.End = v elif self.editing == 2: self.obj.Dimline = v elif self.editing == 3: self.obj.ViewObject.TextPosition = v #--------------------------------------------------------------------------- # EDIT OBJECT TOOLS : ARCH Wall, Windows, Structure, Panel, etc. #--------------------------------------------------------------------------- # SKETCH: just if it's composed by a single segment------------------------- def getSketchPts(self, obj): """ returns the list of edipoints for the given single line sketch (WallTrace) 0 : startpoint 1 : endpoint """ editpoints = [] if obj.GeometryCount == 1: editpoints.append(obj.getGlobalPlacement().multVec(obj.getPoint(0,1))) editpoints.append(obj.getGlobalPlacement().multVec(obj.getPoint(0,2))) return editpoints else: App.Console.PrintWarning(translate("draft", "Sketch is too complex to edit: " "it is suggested to use sketcher default editor") + "\n") return None def updateSketch(self, obj, nodeIndex, v): """ (single segment sketch object, node index as Int, App.Vector) move a single line sketch (WallTrace) vertex according to a given App.Vector 0 : startpoint 1 : endpoint """ if nodeIndex == 0: obj.movePoint(0,1,obj.getGlobalPlacement().inverse().multVec(v)) elif nodeIndex == 1: obj.movePoint(0,2,obj.getGlobalPlacement().inverse().multVec(v)) obj.recompute() #WALL----------------------------------------------------------------------- def getWallPts(self, obj): """ returns the list of edipoints for the given Arch Wall object 0 : height of the wall 1-to end : base object editpoints, in place with the wall """ editpoints = [] #height of the wall editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(0,0,obj.Height))) # try to add here an editpoint based on wall height (maybe should be good to associate it with a circular tracker) if obj.Base: # base points are added to self.trackers under wall-name key basepoints = [] if Draft.getType(obj.Base) in ["Wire","Circle","Rectangle", "Polygon", "Sketch"]: basepoints = self.getEditPoints(obj.Base) for point in basepoints: editpoints.append(obj.Placement.multVec(point)) #works ok except if App::Part is rotated... why? return editpoints def updateWallTrackers(self, obj): """ update self.trackers[obj.Name][0] to match with given object """ pass def updateWall(self, obj, nodeIndex, v): import DraftVecUtils if nodeIndex == 0: delta= obj.getGlobalPlacement().inverse().multVec(v) vz=DraftVecUtils.project(delta,App.Vector(0,0,1)) if vz.Length > 0: obj.Height = vz.Length elif nodeIndex > 0: if obj.Base: if Draft.getType(obj.Base) in ["Wire","Circle","Rectangle", "Polygon", "Sketch"]: self.update(obj.Base, nodeIndex - 1, obj.Placement.inverse().multVec(v)) obj.recompute() #WINDOW--------------------------------------------------------------------- def getWindowPts(self, obj): import DraftGeomUtils editpoints = [] pos = obj.Base.Placement.Base h = float(obj.Height) + pos.z normal = obj.Normal angle = normal.getAngle(App.Vector(1, 0, 0)) editpoints.append(pos) editpoints.append(App.Vector(pos.x + float(obj.Width) * math.cos(angle-math.pi / 2.0), pos.y + float(obj.Width) * math.sin(angle-math.pi / 2.0), pos.z)) editpoints.append(App.Vector(pos.x, pos.y, h)) return editpoints def updateWindow(self, obj, nodeIndex, v): pos=self.obj.Base.Placement.Base if self.editing == 0: self.obj.Base.Placement.Base=v self.obj.Base.recompute() if self.editing == 1: self.obj.Width = pos.sub(v).Length self.obj.Base.recompute() if self.editing == 2: self.obj.Height = pos.sub(v).Length self.obj.Base.recompute() for obj in self.obj.Hosts: obj.recompute() self.obj.recompute() #STRUCTURE------------------------------------------------------------------- def getStructurePts(self, obj): if obj.Nodes: editpoints = [] self.originalDisplayMode = obj.ViewObject.DisplayMode self.originalPoints = obj.ViewObject.NodeSize self.originalNodes = obj.ViewObject.ShowNodes self.obj.ViewObject.DisplayMode = "Wireframe" self.obj.ViewObject.NodeSize = 1 # self.obj.ViewObject.ShowNodes = True for p in obj.Nodes: if self.pl: p = self.pl.multVec(p) editpoints.append(p) return editpoints else: return None def updateStructure(self, obj, nodeIndex, v): nodes = self.obj.Nodes nodes[self.editing] = self.invpl.multVec(v) self.obj.Nodes = nodes #SPACE---------------------------------------------------------------------- def getSpacePts(self, obj): try: editpoints = [] self.editpoints.append(obj.ViewObject.Proxy.getTextPosition(obj.ViewObject)) return editpoints except: pass def updateSpace(self, obj, nodeIndex, v): if self.editing == 0: self.obj.ViewObject.TextPosition = v #PANELS--------------------------------------------------------------------- def getPanelCutPts(self, obj): editpoints = [] if self.obj.TagPosition.Length == 0: pos = obj.Shape.BoundBox.Center else: pos = self.pl.multVec(obj.TagPosition) editpoints.append(pos) return editpoints def updatePanelCut(self, obj, nodeIndex, v): if self.editing == 0: self.obj.TagPosition = self.invpl.multVec(v) def getPanelSheetPts(self, obj): editpoints = [] editpoints.append(self.pl.multVec(obj.TagPosition)) for o in obj.Group: editpoints.append(self.pl.multVec(o.Placement.Base)) return editpoints def updatePanelSheet(self, obj, nodeIndex, v): if self.editing == 0: self.obj.TagPosition = self.invpl.multVec(v) else: self.obj.Group[self.editing-1].Placement.Base = self.invpl.multVec(v) # PART::LINE---------------------------------------------------------------- def getPartLinePts(self, obj): editpoints = [] editpoints.append(self.pl.multVec(App.Vector(obj.X1,obj.Y1,obj.Z1))) editpoints.append(self.pl.multVec(App.Vector(obj.X2,obj.Y2,obj.Z2))) return editpoints def updatePartLine(self, obj, nodeIndex, v): pt=self.invpl.multVec(v) if self.editing == 0: self.obj.X1 = pt.x self.obj.Y1 = pt.y self.obj.Z1 = pt.z elif self.editing == 1: self.obj.X2 = pt.x self.obj.Y2 = pt.y self.obj.Z2 = pt.z # PART::BOX----------------------------------------------------------------- def getPartBoxPts(self, obj): editpoints = [] editpoints.append(obj.Placement.Base) editpoints.append(self.pl.multVec(App.Vector(obj.Length,0,0))) editpoints.append(self.pl.multVec(App.Vector(0,obj.Width,0))) editpoints.append(self.pl.multVec(App.Vector(0,0,obj.Height))) return editpoints def updatePartBox(self, obj, nodeIndex, v): import DraftVecUtils delta = self.invpl.multVec(v) if self.editing == 0: self.obj.Placement.Base = v self.setPlacement(self.obj) elif self.editing == 1: xApp.Vector = DraftVecUtils.project(delta,App.Vector(1,0,0)) self.obj.Length = xApp.Vector.Length elif self.editing == 2: xApp.Vector = DraftVecUtils.project(delta,App.Vector(0,1,0)) self.obj.Width = xApp.Vector.Length elif self.editing == 3: xApp.Vector = DraftVecUtils.project(delta,App.Vector(0,0,1)) self.obj.Height = xApp.Vector.Length self.trackers[self.obj.Name][0].set(self.obj.Placement.Base) self.trackers[self.obj.Name][1].set(self.pl.multVec(App.Vector(self.obj.Length,0,0))) self.trackers[self.obj.Name][2].set(self.pl.multVec(App.Vector(0,self.obj.Width,0))) self.trackers[self.obj.Name][3].set(self.pl.multVec(App.Vector(0,0,self.obj.Height))) #--------------------------------------------------------------------------- # Context menu #--------------------------------------------------------------------------- def display_tracker_menu(self, event): self.tracker_menu = QtGui.QMenu() self.event = event actions = None 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() if Draft.getType(obj) in ["Line", "Wire"]: actions = ["delete point"] elif Draft.getType(obj) in ["Circle"]: if obj.FirstAngle != obj.LastAngle: if ep == 0: # user is over arc start point actions = ["move arc"] elif ep == 1: # user is over arc start point actions = ["set first angle"] elif ep == 2: # user is over arc end point actions = ["set last angle"] elif ep == 3: # user is over arc mid point actions = ["set radius"] elif Draft.getType(obj) in ["BezCurve"]: actions = ["make sharp", "make tangent", "make symmetric", "delete point"] else: return else: # if user is over an edited object pos = self.event.getPosition() obj = self.get_selected_obj_at_position(pos) if Draft.getType(obj) in ["Line", "Wire","BSpline", "BezCurve"]: actions = ["add point"] elif Draft.getType(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(Gui.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()) # Bezier curve menu if action_label in ["make sharp", "make tangent", "make symmetric"]: doc = self.overNode.get_doc_name() obj = App.getDocument(doc).getObject(self.overNode.get_obj_name()) idx = self.overNode.get_subelement_index() if action_label == "make sharp": self.smoothBezPoint(obj, idx, 'Sharp') elif action_label == "make tangent": self.smoothBezPoint(obj, idx, 'Tangent') elif action_label == "make symmetric": self.smoothBezPoint(obj, idx, 'Symmetric') # addPoint and deletePoint menu elif action_label == "delete point": self.delPoint(self.event) elif action_label == "add point": self.addPoint(self.event) # arc tools elif action_label in ["move arc","set radius", "set first angle", "set last angle"]: self.alt_edit_mode = 1 self.startEditing(self.event) elif action_label == "invert arc": pos = self.event.getPosition() obj = self.get_selected_obj_at_position(pos) self.arcInvert(obj) del self.event if App.GuiUp: # setup command Gui.addCommand('Draft_Edit', Edit())