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