FreeCAD

Форк
0
333 строки · 14.9 Кб
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 create offsets from objects.
26

27
It mostly works on lines, polylines, and similar objects with
28
regular geometrical shapes, like rectangles.
29
"""
30
## @package gui_offset
31
# \ingroup draftguitools
32
# \brief Provides GUI tools to create offsets from objects.
33

34
## \addtogroup draftguitools
35
# @{
36
import math
37
from PySide.QtCore import QT_TRANSLATE_NOOP
38

39
import FreeCAD as App
40
import FreeCADGui as Gui
41
import Draft_rc
42
import DraftVecUtils
43
from draftguitools import gui_base_original
44
from draftguitools import gui_tool_utils
45
from draftguitools import gui_trackers as trackers
46
from draftutils import params
47
from draftutils import utils
48
from draftutils.messages import _err, _msg, _toolmsg, _wrn
49
from draftutils.translate import translate
50

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

54

55
class Offset(gui_base_original.Modifier):
56
    """Gui Command for the Offset tool."""
57

58
    def GetResources(self):
59
        """Set icon, menu and tooltip."""
60

61
        return {'Pixmap': 'Draft_Offset',
62
                'Accel': "O, S",
63
                'MenuText': QT_TRANSLATE_NOOP("Draft_Offset", "Offset"),
64
                'ToolTip': QT_TRANSLATE_NOOP("Draft_Offset", "Offsets of the selected object.\nIt can also create an offset copy of the original object.\nCTRL to snap, SHIFT to constrain. Hold ALT and click to create a copy with each click.")}
65

66
    def Activated(self):
67
        """Execute when the command is called."""
68
        self.running = False
69
        super().Activated(name="Offset")
70
        self.ghost = None
71
        self.linetrack = None
72
        self.arctrack = None
73
        if self.ui:
74
            if not Gui.Selection.getSelection():
75
                self.ui.selectUi(on_close_call=self.finish)
76
                _msg(translate("draft", "Select an object to offset"))
77
                self.call = self.view.addEventCallback(
78
                    "SoEvent",
79
                    gui_tool_utils.selectObject)
80
            elif len(Gui.Selection.getSelection()) > 1:
81
                _wrn(translate("draft", "Offset only works "
82
                                        "on one object at a time."))
83
            else:
84
                self.proceed()
85

86
    def proceed(self):
87
        """Proceed with the command if one object was selected."""
88
        if self.call:
89
            self.view.removeEventCallback("SoEvent", self.call)
90
        self.sel = Gui.Selection.getSelection()[0]
91
        if not self.sel.isDerivedFrom("Part::Feature"):
92
            _wrn(translate("draft", "Cannot offset this object type"))
93
            self.finish()
94
        else:
95
            self.step = 0
96
            self.dvec = None
97
            self.npts = None
98
            self.constrainSeg = None
99

100
            self.ui.offsetUi()
101
            occmode = params.get_param("Offset_OCC")
102
            self.ui.occOffset.setChecked(occmode)
103

104
            self.linetrack = trackers.lineTracker()
105
            self.faces = False
106
            self.shape = self.sel.Shape
107
            self.mode = None
108
            if utils.getType(self.sel) in ("Circle", "Arc"):
109
                self.ghost = trackers.arcTracker()
110
                self.mode = "Circle"
111
                self.center = self.shape.Edges[0].Curve.Center
112
                self.ghost.setCenter(self.center)
113
                if self.sel.FirstAngle <= self.sel.LastAngle:
114
                    self.ghost.setStartAngle(math.radians(self.sel.FirstAngle))
115
                else:
116
                    self.ghost.setStartAngle(math.radians(self.sel.FirstAngle) - 2 * math.pi)
117
                self.ghost.setEndAngle(math.radians(self.sel.LastAngle))
118
            elif utils.getType(self.sel) == "BSpline":
119
                self.ghost = trackers.bsplineTracker(points=self.sel.Points)
120
                self.mode = "BSpline"
121
            elif utils.getType(self.sel) == "BezCurve":
122
                _wrn(translate("draft", "Offset of Bezier curves "
123
                                        "is currently not supported"))
124
                self.finish()
125
                return
126
            else:
127
                if len(self.sel.Shape.Edges) == 1:
128
                    import Part
129

130
                    if isinstance(self.sel.Shape.Edges[0].Curve, Part.Circle):
131
                        self.ghost = trackers.arcTracker()
132
                        self.mode = "Circle"
133
                        self.center = self.shape.Edges[0].Curve.Center
134
                        self.ghost.setCenter(self.center)
135
                        if len(self.sel.Shape.Vertexes) > 1:
136
                            _edge = self.sel.Shape.Edges[0]
137
                            self.ghost.setStartAngle(_edge.FirstParameter)
138
                            self.ghost.setEndAngle(_edge.LastParameter)
139
                if not self.ghost:
140
                    self.ghost = trackers.wireTracker(self.shape)
141
                    self.mode = "Wire"
142
            self.call = self.view.addEventCallback("SoEvent", self.action)
143
            _toolmsg(translate("draft", "Pick distance"))
144
            if self.planetrack:
145
                self.planetrack.set(self.shape.Vertexes[0].Point)
146
            self.running = True
147

148
    def action(self, arg):
149
        """Handle the 3D scene events.
