diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index f076366bb0..3186aebb05 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -86,6 +86,7 @@ SET(Draft_view_providers SET(Draft_GUI_tools draftguitools/__init__.py draftguitools/gui_base.py + draftguitools/gui_tool_utils.py draftguitools/gui_circulararray.py draftguitools/gui_orthoarray.py draftguitools/gui_polararray.py diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 0b54833b5f..8925928a89 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -114,144 +114,29 @@ elif defaultWP == 3: plane.alignToPointAndAxis(Vector(0,0,0), Vector(1,0,0), 0) # last snapped objects, for quick intersection calculation lastObj = [0,0] -# set modifier keys -MODS = ["shift","ctrl","alt"] -MODCONSTRAIN = MODS[Draft.getParam("modconstrain",0)] -MODSNAP = MODS[Draft.getParam("modsnap",1)] -MODALT = MODS[Draft.getParam("modalt",2)] +# Set modifier keys +from draftguitools.gui_tool_utils import MODCONSTRAIN +from draftguitools.gui_tool_utils import MODSNAP +from draftguitools.gui_tool_utils import MODALT # --------------------------------------------------------------------------- # General functions # --------------------------------------------------------------------------- -def formatUnit(exp,unit="mm"): - '''returns a formatting string to set a number to the correct unit''' - return FreeCAD.Units.Quantity(exp,FreeCAD.Units.Length).UserString +from draftguitools.gui_tool_utils import formatUnit -def selectObject(arg): - '''this is a scene even handler, to be called from the Draft tools - when they need to select an object''' - if (arg["Type"] == "SoKeyboardEvent"): - if (arg["Key"] == "ESCAPE"): - FreeCAD.activeDraftCommand.finish() - # TODO : this part raises a coin3D warning about scene traversal, to be fixed. - elif (arg["Type"] == "SoMouseButtonEvent"): - if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): - cursor = arg["Position"] - snapped = Draft.get3DView().getObjectInfo((cursor[0],cursor[1])) - if snapped: - obj = FreeCAD.ActiveDocument.getObject(snapped['Object']) - FreeCADGui.Selection.addSelection(obj) - FreeCAD.activeDraftCommand.component=snapped['Component'] - FreeCAD.activeDraftCommand.proceed() +from draftguitools.gui_tool_utils import selectObject -def getPoint(target,args,mobile=False,sym=False,workingplane=True,noTracker=False): - """Function used by the Draft Tools. - returns a constrained 3d point and its original point. - if mobile=True, the constraining occurs from the location of - mouse cursor when Shift is pressed, otherwise from last entered - point. If sym=True, x and y values stay always equal. If workingplane=False, - the point won't be projected on the Working Plane. if noTracker is True, the - tracking line will not be displayed - """ +from draftguitools.gui_tool_utils import getPoint - ui = FreeCADGui.draftToolBar +from draftguitools.gui_tool_utils import getSupport - if target.node: - last = target.node[-1] - else: - last = None +from draftguitools.gui_tool_utils import setWorkingPlaneToObjectUnderCursor - amod = hasMod(args, MODSNAP) - cmod = hasMod(args, MODCONSTRAIN) - point = None +from draftguitools.gui_tool_utils import setWorkingPlaneToSelectedObject - if hasattr(FreeCADGui, "Snapper"): - point = FreeCADGui.Snapper.snap(args["Position"],lastpoint=last,active=amod,constrain=cmod,noTracker=noTracker) - info = FreeCADGui.Snapper.snapInfo - mask = FreeCADGui.Snapper.affinity - if not point: - p = FreeCADGui.ActiveDocument.ActiveView.getCursorPos() - point = FreeCADGui.ActiveDocument.ActiveView.getPoint(p) - info = FreeCADGui.ActiveDocument.ActiveView.getObjectInfo(p) - mask = None +from draftguitools.gui_tool_utils import hasMod - ctrlPoint = Vector(point) - if target.node: - if target.featureName == "Rectangle": - ui.displayPoint(point, target.node[0], plane=plane, mask=mask) - else: - ui.displayPoint(point, target.node[-1], plane=plane, mask=mask) - else: - ui.displayPoint(point, plane=plane, mask=mask) - return point,ctrlPoint,info - -def getSupport(mouseEvent=None): - """returns the supporting object and sets the working plane""" - plane.save() - if mouseEvent: - return setWorkingPlaneToObjectUnderCursor(mouseEvent) - return setWorkingPlaneToSelectedObject() - -def setWorkingPlaneToObjectUnderCursor(mouseEvent): - objectUnderCursor = Draft.get3DView().getObjectInfo(( - mouseEvent["Position"][0], - mouseEvent["Position"][1])) - - if not objectUnderCursor: - return None - - try: - componentUnderCursor = getattr( - FreeCAD.ActiveDocument.getObject( - objectUnderCursor['Object'] - ).Shape, - objectUnderCursor["Component"]) - - if not plane.weak: - return None - - if "Face" in objectUnderCursor["Component"]: - plane.alignToFace(componentUnderCursor) - else: - plane.alignToCurve(componentUnderCursor) - plane.weak = True - return objectUnderCursor - except: - pass - - return None - -def setWorkingPlaneToSelectedObject(): - sel = FreeCADGui.Selection.getSelectionEx() - if len(sel) != 1: - return None - sel = sel[0] - if sel.HasSubObjects \ - and len(sel.SubElementNames) == 1 \ - and "Face" in sel.SubElementNames[0]: - if plane.weak: - plane.alignToFace(sel.SubObjects[0]) - plane.weak = True - return sel.Object - return None - -def hasMod(args,mod): - """checks if args has a specific modifier""" - if mod == "shift": - return args["ShiftDown"] - elif mod == "ctrl": - return args["CtrlDown"] - elif mod == "alt": - return args["AltDown"] - -def setMod(args,mod,state): - """sets a specific modifier state in args""" - if mod == "shift": - args["ShiftDown"] = state - elif mod == "ctrl": - args["CtrlDown"] = state - elif mod == "alt": - args["AltDown"] = state +from draftguitools.gui_tool_utils import setMod # --------------------------------------------------------------------------- @@ -367,12 +252,8 @@ class DraftTool: # --------------------------------------------------------------------------- # Geometry constructors # --------------------------------------------------------------------------- -def redraw3DView(): - """redraw3DView(): forces a redraw of 3d view.""" - try: - FreeCADGui.ActiveDocument.ActiveView.redraw() - except AttributeError as err: - pass +from draftguitools.gui_tool_utils import redraw3DView + class Creator(DraftTool): """A generic Draft Creator Tool used by creation tools such as line or arc""" diff --git a/src/Mod/Draft/draftguitools/gui_tool_utils.py b/src/Mod/Draft/draftguitools/gui_tool_utils.py new file mode 100644 index 0000000000..c26435a37f --- /dev/null +++ b/src/Mod/Draft/draftguitools/gui_tool_utils.py @@ -0,0 +1,389 @@ +# *************************************************************************** +# * (c) 2009 Yorik van Havre * +# * (c) 2010 Ken Cline * +# * (c) 2020 Eliud Cabrera Castillo * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * 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. * +# * * +# * FreeCAD 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 FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides the utility functions for Draft Gui Commands. + +These functions are used by different command classes in the `DraftTools` +module. We assume that the graphical interface was already loaded +as they operate on selections and graphical properties. +""" +## @package gui_tool_utils +# \ingroup DRAFT +# \brief Provides the utility functions for Draft Gui Commands. + +import FreeCAD as App +import FreeCADGui as Gui +import draftutils.gui_utils as gui_utils +import draftutils.utils as utils +from draftutils.messages import _wrn + +# Set modifier keys from the parameter database +MODS = ["shift", "ctrl", "alt"] +MODCONSTRAIN = MODS[utils.get_param("modconstrain", 0)] +MODSNAP = MODS[utils.get_param("modsnap", 1)] +MODALT = MODS[utils.get_param("modalt", 2)] + + +def format_unit(exp, unit="mm"): + """Return a formatting string to set a number to the correct unit.""" + return App.Units.Quantity(exp, App.Units.Length).UserString + + +formatUnit = format_unit + + +def select_object(arg): + """Handle the selection of objects depending on buttons pressed. + + This is a scene event handler, to be called from the Draft tools + when they need to select an object. + :: + self.call = self.view.addEventCallback("SoEvent", select_object) + + Parameters + ---------- + arg: Coin event + The Coin event received from the 3D view. + + If it is of type Keyboard and the `ESCAPE` key, it runs the `finish` + method of the active command. + + If it is of type Mouse button and `BUTTON1` press, + it captures the position of the cursor (x, y) + and the object below that cursor to add it to the active selection; + then it runs the `proceed` method of the active command + to continue with the command's logic. + """ + if arg["Type"] == "SoKeyboardEvent": + if arg["Key"] == "ESCAPE": + App.activeDraftCommand.finish() + # TODO: this part raises a coin3D warning about scene traversal. + # It needs to be fixed. + elif arg["Type"] == "SoMouseButtonEvent": + if arg["State"] == "DOWN" and arg["Button"] == "BUTTON1": + cursor = arg["Position"] + snapped = gui_utils.get_3d_view().getObjectInfo((cursor[0], + cursor[1])) + if snapped: + obj = App.ActiveDocument.getObject(snapped['Object']) + Gui.Selection.addSelection(obj) + App.activeDraftCommand.component = snapped['Component'] + App.activeDraftCommand.proceed() + + +selectObject = select_object + + +def has_mod(args, mod): + """Check if args has a specific modifier. + + Parameters + ---------- + args: Coin event + The Coin event received from the 3D view. + + mod: str + A string indicating the modifier, either `'shift'`, `'ctrl'`, + or `'alt'`. + + Returns + ------- + bool + It returns `args["ShiftDown"]`, `args["CtrlDown"]`, + or `args["AltDown"]`, depending on the passed value of `mod`. + """ + if mod == "shift": + return args["ShiftDown"] + elif mod == "ctrl": + return args["CtrlDown"] + elif mod == "alt": + return args["AltDown"] + + +hasMod = has_mod + + +def set_mod(args, mod, state): + """Set a specific modifier state in args. + + Parameters + ---------- + args: Coin event + The Coin event received from the 3D view. + + mod: str + A string indicating the modifier, either `'shift'`, `'ctrl'`, + or `'alt'`. + + state: bool + The boolean value of `state` is assigned to `args["ShiftDown"]`, + `args["CtrlDown"]`, or `args["AltDown"]` + depending on `mod`. + """ + if mod == "shift": + args["ShiftDown"] = state + elif mod == "ctrl": + args["CtrlDown"] = state + elif mod == "alt": + args["AltDown"] = state + + +setMod = set_mod + + +def get_point(target, args, + mobile=False, sym=False, workingplane=True, noTracker=False): + """Return a constrained 3D point and its original point. + + It is used by the Draft tools. + + Parameters + ---------- + target: object (class) + The target object with a `node` attribute. If this is present, + return the last node, otherwise return `None`. + + In the Draft tools, `target` is essentially the same class + of the Gui command, that is, `self`. Therefore, this method + probably makes more sense as a class method. + + args: Coin event + The Coin event received from the 3D view. + + mobile: bool, optional + It defaults to `False`. + If it is `True` the constraining occurs from the location of + the mouse cursor when `Shift` is pressed; otherwise from the last + entered point. + + sym: bool, optional + It defaults to `False`. + If it is `True`, the x and y values always stay equal. + + workingplane: bool, optional + It defaults to `True`. + If it is `False`, the point won't be projected on the currently + active working plane. + + noTracker: bool, optional + It defaults to `False`. + If it is `True`, the tracking line will not be displayed. + + Returns + ------- + CoinPoint, Base::Vector3, str + It returns a tuple with some information. + + The first is the Coin point returned by `Snapper.snap` + or by the `ActiveView`; the second is that same point + turned into an `App.Vector`, + and the third is some information of the point + returned by the `Snapper` or by the `ActiveView`. + """ + ui = Gui.draftToolBar + + if target.node: + last = target.node[-1] + else: + last = None + + amod = hasMod(args, MODSNAP) + cmod = hasMod(args, MODCONSTRAIN) + point = None + + if hasattr(Gui, "Snapper"): + point = Gui.Snapper.snap(args["Position"], + lastpoint=last, + active=amod, + constrain=cmod, + noTracker=noTracker) + info = Gui.Snapper.snapInfo + mask = Gui.Snapper.affinity + if not point: + p = Gui.ActiveDocument.ActiveView.getCursorPos() + point = Gui.ActiveDocument.ActiveView.getPoint(p) + info = Gui.ActiveDocument.ActiveView.getObjectInfo(p) + mask = None + + ctrlPoint = App.Vector(point) + if target.node: + if target.featureName == "Rectangle": + ui.displayPoint(point, target.node[0], + plane=App.DraftWorkingPlane, mask=mask) + else: + ui.displayPoint(point, target.node[-1], + plane=App.DraftWorkingPlane, mask=mask) + else: + ui.displayPoint(point, plane=App.DraftWorkingPlane, mask=mask) + return point, ctrlPoint, info + + +getPoint = get_point + + +def set_working_plane_to_object_under_cursor(mouseEvent): + """Set the working plane to the object under the cursor. + + It tests for an object under the cursor. + If it is found, it checks whether a `'face'` or `'curve'` component + is selected in the object's `Shape`. + Then it tries to align the working plane to that face or curve. + + The working plane is only aligned to the face if + the working plane is not `'weak'`. + + Parameters + ---------- + mouseEvent: Coin event + Coin event with the mouse, that is, a click. + + Returns + ------- + None + If no object was found in the 3D view under the cursor. + Or if the working plane is not `weak`. + Or if there was an exception with aligning the working plane + to the component under the cursor. + + Coin info + The `getObjectInfo` of the object under the cursor. + """ + objectUnderCursor = gui_utils.get_3d_view().getObjectInfo(( + mouseEvent["Position"][0], + mouseEvent["Position"][1])) + + if not objectUnderCursor: + return None + + try: + # Get the component "face" or "curve" under the "Shape" + # of the selected object + componentUnderCursor = getattr( + App.ActiveDocument.getObject(objectUnderCursor['Object']).Shape, + objectUnderCursor["Component"]) + + if not App.DraftWorkingPlane.weak: + return None + + if "Face" in objectUnderCursor["Component"]: + App.DraftWorkingPlane.alignToFace(componentUnderCursor) + else: + App.DraftWorkingPlane.alignToCurve(componentUnderCursor) + App.DraftWorkingPlane.weak = True + return objectUnderCursor + except Exception: + pass + + return None + + +setWorkingPlaneToObjectUnderCursor = set_working_plane_to_object_under_cursor + + +def set_working_plane_to_selected_object(): + """Set the working plane to the selected object's face. + + The working plane is only aligned to the face if + the working plane is `'weak'`. + + Returns + ------- + None + If more than one object was selected. + Or if the selected object has many subelements. + Or if the single subelement is not a `'Face'`. + + App::DocumentObject + The single object that contains a single selected face. + """ + sel = Gui.Selection.getSelectionEx() + if len(sel) != 1: + return None + sel = sel[0] + if (sel.HasSubObjects + and len(sel.SubElementNames) == 1 + and "Face" in sel.SubElementNames[0]): + if App.DraftWorkingPlane.weak: + App.DraftWorkingPlane.alignToFace(sel.SubObjects[0]) + App.DraftWorkingPlane.weak = True + return sel.Object + return None + + +setWorkingPlaneToSelectedObject = set_working_plane_to_selected_object + + +def get_support(mouseEvent=None): + """Return the supporting object and set the working plane. + + It saves the current working plane, then sets it to the selected object. + + Parameters + ---------- + mouseEvent: Coin event, optional + It defaults to `None`. + Coin event with the mouse, that is, a click. + + If there is a mouse event it calls + `set_working_plane_to_object_under_cursor`. + Otherwise, it calls `set_working_plane_to_selected_object`. + + Returns + ------- + None + If there was a mouse event, but there was nothing under the cursor. + Or if the working plane is not `weak`. + Or if there was an exception with aligning the working plane + to the component under the cursor. + Or if more than one object was selected. + Or if the selected object has many subelements. + Or if the single subelement is not a `'Face'`. + + Coin info + If there was a mouse event, the `getObjectInfo` + of the object under the cursor. + + App::DocumentObject + If there was no mouse event, the single selected object + that contains the single selected face that was used + to align the working plane. + """ + App.DraftWorkingPlane.save() + if mouseEvent: + return setWorkingPlaneToObjectUnderCursor(mouseEvent) + return setWorkingPlaneToSelectedObject() + + +getSupport = get_support + + +def redraw_3d_view(): + """Force a redraw of 3D view or do nothing if it fails.""" + try: + Gui.ActiveDocument.ActiveView.redraw() + except AttributeError as err: + _wrn(err) + + +redraw3DView = redraw_3d_view