File size: 8,499 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
# 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 the object code for the BezCurve object."""
## @package bezcurve
# \ingroup draftobjects
# \brief Provides the object code for the BezCurve object.

## \addtogroup draftobjects
# @{
from PySide.QtCore import QT_TRANSLATE_NOOP

import FreeCAD as App
from draftutils import gui_utils
from draftutils import params
from draftobjects.base import DraftObject


class BezCurve(DraftObject):
    """The BezCurve object"""

    def __init__(self, obj):
        super().__init__(obj, "BezCurve")

        _tip = QT_TRANSLATE_NOOP("App::Property", "The points of the Bezier curve")
        obj.addProperty("App::PropertyVectorList", "Points", "Draft", _tip, locked=True)

        _tip = QT_TRANSLATE_NOOP("App::Property", "The degree of the Bezier function")
        obj.addProperty("App::PropertyInteger", "Degree", "Draft", _tip, locked=True)

        _tip = QT_TRANSLATE_NOOP("App::Property", "Continuity")
        obj.addProperty("App::PropertyIntegerList", "Continuity", "Draft", _tip, locked=True)

        _tip = QT_TRANSLATE_NOOP("App::Property", "If the Bezier curve should be closed or not")
        obj.addProperty("App::PropertyBool", "Closed", "Draft", _tip, locked=True)

        _tip = QT_TRANSLATE_NOOP("App::Property", "Create a face if this curve is closed")
        obj.addProperty("App::PropertyBool", "MakeFace", "Draft", _tip, locked=True)

        _tip = QT_TRANSLATE_NOOP("App::Property", "The length of this object")
        obj.addProperty("App::PropertyLength", "Length", "Draft", _tip, locked=True)

        _tip = QT_TRANSLATE_NOOP("App::Property", "The area of this object")
        obj.addProperty("App::PropertyArea", "Area", "Draft", _tip, locked=True)

        obj.MakeFace = params.get_param("MakeFaceMode")
        obj.Closed = False
        obj.Degree = 3
        obj.Continuity = []
        # obj.setEditorMode("Degree", 2)
        obj.setEditorMode("Continuity", 1)

    def onDocumentRestored(self, obj):
        super().onDocumentRestored(obj)
        gui_utils.restore_view_object(
            obj, vp_module="view_bezcurve", vp_class="ViewProviderBezCurve"
        )

    def execute(self, fp):
        if self.props_changed_placement_only():
            fp.positionBySupport()
            self.props_changed_clear()
            return

        self.createGeometry(fp)
        fp.positionBySupport()
        self.props_changed_clear()

    def _segpoleslst(self, fp):
        """Split the points into segments."""
        if not fp.Closed and len(fp.Points) >= 2:  # allow lower degree segment
            poles = fp.Points[1:]
        elif fp.Closed and len(fp.Points) >= fp.Degree:  # drawable
            # poles=fp.Points[1:(fp.Degree*(len(fp.Points)//fp.Degree))]+fp.Points[0:1]
            poles = fp.Points[1:] + fp.Points[0:1]
        else:
            poles = []
        return [poles[x : x + fp.Degree] for x in range(0, len(poles), (fp.Degree or 1))]

    def resetcontinuity(self, fp):
        fp.Continuity = [0] * (len(self._segpoleslst(fp)) - 1 + 1 * fp.Closed)
        # nump= len(fp.Points)-1+fp.Closed*1
        # numsegments = (nump // fp.Degree) + 1 * (nump % fp.Degree > 0) -1
        # fp.Continuity = [0]*numsegments

    def onChanged(self, fp, prop):
        self.props_changed_store(prop)

        if prop == "Closed":
            # if remove the last entry when curve gets opened
            oldlen = len(fp.Continuity)
            newlen = len(self._segpoleslst(fp)) - 1 + 1 * fp.Closed
            if oldlen > newlen:
                fp.Continuity = fp.Continuity[:newlen]
            if oldlen < newlen:
                fp.Continuity = fp.Continuity + [0] * (newlen - oldlen)

        if (
            hasattr(fp, "Closed")
            and fp.Closed
            and prop in ["Points", "Degree", "Closed"]
            and len(fp.Points) % fp.Degree
        ):
            # the curve editing tools can't handle extra points
            fp.Points = fp.Points[: (fp.Degree * (len(fp.Points) // fp.Degree))]
            # for closed curves

        if prop in ["Degree"] and fp.Degree >= 1:
            self.resetcontinuity(fp)

        if prop in ["Points", "Degree", "Continuity", "Closed"]:
            self.createGeometry(fp)

    def createGeometry(self, fp):
        import Part

        plm = fp.Placement
        if fp.Points:
            startpoint = fp.Points[0]
            edges = []
            for segpoles in self._segpoleslst(fp):
                #               if len(segpoles) == fp.Degree # would skip additional poles
                c = Part.BezierCurve()  # last segment may have lower degree
                c.increase(len(segpoles))
                c.setPoles([startpoint] + segpoles)
                edges.append(Part.Edge(c))
                startpoint = segpoles[-1]
            w = Part.Wire(edges)
            if fp.Closed and w.isClosed():
                try:
                    if hasattr(fp, "MakeFace"):
                        if fp.MakeFace:
                            w = Part.Face(w)
                    else:
                        w = Part.Face(w)
                except Part.OCCError:
                    pass
            fp.Shape = w
            if hasattr(fp, "Area") and hasattr(w, "Area"):
                fp.Area = w.Area
            if hasattr(fp, "Length") and hasattr(w, "Length"):
                fp.Length = w.Length
        fp.Placement = plm

    @classmethod
    def symmetricpoles(cls, knot, p1, p2):
        """Make two poles symmetric respective to the knot."""
        p1h = App.Vector(p1)
        p2h = App.Vector(p2)
        p1h.multiply(0.5)
        p2h.multiply(0.5)
        return (knot + p1h - p2h, knot + p2h - p1h)

    @classmethod
    def tangentpoles(cls, knot, p1, p2, allowsameside=False):
        """Make two poles have the same tangent at knot."""
        p12n = p2.sub(p1)
        p12n.normalize()
        p1k = knot - p1
        p2k = knot - p2
        p1k_ = App.Vector(p12n)
        kon12 = p1k * p12n
        if allowsameside or not (kon12 < 0 or p2k * p12n > 0):  # instead of moving
            p1k_.multiply(kon12)
            pk_k = knot - p1 - p1k_
            return (p1 + pk_k, p2 + pk_k)
        else:
            return cls.symmetricpoles(knot, p1, p2)

    @staticmethod
    def modifysymmetricpole(knot, p1):
        """calculate the coordinates of the opposite pole
        of a symmetric knot"""
        return knot + knot - p1

    @staticmethod
    def modifytangentpole(knot, p1, oldp2):
        """calculate the coordinates of the opposite pole
        of a tangent knot"""
        pn = knot - p1
        pn.normalize()
        pn.multiply((knot - oldp2).Length)
        return pn + knot


# Alias for compatibility with v0.18 and earlier
_BezCurve = BezCurve

## @}