1 # SPDX-License-Identifier: GPL-2.0-or-later
7 from .fake_entities
import ArcEntity
8 from mathutils
import Vector
, Matrix
, Euler
, Color
9 from math
import pi
, radians
, floor
, ceil
, degrees
10 from copy
import deepcopy
13 class ShortVec(Vector
):
15 return "Vec" + str((round(self
.x
, 2), round(self
.y
, 2), round(self
.z
, 2)))
21 def bspline_to_cubic(do
, en
, curve
, errors
=None):
23 do: an instance of Do()
25 curve: Blender geometry data of type "CURVE"
26 inserts knots until every knot has multiplicity of 3; returns new spline controlpoints
27 if degree of the spline is > 3 "None" is returned
30 start
= knots
[:degree
+ 1]
31 end
= knots
[-degree
- 1:]
33 if start
.count(start
[0]) < degree
+ 1:
35 for i
in range(degree
+ 1):
38 if end
.count(end
[0]) < degree
+ 1:
41 for i
in range(lenk
- degree
- 1, lenk
):
44 def insert_knot(t
, k
, p
):
45 """ http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/NURBS-knot-insert.html """
50 return (t
- ui
) / (uip
- ui
)
52 new_spline
= spline
.copy()
53 for pp
in range(p
, 1, -1):
55 ai
= a(t
, knots
[i
], knots
[i
+ p
])
56 new_spline
[i
] = (1 - ai
) * spline
[i
- 1] + ai
* spline
[i
]
58 ai
= a(t
, knots
[k
], knots
[k
+ p
])
59 new_spline
.insert(k
, (1 - ai
) * spline
[k
- 1] + ai
* spline
[k
% len(spline
)])
64 knots
= list(en
.knots
)
65 spline
= [ShortVec(cp
) for cp
in en
.control_points
]
66 degree
= len(knots
) - len(spline
) - 1
71 while k
< len(knots
) - 1:
73 multilen
= knots
[st
:-st
].count(t
)
76 while multilen
< degree
:
77 spline
= insert_knot(t
, k
, degree
)
85 return quad_to_cube(spline
)
88 if len(spline
) % 3 == 0:
89 spline
.append([spline
[-1]])
90 errors
.add("Cubic spline: Something went wrong with knot insertion")
94 def quad_to_cube(spline
):
96 spline: list of (x,y,z)-tuples)
97 Converts quad bezier to cubic bezier curve.
100 for i
, p
in enumerate(spline
):
102 before
= Vector(spline
[i
- 1])
103 after
= Vector(spline
[(i
+ 1) % len(spline
)])
104 s
.append(before
+ 2 / 3 * (Vector(p
) - before
))
105 s
.append(after
+ 2 / 3 * (Vector(p
) - after
))
115 def bulge_to_arc(point
, next
, bulge
):
117 point: start point of segment in lwpolyline
118 next: end point of segment in lwpolyline
119 bulge: number between 0 and 1
120 Converts a bulge of lwpolyline to an arc with a bulge describing the amount of how much a straight segment should
121 be bended to an arc. With the bulge one can find the center point of the arc that replaces the segment.
124 rot
= Matrix(((0, -1, 0), (1, 0, 0), (0, 0, 1)))
125 section
= next
- point
126 section_length
= section
.length
/ 2
127 direction
= -bulge
/ abs(bulge
)
129 sagitta_len
= section_length
* abs(bulge
)
130 radius
= (sagitta_len
**2 + section_length
**2) / (2 * sagitta_len
)
131 if sagitta_len
< radius
:
132 cosagitta_len
= radius
- sagitta_len
134 cosagitta_len
= sagitta_len
- radius
137 center
= point
+ section
/ 2 + section
.normalized() * cosagitta_len
* direction
@ rot
140 cr
= cp
.to_3d().cross(cn
.to_3d()) * correction
141 start
= Vector((1, 0))
144 startangle
= -start
.angle_signed(cp
.to_2d())
145 endangle
= -start
.angle_signed(cn
.to_2d())
148 startangle
= start
.angle_signed(cp
.to_2d())
149 endangle
= start
.angle_signed(cn
.to_2d())
150 return ArcEntity(startangle
, endangle
, center
.to_3d(), radius
, angdir
)
153 def bulgepoly_to_cubic(do
, lwpolyline
):
156 lwpolyline: DXF entity of type polyline
157 Bulges define how much a straight segment of a polyline should be transformed to an arc. Hence do.arc() is called
158 for segments with a bulge and all segments are being connected to a cubic bezier curve in the end.
159 Reference: http://www.afralisp.net/archive/lisp/Bulges1.htm
161 def handle_segment(last
, point
, bulge
):
162 if bulge
!= 0 and not ((point
- last
).length
== 0 or point
== last
):
163 arc
= bulge_to_arc(last
, point
, bulge
)
164 cubic_bezier
= do
.arc(arc
, None, aunits
=1, angdir
=arc
.angdir
, angbase
=0)
168 section
= point
- last
169 cubic_bezier
= [la
, la
+ section
* 1 / 3, la
+ section
* 2 / 3, po
]
172 points
= lwpolyline
.points
173 bulges
= lwpolyline
.bulge
176 for i
in range(1, lenpo
):
177 spline
+= handle_segment(Vector(points
[i
- 1]), Vector(points
[i
]), bulges
[i
- 1])[:-1]
179 if lwpolyline
.is_closed
:
180 spline
+= handle_segment(Vector(points
[-1]), Vector(points
[0]), bulges
[-1])
182 spline
.append(points
[-1])
186 def bulgepoly_to_lenlist(lwpolyline
):
188 returns a list with the segment lengths of a lwpolyline
190 def handle_segment(last
, point
, bulge
):
191 seglen
= (point
- last
).length
192 if bulge
!= 0 and seglen
!= 0:
193 arc
= bulge_to_arc(last
, point
, bulge
)
194 if arc
.startangle
> arc
.endangle
:
195 arc
.endangle
+= 2 * pi
196 angle
= arc
.endangle
- arc
.startangle
197 lenlist
.append(abs(arc
.radius
* angle
))
199 lenlist
.append(seglen
)
201 points
= lwpolyline
.points
202 bulges
= lwpolyline
.bulge
205 for i
in range(1, lenpo
):
206 handle_segment(Vector(points
[i
- 1][:2]), Vector(points
[i
][:2]), bulges
[i
- 1])
208 if lwpolyline
.is_closed
:
209 handle_segment(Vector(points
[-1][:2]), Vector(points
[0][:2]), bulges
[-1])
214 def extrusion_to_matrix(entity
):
216 Converts an extrusion vector to a rotation matrix that denotes the transformation between world coordinate system
217 and the entity's own coordinate system (described by the extrusion vector).
219 def arbitrary_x_axis(extrusion_normal
):
220 world_y
= Vector((0, 1, 0))
221 world_z
= Vector((0, 0, 1))
222 if abs(extrusion_normal
[0]) < 1 / 64 and abs(extrusion_normal
[1]) < 1 / 64:
223 a_x
= world_y
.cross(extrusion_normal
)
225 a_x
= world_z
.cross(extrusion_normal
)
227 return a_x
, extrusion_normal
.cross(a_x
)
229 az
= Vector(entity
.extrusion
)
230 ax
, ay
= arbitrary_x_axis(az
)
237 translation
= Vector((0, 0, 0, 1))
238 if hasattr(entity
, "elevation"):
239 if type(entity
.elevation
) is tuple:
240 translation
= Vector(entity
.elevation
).to_4d()
242 translation
= (az
* entity
.elevation
).to_4d()
243 return Matrix((ax4
, ay4
, az4
, translation
)).transposed()
246 def split_by_width(entity
):
248 Used to split a curve (polyline, lwpolyline) into smaller segments if their width is varying in the overall curve.
251 def __init__(self
, w
):
255 def __eq__(self
, other
):
256 return self
.w1
== other
.w1
and self
.w2
== other
.w2
and self
.w1
== self
.w2
258 if is_
.varying_width(entity
):
260 en_template
= deepcopy(entity
)
261 en_template
.points
= []
262 en_template
.bulge
= []
263 en_template
.width
= []
264 en_template
.tangents
= []
266 # is_closed is an attrib only on polyline
267 if en_template
.dxftype
== 'POLYLINE':
268 en_template
.is_closed
= False
270 # disable closed flag (0x01) when is_closed is a @property
271 en_template
.flags ^
= 1
274 for pair
, same_width
in itertools
.groupby(entity
.width
, key
=lambda w
: WidthTuple(w
)):
275 en
= deepcopy(en_template
)
276 for segment
in same_width
:
277 en
.points
.append(entity
.points
[i
])
278 en
.points
.append(entity
.points
[(i
+ 1) % len(entity
.points
)])
279 en
.bulge
.append(entity
.bulge
[i
])
280 en
.width
.append(entity
.width
[i
])
284 if not entity
.is_closed
: