Cleanup: quiet character escape warnings
[blender-addons.git] / io_import_dxf / dxfimport / do.py
blob65f9c079121b0e2de009a004c2cc5ded81191766
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 bpy
22 import os
23 import re
24 from mathutils import Vector, Matrix, Euler, Color, geometry
25 from math import pi, radians, sqrt
27 import bmesh
28 from .. import dxfgrabber
29 from . import convert, is_, groupsort
30 from .line_merger import line_merger
31 from ..transverse_mercator import TransverseMercator
34 try:
35 from pyproj import Proj, transform as proj_transform
36 PYPROJ = True
37 except:
38 PYPROJ = False
40 BY_LAYER = 0
41 BY_DXFTYPE = 1
42 BY_CLOSED_NO_BULGE_POLY = 2
43 SEPARATED = 3
44 LINKED_OBJECTS = 4
45 GROUP_INSTANCES = 5
46 BY_BLOCK = 6
49 def transform(p1, p2, c1, c2, c3):
50 if PYPROJ:
51 if type(p1) is Proj and type(p2) is Proj:
52 if p1.srs != p2.srs:
53 return proj_transform(p1, p2, c1, c2, c3)
54 else:
55 return (c1, c2, c3)
56 elif type(p2) is TransverseMercator:
57 wgs84 = Proj(init="EPSG:4326")
58 if p1.srs != wgs84.srs:
59 t2, t1, t3 = proj_transform(p1, wgs84, c1, c2, c3)
60 else:
61 t1, t2, t3 = c2, c1, c3 # mind c2, c1 inversion
62 tm1, tm2 = p2.fromGeographic(t1, t2)
63 return (tm1, tm2, t3)
64 else:
65 if p1.spherical:
66 t1, t2 = p2.fromGeographic(c2, c1) # mind c2, c1 inversion
67 return (t1, t2, c3)
68 else:
69 return (c1, c2, c3)
72 def float_len(f):
73 s = str(f)
74 if 'e' in s:
75 return int(s[3:5])
76 else:
77 return len(s[2:])
80 class Indicator:
81 spherical = False
82 euclidean = False
84 def __init__(self, i):
85 if i.upper() == "SPHERICAL":
86 self.spherical = True
87 elif i.upper() == "EUCLIDEAN":
88 self.euclidean = True
89 else:
90 raise AttributeError("Indication must be 'spherical' or 'euclidean'. Given: " + str(i))
93 class Do:
94 __slots__ = (
95 "dwg", "combination", "known_blocks", "import_text", "import_light", "export_acis", "merge_lines",
96 "do_bounding_boxes", "acis_files", "errors", "block_representation", "recenter", "did_group_instance",
97 "objects_before", "pDXF", "pScene", "thickness_and_width", "but_group_by_att", "current_scene",
98 "dxf_unit_scale"
101 def __init__(self, dxf_filename, c=BY_LAYER, import_text=True, import_light=True, export_acis=True,
102 merge_lines=True, do_bbox=True, block_rep=LINKED_OBJECTS, recenter=False, pDXF=None, pScene=None,
103 thicknessWidth=True, but_group_by_att=True, dxf_unit_scale=1.0):
104 self.dwg = dxfgrabber.readfile(dxf_filename, {"assure_3d_coords": True})
105 self.combination = c
106 self.known_blocks = {}
107 self.import_text = import_text
108 self.import_light = import_light
109 self.export_acis = export_acis
110 self.merge_lines = merge_lines
111 self.do_bounding_boxes = do_bbox
112 self.acis_files = []
113 self.errors = set([])
114 self.block_representation = block_rep
115 self.recenter = recenter
116 self.did_group_instance = False
117 self.objects_before = []
118 self.pDXF = pDXF
119 self.pScene = pScene
120 self.thickness_and_width = thicknessWidth
121 self.but_group_by_att = but_group_by_att
122 self.current_scene = None
123 self.dxf_unit_scale = dxf_unit_scale
125 def proj(self, co, elevation=0):
127 :param co: coordinate
128 :param elevation: float (lwpolyline code 38)
129 :return: transformed coordinate if self.pScene is defined
131 if self.pScene is not None and self.pDXF is not None:
132 u = self.dxf_unit_scale
133 if len(co) == 3:
134 c1, c2, c3 = co
135 c3 += elevation
136 else:
137 c1, c2 = co
138 c3 = elevation
139 if u != 1.0:
140 c1 *= u
141 c2 *= u
142 c3 *= u
143 # add
144 add = Vector((0, 0, 0))
145 if "latitude" in self.current_scene and "longitude" in self.current_scene:
146 if PYPROJ and type(self.pScene) not in (TransverseMercator, Indicator):
147 wgs84 = Proj(init="EPSG:4326")
148 cscn_lat = self.current_scene.get('latitude', 0)
149 cscn_lon = self.current_scene.get('longitude', 0)
150 cscn_alt = self.current_scene.get('altitude', 0)
151 add = Vector(transform(wgs84, self.pScene, cscn_lon, cscn_lat, cscn_alt))
153 # projection
154 newco = Vector(transform(self.pDXF, self.pScene, c1, c2, c3))
155 newco = newco - add
156 if any((c == float("inf") or c == float("-inf") for c in newco)):
157 self.errors.add("Projection results in +/- infinity coordinates.")
158 return newco
159 else:
160 u = self.dxf_unit_scale
161 if u != 1:
162 if len(co) == 3:
163 c1, c2, c3 = co
164 c3 += elevation
165 else:
166 c1, c2 = co
167 c3 = elevation
168 c1 *= u
169 c2 *= u
170 c3 *= u
171 return Vector((c1, c2, c3))
172 else:
173 return Vector((co[0], co[1], co[2] + elevation if len(co) == 3 else elevation))
175 def georeference(self, scene, center):
176 if "latitude" not in scene and "longitude" not in scene:
177 if type(self.pScene) is TransverseMercator:
178 scene['latitude'] = self.pScene.lat
179 scene['longitude'] = self.pScene.lon
180 scene['altitude'] = 0
181 elif type(self.pScene) is not None:
182 wgs84 = Proj(init="EPSG:4326")
183 latlon = transform(self.pScene, wgs84, center[0], center[1], center[2])
184 scene['longitude'] = latlon[0]
185 scene['latitude'] = latlon[1]
186 scene['altitude'] = latlon[2]
188 """ GEOMETRY DXF TYPES TO BLENDER CURVES FILTERS"""
189 # type(self, dxf entity, blender curve data)
191 def _cubic_bezier_closed(self, ptuple, curve):
192 count = (len(ptuple)-1)/3
193 points = [ptuple[-2]]
194 ptuples = ptuple[:-2]
195 points += [p for p in ptuples]
197 spl = curve.splines.new('BEZIER')
198 spl.use_cyclic_u = True
199 b = spl.bezier_points
200 b.add(count - 1)
201 for i, j in enumerate(range(1, len(points), 3)):
202 b[i].handle_left = self.proj(points[j - 1])
203 b[i].co = self.proj(points[j])
204 b[i].handle_right = self.proj(points[j + 1])
206 def _cubic_bezier_open(self, points, curve):
207 count = (len(points) - 1) / 3 + 1
208 spl = curve.splines.new('BEZIER')
209 b = spl.bezier_points
210 b.add(count - 1)
212 b[0].co = self.proj(points[0])
213 b[0].handle_left = self.proj(points[0])
214 b[0].handle_right = self.proj(points[1])
216 b[-1].co = self.proj(points[-1])
217 b[-1].handle_right = self.proj(points[-1])
218 b[-1].handle_left = self.proj(points[-2])
220 for i, j in enumerate(range(3, len(points) - 2, 3), 1):
221 b[i].handle_left = self.proj(points[j - 1])
222 b[i].co = self.proj(points[j])
223 b[i].handle_right = self.proj(points[j + 1])
225 def _cubic_bezier(self, points, curve, is_closed):
227 points: control points; list of (x,y,z) tuples
228 curve: Blender curve data of type "CURVE" (object.data) where the bezier should be added to
229 is_closed: True / False to indicate if the curve is open or closed
231 if is_closed:
232 self._cubic_bezier_closed(points, curve)
233 else:
234 self._cubic_bezier_open(points, curve)
236 def _poly(self, points, curve, elevation=0, is_closed=False):
238 points: list of (x,y,z)
239 curve: Blender curve data of type "CURVE" (object.data) to which the poly should be added to
240 param elevation: float (lwpolyline code 38)
241 is_closed: True / False to indicate if the polygon is open or closed
243 p = curve.splines.new("POLY")
244 p.use_smooth = False
245 p.use_cyclic_u = is_closed
246 p.points.add(len(points) - 1)
248 for i, pt in enumerate(points):
249 p.points[i].co = self.proj(pt, elevation).to_4d()
251 def _gen_poly(self, en, curve, elevation=0):
252 if any([b != 0 for b in en.bulge]):
253 self._cubic_bezier(convert.bulgepoly_to_cubic(self, en), curve, en.is_closed)
254 else:
255 self._poly(en.points, curve, elevation, en.is_closed)
257 def polyline(self, en, curve):
259 en: DXF entity of type `POLYLINE`
260 curve: Blender data structure of type `CURVE`
262 self._gen_poly(en, curve)
264 def polygon(self, en, curve):
266 en: DXF entity of type `POLYGON`
267 curve: Blender data structure of type `CURVE`
269 self._gen_poly(en, curve)
271 def lwpolyline(self, en, curve):
273 en: DXF entity of type `LWPOLYLINE`
274 curve: Blender data structure of type `CURVE`
276 self._gen_poly(en, curve, en.elevation)
278 def line(self, en, curve):
280 en: DXF entity of type `LINE`
281 curve: Blender data structure of type `CURVE`
283 self._poly([en.start, en.end], curve, 0, False)
285 def arc(self, en, curve=None, aunits=None, angdir=None, angbase=None):
287 en: dxf entity (en.start_angle, en.end_angle, en.center, en.radius)
288 curve: optional; Blender curve data of type "CURVE" (object.data) to which the arc should be added to
289 return control points of a cubic spline (do be used in a spline with bulges / series of arcs)
290 note: en.start_angle + en.end_angle: angles measured from the angle base (angbase) in the direction of
291 angdir (1 = clockwise, 0 = counterclockwise)
293 threshold = 0.005
295 if aunits is None:
296 aunits = self.dwg.header.get('$AUNITS', 0)
297 if angbase is None:
298 angbase = self.dwg.header.get('$ANGBASE', 0)
299 if angdir is None:
300 angdir = self.dwg.header.get('$ANGDIR', 0)
301 kappa = 0.5522848
303 # TODO: add support for 1 (dms) and 4 (survey)
304 if aunits == 0:
305 # Degree
306 s = radians(en.start_angle+angbase)
307 e = radians(en.end_angle+angbase)
308 elif aunits == 2:
309 # Gradians
310 s = radians(0.9 * (en.start_angle + angbase))
311 e = radians(0.9 * (en.end_angle + angbase))
312 else:
313 # Radians
314 s = en.start_angle+angbase
315 e = en.end_angle+angbase
317 if s > e:
318 e += 2 * pi
319 angle = e - s
321 vc = Vector(en.center)
322 x_vec = Vector((1, 0, 0))
323 radius = en.radius
325 # turn clockwise
326 if angdir == 0:
327 rot = Matrix.Rotation(radians(-90), 3, "Z")
328 start = x_vec * radius @ Matrix.Rotation(-s, 3, "Z")
329 end = x_vec * radius @ Matrix.Rotation(-e, 3, "Z")
331 # turn counter-clockwise
332 else:
333 rot = Matrix.Rotation(radians(90), 3, "Z")
334 start = x_vec * radius @ Matrix.Rotation(s, 3, "Z")
335 end = x_vec * radius @ Matrix.Rotation(e, 3, "Z")
337 # start
338 spline = list()
339 spline.append(vc + start)
340 if abs(angle) - pi / 2 > threshold: # if angle is more than pi/2 incl. threshold
341 spline.append(vc + start + start * kappa @ rot)
342 else:
343 spline.append(vc + start + start * kappa * angle / (pi / 2) @ rot)
345 # fill if angle is larger than 90 degrees
346 a = pi / 2
347 if abs(angle) - threshold > a:
348 fill = start
350 while abs(angle) - a > threshold:
351 fillnext = fill @ rot
352 spline.append(vc + fillnext + fill * kappa)
353 spline.append(vc + fillnext)
354 # if this was the last fill control point
355 if abs(angle) - a - pi / 2 < threshold:
356 end_angle = (abs(angle) - a) * abs(angle) / angle
357 spline.append(vc + fillnext + fillnext * kappa * end_angle / (pi / 2) @ rot)
358 else:
359 spline.append(vc + fillnext + fillnext * kappa @ rot)
360 fill = fillnext
361 a += pi / 2
363 else:
364 end_angle = angle
366 # end
367 spline.append(vc + end + end * -kappa * end_angle / (pi / 2) @ rot)
368 spline.append(vc + end)
370 if len(spline) % 3 != 1:
371 print("DXF-IMPORT: DO ARC: CHECK PLEASE: ", len(spline), spline)
373 # curve is None means arc is called from bulge conversion
374 # nothing should be projected at this stage, since the
375 # lwpolyline (the only entity with bulges) will be projected
376 # as a whole afterwards (small little error; took ages to debug)
377 if curve is not None:
378 self._cubic_bezier_open(spline, curve)
379 return spline
380 else:
381 return spline
383 def circle(self, en, curve, major=Vector((1, 0, 0))):
385 en: dxf entity
386 curve: Blender curve data of type "CURVE" (object.data) to which the circle should be added to
387 major: optional; if the circle is used as a base for an ellipse, major denotes the ellipse's major direction
389 c = curve.splines.new("BEZIER")
390 c.use_cyclic_u = True
391 b = c.bezier_points
392 b.add(3)
394 for i in range(4):
395 b[i].handle_left_type = 'AUTO'
396 b[i].handle_right_type = 'AUTO'
398 vc = Vector(en.center)
399 clockwise = Matrix(((0, -1, 0), (1, 0, 0), (0, 0, 1)))
401 r = major
402 if len(r) == 2:
403 r = r.to_3d()
404 r = r * en.radius
406 try:
407 b[0].co = self.proj(vc + r)
408 b[1].co = self.proj(vc + r @ clockwise)
409 b[2].co = self.proj(vc + r @ clockwise @ clockwise)
410 b[3].co = self.proj(vc + r @ clockwise @ clockwise @ clockwise)
411 except:
412 print("Circle center: ", vc, "radius: ", r)
413 raise
415 return c
417 def scale_controlpoint(self, p, factor):
419 p: Blender control-point
420 factor: (Float)
421 Repositions left and right handle of a bezier control point.
423 p.handle_left = p.co + (p.handle_left - p.co) * factor
424 p.handle_right = p.co + (p.handle_right - p.co) * factor
426 def ellipse(self, en, curve):
428 center: (x,y,z) of circle center
429 major: the ellipse's major direction
430 ratio: ratio between major and minor axis lengths (always < 1)
431 curve: Blender curve data of type "CURVE" (object.data) to which the ellipse should be added to
433 major = Vector(en.major_axis)
434 en.__dict__["radius"] = major.length
435 c = self.circle(en, curve, major.normalized())
436 b = c.bezier_points
438 if en.ratio < 1:
439 for i in range(4):
440 b[i].handle_left_type = 'ALIGNED'
441 b[i].handle_right_type = 'ALIGNED'
443 vc = self.proj(en.center)
444 clockwise = Matrix(((0, -1, 0), (1, 0, 0), (0, 0, 1)))
445 if len(major) == 2:
446 major = major.to_3d()
447 minor = major * en.ratio @ clockwise
449 lh = b[1].handle_left - b[1].co
450 rh = b[1].handle_right - b[1].co
451 b[1].co = vc + minor
452 b[1].handle_left = b[1].co + lh
453 b[1].handle_right = b[1].co + rh
454 b[3].co = vc + minor @ clockwise @ clockwise
455 b[3].handle_left = b[3].co + rh
456 b[3].handle_right = b[3].co + lh
458 self.scale_controlpoint(b[0], en.ratio)
459 self.scale_controlpoint(b[2], en.ratio)
461 def spline(self, en, curve, _3D=True):
463 en: DXF entity of type `SPLINE`
464 curve: Blender data structure of type `CURVE`
466 if _3D:
467 curve.dimensions = "3D"
468 spline = convert.bspline_to_cubic(self, en, curve, self.errors)
469 if spline is None:
470 self.errors.add("Not able to import bspline with degree > 3")
471 else:
472 self._cubic_bezier(spline, curve, en.is_closed)
474 def helix(self, en, curve):
476 en: DXF entity of type `HELIX`
477 curve: Blender data structure of type `CURVE`
479 self.spline(en, curve, not en.is_planar)
481 """ GEOMETRY DXF TYPES TO BLENDER MESHES FILTERS"""
482 # type(self, dxf entity, blender bmesh data)
484 def _gen_meshface(self, points, bm):
486 points: list of (x,y,z) tuples
487 bm: bmesh to add the (face-) points
488 Used by the3dface() and solid()
491 def _is_on_edge(point):
492 return abs(sum((e - point).length for e in (edge1, edge2)) - (edge1 - edge2).length) < 0.01
494 points = list(points)
496 i = 0
497 while i < len(points):
498 if points.count(points[i]) > 1:
499 points.pop(i)
500 else:
501 i += 1
503 verts = []
504 for p in points:
505 verts.append(bm.verts.new(self.proj(p)))
507 # add only an edge if len points < 3
508 if len(points) == 2:
509 bm.edges.new(verts)
510 elif len(points) > 2:
511 face = bm.faces.new(verts)
513 if len(points) == 4:
514 for i in range(2):
515 edge1 = verts[i].co
516 edge2 = verts[i + 1].co
517 opposite1 = verts[i + 2].co
518 opposite2 = verts[(i + 3) % 4].co
519 ii = geometry.intersect_line_line(edge1, edge2, opposite1, opposite2)
520 if ii is not None:
521 if _is_on_edge(ii[0]):
522 try:
523 bm.faces.remove(face)
524 except Exception as e:
525 pass
526 iv = bm.verts.new(ii[0])
527 bm.faces.new((verts[i], iv, verts[(i + 3) % 4]))
528 bm.faces.new((verts[i + 1], iv, verts[i + 2]))
530 def the3dface(self, en, bm):
531 """ f: dxf entity
532 bm: Blender bmesh data to which the 3DFACE should be added to.
533 <-? bm.from_mesh(object.data) ?->
535 if en.points[-1] == en.points[-2]:
536 points = en.points[:3]
537 else:
538 points = en.points
539 self._gen_meshface(points, bm)
541 def solid(self, en, bm):
542 """ f: dxf entity
543 bm: Blender bmesh data to which the SOLID should be added to.
544 <-? bm.from_mesh(object.data) ?->
546 p = en.points
547 points = (p[0], p[1], p[3], p[2])
548 self._gen_meshface(points, bm)
550 def trace(self, en, bm):
551 self.solid(en, bm)
553 def point(self, en, bm):
555 en: DXF entity of type `POINT`
556 bm: Blender bmesh instance
558 bm.verts.new(en.point)
560 def polyface(self, en, bm):
562 pf: polyface
563 bm: Blender bmesh data to which the POLYFACE should be added to.
564 <-? bm.from_mesh(object.data) ?->
566 for v in en.vertices:
567 bm.verts.new(v.location)
569 bm.verts.ensure_lookup_table()
570 for subface in en:
571 idx = subface.indices()
572 points = []
573 for p in idx:
574 if p not in points:
575 points.append(p)
576 if len(points) in (3, 4):
577 bm.faces.new([bm.verts[i] for i in points])
579 def polymesh(self, en, bm):
581 en: POLYMESH entity
582 bm: Blender bmesh instance
584 mc = en.mcount if not en.is_mclosed else en.mcount + 1
585 nc = en.ncount if not en.is_nclosed else en.ncount + 1
586 for i in range(1, mc):
587 i = i % en.mcount
588 i_ = (i - 1) % en.mcount
589 for j in range(1, nc):
590 j = j % en.ncount
591 j_ = (j - 1) % en.ncount
592 face = []
593 face.append(bm.verts.new(en.get_location((i_, j_))))
594 face.append(bm.verts.new(en.get_location((i, j_))))
595 face.append(bm.verts.new(en.get_location((i, j))))
596 face.append(bm.verts.new(en.get_location((i_, j))))
598 bm.faces.new(face)
600 def mesh(self, en, bm):
602 mesh: dxf entity
603 m: Blender MESH data (object.data) to which the dxf-mesh should be added
605 # verts:
606 for v in en.vertices:
607 bm.verts.new(v)
609 # edges:
610 bm.verts.ensure_lookup_table()
611 if any((c < 0 for c in en.edge_crease_list)):
612 layerkey = bm.edges.layers.crease.new("SubsurfCrease")
613 for i, edge in enumerate(en.edges):
614 bme = bm.edges.new([bm.verts[edge[0]], bm.verts[edge[1]]])
615 bme[layerkey] = -en.edge_crease_list[i]
616 else:
617 for i, edge in enumerate(en.edges):
618 bm.edges.new([bm.verts[edge[0]], bm.verts[edge[1]]])
620 # faces:
621 for face in en.faces:
622 bm.faces.new([bm.verts[i] for i in face])
624 """ SEPARATE BLENDER OBJECTS FROM (CON)TEXT / STRUCTURE DXF TYPES """
625 # type(self, dxf entity, name string)
626 # returns blender object
628 def _extrusion(self, obj, entity):
630 extrusion describes the normal vector of the entity
632 if entity.dxftype not in {"LINE", "POINT"}:
633 if is_.extrusion(entity):
634 transformation = convert.extrusion_to_matrix(entity)
635 obj.location = transformation @ obj.location
636 obj.rotation_euler.rotate(transformation)
638 def _bbox(self, objects):
639 xmin = ymin = zmin = float('+inf')
640 xmax = ymax = zmax = float('-inf')
641 bpy.context.view_layer.update()
643 for obj in objects:
644 om = obj.matrix_basis
645 for v in obj.bound_box:
646 p = om @ Vector(v)
647 if p.x < xmin:
648 xmin = p.x
649 if p.x > xmax:
650 xmax = p.x
651 if p.y < ymin:
652 ymin = p.y
653 if p.y > ymax:
654 ymax = p.y
655 if p.z < zmin:
656 zmin = p.z
657 if p.z > zmax:
658 zmax = p.z
660 if xmin == float('+inf'):
661 xmin = 0
662 if ymin == float('+inf'):
663 ymin = 0
664 if zmin == float('+inf'):
665 zmin = 0
666 if xmax == float('-inf'):
667 xmax = 0
668 if ymax == float('-inf'):
669 ymax = 0
670 if zmax == float('-inf'):
671 zmax = 0
673 return xmin, ymin, zmin, xmax, ymax, zmax
675 def _object_bbox(self, objects, name, do_widgets=True):
676 xmin, ymin, zmin, xmax, ymax, zmax = self._bbox(objects)
678 # creating bbox geometry
679 bm = bmesh.new()
680 verts = []
681 # left side vertices
682 verts.append(bm.verts.new((xmin, ymin, zmin)))
683 verts.append(bm.verts.new((xmin, ymin, zmax)))
684 verts.append(bm.verts.new((xmin, ymax, zmax)))
685 verts.append(bm.verts.new((xmin, ymax, zmin)))
686 # right side vertices
687 verts.append(bm.verts.new((xmax, ymin, zmin)))
688 verts.append(bm.verts.new((xmax, ymin, zmax)))
689 verts.append(bm.verts.new((xmax, ymax, zmax)))
690 verts.append(bm.verts.new((xmax, ymax, zmin)))
692 # creating the widgets
693 if do_widgets:
694 for i in range(2):
695 for j in range(4):
696 point = verts[j + (i * 4)]
697 prevp = verts[((j - 1) % 4) + (i * 4)]
698 nextp = verts[((j + 1) % 4) + (i * 4)]
699 neigh = verts[(j + (i * 4) + 4) % 8]
701 for con in (prevp, nextp, neigh):
702 vec = Vector(con.co - point.co) / 10
703 bm.edges.new((point, bm.verts.new(point.co + vec)))
705 d = bpy.data.meshes.new(name + "BBOX")
706 bm.to_mesh(d)
707 o = bpy.data.objects.new(name, d)
708 return o
710 def _vertex_duplication(self, x, y, x_count, y_count):
711 bm = bmesh.new()
712 for i in range(x_count):
713 for j in range(y_count):
714 bm.verts.new(Vector((x * i, y * j, 0.)))
716 d = bpy.data.meshes.new("vertex_duplicator")
717 bm.to_mesh(d)
718 return d
720 def point_object(self, en, scene, name=None):
721 if name is None:
722 name = en.dxftype
723 o = bpy.data.objects.new("Point", None)
724 o.location = self.proj(en.point)
725 self._extrusion(o, en)
726 scene.collection.objects.link(o)
728 group = self._get_group(en.layer)
729 group.objects.link(o)
731 def light(self, en, scene, name):
733 en: dxf entity
734 name: ignored; exists to make separate and merged objects methods universally callable from _call_types()
735 Creates, links and returns a new light object depending on the type and color of the dxf entity.
737 # light_type : distant = 1; point = 2; spot = 3
738 if self.import_light:
739 type_map = ["NONE", "SUN", "POINT", "SPOT"]
740 layer = self.dwg.layers[en.layer]
741 lamp = bpy.data.lights.new(en.name, type_map[en.light_type])
742 if en.color != 256:
743 aci = en.color
744 else:
745 aci = layer.color
746 c = dxfgrabber.aci_to_true_color(aci)
747 lamp.color = Color(c.rgb())
748 if en.light_type == 3:
749 lamp.spot_size = en.hotspot_angle
750 o = bpy.data.objects.new(en.name, lamp)
751 o.location = self.proj(en.position)
752 dir = self.proj(en.target) - self.proj(en.position)
753 o.rotation_quaternion = dir.rotation_difference(Vector((0, 0, -1)))
754 scene.collection.objects.link(o)
755 return o
757 def mtext(self, en, scene, name):
759 en: dxf entity
760 name: ignored; exists to make separate and merged objects methods universally callable from _call_types()
761 Returns a new multi-line text object.
763 if self.import_text:
764 text = en.plain_text()
765 name = text[:8]
766 d = bpy.data.curves.new(name, "FONT")
767 o = bpy.data.objects.new(name, d)
768 d.body = text
769 d.size = en.height
770 if en.rect_width is not None:
771 if en.rect_width > 50:
772 width = 50
773 ratio = 50 / en.rect_width
774 d.size = en.height * ratio * 1.4 # XXX HACK
775 scale = (1 / ratio, 1 / ratio, 1 / ratio)
776 d.space_line = en.line_spacing
777 else:
778 width = en.rect_width
779 scale = (1, 1, 1)
780 d.text_boxes[0].width = width
781 o.scale = scale
783 # HACK
784 d.space_line *= 1.5
786 o.rotation_euler = Vector((1, 0, 0)).rotation_difference(en.xdirection).to_euler()
787 o.location = en.insert
788 return o
790 def text(self, en, scene, name):
792 en: dxf entity
793 name: ignored; exists to make separate and merged objects methods universally callable from _call_types()
794 Returns a new single line text object.
796 if self.import_text:
797 name = en.text[:8]
798 d = bpy.data.curves.new(name, "FONT")
799 d.body = en.plain_text()
800 d.size = en.height
801 o = bpy.data.objects.new(name, d)
802 o.rotation_euler = Euler((0, 0, radians(en.rotation)), 'XYZ')
803 basepoint = self.proj(en.basepoint) if hasattr(en, "basepoint") else self.proj((0, 0, 0))
804 o.location = self.proj((en.insert)) + basepoint
805 if hasattr(en, "thickness"):
806 et = en.thickness / 2
807 d.extrude = abs(et)
808 if et > 0:
809 o.location.z += et
810 elif et < 0:
811 o.location.z -= et
812 return o
814 def block_linked_object(self, entity, scene, name=None, override_group=None, invisible=None, recursion_level=0):
816 entity: DXF entity of type `BLOCK`
817 name: to be consistent with all functions that get mapped to a DXF type; is set when called from insert()
818 override_group: is set when called from insert() (bpy_types.Group)
819 invisible: boolean to control the visibility of the returned object
820 Returns an object. All blocks with the same name share the same geometry (Linked Blender Objects). If the
821 entities in a block have to mapped to different Blender-types (like curve, mesh, surface, text, light) a list of
822 sub-objects is being created. `INSERT`s inside a block are being added to the same list. If the list has more
823 than one element they are being parented to a newly created Blender-Empty which is return instead of the
824 single object in the sub-objects-list that is being returned only if it is the only object in the list.
826 def _recursive_copy_inserts(parent, known_inserts, inserts, group, invisible):
827 for ki in known_inserts:
828 new_insert = ki.copy()
829 _recursive_copy_inserts(new_insert, ki.children, None, group, invisible)
830 if new_insert.name not in group.objects:
831 group.objects.link(new_insert)
832 if invisible is not None:
833 new_insert.hide_viewport = bool(invisible)
834 if inserts is not None:
835 inserts.append(new_insert)
836 new_insert.parent = parent
837 scene.collection.objects.link(new_insert)
839 if name is None:
840 name = entity.name
841 # get group
842 if override_group is not None:
843 group = override_group
844 else:
845 group = self._get_group(entity.layer)
847 # get object(s)
848 objects = []
849 inserts = []
850 if name not in self.known_blocks.keys():
851 block_inserts = [en for en in entity if is_.insert(en.dxftype)]
852 bc = (en for en in entity if is_.combined_entity(en))
853 bs = (en for en in entity if is_.separated_entity(en) and not is_.insert(en.dxftype))
855 if self.combination != SEPARATED:
856 objects += self.combined_objects(bc, scene, "BL|" + name, group)
857 else:
858 bs = (en for en in entity if (is_.combined_entity(en) or is_.separated_entity(en)) and
859 not is_.insert(en.dxftype))
860 objects += self.separated_entities(bs, scene, "BL|" + name, group)
862 # create inserts - RECURSION
863 insert_bounding_boxes = []
864 for INSERT in block_inserts:
865 insert = self.insert(INSERT, scene, None, group, invisible, recursion_level + 1)
866 if len(insert.children) > 0:
867 i_copy = bpy.data.objects.new(insert.name, None)
868 i_copy.matrix_basis = insert.matrix_basis
869 scene.collection.objects.link(i_copy)
870 group.objects.link(i_copy)
871 kids = insert.children[:]
872 for child in kids:
873 child.parent = i_copy
874 inserts.append(i_copy)
875 insert_bounding_boxes.append(insert)
876 else:
877 inserts.append(insert)
879 # determining the main object o
880 if len(objects) > 1 or len(insert_bounding_boxes) > 0:
881 if self.do_bounding_boxes:
882 o = self._object_bbox(objects + insert_bounding_boxes, name, recursion_level == 0)
883 scene.collection.objects.link(o)
884 else:
885 o = bpy.data.objects.new(name, None)
886 scene.collection.objects.link(o)
887 if len(objects) > 0:
888 for obj in objects:
889 obj.parent = o
891 elif len(objects) == 1:
892 o = objects.pop(0)
893 else:
894 # strange case but possible according to the testfiles
895 o = bpy.data.objects.new(name, None)
896 scene.collection.objects.link(o)
898 # unlink bounding boxes of inserts
899 for ib in insert_bounding_boxes:
900 if ib.name in group.objects:
901 group.objects.unlink(ib)
902 scene.collection.objects.unlink(ib)
904 # parent inserts to this block before any transformation on the block is being applied
905 for obj in inserts:
906 obj.parent = o
908 # put a copy of the retrieved objects into the known_blocks dict, so that the attributes being added to
909 # the object from this point onwards (from INSERT attributes) are not being copied to new/other INSERTs
910 self.known_blocks[name] = [[o.copy() for o in objects], inserts]
912 # so that it gets assigned to the group and inherits visibility too
913 objects.append(o)
915 self.known_blocks[name].append(o.copy())
916 else:
917 known_objects, known_inserts, known_o = self.known_blocks[name]
919 for known_object in known_objects:
920 oc = known_object.copy()
921 scene.collection.objects.link(oc)
922 objects.append(oc)
924 o = known_o.copy()
925 scene.collection.objects.link(o)
927 _recursive_copy_inserts(o, known_inserts, inserts, group, invisible)
929 # parent objects to o
930 for obj in objects:
931 obj.parent = o
933 objects.append(o)
935 # link group
936 for obj in objects:
937 if obj.name not in group.objects:
938 group.objects.link(obj)
940 # visibility
941 if invisible is not None:
942 for obj in objects:
943 obj.hide_viewport = bool(invisible)
945 # block transformations
946 o.location = self.proj(entity.basepoint)
948 return o
950 def block_group_instances(self, entity, scene, name=None, override_group=None, invisible=None, recursion_level=0):
951 self.did_group_instance = True
953 if name is None:
954 name = entity.name
955 # get group
956 if override_group is not None:
957 group = override_group
958 else:
959 group = self._get_group(entity.layer)
961 block_group = self._get_group(entity.name+"_BLOCK")
963 if "Blocks" not in bpy.data.scenes:
964 block_scene = bpy.data.scenes.new("Blocks")
965 else:
966 block_scene = bpy.data.scenes["Blocks"]
968 # create the block
969 if len(block_group.objects) == 0 or name not in self.known_blocks.keys():
970 bpy.context.screen.scene = block_scene
971 block_inserts = [en for en in entity if is_.insert(en.dxftype)]
972 bc = (en for en in entity if is_.combined_entity(en))
973 bs = (en for en in entity if is_.separated_entity(en) and not is_.insert(en.dxftype))
975 objects = []
976 if self.combination != SEPARATED:
977 objects += self.combined_objects(bc, block_scene, "BL|" + name, block_group)
978 else:
979 bs = (en for en in entity if (is_.combined_entity(en) or is_.separated_entity(en)) and
980 not is_.insert(en.dxftype))
981 objects += self.separated_entities(bs, block_scene, "BL|" + name, block_group)
983 # create inserts - RECURSION
984 inserts = []
985 for INSERT in block_inserts:
986 i = self.insert(INSERT, block_scene, None, block_group, invisible, recursion_level + 1, True)
987 inserts.append(i)
989 bbox = self._object_bbox(objects + inserts, name, True)
991 for i in inserts:
992 sub_group = i.instance_collection
993 block_scene.collection.objects.unlink(i)
994 block_group.objects.unlink(i)
995 i_empty = bpy.data.objects.new(i.name, None)
996 i_empty.matrix_basis = i.matrix_basis
997 i_empty.instance_type = "COLLECTION"
998 i_empty.instance_collection = sub_group
999 block_group.objects.link(i_empty)
1000 block_scene.collection.objects.link(i_empty)
1002 self.known_blocks[name] = [objects, inserts, bbox]
1003 else:
1004 bbox = self.known_blocks[name][2]
1006 bpy.context.screen.scene = scene
1007 o = bbox.copy()
1008 # o.empty_display_size = 0.3
1009 o.instance_type = "COLLECTION"
1010 o.instance_collection = block_group
1011 group.objects.link(o)
1012 if invisible is not None:
1013 o.hide_viewport = invisible
1014 o.location = self.proj(entity.basepoint)
1015 scene.collection.objects.link(o)
1016 # block_scene.view_layers[0].update()
1018 return o
1020 def insert(self, entity, scene, name, group=None, invisible=None, recursion_level=0, need_group_inst=None):
1022 entity: DXF entity
1023 name: String; not used but required to be consistent with the methods being called from _call_type()
1024 group: Blender group of type (bpy_types.Collection) being set if called from block()
1025 invisible: boolean to control visibility; being set if called from block()
1027 aunits = self.dwg.header.get('$AUNITS', 0)
1029 # check if group instances are needed
1030 kids = sum(1 for i in self.dwg.blocks[entity.name] if i.dxftype == "INSERT")
1031 sep = sum(1 for sep in self.dwg.blocks[entity.name] if is_.separated_entity(sep))
1032 objtypes = sum(1 for ot, ens in groupsort.by_blender_type(en for en in self.dwg.blocks[entity.name]
1033 if is_.combined_entity(en))
1034 if ot in {"object_mesh", "object_curve"})
1035 if need_group_inst is None:
1036 need_group_inst = (entity.row_count or entity.col_count) > 1 and \
1037 (kids > 0 or objtypes > 1 or sep > 1 or (objtypes > 0 and sep > 0))
1039 if group is None:
1040 group = self._get_group(entity.layer)
1042 if self.block_representation == GROUP_INSTANCES or need_group_inst:
1043 o = self.block_group_instances(self.dwg.blocks[entity.name], scene, entity.name, group,
1044 entity.invisible, recursion_level)
1045 else:
1046 o = self.block_linked_object(self.dwg.blocks[entity.name], scene, entity.name, group,
1047 entity.invisible, recursion_level)
1049 # column & row
1050 if (entity.row_count or entity.col_count) > 1:
1051 if len(o.children) == 0 and self.block_representation == LINKED_OBJECTS and not need_group_inst:
1052 arr_row = o.modifiers.new("ROW", "ARRAY")
1053 arr_col = o.modifiers.new("COL", "ARRAY")
1054 arr_row.show_expanded = False
1055 arr_row.count = entity.row_count
1056 arr_row.relative_offset_displace = (0, entity.row_spacing, 0)
1057 arr_col.show_expanded = False
1058 arr_col.count = entity.col_count
1059 arr_col.relative_offset_displace = (entity.col_spacing / 2, 0, 0)
1061 else:
1062 instance = o
1063 x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length
1064 y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
1065 dm = self._vertex_duplication(x * entity.col_spacing / 2, y * entity.row_spacing,
1066 entity.col_count, entity.row_count)
1067 o = bpy.data.objects.new(entity.name, dm)
1068 instance.parent = o
1069 o.instance_type = "VERTS"
1071 # insert transformations
1072 rot = radians(entity.rotation) if aunits == 0 else entity.rotation
1073 o.location += self.proj(entity.insert)
1074 o.rotation_euler = Euler((0, 0, rot))
1075 o.scale = entity.scale
1077 # mirror (extrusion value of an INSERT ENTITY)
1078 self._extrusion(o, entity)
1080 # visibility
1081 if invisible is None:
1082 o.hide_viewport = bool(entity.invisible)
1083 else:
1084 o.hide_viewport = bool(invisible)
1086 # attributes
1087 if self.import_text:
1088 if entity.attribsfollow:
1089 for a in entity.attribs:
1090 # Blender custom property
1091 o[a.tag] = a.text
1092 attname = entity.name + "_" + a.tag
1093 scene.collection.objects.link(self.text(a, scene, attname))
1095 return o
1097 """ COMBINED BLENDER OBJECT FROM GEOMETRY DXF TYPES """
1098 # type(self, dxf entities, object name string)
1099 # returns blender object
1101 def _check3D_object(self, curve):
1103 Checks if a curve object has coordinates that are elevated from the z-plane.
1104 If so the curve.dimensions are set to 3D.
1106 if any((p.co.z != 0 for spline in curve.splines for p in spline.points)) or \
1107 any((p.co.z != 0 for spline in curve.splines for p in spline.bezier_points)):
1108 curve.dimensions = '3D'
1110 def _merge_lines(self, lines, curve):
1112 lines: list of LINE entities
1113 curve: Blender curve data
1114 merges a list of LINE entities to a polygon-point-list and adds it to the Blender curve
1116 polylines = line_merger(lines)
1117 for polyline in polylines:
1118 self._poly(polyline, curve, 0, polyline[0] == polyline[-1])
1120 def _thickness(self, bm, thickness):
1122 Used for mesh types
1124 if not self.thickness_and_width:
1125 return
1126 original_faces = [face for face in bm.faces]
1127 bm.normal_update()
1128 for face in original_faces:
1129 normal = face.normal.copy()
1130 if normal.z < 0:
1131 normal *= -1
1132 ret = bmesh.ops.extrude_face_region(bm, geom=[face])
1133 new_geom = ret["geom"]
1134 verts = (g for g in new_geom if type(g) == bmesh.types.BMVert)
1135 for v in verts:
1136 v.co += normal * thickness
1137 del ret
1138 del original_faces
1140 def _thickness_and_width(self, obj, entity, scene):
1142 Used for curve types
1144 if not self.thickness_and_width:
1145 return
1146 has_varying_width = is_.varying_width(entity)
1147 th = entity.thickness
1148 w = 0
1149 if hasattr(entity, "width"):
1150 if len(entity.width) > 0 and len(entity.width[0]) > 0:
1151 w = entity.width[0][0]
1153 if w == 0 and not has_varying_width:
1154 if th != 0:
1155 obj.data.extrude = abs(th / 2)
1156 if th > 0:
1157 obj.location.z += th / 2
1158 else:
1159 obj.location.z -= th / 2
1160 obj.data.dimensions = "3D"
1161 obj.data.twist_mode = "Z_UP"
1163 else:
1164 # CURVE BEVEL
1165 ew = entity.width
1166 max_w = max((w for w_pair in ew for w in w_pair))
1168 bevd = bpy.data.curves.new("BEVEL", "CURVE")
1169 bevdp = bevd.splines.new("POLY")
1170 bevdp.points.add(1)
1171 bevdp.points[0].co = Vector((-max_w / 2, 0, 0, 0))
1172 bevdp.points[1].co = Vector((max_w / 2, 0, 0, 0))
1174 bevel = bpy.data.objects.new("BEVEL", bevd)
1175 obj.data.bevel_object = bevel
1176 scene.collection.objects.link(bevel)
1178 # CURVE TAPER
1179 if has_varying_width and len(ew) == 1:
1180 tapd = bpy.data.curves.new("TAPER", "CURVE")
1181 tapdp = tapd.splines.new("POLY")
1182 # lenlist = convert.bulgepoly_to_lenlist(entity)
1183 # amount = len(ew) if entity.is_closed else len(ew) - 1
1185 tapdp.points[0].co = Vector((0, ew[0][0] / max_w, 0, 0))
1186 tapdp.points.add(1)
1187 tapdp.points[1].co = Vector((1, ew[0][1] / max_w, 0, 0))
1189 # for i in range(1, amount):
1190 # start_w = ew[i][0]
1191 # end_w = ew[i][1]
1192 # tapdp.points.add(2)
1193 # tapdp.points[-2].co = Vector((sum(lenlist[:i]), start_w / max_w, 0, 0))
1194 # tapdp.points[-1].co = Vector((sum(lenlist[:i + 1]), end_w / max_w, 0, 0))
1196 taper = bpy.data.objects.new("TAPER", tapd)
1197 obj.data.taper_object = taper
1198 scene.collection.objects.link(taper)
1200 # THICKNESS FOR CURVES HAVING A WIDTH
1201 if th != 0:
1202 solidify = obj.modifiers.new("THICKNESS", "SOLIDIFY")
1203 solidify.thickness = th
1204 solidify.use_even_offset = True
1205 solidify.offset = 1
1206 solidify.show_expanded = False
1208 # make the shading look good
1209 esp = obj.modifiers.new("EdgeSplit", "EDGE_SPLIT")
1210 esp.show_expanded = False
1212 def _subdivision(self, obj, entity):
1213 if entity.subdivision_levels > 0:
1214 subd = obj.modifiers.new("SubD", "SUBSURF")
1215 subd.levels = entity.subdivision_levels
1216 subd.show_expanded = False
1218 def polys_to_mesh(self, entities, scene, name):
1219 d = bpy.data.meshes.new(name)
1220 bm = bmesh.new()
1221 m = Matrix(((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)))
1222 for en in entities:
1223 t = m
1224 verts = []
1225 if is_.extrusion(en):
1226 t = convert.extrusion_to_matrix(en)
1227 for p in en.points:
1228 verts.append(bm.verts.new(self.proj((t*Vector(p)).to_3d())))
1229 if len(verts) > 2:
1230 bm.faces.new(verts)
1231 elif len(verts) == 2:
1232 bm.edges.new(verts)
1234 bm.to_mesh(d)
1235 o = bpy.data.objects.new(name, d)
1236 scene.collection.objects.link(o)
1237 return o
1239 def object_mesh(self, entities, scene, name):
1241 entities: list of DXF entities
1242 name: name of the returned Blender object (String)
1243 Accumulates all entities into a Blender bmesh and returns a Blender object containing it.
1245 d = bpy.data.meshes.new(name)
1246 bm = bmesh.new()
1248 i = 0
1249 for en in entities:
1250 i += 1
1251 if en.dxftype == "3DFACE":
1252 self.the3dface(en, bm)
1253 else:
1254 dxftype = getattr(self, en.dxftype.lower(), None)
1255 if dxftype is not None:
1256 dxftype(en, bm)
1257 else:
1258 self.errors.add(en.dxftype.lower() + " - unknown dxftype")
1259 if i > 0:
1260 if hasattr(en, "thickness"):
1261 if en.thickness != 0:
1262 self._thickness(bm, en.thickness)
1263 bm.to_mesh(d)
1264 o = bpy.data.objects.new(name, d)
1265 # for POLYFACE
1266 if hasattr(en, "extrusion"):
1267 self._extrusion(o, en)
1268 if hasattr(en, "subdivision_levels"):
1269 self._subdivision(o, en)
1270 return o
1271 return None
1273 def object_curve(self, entities, scene, name):
1275 entities: list of DXF entities
1276 name: name of the returned Blender object (String)
1277 Accumulates all entities in the list into a Blender curve and returns a Blender object containing it.
1279 d = bpy.data.curves.new(name, "CURVE")
1281 i = 0
1282 lines = []
1283 for en in entities:
1284 i += 1
1285 TYPE = en.dxftype
1286 if TYPE == "LINE" and self.merge_lines:
1287 lines.append(en)
1288 continue
1289 typefunc = getattr(self, TYPE.lower(), None)
1290 if typefunc is not None:
1291 typefunc(en, d)
1292 else:
1293 self.errors.add(en.dxftype.lower() + " - unknown dxftype")
1295 if len(lines) > 0:
1296 self._merge_lines(lines, d)
1298 if i > 0:
1299 self._check3D_object(d)
1300 o = bpy.data.objects.new(name, d)
1301 self._thickness_and_width(o, en, scene)
1302 self._extrusion(o, en)
1303 return o
1305 return None
1307 def object_surface(self, entities, scene, name):
1309 entities: list of DXF entities
1310 name: name of the returned Blender object (String) (for future use and also to make it callable from
1311 _call_types()
1312 Returns None. Exports all NURB entities to ACIS files if the GUI option for it is set.
1314 def _get_acis_filename(name, ending):
1315 df = self.dwg.filename
1316 dir = os.path.dirname(df)
1317 filename = bpy.path.display_name(df)
1318 return os.path.join(dir, "{}_{}.{}".format(filename, name, ending))
1320 if self.export_acis:
1321 for en in entities:
1322 if name in self.acis_files:
1323 name = name + "." + str(len([n for n in self.acis_files if name in n])).zfill(3)
1324 # store SAB files
1325 if self.dwg.header.get("$ACADVER", "AC1024") > "AC1024":
1326 filename = _get_acis_filename(name, "sab")
1327 self.acis_files.append(name)
1328 with open(filename, 'wb') as f:
1329 f.write(en.acis)
1330 # store SAT files
1331 else:
1332 filename = _get_acis_filename(name, "sat")
1333 self.acis_files.append(name)
1334 with open(filename, 'w') as f:
1335 f.write('\n'.join(en.acis))
1336 return None
1338 """ ITERATE OVER DXF ENTITIES AND CREATE BLENDER OBJECTS """
1340 def _get_group(self, name):
1342 name: name of group (String)
1343 Finds group by name or creates it if it does not exist.
1345 groups = bpy.data.collections
1346 if name in groups.keys():
1347 group = groups[name]
1348 else:
1349 group = bpy.data.collections.new(name)
1350 return group
1352 def _call_object_types(self, TYPE, entities, group, name, scene, separated=False):
1354 TYPE: DXF type
1355 entities: list of DXF entities
1356 group: Blender group (type: bpy_types.Group)
1357 name: name of the object that is being created and returned (String)
1358 separated: flag to make _call_types uniformly available for combined_objects() and separated_objects()
1360 if separated:
1361 entity = entities[0]
1362 else:
1363 entity = entities
1365 # call merged geometry methods
1367 if TYPE is True: # TYPE == True == is_.closed_poly_no_bulge for all entities
1368 o = self.polys_to_mesh(entities, scene, name)
1369 elif is_.mesh(TYPE):
1370 o = self.object_mesh(entities, scene, name)
1371 elif is_.curve(TYPE):
1372 o = self.object_curve(entities, scene, name)
1373 elif is_.nurbs(TYPE):
1374 o = self.object_surface(entities, scene, name)
1376 # call separate object methods (or merged geometry if TYPE depending on type)
1377 else:
1378 try:
1379 type_func = getattr(self, TYPE.lower(), None)
1380 o = type_func(entity, scene, name)
1381 except (AttributeError, TypeError):
1382 # don't call self.light(en), self.mtext(o, en), self.text(o, en) with a list of entities
1383 if is_.separated(TYPE) and not separated:
1384 self.errors.add("DXF-Import: multiple %ss cannot be merged into a Blender object." % TYPE)
1385 elif TYPE == "not_mergeable":
1386 self.errors.add("DXF-Import: Not mergeable dxf type '%s' should not be called in merge-mode" % TYPE)
1387 else:
1388 self.errors.add("DXF-import: Unsupported dxftype: %s" % TYPE)
1389 raise
1391 if type(o) == bpy.types.Object:
1392 if o.name not in scene.objects:
1393 scene.collection.objects.link(o)
1395 if o.name not in group.objects:
1396 group.objects.link(o)
1397 return o
1399 def _recenter(self, scene, name):
1400 bpy.context.screen.scene = scene
1401 bpy.context.view_layer.update()
1402 bpy.ops.object.select_all(action='DESELECT')
1404 recenter_objects = (o for o in scene.objects if "BEVEL" not in o.name and "TAPER" not in o.name
1405 and o not in self.objects_before)
1406 xmin, ymin, zmin, xmax, ymax, zmax = self._bbox(recenter_objects)
1407 vmin = Vector((xmin, ymin, zmin))
1408 vmax = Vector((xmax, ymax, zmax))
1409 center = vmin + (vmax - vmin) / 2
1410 for o in (o for o in scene.objects if "BEVEL" not in o.name and "TAPER" not in o.name
1411 and o not in self.objects_before and o.parent is None):
1412 o.location = o.location - center
1413 o.select_set(True)
1415 if not self.did_group_instance:
1416 bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
1418 if self.pDXF is not None:
1419 self.georeference(scene, center)
1420 else:
1421 scene[name + "_recenter"] = center
1423 def _dupliface(self, blockname, inserts, blgroup, scene):
1425 go through all inserts and check if there is one having no rotation or extrusion (if there is none
1426 then there is none... any other measure to hide the original is also not water proof; just try the first
1427 and most obvious way and if it doesn't work, we will not try to cover uncoverable cases)
1428 - Place the duplicator object with a first face according to the chosen start insert.
1429 - Add the block as a child object to the duplicator object. If there are any recursive inserts in the
1430 insert, keep appending children to the children.
1431 - Any subsequent inserts in the list are represented just by a face in the duplicator object.
1433 aunits = self.dwg.header.get('$AUNITS', 0)
1434 f = 20
1435 base = [
1436 Vector(( sqrt(((1/f)**2))/2, sqrt(((1/f)**2))/2,0)),
1437 Vector(( sqrt(((1/f)**2))/2,-sqrt(((1/f)**2))/2,0)),
1438 Vector((-sqrt(((1/f)**2))/2,-sqrt(((1/f)**2))/2,0)),
1439 Vector((-sqrt(((1/f)**2))/2, sqrt(((1/f)**2))/2,0)),
1442 bm = bmesh.new()
1443 location = None
1444 for entity in inserts:
1445 extrusion = convert.extrusion_to_matrix(entity)
1446 scale = Matrix(((entity.scale[0],0,0,0),(0,entity.scale[1],0,0),(0,0,entity.scale[2],0),(0,0,0,1)))
1447 rotation = radians(entity.rotation) if aunits == 0 else entity.rotation
1448 rotm = scale @ extrusion @ extrusion.Rotation(rotation, 4, "Z")
1449 if location is None:
1450 location = rotm @ Vector(entity.insert)
1451 entity.insert = (0, 0, 0)
1452 transformation = rotm
1453 else:
1454 transformation = rotm.Translation((extrusion @ Vector(entity.insert))-location) @ rotm
1455 verts = []
1456 for v in base:
1457 verts.append(bm.verts.new(transformation @ v))
1458 bm.faces.new(verts)
1462 m = bpy.data.meshes.new(blockname+"_geometry")
1463 bm.to_mesh(m)
1464 o = bpy.data.objects.new(blockname, m)
1465 o.location = location
1466 scene.collection.objects.link(o)
1468 self._nest_block(o, blockname, blgroup, scene)
1469 o.instance_type = "FACES"
1470 o.use_instance_faces_scale = True
1471 o.instance_faces_scale = f
1473 def _nest_block(self, parent, name, blgroup, scene):
1474 b = self.dwg.blocks[name]
1475 e = bpy.data.objects.new(name, None)
1476 scene.collection.objects.link(e)
1477 #e.location = parent.location
1478 e.parent = parent
1479 for TYPE, grouped in groupsort.by_dxftype(b):
1480 if TYPE == "INSERT":
1481 for en in grouped:
1482 self._nest_block(e, en.name, blgroup, scene)
1483 else:
1484 o = self._call_object_types(TYPE, grouped, blgroup, name+"_"+TYPE, scene)
1485 #o.location = e.location
1486 o.parent = e
1488 def combined_objects(self, entities, scene, override_name=None, override_group=None):
1490 entities: list of dxf entities
1491 override_group & override_name: for use within insert() and block()
1492 Adds multiple dxf entities to one Blender object (per blender or dxf type).
1494 objects = []
1495 for layer_name, layer_ents in groupsort.by_layer(entities):
1496 # group and name
1497 if override_group is None:
1498 group = self._get_group(layer_name)
1499 else:
1500 group = override_group
1501 if override_name is not None:
1502 layer_name = override_name
1504 # sort
1505 if self.combination == BY_LAYER:
1506 group_sorted = groupsort.by_blender_type(layer_ents)
1507 elif self.combination == BY_DXFTYPE or self.combination == BY_BLOCK:
1508 group_sorted = groupsort.by_dxftype(layer_ents)
1509 elif self.combination == BY_CLOSED_NO_BULGE_POLY:
1510 group_sorted = groupsort.by_closed_poly_no_bulge(layer_ents)
1511 else:
1512 break
1514 for TYPE, grouped_entities in group_sorted:
1515 if self.but_group_by_att and self.combination != BY_CLOSED_NO_BULGE_POLY and self.combination != BY_BLOCK:
1516 for atts, by_att in groupsort.by_attributes(grouped_entities):
1517 thickness, subd, width, extrusion = atts
1518 if extrusion is None: # unset extrusion defaults to (0, 0, 1)
1519 extrusion = (0, 0, 1)
1520 att = ""
1521 if thickness != 0:
1522 att += "thickness" + str(thickness) + ", "
1523 if subd > 0:
1524 att += "subd" + str(subd) + ", "
1525 if width != [(0, 0)]:
1526 att += "width" + str(width) + ", "
1527 if extrusion != (0, 0, 1):
1528 att += "extrusion" + str([str(round(c, 1)) + ".." + str(c)[-1:] for c in extrusion]) + ", "
1529 name = layer_name + "_" + TYPE.replace("object_", "") + "_" + att
1531 o = self._call_object_types(TYPE, by_att, group, name, scene, False)
1532 if o is not None:
1533 objects.append(o)
1534 else:
1535 if type(TYPE) is bool and not TYPE:
1536 for ttype, sub_entities in groupsort.by_blender_type(grouped_entities):
1537 name = layer_name + "_" + ttype.replace("object_", "")
1538 o = self._call_object_types(ttype, sub_entities, group, name, scene, False)
1539 if o is not None:
1540 objects.append(o)
1541 else:
1542 if TYPE == "INSERT" and self.combination == BY_BLOCK:
1543 for NAME, grouped_inserts in groupsort.by_insert_block_name(grouped_entities):
1544 sorted_inserts = []
1545 separates = []
1546 for i in grouped_inserts:
1547 sames = 1
1548 for c in range(2):
1549 if i.scale[c+1] - i.scale[0] < 0.00001:
1550 sames += 1
1551 if not (sames == 3 or (sames == 2 and i.scale[2] == 1)):
1552 print(i.scale)
1553 separates.append(i)
1554 else:
1555 if i.extrusion == (0, 0, 1) and i.rotation == 0.0 and i.scale == (1, 1, 1):
1556 sorted_inserts.insert(0, i)
1557 else:
1558 sorted_inserts.append(i)
1560 if len(sorted_inserts) > 0:
1561 self._dupliface(NAME, sorted_inserts, group, scene)
1562 for s in separates:
1563 self.insert(s, scene, NAME, group)
1564 else:
1565 name = layer_name + "_" + TYPE.replace("object_", "") if type(TYPE) is str else "MERGED_POLYS"
1566 o = self._call_object_types(TYPE, grouped_entities, group, name, scene, False)
1567 if o is not None:
1568 objects.append(o)
1569 return objects
1571 def separated_entities(self, entities, scene, override_name=None, override_group=None):
1573 entities: list of dxf entities
1574 override_group & override_name: for use within insert() and block()
1575 Adds multiple DXF entities to multiple Blender objects.
1577 def _do_it(en):
1578 # group and name
1579 if override_group is None:
1580 group = self._get_group(en.layer)
1581 else:
1582 group = override_group
1584 if override_name is None:
1585 name = en.dxftype
1586 else:
1587 name = override_name
1589 if en.dxftype == "POINT":
1590 o = self.point_object(en)
1591 else:
1592 o = self._call_object_types(en.dxftype, [en], group, name, scene, separated=True)
1594 if o is not None:
1595 objects.append(o)
1597 objects = []
1598 split_entities = []
1600 for en in entities:
1601 split = convert.split_by_width(en)
1602 if len(split) > 1:
1603 split_entities.extend(split)
1604 continue
1605 _do_it(en)
1607 for en in split_entities:
1608 _do_it(en)
1610 return objects
1612 def entities(self, name, scene=None):
1614 Iterates over all DXF entities according to the options set by user.
1616 if scene is None:
1617 scene = bpy.context.scene
1619 self.current_scene = scene
1621 if self.recenter:
1622 self.objects_before += scene.objects[:]
1624 if self.combination == BY_BLOCK:
1625 self.combined_objects((en for en in self.dwg.modelspace()), scene)
1626 elif self.combination != SEPARATED:
1627 self.combined_objects((en for en in self.dwg.modelspace() if is_.combined_entity(en)), scene)
1628 self.separated_entities((en for en in self.dwg.modelspace() if is_.separated_entity(en)), scene)
1629 else:
1630 self.separated_entities((en for en in self.dwg.modelspace() if en.dxftype != "ATTDEF"), scene)
1632 if self.recenter:
1633 self._recenter(scene, name)
1634 elif self.pDXF is not None:
1635 self.georeference(scene, Vector((0, 0, 0)))
1637 if type(self.pScene) is TransverseMercator:
1638 scene['SRID'] = "tmerc"
1639 elif self.pScene is not None: # assume Proj
1640 scene['SRID'] = re.findall(r"\+init=(.+)\s", self.pScene.srs)[0]
1642 #bpy.context.screen.scene = scene
1644 return self.errors
1645 # trying to import dimensions:
1646 # self.separated_objects((block for block in self.dwg.blocks if block.name.startswith("*")))