150

151
        This is installed as an EventCallback in the Inventor view.
152

153
        Parameters
154
        ----------
155
        arg: dict
156
            Dictionary with strings that indicates the type of event received
157
            from the 3D view.
158
        """
159
        import DraftGeomUtils
160

161
        if arg["Type"] == "SoKeyboardEvent":
162
            if arg["Key"] == "ESCAPE":
163
                self.finish()
164
        elif arg["Type"] == "SoLocation2Event":
165
            self.point, ctrlPoint, info = gui_tool_utils.getPoint(self, arg)
166
            if (gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_constrain_key())
167
                    and self.constrainSeg):
168
                dist = DraftGeomUtils.findPerpendicular(self.point,
169
                                                        self.shape,
170
                                                        self.constrainSeg[1])
171
            else:
172
                dist = DraftGeomUtils.findPerpendicular(self.point,
173
                                                        self.shape.Edges)
174
            if dist:
175
                self.ghost.on()
176
                if self.mode == "Wire":
177
                    d = dist[0].negative()
178
                    v1 = DraftGeomUtils.getTangent(self.shape.Edges[0],
179
                                                   self.point)
180
                    v2 = DraftGeomUtils.getTangent(self.shape.Edges[dist[1]],
181
                                                   self.point)
182
                    a = -DraftVecUtils.angle(v1, v2, self.wp.axis)
183
                    self.dvec = DraftVecUtils.rotate(d, a, self.wp.axis)
184
                    occmode = self.ui.occOffset.isChecked()
185
                    params.set_param("Offset_OCC", occmode)
186
                    _wire = DraftGeomUtils.offsetWire(self.shape,
187
                                                      self.dvec,
188
                                                      occ=occmode)
189
                    self.ghost.update(_wire, forceclosed=occmode)
190
                elif self.mode == "BSpline":
191
                    d = dist[0].negative()
192
                    e = self.shape.Edges[0]
193
                    basetan = DraftGeomUtils.getTangent(e, self.point)
194
                    self.npts = []
195
                    for p in self.sel.Points:
196
                        currtan = DraftGeomUtils.getTangent(e, p)
197
                        a = -DraftVecUtils.angle(currtan, basetan, self.wp.axis)
198
                        self.dvec = DraftVecUtils.rotate(d, a, self.wp.axis)
199
                        self.npts.append(p.add(self.dvec))
200
                    self.ghost.update(self.npts)
201
                elif self.mode == "Circle":
202
                    self.dvec = self.point.sub(self.center).Length
203
                    self.ghost.setRadius(self.dvec)
204
                self.constrainSeg = dist
205
                self.linetrack.on()
206
                self.linetrack.p1(self.point)
207
                self.linetrack.p2(self.point.add(dist[0]))
208
                self.ui.setRadiusValue(dist[0].Length, unit="Length")
209
            else:
210
                self.dvec = None
211
                self.ghost.off()
212
                self.constrainSeg = None
213
                self.linetrack.off()
214
                self.ui.radiusValue.setText("off")
215
            self.ui.radiusValue.setFocus()
216
            self.ui.radiusValue.selectAll()
217
            if self.extendedCopy:
218
                if not gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_alt_key()):
219
                    self.finish()
220
            gui_tool_utils.redraw3DView()
221

222
        elif arg["Type"] == "SoMouseButtonEvent":
223
            if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
224
                copymode = False
225
                occmode = self.ui.occOffset.isChecked()
226
                params.set_param("Offset_OCC", occmode)
227
                if (gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_alt_key())
228
                        or self.ui.isCopy.isChecked()):
229
                    copymode = True
230
                Gui.addModule("Draft")
231
                if self.npts:
232
                    _cmd = 'Draft.offset'
233
                    _cmd += '('
234
                    _cmd += 'FreeCAD.ActiveDocument.'
235
                    _cmd += self.sel.Name + ', '
236
                    _cmd += DraftVecUtils.toString(self.npts) + ', '
237
                    _cmd += 'copy=' + str(copymode)
238
                    _cmd += ')'
239
                    _cmd_list = ['offst = ' + _cmd,
240
                                 'FreeCAD.ActiveDocument.recompute()']
241
                    self.commit(translate("draft", "Offset"),
242
                                _cmd_list)
243
                elif self.dvec:
244
                    if isinstance(self.dvec, float):
245
                        delta = str(self.dvec)
246
                    else:
247
                        delta = DraftVecUtils.toString(self.dvec)
248
                    _cmd = 'Draft.offset'
249
                    _cmd += '('
250
                    _cmd += 'FreeCAD.ActiveDocument.'
251
                    _cmd += self.sel.Name + ', '
252
                    _cmd += delta + ', '
253
                    _cmd += 'copy=' + str(copymode) + ', '
254
                    _cmd += 'occ=' + str(occmode)
255
                    _cmd += ')'
256
                    _cmd_list = ['offst = ' + _cmd,
257
                                 'FreeCAD.ActiveDocument.recompute()']
258
                    self.commit(translate("draft", "Offset"),
259
                                _cmd_list)
260
                if gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_alt_key()):
261
                    self.extendedCopy = True
262
                else:
263
                    self.finish()
264

265
    def finish(self, cont=False):
266
        """Finish the offset operation."""
267
        self.end_callbacks(self.call)
268
        if self.running:
269
            if self.linetrack:
270
                self.linetrack.finalize()
271
            if self.ghost:
272
                self.ghost.finalize()
273
        super().finish()
274

275
    def numericRadius(self, rad):
276
        """Validate the radius entry field in the user interface.
