1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
23 from .fake_entities
import ArcEntity
24 from mathutils
import Vector
, Matrix
, Euler
, Color
25 from math
import pi
, radians
, floor
, ceil
, degrees
26 from copy
import deepcopy
29 class ShortVec(Vector
):
31 return "Vec" + str((round(self
.x
, 2), round(self
.y
, 2), round(self
.z
, 2)))
37 def bspline_to_cubic(do
, en
, curve
, errors
=None):
39 do: an instance of Do()
41 curve: Blender geometry data of type "CURVE"
42 inserts knots until every knot has multiplicity of 3; returns new spline controlpoints
43 if degree of the spline is > 3 "None" is returned
46 start
= knots
[:degree
+ 1]
47 end
= knots
[-degree
- 1:]
49 if start
.count(start
[0]) < degree
+ 1:
51 for i
in range(degree
+ 1):
54 if end
.count(end
[0]) < degree
+ 1:
57 for i
in range(lenk
- degree
- 1, lenk
):
60 def insert_knot(t
, k
, p
):
61 """ http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/NURBS-knot-insert.html """
66 return (t
- ui
) / (uip
- ui
)
68 new_spline
= spline
.copy()
69 for pp
in range(p
, 1, -1):
71 ai
= a(t
, knots
[i
], knots
[i
+ p
])
72 new_spline
[i
] = (1 - ai
) * spline
[i
- 1] + ai
* spline
[i
]
74 ai
= a(t
, knots
[k
], knots
[k
+ p
])
75 new_spline
.insert(k
, (1 - ai
) * spline
[k
- 1] + ai
* spline
[k
% len(spline
)])
80 knots
= list(en
.knots
)
81 spline
= [ShortVec(cp
) for cp
in en
.control_points
]
82 degree
= len(knots
) - len(spline
) - 1
87 while k
< len(knots
) - 1:
89 multilen
= knots
[st
:-st
].count(t
)
92 while multilen
< degree
:
93 spline
= insert_knot(t
, k
, degree
)
101 return quad_to_cube(spline
)
104 if len(spline
) % 3 == 0:
105 spline
.append([spline
[-1]])
106 errors
.add("Cubic spline: Something went wrong with knot insertion")
110 def quad_to_cube(spline
):
112 spline: list of (x,y,z)-tuples)
113 Converts quad bezier to cubic bezier curve.
116 for i
, p
in enumerate(spline
):
118 before
= Vector(spline
[i
- 1])
119 after
= Vector(spline
[(i
+ 1) % len(spline
)])
120 s
.append(before
+ 2 / 3 * (Vector(p
) - before
))
121 s
.append(after
+ 2 / 3 * (Vector(p
) - after
))
131 def bulge_to_arc(point
, next
, bulge
):
133 point: start point of segment in lwpolyline
134 next: end point of segment in lwpolyline
135 bulge: number between 0 and 1
136 Converts a bulge of lwpolyline to an arc with a bulge describing the amount of how much a straight segment should
137 be bended to an arc. With the bulge one can find the center point of the arc that replaces the segment.
140 rot
= Matrix(((0, -1, 0), (1, 0, 0), (0, 0, 1)))
141 section
= next
- point
142 section_length
= section
.length
/ 2
143 direction
= -bulge
/ abs(bulge
)
145 sagitta_len
= section_length
* abs(bulge
)
146 radius
= (sagitta_len
**2 + section_length
**2) / (2 * sagitta_len
)
147 if sagitta_len
< radius
:
148 cosagitta_len
= radius
- sagitta_len
150 cosagitta_len
= sagitta_len
- radius
153 center
= point
+ section
/ 2 + section
.normalized() * cosagitta_len
* direction
@ rot
156 cr
= cp
.to_3d().cross(cn
.to_3d()) * correction
157 start
= Vector((1, 0))
160 startangle
= -start
.angle_signed(cp
.to_2d())
161 endangle
= -start
.angle_signed(cn
.to_2d())
164 startangle
= start
.angle_signed(cp
.to_2d())
165 endangle
= start
.angle_signed(cn
.to_2d())
166 return ArcEntity(startangle
, endangle
, center
.to_3d(), radius
, angdir
)
169 def bulgepoly_to_cubic(do
, lwpolyline
):
172 lwpolyline: DXF entity of type polyline
173 Bulges define how much a straight segment of a polyline should be transformed to an arc. Hence do.arc() is called
174 for segments with a bulge and all segments are being connected to a cubic bezier curve in the end.
175 Reference: http://www.afralisp.net/archive/lisp/Bulges1.htm
177 def handle_segment(last
, point
, bulge
):
178 if bulge
!= 0 and not ((point
- last
).length
== 0 or point
== last
):
179 arc
= bulge_to_arc(last
, point
, bulge
)
180 cubic_bezier
= do
.arc(arc
, None, aunits
=1, angdir
=arc
.angdir
, angbase
=0)
184 section
= point
- last
185 cubic_bezier
= [la
, la
+ section
* 1 / 3, la
+ section
* 2 / 3, po
]
188 points
= lwpolyline
.points
189 bulges
= lwpolyline
.bulge
192 for i
in range(1, lenpo
):
193 spline
+= handle_segment(Vector(points
[i
- 1]), Vector(points
[i
]), bulges
[i
- 1])[:-1]
195 if lwpolyline
.is_closed
:
196 spline
+= handle_segment(Vector(points
[-1]), Vector(points
[0]), bulges
[-1])
198 spline
.append(points
[-1])
202 def bulgepoly_to_lenlist(lwpolyline
):
204 returns a list with the segment lengths of a lwpolyline
206 def handle_segment(last
, point
, bulge
):
207 seglen
= (point
- last
).length
208 if bulge
!= 0 and seglen
!= 0:
209 arc
= bulge_to_arc(last
, point
, bulge
)
210 if arc
.startangle
> arc
.endangle
:
211 arc
.endangle
+= 2 * pi
212 angle
= arc
.endangle
- arc
.startangle
213 lenlist
.append(abs(arc
.radius
* angle
))
215 lenlist
.append(seglen
)
217 points
= lwpolyline
.points
218 bulges
= lwpolyline
.bulge
221 for i
in range(1, lenpo
):
222 handle_segment(Vector(points
[i
- 1][:2]), Vector(points
[i
][:2]), bulges
[i
- 1])
224 if lwpolyline
.is_closed
:
225 handle_segment(Vector(points
[-1][:2]), Vector(points
[0][:2]), bulges
[-1])
230 def extrusion_to_matrix(entity
):
232 Converts an extrusion vector to a rotation matrix that denotes the transformation between world coordinate system
233 and the entity's own coordinate system (described by the extrusion vector).
235 def arbitrary_x_axis(extrusion_normal
):
236 world_y
= Vector((0, 1, 0))
237 world_z
= Vector((0, 0, 1))
238 if abs(extrusion_normal
[0]) < 1 / 64 and abs(extrusion_normal
[1]) < 1 / 64:
239 a_x
= world_y
.cross(extrusion_normal
)
241 a_x
= world_z
.cross(extrusion_normal
)
243 return a_x
, extrusion_normal
.cross(a_x
)
245 az
= Vector(entity
.extrusion
)
246 ax
, ay
= arbitrary_x_axis(az
)
253 translation
= Vector((0, 0, 0, 1))
254 if hasattr(entity
, "elevation"):
255 if type(entity
.elevation
) is tuple:
256 translation
= Vector(entity
.elevation
).to_4d()
258 translation
= (az
* entity
.elevation
).to_4d()
259 return Matrix((ax4
, ay4
, az4
, translation
)).transposed()
262 def split_by_width(entity
):
264 Used to split a curve (polyline, lwpolyline) into smaller segments if their width is varying in the overall curve.
267 def __init__(self
, w
):
271 def __eq__(self
, other
):
272 return self
.w1
== other
.w1
and self
.w2
== other
.w2
and self
.w1
== self
.w2
274 if is_
.varying_width(entity
):
276 en_template
= deepcopy(entity
)
277 en_template
.points
= []
278 en_template
.bulge
= []
279 en_template
.width
= []
280 en_template
.tangents
= []
282 # is_closed is an attrib only on polyline
283 if en_template
.dxftype
== 'POLYLINE':
284 en_template
.is_closed
= False
286 # disable closed flag (0x01) when is_closed is a @property
287 en_template
.flags ^
= 1
290 for pair
, same_width
in itertools
.groupby(entity
.width
, key
=lambda w
: WidthTuple(w
)):
291 en
= deepcopy(en_template
)
292 for segment
in same_width
:
293 en
.points
.append(entity
.points
[i
])
294 en
.points
.append(entity
.points
[(i
+ 1) % len(entity
.points
)])
295 en
.bulge
.append(entity
.bulge
[i
])
296 en
.width
.append(entity
.width
[i
])
300 if not entity
.is_closed
: