FreeCAD

Форк
0
426 строк · 17.7 Кб
1
# ***************************************************************************
2
# *   (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net>                  *
3
# *   (c) 2009, 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 GUI tools to rotate objects in the 3D space."""
26
## @package gui_rotate
27
# \ingroup draftguitools
28
# \brief Provides GUI tools to rotate objects in the 3D space.
29

30
## \addtogroup draftguitools
31
# @{
32
import math
33
from PySide.QtCore import QT_TRANSLATE_NOOP
34

35
import FreeCAD as App
36
import FreeCADGui as Gui
37
import Draft_rc
38
import DraftVecUtils
39
import draftutils.groups as groups
40
import draftutils.todo as todo
41
import draftguitools.gui_base_original as gui_base_original
42
import draftguitools.gui_tool_utils as gui_tool_utils
43
import draftguitools.gui_trackers as trackers
44

45
from FreeCAD import Units as U
46
from draftutils.messages import _msg, _err, _toolmsg
47
from draftutils.translate import translate
48

49
# The module is used to prevent complaints from code checkers (flake8)
50
True if Draft_rc.__name__ else False
51

52

53
class Rotate(gui_base_original.Modifier):
54
    """Gui Command for the Rotate tool."""
55

56
    def GetResources(self):
57
        """Set icon, menu and tooltip."""
58
        _tip = ()
59

60
        return {'Pixmap': 'Draft_Rotate',
61
                'Accel': "R, O",
62
                'MenuText': QT_TRANSLATE_NOOP("Draft_Rotate", "Rotate"),
63
                'ToolTip': QT_TRANSLATE_NOOP("Draft_Rotate", "Rotates the selected objects. Choose the center of rotation, then the initial angle, and then the final angle.\nIf the \"copy\" option is active, it will create rotated copies.\nCTRL to snap, SHIFT to constrain. Hold ALT and click to create a copy with each click.")}
64

65
    def Activated(self):
66
        """Execute when the command is called."""
67
        super().Activated(name="Rotate")
68
        if not self.ui:
69
            return
70
        self.ghosts = []
71
        self.arctrack = None
72
        self.get_object_selection()
73

74
    def get_object_selection(self):
75
        """Get the object selection."""
76
        if Gui.Selection.getSelection():
77
            return self.proceed()
78
        self.ui.selectUi(on_close_call=self.finish)
79
        _msg(translate("draft", "Select an object to rotate"))
80
        self.call = \
81
            self.view.addEventCallback("SoEvent", gui_tool_utils.selectObject)
82

83
    def proceed(self):
84
        """Continue with the command after a selection has been made."""
85
        if self.call:
86
            self.view.removeEventCallback("SoEvent", self.call)
87
        self.selected_objects = Gui.Selection.getSelection()
88
        self.selected_objects = \
89
            groups.get_group_contents(self.selected_objects,
90
                                      addgroups=True,
91
                                      spaces=True,
92
                                      noarchchild=True)
93
        self.selected_subelements = Gui.Selection.getSelectionEx()
94
        self.step = 0
95
        self.center = None
96
        self.ui.rotateSetCenterUi()
97
        self.arctrack = trackers.arcTracker()
98
        self.call = self.view.addEventCallback("SoEvent", self.action)
99
        _toolmsg(translate("draft", "Pick rotation center"))
100

101
    def action(self, arg):
102
        """Handle the 3D scene events.
103

104
        This is installed as an EventCallback in the Inventor view.
105

106
        Parameters
107
        ----------
108
        arg: dict
109
            Dictionary with strings that indicates the type of event received
110
            from the 3D view.
111
        """
112
        if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE":
113
            self.finish()
114
        elif arg["Type"] == "SoLocation2Event":
115
            self.handle_mouse_move_event(arg)
116
        elif (arg["Type"] == "SoMouseButtonEvent"
117
              and arg["State"] == "DOWN"
118
              and arg["Button"] == "BUTTON1"):
119
            self.handle_mouse_click_event(arg)
120

121
    def handle_mouse_move_event(self, arg):
122
        """Handle the mouse when moving."""
123
        for ghost in self.ghosts:
124
            ghost.off()
125
        self.point, ctrlPoint, info = gui_tool_utils.getPoint(self, arg)
126
        # this is to make sure radius is what you see on screen
