freecad-cam/Mod/Draft/draftguitools/gui_tool_utils.py
2026-02-01 01:59:24 +01:00

350 lines
11 KiB
Python

# ***************************************************************************
# * (c) 2009 Yorik van Havre <yorik@uncreated.net> *
# * (c) 2010 Ken Cline <cline@frii.com> *
# * (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> *
# * *
# * 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 utility functions that are used by many 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 draftguitools
# \brief Provides utility functions that are used by many Draft Gui Commands.
## \addtogroup draftguitools
# @{
import FreeCAD as App
import FreeCADGui as Gui
import WorkingPlane
from draftutils import gui_utils
from draftutils import params
from draftutils import utils
from draftutils.messages import _wrn
# Set modifier keys from the parameter database
MODS = ["shift", "ctrl", "alt"]
def get_mod_constrain_key():
return MODS[params.get_param("modconstrain")]
def get_mod_snap_key():
return MODS[params.get_param("modsnap")]
def get_mod_alt_key():
return MODS[params.get_param("modalt")]
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 Ctrl key is pressed, multiple selection is enabled until the
button is released.
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 not arg["CtrlDown"] and Gui.Selection.hasSelection():
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, 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.
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 not ui.mouse:
return None, None, None
if target.node:
last = target.node[-1]
else:
last = None
smod = has_mod(args, get_mod_snap_key())
cmod = has_mod(args, get_mod_constrain_key())
point = None
if hasattr(Gui, "Snapper"):
point = Gui.Snapper.snap(args["Position"],
lastpoint=last,
active=smod,
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)
wp = WorkingPlane.get_working_plane(update=False)
if target.node:
if target.featureName == "Rectangle":
ui.displayPoint(point, target.node[0], plane=wp, mask=mask)
else:
ui.displayPoint(point, target.node[-1], plane=wp, mask=mask)
else:
ui.displayPoint(point, plane=wp, mask=mask)
return point, ctrlPoint, info
getPoint = get_point
def set_working_plane_to_object_under_cursor(mouseEvent):
"""Align the working plane to the face under the cursor.
The working plane is only aligned if it is `'auto'`.
Parameters
----------
mouseEvent: Coin event
Coin mouse event.
Returns
-------
App::DocumentObject or None
The parent object the face belongs to, if alignment occurred, or None.
"""
objectUnderCursor = gui_utils.get_3d_view().getObjectInfo((
mouseEvent["Position"][0],
mouseEvent["Position"][1]))
if not objectUnderCursor:
return None
if "Face" not in objectUnderCursor["Component"]:
return None
wp = WorkingPlane.get_working_plane(update=False)
if not wp.auto:
return None
import Part
if "ParentObject" in objectUnderCursor:
obj = objectUnderCursor["ParentObject"]
sub = objectUnderCursor["SubName"]
else:
obj = App.ActiveDocument.getObject(objectUnderCursor["Object"])
sub = objectUnderCursor["Component"]
shape = Part.getShape(obj, sub, needSubElement=True, retType=0)
if wp.align_to_face(shape, _hist_add=False):
wp.auto = True
return obj
return None
setWorkingPlaneToObjectUnderCursor = set_working_plane_to_object_under_cursor
def set_working_plane_to_selected_object():
"""Align the working plane to a preselected face.
The working plane is only aligned if it is `'auto'`.
Returns
-------
App::DocumentObject or None
The parent object the face belongs to, if alignment occurred, or None.
"""
wp = WorkingPlane.get_working_plane(update=False)
if not wp.auto:
return None
sels = Gui.Selection.getSelectionEx("", 0)
if len(sels) == 1 \
and len(sels[0].SubObjects) == 1 \
and sels[0].SubObjects[0].ShapeType == "Face":
import Part
shape = Part.getShape(sels[0].Object,
sels[0].SubElementNames[0],
needSubElement=True,
retType=0)
if wp.align_to_face(shape, _hist_add=False):
wp.auto = True
return sels[0].Object
return None
setWorkingPlaneToSelectedObject = set_working_plane_to_selected_object
def get_support(mouseEvent=None):
""""Align the working plane to a preselected face or the face under the cursor.
The working plane is only aligned if it is `'auto'`.
Parameters
----------
mouseEvent: Coin event, optional
Defaults to `None`.
Coin mouse event.
Returns
-------
App::DocumentObject or None
The parent object the face belongs to, if alignment occurred, or None.
"""
if mouseEvent is None:
return set_working_plane_to_selected_object()
return set_working_plane_to_object_under_cursor(mouseEvent)
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
## @}