File size: 10,323 Bytes
985c397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# SPDX-License-Identifier: LGPL-2.1-or-later

# ***************************************************************************
# *   Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net>        *
# *   Copyright (c) 2009, 2010 Ken Cline <cline@frii.com>                   *
# *   Copyright (c) 2020 FreeCAD Developers                                 *
# *                                                                         *
# *   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.                                 *
# *                                                                         *
# *   This program 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 this program; if not, write to the Free Software   *
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************
"""Provides functions to create offsets of different shapes."""
## @package offset
# \ingroup draftfunctions
# \brief Provides functions to create offsets of different shapes.

## \addtogroup draftfunctions
# @{
import math

import FreeCAD as App
import DraftVecUtils
from draftutils import gui_utils
from draftutils import params
from draftutils import utils

from draftmake.make_rectangle import make_rectangle
from draftmake.make_wire import make_wire
from draftmake.make_polygon import make_polygon
from draftmake.make_circle import make_circle
from draftmake.make_bspline import make_bspline


def offset(obj, delta, copy=False, bind=False, sym=False, occ=False):
    """offset(object,delta,[copymode],[bind])

    Offset the given wire by applying the given delta Vector to its first
    vertex.

    Parameters
    ----------
    obj :

    delta : Base.Vector or list of Base.Vector
        If offsetting a BSpline, the delta must not be a Vector but a list
        of Vectors, one for each node of the spline.

    copy : bool
        If copymode is True, another object is created, otherwise the same
        object gets offsetted.

    copy : bool
        If bind is True, and provided the wire is open, the original
        and the offset wires will be bound by their endpoints, forming a face.

    sym : bool
        if sym is True, bind must be true too, and the offset is made on both
        sides, the total width being the given delta length.
    """
    import Part
    import DraftGeomUtils

    newwire = None
    delete = None

    if copy is False and (
        utils.get_type(obj).startswith("Sketcher::")
        or utils.get_type(obj).startswith("Part::")
        or utils.get_type(obj).startswith("PartDesign::")
    ):  # For PartDesign_SubShapeBinders which can reference sketches.
        print(
            "the offset tool is currently unable to offset a non-Draft object directly - Creating a copy"
        )
        copy = True

    def getRect(p, obj):
        """returns length,height,placement"""
        pl = obj.Placement.copy()
        pl.Base = p[0]
        diag = p[2].sub(p[0])
        bb = p[1].sub(p[0])
        bh = p[3].sub(p[0])
        nb = DraftVecUtils.project(diag, bb)
        nh = DraftVecUtils.project(diag, bh)
        if obj.Length.Value < 0:
            l = -nb.Length
        else:
            l = nb.Length
        if obj.Height.Value < 0:
            h = -nh.Length
        else:
            h = nh.Length
        return l, h, pl

    def getRadius(obj, delta):
        """returns a new radius for a regular polygon"""
        an = math.pi / obj.FacesNumber
        nr = DraftVecUtils.rotate(delta, -an)
        nr.multiply(1 / math.cos(an))
        nr = obj.Shape.Vertexes[0].Point.add(nr)
        nr = nr.sub(obj.Placement.Base)
        nr = nr.Length
        if obj.DrawMode == "inscribed":
            return nr
        else:
            return nr * math.cos(math.pi / obj.FacesNumber)

    newwire = None
    if utils.get_type(obj) == "Circle":
        pass
    elif utils.get_type(obj) == "BSpline":
        pass
    else:
        if sym:
            d1 = App.Vector(delta).multiply(0.5)
            d2 = d1.negative()
            n1 = DraftGeomUtils.offsetWire(obj.Shape, d1)
            n2 = DraftGeomUtils.offsetWire(obj.Shape, d2)
        else:
            if isinstance(delta, float) and (len(obj.Shape.Edges) == 1):
                # circle
                c = obj.Shape.Edges[0].Curve
                nc = Part.Circle(c.Center, c.Axis, delta)
                if len(obj.Shape.Vertexes) > 1:
                    nc = Part.ArcOfCircle(
                        nc, obj.Shape.Edges[0].FirstParameter, obj.Shape.Edges[0].LastParameter
                    )
                newwire = Part.Wire(nc.toShape())
                p = []
            else:
                newwire = DraftGeomUtils.offsetWire(obj.Shape, delta)
                if DraftGeomUtils.hasCurves(newwire) and copy:
                    p = []
                else:
                    p = DraftGeomUtils.getVerts(newwire)
    if occ:
        newobj = App.ActiveDocument.addObject("Part::Feature", "Offset")
        newobj.Shape = DraftGeomUtils.offsetWire(obj.Shape, delta, occ=True)
        gui_utils.formatObject(newobj, obj)
        if not copy:
            delete = obj.Name
    elif bind:
        if not DraftGeomUtils.isReallyClosed(obj.Shape):
            if sym:
                s1 = n1
                s2 = n2
            else:
                s1 = obj.Shape
                s2 = newwire
            if s1 and s2:
                w1 = s1.Edges
                w2 = s2.Edges
                w3 = Part.LineSegment(s1.Vertexes[0].Point, s2.Vertexes[0].Point).toShape()
                w4 = Part.LineSegment(s1.Vertexes[-1].Point, s2.Vertexes[-1].Point).toShape()
                newobj = App.ActiveDocument.addObject("Part::Feature", "Offset")
                newobj.Shape = Part.Face(Part.Wire(w1 + [w3] + w2 + [w4]))
            else:
                print("Draft.offset: Unable to bind wires")
        else:
            newobj = App.ActiveDocument.addObject("Part::Feature", "Offset")
            newobj.Shape = Part.Face(obj.Shape.Wires[0])
        if not copy:
            delete = obj.Name
    elif copy:
        newobj = None
        if sym:
            return None
        if utils.get_type(obj) == "Wire":
            if p:
                newobj = make_wire(p)
                newobj.Closed = obj.Closed
            elif newwire:
                newobj = App.ActiveDocument.addObject("Part::Feature", "Offset")
                newobj.Shape = newwire
            else:
                print("Draft.offset: Unable to duplicate this object")
        elif utils.get_type(obj) == "Rectangle":
            if p:
                length, height, plac = getRect(p, obj)
                newobj = make_rectangle(length, height, plac)
            elif newwire:
                newobj = App.ActiveDocument.addObject("Part::Feature", "Offset")
                newobj.Shape = newwire
            else:
                print("Draft.offset: Unable to duplicate this object")
        elif utils.get_type(obj) == "Circle":
            pl = obj.Placement
            newobj = make_circle(delta)
            newobj.FirstAngle = obj.FirstAngle
            newobj.LastAngle = obj.LastAngle
            newobj.Placement = pl
        elif utils.get_type(obj) == "Polygon":
            pl = obj.Placement
            newobj = make_polygon(obj.FacesNumber)
            newobj.Radius = getRadius(obj, delta)
            newobj.DrawMode = obj.DrawMode
            newobj.Placement = pl
        elif utils.get_type(obj) == "BSpline":
            newobj = make_bspline(delta)
            newobj.Closed = obj.Closed
        else:
            # try to offset anyway
            try:
                if p:
                    newobj = make_wire(p)
                    newobj.Closed = DraftGeomUtils.isReallyClosed(obj.Shape)
            except Part.OCCError:
                pass
            if (not newobj) and newwire:
                newobj = App.ActiveDocument.addObject("Part::Feature", "Offset")
                newobj.Shape = newwire
            if not newobj:
                print("Draft.offset: Unable to create an offset")
        if newobj:
            gui_utils.formatObject(newobj, obj)
    else:
        newobj = None
        if sym:
            return None
        if utils.get_type(obj) == "Wire":
            if obj.Base or obj.Tool:
                App.Console.PrintWarning("Warning: object history removed\n")
                obj.Base = None
                obj.Tool = None
            obj.Placement = App.Placement()  # p points are in the global coordinate system
            obj.Points = p
        elif utils.get_type(obj) == "BSpline":
            # print(delta)
            obj.Points = delta
            # print("done")
        elif utils.get_type(obj) == "Rectangle":
            length, height, plac = getRect(p, obj)
            obj.Placement = plac
            obj.Length = length
            obj.Height = height
        elif utils.get_type(obj) == "Circle":
            obj.Radius = delta
        elif utils.get_type(obj) == "Polygon":
            obj.Radius = getRadius(obj, delta)
        elif utils.get_type(obj) == "Part":
            print("unsupported object")  # TODO
        newobj = obj
    if copy and params.get_param("selectBaseObjects"):
        gui_utils.select(obj)
    else:
        gui_utils.select(newobj)
    if delete:
        App.ActiveDocument.removeObject(delete)
    return newobj


## @}