277

278
        This function is called by the toolbar or taskpanel interface
279
        when a valid radius has been entered in the input field.
280
        """
281
        # print("dvec:", self.dvec)
282
        # print("rad:", rad)
283
        if self.dvec:
284
            if isinstance(self.dvec, float):
285
                if self.mode == "Circle":
286
                    r1 = self.shape.Edges[0].Curve.Radius
287
                    r2 = self.ghost.getRadius()
288
                    if r2 >= r1:
289
                        rad = r1 + rad
290
                    else:
291
                        rad = r1 - rad
292
                    delta = str(rad)
293
                else:
294
                    _err("Draft.Offset error: Unhandled case")
295
            # to offset bspline
296
            elif self.mode == "BSpline":
297
                new_points = []
298
                for old_point, new_point in zip(self.sel.Points, self.npts):
299
                    diff_direction = new_point.sub(old_point).normalize()
300
                    new_points.append(old_point.add(diff_direction*rad))
301
                delta = DraftVecUtils.toString(new_points)
302
            else:
303
                self.dvec.normalize()
304
                self.dvec.multiply(rad)
305
                delta = DraftVecUtils.toString(self.dvec)
306
            copymode = False
307
            occmode = self.ui.occOffset.isChecked()
308
            params.set_param("Offset_OCC", occmode)
309

310
            if self.ui.isCopy.isChecked():
311
                copymode = True
312
            Gui.addModule("Draft")
313
            _cmd = 'Draft.offset'
314
            _cmd += '('
315
            _cmd += 'FreeCAD.ActiveDocument.'
316
            _cmd += self.sel.Name + ', '
317
            _cmd += delta + ', '
318
            _cmd += 'copy=' + str(copymode) + ', '
319
            _cmd += 'occ=' + str(occmode)
320
            _cmd += ')'
321
            _cmd_list = ['offst = ' + _cmd,
322
                         'FreeCAD.ActiveDocument.recompute()']
323
            self.commit(translate("draft", "Offset"),
324
                        _cmd_list)
325
            self.finish()
326
        else:
327
            _err(translate("Draft",
328
                           "Offset direction is not defined. Please move the mouse on either side of the object first to indicate a direction"))
329

330

331
Gui.addCommand('Draft_Offset', Offset())
332

333
## @}
334

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

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

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

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