127
        if self.center and DraftVecUtils.dist(self.point, self.center):
128
            viewdelta = DraftVecUtils.project(self.point.sub(self.center),
129
                                              self.wp.axis)
130
            if not DraftVecUtils.isNull(viewdelta):
131
                self.point = self.point.add(viewdelta.negative())
132
        if self.extendedCopy:
133
            if not gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_alt_key()):
134
                self.step = 3
135
                self.finish()
136
        if self.step == 0:
137
            pass
138
        elif self.step == 1:
139
            currentrad = DraftVecUtils.dist(self.point, self.center)
140
            if currentrad != 0:
141
                angle = DraftVecUtils.angle(self.wp.u,
142
                                            self.point.sub(self.center),
143
                                            self.wp.axis)
144
            else:
145
                angle = 0
146
            self.ui.setRadiusValue(math.degrees(angle), unit="Angle")
147
            self.firstangle = angle
148
            self.ui.radiusValue.setFocus()
149
            self.ui.radiusValue.selectAll()
150
        elif self.step == 2:
151
            currentrad = DraftVecUtils.dist(self.point, self.center)
152
            if currentrad != 0:
153
                angle = DraftVecUtils.angle(self.wp.u,
154
                                            self.point.sub(self.center),
155
                                            self.wp.axis)
156
            else:
157
                angle = 0
158
            if angle < self.firstangle:
159
                sweep = (2 * math.pi - self.firstangle) + angle
160
            else:
161
                sweep = angle - self.firstangle
162
            self.arctrack.setApertureAngle(sweep)
163
            for ghost in self.ghosts:
164
                ghost.rotate(self.wp.axis, sweep)
165
                ghost.on()
166
            self.ui.setRadiusValue(math.degrees(sweep), 'Angle')
167
            self.ui.radiusValue.setFocus()
168
            self.ui.radiusValue.selectAll()
169
        gui_tool_utils.redraw3DView()
170

171
    def handle_mouse_click_event(self, arg):
172
        """Handle the mouse when the first button is clicked."""
173
        if not self.point:
174
            return
175
        if self.step == 0:
176
            self.set_center()
177
        elif self.step == 1:
178
            self.set_start_point()
179
        else:
180
            self.set_rotation_angle(arg)
181

182
    def set_center(self):
183
        """Set the center of the rotation."""
184
        if not self.ghosts:
185
            self.set_ghosts()
186
        self.center = self.point
187
        self.node = [self.point]
188
        self.ui.radiusUi()
189
        self.ui.radiusValue.setText(U.Quantity(0, U.Angle).UserString)
190
        self.ui.hasFill.hide()
191
        self.ui.labelRadius.setText(translate("draft", "Base angle"))
192
        self.ui.radiusValue.setToolTip(translate("draft", "The base angle you wish to start the rotation from"))
193
        self.arctrack.setCenter(self.center)
194
        for ghost in self.ghosts:
195
            ghost.center(self.center)
196
        self.step = 1
197
        _toolmsg(translate("draft", "Pick base angle"))
198
        if self.planetrack:
199
            self.planetrack.set(self.point)
200

201
    def set_start_point(self):
202
        """Set the starting point of the rotation."""
203
        self.ui.labelRadius.setText(translate("draft", "Rotation"))
204
        self.ui.radiusValue.setToolTip(translate("draft", "The amount of rotation you wish to perform.\nThe final angle will be the base angle plus this amount."))
205
        self.rad = DraftVecUtils.dist(self.point, self.center)
206
        self.arctrack.on()
207
        self.arctrack.setStartPoint(self.point)
208
        for ghost in self.ghosts:
209
            ghost.on()
210
        self.step = 2
211
        _toolmsg(translate("draft", "Pick rotation angle"))
212

213
    def set_rotation_angle(self, arg):
214
        """Set the rotation angle."""
215

216
        # currentrad = DraftVecUtils.dist(self.point, self.center)
217
        angle = self.point.sub(self.center).getAngle(self.wp.u)
218
        _v = DraftVecUtils.project(self.point.sub(self.center), self.wp.v)
219
        if _v.getAngle(self.wp.v) > 1:
220
            angle = -angle
221
        if angle < self.firstangle:
222
            self.angle = (2 * math.pi - self.firstangle) + angle
223
        else:
224
            self.angle = angle - self.firstangle
