1 # SPDX-License-Identifier: GPL-2.0-or-later
5 from bpy
.props
import StringProperty
, BoolProperty
, EnumProperty
, IntProperty
, FloatProperty
6 from .dxfimport
.do
import Do
, Indicator
7 from .transverse_mercator
import TransverseMercator
11 from pyproj
import Proj
, transform
17 "name": "Import AutoCAD DXF Format (.dxf)",
18 "author": "Lukas Treyer, Manfred Moitzi (support + dxfgrabber library), Vladimir Elistratov, Bastien Montagne, Remigiusz Fiedler (AKA migius)",
20 "blender": (2, 80, 0),
21 "location": "File > Import > AutoCAD DXF",
22 "description": "Import files in the Autocad DXF format (.dxf)",
23 "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/scene_dxf.html",
24 "category": "Import-Export",
29 ('NONE', "None", "No Coordinate System is available / will be set"),
32 ('USER', "User Defined", "Define the EPSG code"),
35 ('TMERC', "Transverse Mercator", "Mercator Projection using a lat/lon coordinate as its geo-reference"),
38 ('EPSG:4326', "WGS84", "World Geodetic System 84; default for lat / lon; EPSG:4326"),
39 ('EPSG:3857', "Spherical Mercator", "Webbrowser mapping service standard (Google, OpenStreetMap, ESRI); EPSG:3857"),
40 ('EPSG:27700', "National Grid U.K",
41 "Ordnance Survey National Grid reference system used in Great Britain; EPSG:27700"),
42 ('EPSG:2154', "France (Lambert 93)", "Lambert Projection for France; EPSG:2154"),
43 ('EPSG:5514', "Czech Republic & Slovakia", "Coordinate System for Czech Republic and Slovakia; EPSG:5514"),
44 ('EPSG:5243', "LLC Germany", "Projection for Germany; EPSG:5243"),
45 ('EPSG:28992', "Amersfoort Netherlands", "Amersfoort / RD New -- Netherlands; EPSG:28992"),
46 ('EPSG:21781', "Swiss CH1903 / LV03", "Switzerland and Lichtenstein; EPSG:21781"),
47 ('EPSG:5880', "Brazil Polyconic", "Cartesian 2D; Central, South America; EPSG:5880 "),
48 ('EPSG:42103', "LCC USA", "Lambert Conformal Conic Projection; EPSG:42103"),
49 ('EPSG:3350', "Russia: Pulkovo 1942 / CS63 zone C0", "Russian Federation - onshore and offshore; EPSG:3350"),
50 ('EPSG:22293', "Cape / Lo33 South Africa", "South Africa; EPSG:22293"),
51 ('EPSG:27200', "NZGD49 / New Zealand Map Grid", "NZGD49 / New Zealand Map Grid; EPSG:27200"),
52 ('EPSG:3112', "GDA94 Australia Lambert", "GDA94 / Geoscience Australia Lambert; EPSG:3112"),
53 ('EPSG:24378', "India zone I", "Kalianpur 1975 / India zone I; EPSG:24378"),
54 ('EPSG:2326', "Hong Kong 1980 Grid System", "Hong Kong 1980 Grid System; EPSG:2326"),
55 ('EPSG:3414', "SVY21 / Singapore TM", "SVY21 / Singapore TM; EPSG:3414"),
58 proj_epsg_dict
= {e
[0]: e
[1] for e
in proj_epsg_items
}
60 __version__
= '.'.join([str(s
) for s
in bl_info
['version']])
64 BY_CLOSED_NO_BULGE_POLY
= 2
70 merge_map
= {"BY_LAYER": BY_LAYER
, "BY_TYPE": BY_DXFTYPE
,
71 "BY_CLOSED_NO_BULGE_POLY": BY_CLOSED_NO_BULGE_POLY
, "BY_BLOCKS": BY_BLOCKS
}
78 T_OutlinerGroups
= True
80 T_CreateNewScene
= False
82 T_ThicknessBevel
= True
89 def is_ref_scene(scene
):
90 return "latitude" in scene
and "longitude" in scene
93 def read(report
, filename
, obj_merge
=BY_LAYER
, import_text
=True, import_light
=True, export_acis
=True, merge_lines
=True,
94 do_bbox
=True, block_rep
=LINKED_OBJECTS
, new_scene
=None, recenter
=False, projDXF
=None, projSCN
=None,
95 thicknessWidth
=True, but_group_by_att
=True, dxf_unit_scale
=1.0):
96 # import dxf and export nurbs types to sat/sab files
97 # because that's how autocad stores nurbs types in a dxf...
98 do
= Do(filename
, obj_merge
, import_text
, import_light
, export_acis
, merge_lines
, do_bbox
, block_rep
, recenter
,
99 projDXF
, projSCN
, thicknessWidth
, but_group_by_att
, dxf_unit_scale
)
101 errors
= do
.entities(os
.path
.basename(filename
).replace(".dxf", ""), new_scene
)
105 report({'ERROR', 'INFO'}, error
)
107 # inform the user about the sat/sab files
108 if len(do
.acis_files
) > 0:
109 report({'INFO'}, "Exported %d NURBS objects to sat/sab files next to your DXF file" % len(do
.acis_files
))
112 def display_groups_in_outliner():
113 outliners
= (a
for a
in bpy
.context
.screen
.areas
if a
.type == "OUTLINER")
114 for outliner
in outliners
:
116 #outliner.spaces[0].display_mode = "GROUPS"
119 # Update helpers (must be globals to be re-usable).
120 def _update_use_georeferencing_do(self
, context
):
121 if not self
.create_new_scene
:
122 scene
= context
.scene
123 # Try to get Scene SRID (ESPG) data from current scene.
124 srid
= scene
.get("SRID", None)
126 self
.internal_using_scene_srid
= True
129 self
.proj_scene
= 'TMERC'
130 self
.merc_scene_lat
= scene
.get('latitude', 0)
131 self
.merc_scene_lon
= scene
.get('longitude', 0)
133 if srid
in (p
[0] for p
in proj_epsg_items
):
134 self
.proj_scene
= srid
136 self
.proj_scene
= 'USER'
137 self
.epsg_scene_user
= srid
139 self
.internal_using_scene_srid
= False
141 self
.internal_using_scene_srid
= False
144 def _recenter_allowed(self
):
145 scene
= bpy
.context
.scene
146 conditional_requirement
= self
.proj_scene
== 'TMERC' if PYPROJ
else self
.dxf_indi
== "SPHERICAL"
148 self
.use_georeferencing
and
150 conditional_requirement
or
151 (not self
.create_new_scene
and is_ref_scene(scene
))
156 def _set_recenter(self
, value
):
157 self
.recenter
= value
if _recenter_allowed(self
) else False
160 def _update_proj_scene_do(self
, context
):
161 # make sure scene EPSG is not None if DXF EPSG is not None
162 if self
.proj_scene
== 'NONE' and self
.proj_dxf
!= 'NONE':
163 self
.proj_scene
= self
.proj_dxf
166 def _update_import_atts_do(self
, context
):
167 mo
= merge_map
[self
.merge_options
]
168 if mo
== BY_CLOSED_NO_BULGE_POLY
or mo
== BY_BLOCKS
:
169 self
.import_atts
= False
170 self
.represent_thickness_and_width
= False
171 elif self
.represent_thickness_and_width
and self
.merge
:
172 self
.import_atts
= True
174 self
.import_atts
= False
177 class IMPORT_OT_dxf(bpy
.types
.Operator
):
178 """Import from DXF file format (.dxf)"""
179 bl_idname
= "import_scene.dxf"
180 bl_description
= 'Import from DXF file format (.dxf)'
181 bl_label
= "Import DXf v." + __version__
182 bl_space_type
= 'PROPERTIES'
183 bl_region_type
= 'WINDOW'
184 bl_options
= {'UNDO'}
186 filepath
: StringProperty(
191 filename_ext
= ".dxf"
193 filter_glob
: StringProperty(
198 def _update_merge(self
, context
):
199 _update_import_atts_do(self
, context
)
201 name
="Merged Objects",
202 description
="Merge DXF entities to Blender objects",
207 def _update_merge_options(self
, context
):
208 _update_import_atts_do(self
, context
)
210 merge_options
: EnumProperty(
212 description
="Merge multiple DXF entities into one Blender object",
213 items
=[('BY_LAYER', "By Layer", "Merge DXF entities of a layer to an object"),
214 ('BY_TYPE', "By Layer AND DXF-Type", "Merge DXF entities by type AND layer"),
215 ('BY_CLOSED_NO_BULGE_POLY', "By Layer AND closed no-bulge polys", "Polys can have a transformation attribute that makes DXF polys resemble Blender mesh faces quite a bit. Merging them results in one MESH object."),
216 ('BY_BLOCKS', "By Layer AND DXF-Type AND Blocks", "Merging blocks results in all uniformly scaled blocks being referenced by a dupliface mesh instead of object containers. Non-uniformly scaled blocks will be imported as indicated by 'Blocks As'.")],
218 update
=_update_merge_options
221 merge_lines
: BoolProperty(
222 name
="Combine LINE entities to polygons",
223 description
="Checks if lines are connect on start or end and merges them to a polygon",
227 import_text
: BoolProperty(
229 description
="Import DXF Text Entities MTEXT and TEXT",
230 default
=T_ImportText
,
233 import_light
: BoolProperty(
234 name
="Import Lights",
235 description
="Import DXF Text Entity LIGHT",
236 default
=T_ImportLight
239 export_acis
: BoolProperty(
240 name
="Export ACIS Entities",
241 description
="Export Entities consisting of ACIS code to ACIS .sat/.sab files",
245 outliner_groups
: BoolProperty(
246 name
="Display Groups in Outliner(s)",
247 description
="Make all outliners in current screen layout show groups",
248 default
=T_OutlinerGroups
251 do_bbox
: BoolProperty(
252 name
="Parent Blocks to Bounding Boxes",
253 description
="Create a bounding box for blocks with more than one object (faster without)",
259 block_options
: EnumProperty(
261 description
="Select the representation of DXF blocks: linked objects or group instances",
262 items
=[('LINKED_OBJECTS', "Linked Objects", "Block objects get imported as linked objects"),
263 ('GROUP_INSTANCES', "Group Instances", "Block objects get imported as group instances")],
264 default
='LINKED_OBJECTS',
268 def _update_create_new_scene(self
, context
):
269 _update_use_georeferencing_do(self
, context
)
270 _set_recenter(self
, self
.recenter
)
271 create_new_scene
: BoolProperty(
272 name
="Import DXF to new scene",
273 description
="Creates a new scene with the name of the imported file",
274 default
=T_CreateNewScene
,
275 update
=_update_create_new_scene
,
278 recenter
: BoolProperty(
279 name
="Center geometry to scene",
280 description
="Moves geometry to the center of the scene",
284 def _update_thickness_width(self
, context
):
285 _update_import_atts_do(self
, context
)
286 represent_thickness_and_width
: BoolProperty(
287 name
="Represent line thickness/width",
288 description
="Map thickness and width of lines to Bevel objects and extrusion attribute",
289 default
=T_ThicknessBevel
,
290 update
=_update_thickness_width
293 import_atts
: BoolProperty(
294 name
="Merge by attributes",
295 description
="If 'Merge objects' is on but thickness and width are not chosen to be represented, with this "
296 "option object still can be merged by thickness, with, subd and extrusion attributes "
297 "(extrusion = transformation matrix of DXF objects)",
298 default
=T_import_atts
303 def _update_use_georeferencing(self
, context
):
304 _update_use_georeferencing_do(self
, context
)
305 _set_recenter(self
, self
.recenter
)
306 use_georeferencing
: BoolProperty(
307 name
="Geo Referencing",
308 description
="Project coordinates to a given coordinate system or reference point",
310 update
=_update_use_georeferencing
,
313 def _update_dxf_indi(self
, context
):
314 _set_recenter(self
, self
.recenter
)
315 dxf_indi
: EnumProperty(
316 name
="DXF coordinate type",
317 description
="Indication for spherical or euclidean coordinates",
318 items
=[('EUCLIDEAN', "Euclidean", "Coordinates in x/y"),
319 ('SPHERICAL', "Spherical", "Coordinates in lat/lon")],
321 update
=_update_dxf_indi
,
324 # Note: FloatProperty is not precise enough, e.g. 1.0 becomes 0.999999999. Python is more precise here (it uses
325 # doubles internally), so we store it as string here and convert to number with py's float() func.
326 dxf_scale
: StringProperty(
328 description
="Coordinates are assumed to be in meters; deviation must be indicated here",
332 def _update_proj(self
, context
):
333 _update_proj_scene_do(self
, context
)
334 _set_recenter(self
, self
.recenter
)
336 pitems
= proj_none_items
+ proj_user_items
+ proj_epsg_items
337 proj_dxf
: EnumProperty(
339 description
="The coordinate system for the DXF file (check http://epsg.io)",
345 epsg_dxf_user
: StringProperty(name
="EPSG-Code", default
="EPSG")
346 merc_dxf_lat
: FloatProperty(name
="Geo-Reference Latitude", default
=0.0)
347 merc_dxf_lon
: FloatProperty(name
="Geo-Reference Longitude", default
=0.0)
349 pitems
= proj_none_items
+ ((proj_user_items
+ proj_tmerc_items
+ proj_epsg_items
) if PYPROJ
else proj_tmerc_items
)
350 proj_scene
: EnumProperty(
352 description
="The coordinate system for the Scene (check http://epsg.io)",
358 epsg_scene_user
: StringProperty(name
="EPSG-Code", default
="EPSG")
359 merc_scene_lat
: FloatProperty(name
="Geo-Reference Latitude", default
=0.0)
360 merc_scene_lon
: FloatProperty(name
="Geo-Reference Longitude", default
=0.0)
363 internal_using_scene_srid
: BoolProperty(default
=False, options
={'HIDDEN'})
365 def draw(self
, context
):
367 scene
= context
.scene
370 layout
.label(text
="Merge Options:")
373 #sub.enabled = merge_map[self.merge_options] != BY_BLOCKS
374 sub
.prop(self
, "block_options")
375 box
.prop(self
, "do_bbox")
376 box
.prop(self
, "merge")
378 sub
.enabled
= self
.merge
379 sub
.prop(self
, "merge_options")
380 box
.prop(self
, "merge_lines")
383 layout
.label(text
="Line thickness and width:")
385 box
.enabled
= not merge_map
[self
.merge_options
] == BY_CLOSED_NO_BULGE_POLY
386 box
.prop(self
, "represent_thickness_and_width")
388 sub
.enabled
= (not self
.represent_thickness_and_width
and self
.merge
)
389 sub
.prop(self
, "import_atts")
392 layout
.label(text
="Optional Objects:")
394 box
.prop(self
, "import_text")
395 box
.prop(self
, "import_light")
396 box
.prop(self
, "export_acis")
399 layout
.label(text
="View Options:")
401 box
.prop(self
, "outliner_groups")
402 box
.prop(self
, "create_new_scene")
404 sub
.enabled
= _recenter_allowed(self
)
405 sub
.prop(self
, "recenter")
408 layout
.prop(self
, "use_georeferencing", text
="Geo Referencing:")
410 box
.enabled
= self
.use_georeferencing
411 self
.draw_pyproj(box
, context
.scene
) if PYPROJ
else self
.draw_merc(box
)
413 def draw_merc(self
, box
):
414 box
.label(text
="DXF File:")
415 box
.prop(self
, "dxf_indi")
416 box
.prop(self
, "dxf_scale")
419 sub
.enabled
= not _recenter_allowed(self
)
420 sub
.label(text
="Geo Reference:")
422 sub
.enabled
= not _recenter_allowed(self
)
423 if is_ref_scene(bpy
.context
.scene
):
425 sub
.prop(self
, "merc_scene_lat", text
="Lat")
426 sub
.prop(self
, "merc_scene_lon", text
="Lon")
428 def draw_pyproj(self
, box
, scene
):
429 valid_dxf_srid
= True
432 box
.prop(self
, "dxf_scale")
435 box
.alert
= (self
.proj_scene
!= 'NONE' and (not valid_dxf_srid
or self
.proj_dxf
== 'NONE'))
436 box
.prop(self
, "proj_dxf")
438 if self
.proj_dxf
== 'USER':
440 Proj(init
=self
.epsg_dxf_user
)
443 valid_dxf_srid
= False
444 box
.prop(self
, "epsg_dxf_user")
451 # Only info in case of pre-defined EPSG from current scene.
452 if self
.internal_using_scene_srid
:
455 col
.prop(self
, "proj_scene")
457 if self
.proj_scene
== 'USER':
459 Proj(init
=self
.epsg_scene_user
)
460 except Exception as e
:
462 col
.prop(self
, "epsg_scene_user")
464 col
.label(text
="") # Placeholder.
465 elif self
.proj_scene
== 'TMERC':
466 col
.prop(self
, "merc_scene_lat", text
="Lat")
467 col
.prop(self
, "merc_scene_lon", text
="Lon")
469 col
.label(text
="") # Placeholder.
470 col
.label(text
="") # Placeholder.
473 if self
.proj_scene
!= 'NONE':
474 if not valid_dxf_srid
:
475 box
.label(text
="DXF SRID not valid", icon
="ERROR")
476 if self
.proj_dxf
== 'NONE':
477 box
.label(text
="", icon
='ERROR')
478 box
.label(text
="DXF SRID must be set, otherwise")
479 if self
.proj_scene
== 'USER':
480 code
= self
.epsg_scene_user
482 code
= self
.proj_scene
483 box
.label(text
='Scene SRID %r is ignored!' % code
)
485 def execute(self
, context
):
486 block_map
= {"LINKED_OBJECTS": LINKED_OBJECTS
, "GROUP_INSTANCES": GROUP_INSTANCES
}
487 merge_options
= SEPARATED
489 merge_options
= merge_map
[self
.merge_options
]
490 scene
= bpy
.context
.scene
491 if self
.create_new_scene
:
492 scene
= bpy
.data
.scenes
.new(os
.path
.basename(self
.filepath
).replace(".dxf", ""))
497 if self
.use_georeferencing
:
498 dxf_unit_scale
= float(self
.dxf_scale
.replace(",", "."))
500 if self
.proj_dxf
!= 'NONE':
501 if self
.proj_dxf
== 'USER':
502 proj_dxf
= Proj(init
=self
.epsg_dxf_user
)
504 proj_dxf
= Proj(init
=self
.proj_dxf
)
505 if self
.proj_scene
!= 'NONE':
506 if self
.proj_scene
== 'USER':
507 proj_scn
= Proj(init
=self
.epsg_scene_user
)
508 elif self
.proj_scene
== 'TMERC':
509 proj_scn
= TransverseMercator(lat
=self
.merc_scene_lat
, lon
=self
.merc_scene_lon
)
511 proj_scn
= Proj(init
=self
.proj_scene
)
513 proj_dxf
= Indicator(self
.dxf_indi
)
514 proj_scn
= TransverseMercator(lat
=self
.merc_scene_lat
, lon
=self
.merc_scene_lon
)
517 # for release testing
521 read(self
.report
, self
.filepath
, merge_options
, self
.import_text
, self
.import_light
, self
.export_acis
,
522 self
.merge_lines
, self
.do_bbox
, block_map
[self
.block_options
], scene
, self
.recenter
,
523 proj_dxf
, proj_scn
, self
.represent_thickness_and_width
, self
.import_atts
, dxf_unit_scale
)
525 if self
.outliner_groups
:
526 display_groups_in_outliner()
530 def invoke(self
, context
, event
):
531 # Force first update...
532 self
._update
_use
_georeferencing
(context
)
534 wm
= context
.window_manager
535 wm
.fileselect_add(self
)
536 return {'RUNNING_MODAL'}
539 def menu_func(self
, context
):
540 self
.layout
.operator(IMPORT_OT_dxf
.bl_idname
, text
="AutoCAD DXF")
544 bpy
.utils
.register_class(IMPORT_OT_dxf
)
545 bpy
.types
.TOPBAR_MT_file_import
.append(menu_func
)
549 bpy
.utils
.unregister_class(IMPORT_OT_dxf
)
550 bpy
.types
.TOPBAR_MT_file_import
.remove(menu_func
)
553 if __name__
== "__main__":