Download src/Mod/Draft/draftguitools/gui_edit.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 36.6 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftguitools/gui_edit.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Draft/draftguitools/gui_edit.py
-
curl -L -o gui_edit.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftguitools/gui_edit.py
36.6 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> * | |
| # * Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> * | |
| # * Copyright (c) 2019, 2020 Carlo Pavan <carlopav@gmail.com> * | |
| # * * | |
| # * 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 * | |
| # * * | |
| # *************************************************************************** | |
| """Provides GUI tools to start the edit mode of different objects.""" | |
| ## @package gui_edit | |
| # \ingroup draftguitools | |
| # \brief Provides GUI tools to start the edit mode of different objects. | |
| __title__ = "FreeCAD Draft Edit Tool" | |
| __author__ = ( | |
| "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " "Dmitry Chigrin, Carlo Pavan" | |
| ) | |
| __url__ = "https://www.freecad.org" | |
| ## \addtogroup draftguitools | |
| # @{ | |
| import math | |
| import pivy.coin as coin | |
| import PySide.QtCore as QtCore | |
| import PySide.QtWidgets as QtWidgets | |
| import FreeCAD as App | |
| import FreeCADGui as Gui | |
| import DraftVecUtils | |
| from draftutils import gui_utils | |
| from draftutils import params | |
| from draftutils import utils | |
| from draftutils.translate import translate | |
| from draftguitools import gui_base_original | |
| from draftguitools import gui_edit_arch_objects as edit_arch | |
| from draftguitools import gui_edit_draft_objects as edit_draft | |
| from draftguitools import gui_edit_part_objects as edit_part | |
| from draftguitools import gui_edit_sketcher_objects as edit_sketcher | |
| from draftguitools import gui_tool_utils | |
| from draftguitools import gui_trackers as trackers | |
| COLORS = { | |
| "default": utils.get_rgba_tuple(params.get_param("snapcolor"))[:3], | |
| "black": (0.0, 0.0, 0.0), | |
| "white": (1.0, 1.0, 1.0), | |
| "grey": (0.5, 0.5, 0.5), | |
| "red": (1.0, 0.0, 0.0), | |
| "green": (0.0, 1.0, 0.0), | |
| "blue": (0.0, 0.0, 1.0), | |
| "yellow": (1.0, 1.0, 0.0), | |
| "cyan": (0.0, 1.0, 1.0), | |
| "magenta": (1.0, 0.0, 1.0), | |
| } | |
| class Edit(gui_base_original.Modifier): | |
| """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 | |
| Draft_Edit uses the taskpanel in 2 ways: | |
| 1 - the user can select select an object and close the operation | |
| self.ui.editUi() | |
| 2 - when editing, lineUi support clicking destination point | |
| by self.startEditing | |
| self.ui.lineUi() | |
| 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 | |
| ----------- | |
| max_objects: 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 is 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 | |
| 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 utils.get_type(obj) to compare object type | |
| to the list. | |
| supportedCppObjs: List | |
| List of supported Part Objects. | |
| The tool use utils.get_type(obj) and obj.TypeId to compare | |
| object type to the list. | |
| """ | |
| def __init__(self): | |
| super().__init__() | |
| """Initialize Draft_Edit Command.""" | |
| self.running = False | |
| self.trackers = {"object": []} | |
| self.overNode = None # preselected node with mouseover | |
| self.edited_objects = [] | |
| self.obj = None | |
| self.editing = None | |
| # event callbacks | |
| self.selection_callback = None | |
| self._keyPressedCB = None | |
| self._mouseMovedCB = None | |
| self._mousePressedCB = None | |
| # this stores the DisplayMode of the object to restore it after editing | |
| # only used by Arch Structure | |
| self.objs_formats = {} | |
| # settings (get updated in Activated) | |
| self.max_objects = 5 | |
| self.pick_radius = 20 | |
| self.alt_edit_mode = 0 # default edit mode for objects | |
| # preview | |
| self.ghost = None | |
| # setup gui_tools for every supported object | |
| self.gui_tools_repository = GuiToolsRepository() | |
| self.gui_tools_repository.add("Wire", edit_draft.DraftWireGuiTools()) | |
| self.gui_tools_repository.add("BSpline", edit_draft.DraftBSplineGuiTools()) | |
| self.gui_tools_repository.add("BezCurve", edit_draft.DraftBezCurveGuiTools()) | |
| self.gui_tools_repository.add("Circle", edit_draft.DraftCircleGuiTools()) | |
| self.gui_tools_repository.add("Rectangle", edit_draft.DraftRectangleGuiTools()) | |
| self.gui_tools_repository.add("Polygon", edit_draft.DraftPolygonGuiTools()) | |
| self.gui_tools_repository.add("Ellipse", edit_draft.DraftEllipseGuiTools()) | |
| self.gui_tools_repository.add( | |
| "Dimension", edit_draft.DraftDimensionGuiTools() | |
| ) # Backward compatibility | |
| self.gui_tools_repository.add("LinearDimension", edit_draft.DraftDimensionGuiTools()) | |
| self.gui_tools_repository.add("Label", edit_draft.DraftLabelGuiTools()) | |
| self.gui_tools_repository.add("Wall", edit_arch.ArchWallGuiTools()) | |
| self.gui_tools_repository.add("Window", edit_arch.ArchWindowGuiTools()) | |
| self.gui_tools_repository.add("Structure", edit_arch.ArchStructureGuiTools()) | |
| self.gui_tools_repository.add("Space", edit_arch.ArchSpaceGuiTools()) | |
| self.gui_tools_repository.add("PanelCut", edit_arch.ArchPanelCutGuiTools()) | |
| self.gui_tools_repository.add("PanelSheet", edit_arch.ArchPanelSheetGuiTools()) | |
| self.gui_tools_repository.add("Part::Line", edit_part.PartLineGuiTools()) | |
| self.gui_tools_repository.add("Part::Box", edit_part.PartBoxGuiTools()) | |
| self.gui_tools_repository.add("Part::Cylinder", edit_part.PartCylinderGuiTools()) | |
| self.gui_tools_repository.add("Part::Cone", edit_part.PartConeGuiTools()) | |
| self.gui_tools_repository.add("Part::Sphere", edit_part.PartSphereGuiTools()) | |
| self.gui_tools_repository.add( | |
| "Sketcher::SketchObject", edit_sketcher.SketcherSketchObjectGuiTools() | |
| ) | |
| 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() | |
| super(Edit, self).Activated("Edit") | |
| if not App.ActiveDocument: | |
| self.finish() | |
| self.ui = Gui.draftToolBar | |
| self.view = gui_utils.get_3d_view() | |
| self.max_objects = params.get_param("DraftEditMaxObjects") | |
| self.pick_radius = params.get_param("DraftEditPickRadius") | |
| if Gui.Selection.getSelection(): | |
| self.proceed() | |
| else: | |
| self.ui.selectUi(on_close_call=self.finish) | |
| App.Console.PrintMessage(translate("draft", "Select a Draft object to edit") + "\n") | |
| self.register_selection_callback() | |
| def proceed(self): | |
| """this method set the editTrackers""" | |
| self.unregister_selection_callback() | |
| self.edited_objects = self.getObjsFromSelection() | |
| if not self.edited_objects: | |
| return self.finish() | |
| self.format_objects_for_editing(self.edited_objects) | |
| # start object editing | |
| Gui.Selection.clearSelection() | |
| Gui.Snapper.setSelectMode(True) | |
| self.ui.editUi() | |
| for obj in self.edited_objects: | |
| self.setTrackers(obj, self.getEditPoints(obj)) | |
| self.register_editing_callbacks() | |
| def numericInput(self, numx, numy, numz): | |
| """Execute callback by the toolbar to activate the update function. | |
| This function gets called by the toolbar | |
| or by the mouse click and activate the update function. | |
| """ | |
| self.endEditing(self.obj, self.editing, App.Vector(numx, numy, numz)) | |
| App.ActiveDocument.recompute() | |
| def finish(self, cont=False): | |
| """Terminate Edit Tool.""" | |
| self.unregister_selection_callback() | |
| self.unregister_editing_callbacks() | |
| self.editing = None | |
| self.finalizeGhost() | |
| Gui.Snapper.setSelectMode(False) | |
| if self.ui: | |
| self.removeTrackers() | |
| if self.edited_objects: | |
| self.deformat_objects_after_editing(self.edited_objects) | |
| super(Edit, self).finish() | |
| self.running = False | |
| # delay resetting edit mode otherwise it doesn't happen | |
| from PySide import QtCore | |
| QtCore.QTimer.singleShot(0, self.reset_edit) | |
| def reset_edit(self): | |
| if Gui.ActiveDocument is not None: | |
| 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", gui_tool_utils.selectObject) | |
| def unregister_selection_callback(self): | |
| """ | |
| remove selection callback if it exists | |
| """ | |
| try: | |
| if self.selection_callback: | |
| self.view.removeEventCallback("SoEvent", self.selection_callback) | |
| except RuntimeError: | |
| # the view has been deleted already | |
| pass | |
| self.selection_callback = None | |
| def register_editing_callbacks(self): | |
| """ | |
| register editing callbacks (former action function) | |
| """ | |
| self.render_manager = self.view.getViewer().getSoRenderManager() | |
| if self._keyPressedCB is None: | |
| self._keyPressedCB = self.view.addEventCallbackPivy( | |
| coin.SoKeyboardEvent.getClassTypeId(), self.keyPressed | |
| ) | |
| if self._mouseMovedCB is None: | |
| self._mouseMovedCB = self.view.addEventCallbackPivy( | |
| coin.SoLocation2Event.getClassTypeId(), self.mouseMoved | |
| ) | |
| if self._mousePressedCB is None: | |
| self._mousePressedCB = self.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 | |
| """ | |
| try: | |
| if self._keyPressedCB: | |
| self.view.removeEventCallbackSWIG( | |
| coin.SoKeyboardEvent.getClassTypeId(), self._keyPressedCB | |
| ) | |
| # App.Console.PrintMessage("Draft edit keyboard callback unregistered \n") | |
| if self._mouseMovedCB: | |
| self.view.removeEventCallbackSWIG( | |
| coin.SoLocation2Event.getClassTypeId(), self._mouseMovedCB | |
| ) | |
| # App.Console.PrintMessage("Draft edit location callback unregistered \n") | |
| if self._mousePressedCB: | |
| self.view.removeEventCallbackSWIG( | |
| coin.SoMouseButtonEvent.getClassTypeId(), self._mousePressedCB | |
| ) | |
| # App.Console.PrintMessage("Draft edit mouse button callback unregistered \n") | |
| except RuntimeError: | |
| # the view has been deleted already | |
| pass | |
| self._keyPressedCB = None | |
| self._mouseMovedCB = None | |
| self._mousePressedCB = None | |
| # ------------------------------------------------------------------------- | |
| # SCENE EVENT HANDLERS | |
| # ------------------------------------------------------------------------- | |
| def keyPressed(self, event_callback): | |
| """Execute as callback for keyboard event.""" | |
| # TODO: Get the keys from preferences | |
| event = event_callback.getEvent() | |
| if event.getState() in (coin.SoKeyboardEvent.DOWN, coin.SoKeyboardEvent.UP): | |
| key = event.getKey() | |
| # App.Console.PrintMessage("pressed key : "+str(key)+"\n") | |
| if key == 65307: # ESC | |
| self.finish() | |
| if key == 101: # "e" | |
| self.display_tracker_menu(event) | |
| if ( | |
| key == 65535 and Gui.Selection.getSelection() is None | |
| ): # BUG: delete key activate Std::Delete command at the same time! | |
| print("DELETE PRESSED\n") | |
| self.delPoint(event) | |
| 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.editing is None: | |
| pos = event.getPosition() | |
| node = self.getEditNode(pos) | |
| node_idx = self.getEditNodeIndex(node) | |
| if node_idx is None: | |
| return | |
| doc = App.getDocument(str(node.documentName.getValue())) | |
| obj = doc.getObject(str(node.objectName.getValue())) | |
| self.startEditing(obj, node_idx) | |
| 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): | |
| """Execute as callback for mouse movement. | |
| Update tracker position and update preview ghost. | |
| """ | |
| event = event_callback.getEvent() | |
| 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] | |
| else: | |
| if self.overNode is not None: | |
| self.overNode.setColor(COLORS["default"]) | |
| self.overNode = None | |
| def startEditing(self, obj, node_idx): | |
| """Start editing selected EditNode.""" | |
| self.obj = obj # this is still needed to handle preview | |
| if obj is None: | |
| return | |
| App.Console.PrintMessage(obj.Name + ": editing node number " + str(node_idx) + "\n") | |
| self.ui.lineUi(title=translate("draft", "Edit Node"), icon="Draft_Edit") | |
| self.ui.continueCmd.hide() | |
| self.editing = node_idx | |
| self.trackers[obj.Name][node_idx].off() | |
| self.finalizeGhost() | |
| self.initGhost(obj) | |
| self.node.append(self.trackers[obj.Name][node_idx].get()) | |
| Gui.Snapper.setSelectMode(False) | |
| self.hideTrackers() | |
| def updateTrackerAndGhost(self, event): | |
| """Update 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], mask=Gui.Snapper.affinity) | |
| if self.ghost: | |
| self.updateGhost(obj=self.obj, node_idx=self.editing, v=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.node = [] | |
| self.editing = None | |
| self.showTrackers() | |
| gui_tool_utils.redraw_3d_view() | |
| # ------------------------------------------------------------------------- | |
| # EDIT TRACKERS functions | |
| # ------------------------------------------------------------------------- | |
| def setTrackers(self, obj, points=None): | |
| """Set Edit Trackers for editpoints collected from self.obj.""" | |
| if utils.get_type(obj) == "BezCurve": | |
| return self.resetTrackersBezier(obj) | |
| if points is None or len(points) == 0: | |
| _wrn = translate("draft", "No edit point found for selected object") | |
| App.Console.PrintWarning(_wrn + "\n") | |
| # do not finish if some trackers are still present | |
| if self.trackers == {"object": []}: | |
| self.finish() | |
| return | |
| self.trackers[obj.Name] = [] | |
| if obj.Name in self.trackers: | |
| self.removeTrackers(obj) | |
| for ep in range(len(points)): | |
| self.trackers[obj.Name].append( | |
| trackers.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 resetTrackersBezier(self, obj): | |
| # in future move tracker definition to DraftTrackers | |
| size = params.get_param_view("MarkerSize") | |
| knotmarkers = ( | |
| Gui.getMarkerIndex("DIAMOND_FILLED", size), # sharp | |
| Gui.getMarkerIndex("SQUARE_FILLED", size), # tangent | |
| Gui.getMarkerIndex("HOURGLASS_FILLED", size), # symmetric | |
| ) | |
| polemarker = Gui.getMarkerIndex("CIRCLE_FILLED", size) # 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 | |
| p = obj.Placement.inverse().multVec(p) | |
| p = obj.getGlobalPlacement().multVec(p) | |
| self.trackers[obj.Name].append( | |
| trackers.editTracker(p, obj.Name, index, obj.ViewObject.LineColor, marker=marker) | |
| ) | |
| def removeTrackers(self, obj=None): | |
| """Remove Edit Trackers. | |
| Attributes | |
| ---------- | |
| obj: FreeCAD object | |
| Removes trackers only for given object, | |
| if obj is None, removes all trackers | |
| """ | |
| if obj is None: | |
| for key in self.trackers: | |
| for t in self.trackers[key]: | |
| t.finalize() | |
| self.trackers = {"object": []} | |
| else: | |
| key = obj.Name | |
| if key in self.trackers: | |
| for t in self.trackers[key]: | |
| t.finalize() | |
| self.trackers[key] = [] | |
| 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): | |
| self.current_editing_object_gui_tools = self.get_obj_gui_tools(obj) | |
| if self.current_editing_object_gui_tools: | |
| self.ghost = self.current_editing_object_gui_tools.init_preview_object(obj) | |
| def updateGhost(self, obj, node_idx, v): | |
| self.ghost.on() | |
| if self.current_editing_object_gui_tools: | |
| self.current_editing_object_gui_tools.update_preview_object(self, obj, node_idx, v) | |
| gui_tool_utils.redraw_3d_view() | |
| def finalizeGhost(self): | |
| try: | |
| self.current_editing_object_gui_tools = None | |
| self.ghost.finalize() | |
| self.ghost = None | |
| except Exception: | |
| return | |
| # ------------------------------------------------------------------------ | |
| # DRAFT EDIT Context menu | |
| # ------------------------------------------------------------------------ | |
| def display_tracker_menu(self, event): | |
| self.tracker_menu = QtWidgets.QMenu() | |
| 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() | |
| obj_gui_tools = self.get_obj_gui_tools(obj) | |
| if obj_gui_tools: | |
| actions = obj_gui_tools.get_edit_point_context_menu(self, obj, ep) | |
| else: | |
| # try if user is over an edited object | |
| pos = event.getPosition().getValue() | |
| obj = self.get_selected_obj_at_position(pos) | |
| obj_gui_tools = self.get_obj_gui_tools(obj) | |
| if obj_gui_tools: | |
| actions = obj_gui_tools.get_edit_obj_context_menu(self, obj, pos) | |
| if actions is None: | |
| return | |
| for label, callback in actions: | |
| def wrapper(callback=callback): | |
| callback() | |
| self.resetTrackers(obj) | |
| action = self.tracker_menu.addAction(label) | |
| action.setData(wrapper) | |
| 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, action): | |
| callback = action.data() | |
| callback() | |
| # ------------------------------------------------------------------------- | |
| # EDIT OBJECT TOOLS | |
| # | |
| # This section contains the code to retrieve the object points and update them | |
| # | |
| # ------------------------------------------------------------------------- | |
| def getEditPoints(self, obj): | |
| """Return a list of App.Vectors according to the given object edit nodes.""" | |
| eps = None | |
| obj_gui_tools = self.get_obj_gui_tools(obj) | |
| if obj_gui_tools: | |
| eps = obj_gui_tools.get_edit_points(obj) | |
| if eps: | |
| return self.globalize_vectors(obj, eps) | |
| else: | |
| return None | |
| def update(self, obj, nodeIndex, v): | |
| """Apply the App.Vector to the modified point and update obj.""" | |
| v = self.localize_vector(obj, v) | |
| App.ActiveDocument.openTransaction("Edit") | |
| self.update_object(obj, nodeIndex, v) | |
| App.ActiveDocument.commitTransaction() | |
| self.resetTrackers(obj) | |
| try: | |
| gui_tool_utils.redraw_3d_view() | |
| except AttributeError as err: | |
| pass | |
| def update_object(self, obj, nodeIndex, v): | |
| """Update the object according to the given modified editpoint.""" | |
| obj_gui_tools = self.get_obj_gui_tools(obj) | |
| if obj_gui_tools: | |
| eps = obj_gui_tools.update_object_from_edit_points( | |
| obj, nodeIndex, v, self.alt_edit_mode | |
| ) | |
| obj.recompute() | |
| # ------------------------------------------------------------------------- | |
| # UTILS | |
| # ------------------------------------------------------------------------- | |
| def has_obj_gui_tools(self, obj): | |
| """Check if the object has the GuiTools to provide information to edit it.""" | |
| if ( | |
| hasattr(obj, "obj_gui_tools") | |
| or (hasattr(obj, "Proxy") and hasattr(obj.Proxy, "obj_gui_tools")) | |
| or (utils.get_type(obj) in self.gui_tools_repository.keys()) | |
| ): | |
| return True | |
| else: | |
| return False | |
| def get_obj_gui_tools(self, obj): | |
| """Retrieve the obj_gui_tools to support Draft Edit.""" | |
| try: | |
| obj_gui_tools = obj.obj_gui_tools | |
| except AttributeError: | |
| try: | |
| obj_gui_tools = obj.Proxy.obj_gui_tools | |
| except AttributeError: | |
| try: | |
| obj_gui_tools = self.gui_tools_repository.get(utils.get_type(obj)) | |
| except Exception: | |
| obj_gui_tools = None | |
| return obj_gui_tools | |
| def getObjsFromSelection(self): | |
| """Evaluate selection and return a valid object to edit. | |
| #to be used for app link support | |
| for selobj in Gui.Selection.getSelectionEx('', 0): | |
| for sub in selobj.SubElementNames: | |
| obj = selobj.Object | |
| obj_matrix = selobj.Object.getSubObject(sub, retType=4) | |
| """ | |
| selection = Gui.Selection.getSelection() | |
| self.edited_objects = [] | |
| if len(selection) > self.max_objects: | |
| _err = translate("draft", "Too many objects selected, maximum number set to:") | |
| App.Console.PrintMessage(_err + " " + str(self.max_objects) + "\n") | |
| return None | |
| for obj in selection: | |
| if self.has_obj_gui_tools(obj): | |
| self.edited_objects.append(obj) | |
| else: | |
| _wrn = translate("draft", ": this object is not editable") | |
| App.Console.PrintWarning(obj.Name + _wrn + "\n") | |
| return self.edited_objects | |
| def format_objects_for_editing(self, objs): | |
| """Change objects style during editing mode.""" | |
| for obj in objs: | |
| obj_gui_tools = self.get_obj_gui_tools(obj) | |
| if obj_gui_tools: | |
| self.objs_formats[obj.Name] = obj_gui_tools.get_object_style(obj) | |
| obj_gui_tools.set_object_editing_style(obj) | |
| def deformat_objects_after_editing(self, objs): | |
| """Restore objects style during editing mode.""" | |
| for obj in objs: | |
| if utils.is_deleted(obj): | |
| continue | |
| obj_gui_tools = self.get_obj_gui_tools(obj) | |
| if obj_gui_tools: | |
| obj_gui_tools.restore_object_style(obj, self.objs_formats[obj.Name]) | |
| def get_specific_object_info(self, obj, pos): | |
| """Return info of a specific object at a given position.""" | |
| selobjs = self.view.getObjectsInfo((pos[0], pos[1])) | |
| if not selobjs: | |
| return | |
| for info in selobjs: | |
| if not info: | |
| continue | |
| if obj.Name == info["Object"] and "x" in info: | |
| # prefer "real" 3D location over working-plane-driven one if possible | |
| pt = App.Vector(info["x"], info["y"], info["z"]) | |
| return info, pt | |
| 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 = self.view.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 globalize_vectors(self, obj, pointList): | |
| """Return the given point list in the global coordinate system.""" | |
| plist = [] | |
| for p in pointList: | |
| point = self.globalize_vector(obj, p) | |
| plist.append(point) | |
| return plist | |
| def globalize_vector(self, obj, point): | |
| """Return the given point in the global coordinate system.""" | |
| if hasattr(obj, "getGlobalPlacement"): | |
| return obj.getGlobalPlacement().multVec(point) | |
| else: | |
| return point | |
| def localize_vectors(self, obj, pointList): | |
| """Return the given point list in the given object coordinate system.""" | |
| plist = [] | |
| for p in pointList: | |
| point = self.localize_vector(obj, p) | |
| plist.append(point) | |
| return plist | |
| def localize_vector(self, obj, point): | |
| """Return the given point in the given object coordinate system.""" | |
| if hasattr(obj, "getGlobalPlacement"): | |
| return obj.getGlobalPlacement().inverse().multVec(point) | |
| else: | |
| return point | |
| def getEditNode(self, pos): | |
| """Get edit node from given screen position.""" | |
| node = self.sendRay(pos) | |
| return node | |
| def sendRay(self, mouse_pos): | |
| """Send 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 return 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 | |
| class GuiToolsRepository: | |
| """This object provide a repository to collect all the specific objects | |
| editing tools. | |
| """ | |
| def __init__(self): | |
| self.obj_gui_tools = {} | |
| def get(self, obj_type): | |
| return self.obj_gui_tools[obj_type] | |
| def add(self, type, gui_tools): | |
| self.obj_gui_tools[type] = gui_tools | |
| def keys(self): | |
| return self.obj_gui_tools.keys() | |
| Gui.addCommand("Draft_Edit", Edit()) | |
| ## @} | |