225
        self.rotate(self.ui.isCopy.isChecked()
226
                    or gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_alt_key()))
227
        if gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_alt_key()):
228
            self.extendedCopy = True
229
        else:
230
            self.finish(cont=None)
231

232
    def set_ghosts(self):
233
        """Set the ghost to display."""
234
        for ghost in self.ghosts:
235
            ghost.remove()
236
        if self.ui.isSubelementMode.isChecked():
237
            self.ghosts = self.get_subelement_ghosts()
238
        else:
239
            self.ghosts = [trackers.ghostTracker(self.selected_objects)]
240
        if self.center:
241
            for ghost in self.ghosts:
242
                ghost.center(self.center)
243

244
    def get_subelement_ghosts(self):
245
        """Get ghost for the subelements (vertices, edges)."""
246
        import Part
247

248
        ghosts = []
249
        for sel in Gui.Selection.getSelectionEx("", 0):
250
            for sub in sel.SubElementNames if sel.SubElementNames else [""]:
251
                if "Vertex" in sub or "Edge" in sub:
252
                    shape = Part.getShape(sel.Object, sub, needSubElement=True, retType=0)
253
                    ghosts.append(trackers.ghostTracker(shape))
254
        return ghosts
255

256
    def finish(self, cont=False):
257
        """Terminate the operation.
258

259
        Parameters
260
        ----------
261
        cont: bool or None, optional
262
            Restart (continue) the command if `True`, or if `None` and
263
            `ui.continueMode` is `True`.
264
        """
265
        self.end_callbacks(self.call)
266
        if self.arctrack:
267
            self.arctrack.finalize()
268
        for ghost in self.ghosts:
269
            ghost.finalize()
270
        super().finish()
271
        if cont or (cont is None and self.ui and self.ui.continueMode):
272
            todo.ToDo.delayAfter(self.Activated, [])
273

274
    def rotate(self, is_copy=False):
275
        """Perform the rotation of the subelements or the entire object."""
276
        if self.ui.isSubelementMode.isChecked():
277
            self.rotate_subelements(is_copy)
278
        else:
279
            self.rotate_object(is_copy)
280

281
    def rotate_subelements(self, is_copy):
282
        """Rotate the subelements."""
283
        Gui.addModule("Draft")
284
        try:
285
            if is_copy:
286
                self.commit(translate("draft", "Copy"),
287
                            self.build_copy_subelements_command())
288
            else:
289
                self.commit(translate("draft", "Rotate"),
290
                            self.build_rotate_subelements_command())
291
        except Exception:
292
            _err(translate("draft", "Some subelements could not be moved."))
293

294
    def build_copy_subelements_command(self):
295
        """Build the string to commit to copy the subelements."""
296
        import Part
297

298
        command = []
299
        arguments = []
300
        E = len("Edge")
301
        for obj in self.selected_subelements:
302
            for index, subelement in enumerate(obj.SubObjects):
303
                if not isinstance(subelement, Part.Edge):
304
                    continue
305
                _edge_index = int(obj.SubElementNames[index][E:]) - 1
306
                _cmd = '['
307
                _cmd += 'FreeCAD.ActiveDocument.'
308
                _cmd += obj.ObjectName + ', '
309
                _cmd += str(_edge_index) + ', '
310
                _cmd += str(math.degrees(self.angle)) + ', '
311
                _cmd += DraftVecUtils.toString(self.center) + ', '
312
                _cmd += DraftVecUtils.toString(self.wp.axis)
313
                _cmd += ']'
314
                arguments.append(_cmd)
315

316
        all_args = ', '.join(arguments)
317
        command.append('Draft.copy_rotated_edges([' + all_args + '])')
318
        command.append('FreeCAD.ActiveDocument.recompute()')
319
        return command
320

321
    def build_rotate_subelements_command(self):
322
        """Build the string to commit to rotate the subelements."""
323
        import Part
324

325
        command = []
326
        V = len("Vertex")
327
        E = len("Edge")
328
        for obj in self.selected_subelements:
329
            for index, subelement in enumerate(obj.SubObjects):
330
                if isinstance(subelement, Part.Vertex):
331
                    _vertex_index = int(obj.SubElementNames[index][V:]) - 1
332
                    _cmd = 'Draft.rotate_vertex'
333
                    _cmd += '('
