This includes `gui_annotationstyleeditor`, `gui_arcs`, `gui_array_simple`, `gui_arrays`, `gui_base`, `gui_base_original`, `gui_beziers`, `gui_circles`, `gui_circulararray`, `gui_clone`, `gui_circulararray`, `gui_clone`, `gui_dimension_ops`, `gui_dimensions`, `gui_downgrade`, `gui_draft2sketch`, `gui_drawing`, `gui_edit`, `gui_edit_arch_objects`, `gui_edit_draft_objects`, `gui_edit_part_objects`, `gui_edit_sketcher_objects`, `gui_ellipses`, `gui_facebinders`, `gui_fillets`, `gui_grid`, `gui_groups`, `gui_heal`, `gui_join`, `gui_labels`, `gui_line_add_delete`, `gui_lineops`, `gui_lines`, `gui_lineslope`, `gui_mirror`, `gui_move`, `gui_offset`, `gui_orthoarray`, `gui_patharray`, `gui_planeproxy`, `gui_pointarray`, `gui_points`, `gui_polararray`, `gui_polygons`, `gui_rectangles`, `gui_rotate`, `gui_scale`, `gui_selectplane`, `gui_shape2dview`, `gui_shapestrings`, `gui_snapper`, `gui_snaps`, `gui_splines`, `gui_split`, `gui_stretch`, `gui_styles`, `gui_subeleemnts`, `gui_texts`, `gui_togglemodes`, `gui_tools_utils`, `gui_trackers`, `gui_trimex`, `gui_upgrade`, `gui_wire2spline`. These are added to the `draftguitools` Doxygen group so that the functions and classes contained in each module are listed appropriately in the automatically generated documentation.
395 lines
13 KiB
Python
395 lines
13 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 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
|
|
|
|
## @}
|