FreeCAD
349 строк · 10.7 Кб
1# ***************************************************************************
2# * (c) 2009 Yorik van Havre <yorik@uncreated.net> *
3# * (c) 2010 Ken Cline <cline@frii.com> *
4# * (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> *
5# * *
6# * This file is part of the FreeCAD CAx development system. *
7# * *
8# * This program is free software; you can redistribute it and/or modify *
9# * it under the terms of the GNU Lesser General Public License (LGPL) *
10# * as published by the Free Software Foundation; either version 2 of *
11# * the License, or (at your option) any later version. *
12# * for detail see the LICENCE text file. *
13# * *
14# * FreeCAD is distributed in the hope that it will be useful, *
15# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17# * GNU Library General Public License for more details. *
18# * *
19# * You should have received a copy of the GNU Library General Public *
20# * License along with FreeCAD; if not, write to the Free Software *
21# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
22# * USA *
23# * *
24# ***************************************************************************
25"""Provides utility functions that are used by many Draft Gui Commands.
26
27These functions are used by different command classes in the `DraftTools`
28module. We assume that the graphical interface was already loaded
29as they operate on selections and graphical properties.
30"""
31## @package gui_tool_utils
32# \ingroup draftguitools
33# \brief Provides utility functions that are used by many Draft Gui Commands.
34
35## \addtogroup draftguitools
36# @{
37import FreeCAD as App
38import FreeCADGui as Gui
39import WorkingPlane
40from draftutils import gui_utils
41from draftutils import params
42from draftutils import utils
43from draftutils.messages import _wrn
44
45# Set modifier keys from the parameter database
46MODS = ["shift", "ctrl", "alt"]
47
48
49def get_mod_constrain_key():
50return MODS[params.get_param("modconstrain")]
51
52
53def get_mod_snap_key():
54return MODS[params.get_param("modsnap")]
55
56
57def get_mod_alt_key():
58return MODS[params.get_param("modalt")]
59
60
61def format_unit(exp, unit="mm"):
62"""Return a formatting string to set a number to the correct unit."""
63return App.Units.Quantity(exp, App.Units.Length).UserString
64
65
66formatUnit = format_unit
67
68
69def select_object(arg):
70"""Handle the selection of objects depending on buttons pressed.
71
72This is a scene event handler, to be called from the Draft tools
73when they need to select an object.
74::
75self.call = self.view.addEventCallback("SoEvent", select_object)
76
77Parameters
78----------
79arg: Coin event
80The Coin event received from the 3D view.
81
82If it is of type Keyboard and the `ESCAPE` key, it runs the `finish`
83method of the active command.
84
85If Ctrl key is pressed, multiple selection is enabled until the
86button is released.
87Then it runs the `proceed` method of the active command
88to continue with the command's logic.
89"""
90if arg["Type"] == "SoKeyboardEvent":
91if arg["Key"] == "ESCAPE":
92App.activeDraftCommand.finish()
93# TODO: this part raises a coin3D warning about scene traversal.
94# It needs to be fixed.
95elif not arg["CtrlDown"] and Gui.Selection.hasSelection():
96App.activeDraftCommand.proceed()
97
98
99selectObject = select_object
100
101
102def has_mod(args, mod):
103"""Check if args has a specific modifier.
104
105Parameters
106----------
107args: Coin event
108The Coin event received from the 3D view.
109
110mod: str
111A string indicating the modifier, either `'shift'`, `'ctrl'`,
112or `'alt'`.
113
114Returns
115-------
116bool
117It returns `args["ShiftDown"]`, `args["CtrlDown"]`,
118or `args["AltDown"]`, depending on the passed value of `mod`.
119"""
120if mod == "shift":
121return args["ShiftDown"]
122elif mod == "ctrl":
123return args["CtrlDown"]
124elif mod == "alt":
125return args["AltDown"]
126
127
128hasMod = has_mod
129
130
131def set_mod(args, mod, state):
132"""Set a specific modifier state in args.
133
134Parameters
135----------
136args: Coin event
137The Coin event received from the 3D view.
138
139mod: str
140A string indicating the modifier, either `'shift'`, `'ctrl'`,
141or `'alt'`.
142
143state: bool
144The boolean value of `state` is assigned to `args["ShiftDown"]`,
145`args["CtrlDown"]`, or `args["AltDown"]`
146depending on `mod`.
147"""
148if mod == "shift":
149args["ShiftDown"] = state
150elif mod == "ctrl":
151args["CtrlDown"] = state
152elif mod == "alt":
153args["AltDown"] = state
154
155
156setMod = set_mod
157
158
159def get_point(target, args, noTracker=False):
160"""Return a constrained 3D point and its original point.
161
162It is used by the Draft tools.
163
164Parameters
165----------
166target: object (class)
167The target object with a `node` attribute. If this is present,
168return the last node, otherwise return `None`.
169
170In the Draft tools, `target` is essentially the same class
171of the Gui command, that is, `self`. Therefore, this method
172probably makes more sense as a class method.
173
174args: Coin event
175The Coin event received from the 3D view.
176
177noTracker: bool, optional
178It defaults to `False`.
179If it is `True`, the tracking line will not be displayed.
180
181Returns
182-------
183CoinPoint, Base::Vector3, str
184It returns a tuple with some information.
185
186The first is the Coin point returned by `Snapper.snap`
187or by the `ActiveView`; the second is that same point
188turned into an `App.Vector`,
189and the third is some information of the point
190returned by the `Snapper` or by the `ActiveView`.
191"""
192ui = Gui.draftToolBar
193if not ui.mouse:
194return None, None, None
195
196if target.node:
197last = target.node[-1]
198else:
199last = None
200
201smod = has_mod(args, get_mod_snap_key())
202cmod = has_mod(args, get_mod_constrain_key())
203point = None
204
205if hasattr(Gui, "Snapper"):
206point = Gui.Snapper.snap(args["Position"],
207lastpoint=last,
208active=smod,
209constrain=cmod,
210noTracker=noTracker)
211info = Gui.Snapper.snapInfo
212mask = Gui.Snapper.affinity
213if not point:
214p = Gui.ActiveDocument.ActiveView.getCursorPos()
215point = Gui.ActiveDocument.ActiveView.getPoint(p)
216info = Gui.ActiveDocument.ActiveView.getObjectInfo(p)
217mask = None
218
219ctrlPoint = App.Vector(point)
220wp = WorkingPlane.get_working_plane(update=False)
221if target.node:
222if target.featureName == "Rectangle":
223ui.displayPoint(point, target.node[0], plane=wp, mask=mask)
224else:
225ui.displayPoint(point, target.node[-1], plane=wp, mask=mask)
226else:
227ui.displayPoint(point, plane=wp, mask=mask)
228return point, ctrlPoint, info
229
230
231getPoint = get_point
232
233
234def set_working_plane_to_object_under_cursor(mouseEvent):
235"""Align the working plane to the face under the cursor.
236
237The working plane is only aligned if it is `'auto'`.
238
239Parameters
240----------
241mouseEvent: Coin event
242Coin mouse event.
243
244Returns
245-------
246App::DocumentObject or None
247The parent object the face belongs to, if alignment occurred, or None.
248"""
249objectUnderCursor = gui_utils.get_3d_view().getObjectInfo((
250mouseEvent["Position"][0],
251mouseEvent["Position"][1]))
252
253if not objectUnderCursor:
254return None
255if "Face" not in objectUnderCursor["Component"]:
256return None
257wp = WorkingPlane.get_working_plane(update=False)
258if not wp.auto:
259return None
260
261import Part
262if "ParentObject" in objectUnderCursor:
263obj = objectUnderCursor["ParentObject"]
264sub = objectUnderCursor["SubName"]
265else:
266obj = App.ActiveDocument.getObject(objectUnderCursor["Object"])
267sub = objectUnderCursor["Component"]
268shape = Part.getShape(obj, sub, needSubElement=True, retType=0)
269
270if wp.align_to_face(shape, _hist_add=False):
271wp.auto = True
272return obj
273
274return None
275
276
277setWorkingPlaneToObjectUnderCursor = set_working_plane_to_object_under_cursor
278
279
280def set_working_plane_to_selected_object():
281"""Align the working plane to a preselected face.
282
283The working plane is only aligned if it is `'auto'`.
284
285Returns
286-------
287App::DocumentObject or None
288The parent object the face belongs to, if alignment occurred, or None.
289"""
290wp = WorkingPlane.get_working_plane(update=False)
291if not wp.auto:
292return None
293
294sels = Gui.Selection.getSelectionEx("", 0)
295
296if len(sels) == 1 \
297and len(sels[0].SubObjects) == 1 \
298and sels[0].SubObjects[0].ShapeType == "Face":
299import Part
300shape = Part.getShape(sels[0].Object,
301sels[0].SubElementNames[0],
302needSubElement=True,
303retType=0)
304
305if wp.align_to_face(shape, _hist_add=False):
306wp.auto = True
307return sels[0].Object
308
309return None
310
311
312setWorkingPlaneToSelectedObject = set_working_plane_to_selected_object
313
314
315def get_support(mouseEvent=None):
316""""Align the working plane to a preselected face or the face under the cursor.
317
318The working plane is only aligned if it is `'auto'`.
319
320Parameters
321----------
322mouseEvent: Coin event, optional
323Defaults to `None`.
324Coin mouse event.
325
326Returns
327-------
328App::DocumentObject or None
329The parent object the face belongs to, if alignment occurred, or None.
330"""
331if mouseEvent is None:
332return set_working_plane_to_selected_object()
333return set_working_plane_to_object_under_cursor(mouseEvent)
334
335
336getSupport = get_support
337
338
339def redraw_3d_view():
340"""Force a redraw of 3D view or do nothing if it fails."""
341try:
342Gui.ActiveDocument.ActiveView.redraw()
343except AttributeError as err:
344_wrn(err)
345
346
347redraw3DView = redraw_3d_view
348
349## @}
350