334
                    _cmd += 'FreeCAD.ActiveDocument.'
335
                    _cmd += obj.ObjectName + ', '
336
                    _cmd += str(_vertex_index) + ', '
337
                    _cmd += str(math.degrees(self.angle)) + ', '
338
                    _cmd += DraftVecUtils.toString(self.center) + ', '
339
                    _cmd += DraftVecUtils.toString(self.wp.axis)
340
                    _cmd += ')'
341
                    command.append(_cmd)
342
                elif isinstance(subelement, Part.Edge):
343
                    _edge_index = int(obj.SubElementNames[index][E:]) - 1
344
                    _cmd = 'Draft.rotate_edge'
345
                    _cmd += '('
346
                    _cmd += 'FreeCAD.ActiveDocument.'
347
                    _cmd += obj.ObjectName + ', '
348
                    _cmd += str(_edge_index) + ', '
349
                    _cmd += str(math.degrees(self.angle)) + ', '
350
                    _cmd += DraftVecUtils.toString(self.center) + ', '
351
                    _cmd += DraftVecUtils.toString(self.wp.axis)
352
                    _cmd += ')'
353
                    command.append(_cmd)
354
        command.append('FreeCAD.ActiveDocument.recompute()')
355
        return command
356

357
    def rotate_object(self, is_copy):
358
        """Move the object."""
359
        _doc = 'FreeCAD.ActiveDocument.'
360
        _selected = self.selected_objects
361

362
        objects = '['
363
        objects += ','.join([_doc + obj.Name for obj in _selected])
364
        objects += ']'
365

366
        _cmd = 'Draft.rotate'
367
        _cmd += '('
368
        _cmd += objects + ', '
369
        _cmd += str(math.degrees(self.angle)) + ', '
370
        _cmd += DraftVecUtils.toString(self.center) + ', '
371
        _cmd += 'axis=' + DraftVecUtils.toString(self.wp.axis) + ', '
372
        _cmd += 'copy=' + str(is_copy)
373
        _cmd += ')'
374
        _cmd_list = [_cmd,
375
                     'FreeCAD.ActiveDocument.recompute()']
376

377
        _mode = "Copy" if is_copy else "Rotate"
378
        Gui.addModule("Draft")
379
        self.commit(translate("draft", _mode),
380
                    _cmd_list)
381

382
    def numericInput(self, numx, numy, numz):
383
        """Validate the entry fields in the user interface.
384

385
        This function is called by the toolbar or taskpanel interface
386
        when valid x, y, and z have been entered in the input fields.
387
        """
388
        self.center = App.Vector(numx, numy, numz)
389
        self.node = [self.center]
390
        self.arctrack.setCenter(self.center)
391
        for ghost in self.ghosts:
392
            ghost.center(self.center)
393
        self.ui.radiusUi()
394
        self.ui.hasFill.hide()
395
        self.ui.labelRadius.setText(translate("draft", "Base angle"))
396
        self.ui.radiusValue.setToolTip(translate("draft", "The base angle you wish to start the rotation from"))
397
        self.ui.radiusValue.setText(U.Quantity(0, U.Angle).UserString)
398
        self.step = 1
399
        _toolmsg(translate("draft", "Pick base angle"))
400

401
    def numericRadius(self, rad):
402
        """Validate the radius entry field in the user interface.
403

404
        This function is called by the toolbar or taskpanel interface
405
        when a valid radius has been entered in the input field.
406
        """
407
        if self.step == 1:
408
            self.ui.labelRadius.setText(translate("draft", "Rotation"))
409
            self.ui.radiusValue.setToolTip(translate("draft", "The amount of rotation you wish to perform.\nThe final angle will be the base angle plus this amount."))
410
            self.ui.radiusValue.setText(U.Quantity(0, U.Angle).UserString)
411
            self.firstangle = math.radians(rad)
412
            self.arctrack.setStartAngle(self.firstangle)
413
            self.arctrack.on()
414
            for ghost in self.ghosts:
415
                ghost.on()
416
            self.step = 2
417
            _toolmsg(translate("draft", "Pick rotation angle"))
418
        else:
419
            self.angle = math.radians(rad)
420
            self.rotate(self.ui.isCopy.isChecked())
421
            self.finish(cont=None)
422

423

424
Gui.addCommand('Draft_Rotate', Rotate())
425

426
## @}
427

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.