1 # SPDX-FileCopyrightText: 2014-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
8 from mathutils
import Vector
, Matrix
, Euler
, Color
, geometry
9 from math
import pi
, radians
, sqrt
12 from .. import dxfgrabber
13 from . import convert
, is_
, groupsort
14 from .line_merger
import line_merger
15 from ..transverse_mercator
import TransverseMercator
19 from pyproj
import Proj
, transform
as proj_transform
26 BY_CLOSED_NO_BULGE_POLY
= 2
33 def transform(p1
, p2
, c1
, c2
, c3
):
35 if type(p1
) is Proj
and type(p2
) is Proj
:
37 return proj_transform(p1
, p2
, 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
)
45 t1
, t2
, t3
= c2
, c1
, c3
# mind c2, c1 inversion
46 tm1
, tm2
= p2
.fromGeographic(t1
, t2
)
50 t1
, t2
= p2
.fromGeographic(c2
, c1
) # mind c2, c1 inversion
68 def __init__(self
, i
):
69 if i
.upper() == "SPHERICAL":
71 elif i
.upper() == "EUCLIDEAN":
74 raise AttributeError("Indication must be 'spherical' or 'euclidean'. Given: " + str(i
))
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",
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})
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
98 self
.block_representation
= block_rep
99 self
.recenter
= recenter
100 self
.did_group_instance
= False
101 self
.objects_before
= []
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
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
))
139 newco
= Vector(transform(self
.pDXF
, self
.pScene
, c1
, c2
, c3
))
141 if any((c
== float("inf") or c
== float("-inf") for c
in newco
)):
142 self
.errors
.add("Projection results in +/- infinity coordinates.")
145 u
= self
.dxf_unit_scale
156 return Vector((c1
, c2
, c3
))
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
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
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
217 self
._cubic
_bezier
_closed
(points
, curve
)
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")
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
)
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)
281 aunits
= self
.dwg
.header
.get('$AUNITS', 0)
283 angbase
= self
.dwg
.header
.get('$ANGBASE', 0)
285 angdir
= self
.dwg
.header
.get('$ANGDIR', 0)
288 # TODO: add support for 1 (dms) and 4 (survey)
291 s
= radians(en
.start_angle
+angbase
)
292 e
= radians(en
.end_angle
+angbase
)
295 s
= radians(0.9 * (en
.start_angle
+ angbase
))
296 e
= radians(0.9 * (en
.end_angle
+ angbase
))
299 s
= en
.start_angle
+angbase
300 e
= en
.end_angle
+angbase
306 vc
= Vector(en
.center
)
307 x_vec
= Vector((1, 0, 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
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")
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
)
328 spline
.append(vc
+ start
+ start
* kappa
* angle
/ (pi
/ 2) @ rot
)
330 # fill if angle is larger than 90 degrees
332 if abs(angle
) - threshold
> a
:
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
)
344 spline
.append(vc
+ fillnext
+ fillnext
* kappa
@ rot
)
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
)
368 def circle(self
, en
, curve
, major
=Vector((1, 0, 0))):
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
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)))
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
)
397 print("Circle center: ", vc
, "radius: ", r
)
402 def scale_controlpoint(self
, p
, factor
):
404 p: Blender control-point
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())
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)))
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
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`
452 curve
.dimensions
= "3D"
453 spline
= convert
.bspline_to_cubic(self
, en
, curve
, self
.errors
)
455 self
.errors
.add("Not able to import bspline with degree > 3")
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
)
482 while i
< len(points
):
483 if points
.count(points
[i
]) > 1:
490 verts
.append(bm
.verts
.new(self
.proj(p
)))
492 # add only an edge if len points < 3
495 elif len(points
) > 2:
496 face
= bm
.faces
.new(verts
)
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
)
506 if _is_on_edge(ii
[0]):
508 bm
.faces
.remove(face
)
509 except Exception as e
:
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
):
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]
524 self
._gen
_meshface
(points
, bm
)
526 def solid(self
, en
, bm
):
528 bm: Blender bmesh data to which the SOLID should be added to.
529 <-? bm.from_mesh(object.data) ?->
532 points
= (p
[0], p
[1], p
[3], p
[2])
533 self
._gen
_meshface
(points
, bm
)
535 def trace(self
, 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
):
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()
556 idx
= subface
.indices()
561 if len(points
) in (3, 4):
562 bm
.faces
.new([bm
.verts
[i
] for i
in points
])
564 def polymesh(self
, en
, bm
):
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
):
573 i_
= (i
- 1) % en
.mcount
574 for j
in range(1, nc
):
576 j_
= (j
- 1) % en
.ncount
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
))))
585 def mesh(self
, en
, bm
):
588 m: Blender MESH data (object.data) to which the dxf-mesh should be added
591 for v
in en
.vertices
:
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
]
602 for i
, edge
in enumerate(en
.edges
):
603 bm
.edges
.new([bm
.verts
[edge
[0]], bm
.verts
[edge
[1]]])
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()
629 om
= obj
.matrix_basis
630 for v
in obj
.bound_box
:
645 if xmin
== float('+inf'):
647 if ymin
== float('+inf'):
649 if zmin
== float('+inf'):
651 if xmax
== float('-inf'):
653 if ymax
== float('-inf'):
655 if zmax
== float('-inf'):
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
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
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")
692 o
= bpy
.data
.objects
.new(name
, d
)
695 def _vertex_duplication(self
, x
, y
, x_count
, y_count
):
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")
705 def point_object(self
, en
, scene
, name
=None):
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
):
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
])
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
)
742 def mtext(self
, en
, scene
, name
):
745 name: ignored; exists to make separate and merged objects methods universally callable from _call_types()
746 Returns a new multi-line text object.
749 text
= en
.plain_text()
751 d
= bpy
.data
.curves
.new(name
, "FONT")
752 o
= bpy
.data
.objects
.new(name
, d
)
755 if en
.rect_width
is not None:
756 if en
.rect_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
763 width
= en
.rect_width
765 d
.text_boxes
[0].width
= width
771 o
.rotation_euler
= Vector((1, 0, 0)).rotation_difference(en
.xdirection
).to_euler()
772 o
.location
= en
.insert
775 def text(self
, en
, scene
, name
):
778 name: ignored; exists to make separate and merged objects methods universally callable from _call_types()
779 Returns a new single line text object.
783 d
= bpy
.data
.curves
.new(name
, "FONT")
784 d
.body
= en
.plain_text()
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
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
)
827 if override_group
is not None:
828 group
= override_group
830 group
= self
._get
_group
(entity
.layer
)
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
)
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
[:]
858 child
.parent
= i_copy
859 inserts
.append(i_copy
)
860 insert_bounding_boxes
.append(insert
)
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
)
870 o
= bpy
.data
.objects
.new(name
, None)
871 self
.current_collection
.objects
.link(o
)
876 elif len(objects
) == 1:
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
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
900 self
.known_blocks
[name
].append(o
.copy())
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
)
910 self
.current_collection
.objects
.link(o
)
912 _recursive_copy_inserts(o
, known_inserts
, inserts
, group
, invisible
)
914 # parent objects to o
922 if obj
.name
not in group
.objects
:
923 group
.objects
.link(obj
)
926 if invisible
is not None:
928 obj
.hide_viewport
= bool(invisible
)
930 # block transformations
931 o
.location
= self
.proj(entity
.basepoint
)
935 def block_group_instances(self
, entity
, scene
, name
=None, override_group
=None, invisible
=None, recursion_level
=0):
936 self
.did_group_instance
= True
941 if override_group
is not None:
942 group
= override_group
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")
951 block_scene
= bpy
.data
.scenes
["Blocks"]
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
))
961 if self
.combination
!= SEPARATED
:
962 objects
+= self
.combined_objects(bc
, block_scene
, "BL|" + name
, block_group
)
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
970 for INSERT
in block_inserts
:
971 i
= self
.insert(INSERT
, block_scene
, None, block_group
, invisible
, recursion_level
+ 1, True)
974 bbox
= self
._object
_bbox
(objects
+ inserts
, name
, True)
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
]
989 bbox
= self
.known_blocks
[name
][2]
991 bpy
.context
.window
.scene
= scene
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()
1005 def insert(self
, entity
, scene
, name
, group
=None, invisible
=None, recursion_level
=0, need_group_inst
=None):
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))
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
)
1031 o
= self
.block_linked_object(self
.dwg
.blocks
[entity
.name
], scene
, entity
.name
, group
,
1032 entity
.invisible
, recursion_level
)
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)
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
)
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
)
1066 if invisible
is None:
1067 o
.hide_viewport
= bool(entity
.invisible
)
1069 o
.hide_viewport
= bool(invisible
)
1072 if self
.import_text
:
1073 if entity
.attribsfollow
:
1074 for a
in entity
.attribs
:
1075 # Blender custom property
1077 attname
= entity
.name
+ "_" + a
.tag
1078 self
.current_collection
.objects
.link(self
.text(a
, scene
, attname
))
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
):
1109 if not self
.thickness_and_width
:
1111 original_faces
= [face
for face
in bm
.faces
]
1113 for face
in original_faces
:
1114 normal
= face
.normal
.copy()
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
)
1121 v
.co
+= normal
* thickness
1125 def _thickness_and_width(self
, obj
, entity
, scene
):
1127 Used for curve types
1129 if not self
.thickness_and_width
:
1131 has_varying_width
= is_
.varying_width(entity
)
1132 th
= entity
.thickness
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
:
1140 obj
.data
.extrude
= abs(th
/ 2)
1142 obj
.location
.z
+= th
/ 2
1144 obj
.location
.z
-= th
/ 2
1145 obj
.data
.dimensions
= "3D"
1146 obj
.data
.twist_mode
= "Z_UP"
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")
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
)
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))
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]
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
1187 solidify
= obj
.modifiers
.new("THICKNESS", "SOLIDIFY")
1188 solidify
.thickness
= th
1189 solidify
.use_even_offset
= True
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
)
1206 m
= Matrix(((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)))
1210 if is_
.extrusion(en
):
1211 t
= convert
.extrusion_to_matrix(en
)
1213 verts
.append(bm
.verts
.new(self
.proj((t
*Vector(p
)).to_3d())))
1216 elif len(verts
) == 2:
1220 o
= bpy
.data
.objects
.new(name
, d
)
1221 self
.current_collection
.objects
.link(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
)
1236 if en
.dxftype
== "3DFACE":
1237 self
.the3dface(en
, bm
)
1239 dxftype
= getattr(self
, en
.dxftype
.lower(), None)
1240 if dxftype
is not None:
1243 self
.errors
.add(en
.dxftype
.lower() + " - unknown dxftype")
1245 if hasattr(en
, "thickness"):
1246 if en
.thickness
!= 0:
1247 self
._thickness
(bm
, en
.thickness
)
1249 o
= bpy
.data
.objects
.new(name
, d
)
1251 if hasattr(en
, "extrusion"):
1252 self
._extrusion
(o
, en
)
1253 if hasattr(en
, "subdivision_levels"):
1254 self
._subdivision
(o
, en
)
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")
1271 if TYPE
== "LINE" and self
.merge_lines
:
1274 typefunc
= getattr(self
, TYPE
.lower(), None)
1275 if typefunc
is not None:
1278 self
.errors
.add(en
.dxftype
.lower() + " - unknown dxftype")
1281 self
._merge
_lines
(lines
, d
)
1284 self
._check
3D
_object
(d
)
1285 o
= bpy
.data
.objects
.new(name
, d
)
1286 self
._thickness
_and
_width
(o
, en
, scene
)
1287 self
._extrusion
(o
, en
)
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
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
:
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)
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
:
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
))
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
]
1334 group
= bpy
.data
.collections
.new(name
)
1337 def _call_object_types(self
, TYPE
, entities
, group
, name
, scene
, separated
=False):
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()
1346 entity
= entities
[0]
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)
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
)
1373 self
.errors
.add("DXF-import: Unsupported dxftype: %s" % TYPE
)
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
)
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
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
)
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)
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)),
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
1439 transformation
= rotm
.Translation((extrusion
@ Vector(entity
.insert
))-location
) @ rotm
1442 verts
.append(bm
.verts
.new(transformation
@ v
))
1447 m
= bpy
.data
.meshes
.new(blockname
+"_geometry")
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
1464 for TYPE
, grouped
in groupsort
.by_dxftype(b
):
1465 if TYPE
== "INSERT":
1467 self
._nest
_block
(e
, en
.name
, blgroup
, scene
)
1469 o
= self
._call
_object
_types
(TYPE
, grouped
, blgroup
, name
+"_"+TYPE
, scene
)
1470 #o.location = e.location
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).
1480 for layer_name
, layer_ents
in groupsort
.by_layer(entities
):
1482 if override_group
is None:
1483 group
= self
._get
_group
(layer_name
)
1485 group
= override_group
1486 if override_name
is not None:
1487 layer_name
= override_name
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
)
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)
1507 att
+= "thickness" + str(thickness
) + ", "
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)
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)
1527 if TYPE
== "INSERT" and self
.combination
== BY_BLOCK
:
1528 for NAME
, grouped_inserts
in groupsort
.by_insert_block_name(grouped_entities
):
1531 for i
in grouped_inserts
:
1534 if i
.scale
[c
+1] - i
.scale
[0] < 0.00001:
1536 if not (sames
== 3 or (sames
== 2 and i
.scale
[2] == 1)):
1540 if i
.extrusion
== (0, 0, 1) and i
.rotation
== 0.0 and i
.scale
== (1, 1, 1):
1541 sorted_inserts
.insert(0, i
)
1543 sorted_inserts
.append(i
)
1545 if len(sorted_inserts
) > 0:
1546 self
._dupliface
(NAME
, sorted_inserts
, group
, scene
)
1548 self
.insert(s
, scene
, NAME
, group
)
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)
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.
1564 if override_group
is None:
1565 group
= self
._get
_group
(en
.layer
)
1567 group
= override_group
1569 if override_name
is None:
1572 name
= override_name
1574 if en
.dxftype
== "POINT":
1575 o
= self
.point_object(en
)
1577 o
= self
._call
_object
_types
(en
.dxftype
, [en
], group
, name
, scene
, separated
=True)
1586 split
= convert
.split_by_width(en
)
1588 split_entities
.extend(split
)
1592 for en
in split_entities
:
1597 def entities(self
, name
, scene
=None, collection
=None):
1599 Iterates over all DXF entities according to the options set by user.
1602 scene
= bpy
.context
.scene
1604 if collection
is None:
1605 collection
= scene
.collection
1607 self
.current_scene
= scene
1608 self
.current_collection
= collection
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
)
1619 self
.separated_entities((en
for en
in self
.dwg
.modelspace() if en
.dxftype
!= "ATTDEF"), scene
)
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
1634 # trying to import dimensions:
1635 # self.separated_objects((block for block in self.dwg.blocks if block.name.startswith("*")))