Fix T60879: DXF Import: Can't Import Polylines
[blender-addons.git] / io_import_dxf / dxfimport / convert.py
blobf3f4be837ed7f6d2a68d25089fa715911f037a14
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 #####
19 # <pep8 compliant>
21 import itertools
22 from . import is_
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):
30 def __str__(self):
31 return "Vec" + str((round(self.x, 2), round(self.y, 2), round(self.z, 2)))
33 def __repr__(self):
34 return self.__str__()
37 def bspline_to_cubic(do, en, curve, errors=None):
38 """
39 do: an instance of Do()
40 en: a DXF entity
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
44 """
45 def clean_knots():
46 start = knots[:degree + 1]
47 end = knots[-degree - 1:]
49 if start.count(start[0]) < degree + 1:
50 maxa = max(start)
51 for i in range(degree + 1):
52 knots[i] = maxa
54 if end.count(end[0]) < degree + 1:
55 mina = min(end)
56 lenk = len(knots)
57 for i in range(lenk - degree - 1, lenk):
58 knots[i] = mina
60 def insert_knot(t, k, p):
61 """ http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/NURBS-knot-insert.html """
62 def a(t, ui, uip):
63 if uip == ui:
64 print("zero!")
65 return 0
66 return (t - ui) / (uip - ui)
68 new_spline = spline.copy()
69 for pp in range(p, 1, -1):
70 i = k - pp + 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)])
76 knots.insert(k, t)
78 return new_spline
80 knots = list(en.knots)
81 spline = [ShortVec(cp) for cp in en.control_points]
82 degree = len(knots) - len(spline) - 1
83 if degree <= 3:
84 clean_knots()
85 k = 1
86 st = 1
87 while k < len(knots) - 1:
88 t = knots[k]
89 multilen = knots[st:-st].count(t)
90 if multilen < degree:
91 before = multilen
92 while multilen < degree:
93 spline = insert_knot(t, k, degree)
94 multilen += 1
95 k += 1
96 k += before
97 else:
98 k += degree
100 if degree <= 2:
101 return quad_to_cube(spline)
103 # the ugly truth
104 if len(spline) % 3 == 0:
105 spline.append([spline[-1]])
106 errors.add("Cubic spline: Something went wrong with knot insertion")
107 return spline
110 def quad_to_cube(spline):
112 spline: list of (x,y,z)-tuples)
113 Converts quad bezier to cubic bezier curve.
115 s = []
116 for i, p in enumerate(spline):
117 if i % 2 == 1:
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))
122 else:
123 s.append(p)
125 # degree == 1
126 if len(spline) == 2:
127 s.append(spline[-1])
128 return s
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)
144 correction = 1
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
149 else:
150 cosagitta_len = sagitta_len - radius
151 direction *= -1
152 correction *= -1
153 center = point + section / 2 + section.normalized() * cosagitta_len * direction @ rot
154 cp = point - center
155 cn = next - center
156 cr = cp.to_3d().cross(cn.to_3d()) * correction
157 start = Vector((1, 0))
158 if cr[2] > 0:
159 angdir = 0
160 startangle = -start.angle_signed(cp.to_2d())
161 endangle = -start.angle_signed(cn.to_2d())
162 else:
163 angdir = 1
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):
171 do: instance of Do()
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)
181 else:
182 la = last.to_3d()
183 po = point.to_3d()
184 section = point - last
185 cubic_bezier = [la, la + section * 1 / 3, la + section * 2 / 3, po]
186 return cubic_bezier
188 points = lwpolyline.points
189 bulges = lwpolyline.bulge
190 lenpo = len(points)
191 spline = []
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])
197 else:
198 spline.append(points[-1])
199 return spline
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))
214 else:
215 lenlist.append(seglen)
217 points = lwpolyline.points
218 bulges = lwpolyline.bulge
219 lenpo = len(points)
220 lenlist = []
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])
227 return lenlist
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)
240 else:
241 a_x = world_z.cross(extrusion_normal)
242 a_x.normalize()
243 return a_x, extrusion_normal.cross(a_x)
245 az = Vector(entity.extrusion)
246 ax, ay = arbitrary_x_axis(az)
247 ax4 = ax.to_4d()
248 ay4 = ay.to_4d()
249 az4 = az.to_4d()
250 ax4[3] = 0
251 ay4[3] = 0
252 az4[3] = 0
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()
257 else:
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.
266 class WidthTuple:
267 def __init__(self, w):
268 self.w1 = w[0]
269 self.w2 = w[1]
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):
275 entities = []
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
285 else:
286 # disable closed flag (0x01) when is_closed is a @property
287 en_template.flags ^= 1
289 i = 0
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])
297 i += 1
298 entities.append(en)
300 if not entity.is_closed:
301 entities.pop(-1)
302 return entities
304 else:
305 return [entity]