1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
23 from bpy
.props
import StringProperty
, BoolProperty
, EnumProperty
, IntProperty
, FloatProperty
24 from .dxfimport
.do
import Do
, Indicator
25 from .transverse_mercator
import TransverseMercator
29 from pyproj
import Proj
, transform
35 "name": "Import AutoCAD DXF Format (.dxf)",
36 "author": "Lukas Treyer, Manfred Moitzi (support + dxfgrabber library), Vladimir Elistratov, Bastien Montagne",
39 "location": "File > Import > AutoCAD DXF",
40 "description": "Import files in the Autocad DXF format (.dxf)",
41 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Import-Export/DXF_Importer",
42 "category": "Import-Export",
47 ('NONE', "None", "No Coordinate System is available / will be set"),
50 ('USER', "User Defined", "Define the EPSG code"),
53 ('TMERC', "Transverse Mercator", "Mercator Projection using a lat/lon coordinate as its geo-reference"),
56 ('EPSG:4326', "WGS84", "World Geodetic System 84; default for lat / lon; EPSG:4326"),
57 ('EPSG:3857', "Spherical Mercator", "Webbrowser mapping service standard (Google, OpenStreetMap, ESRI); EPSG:3857"),
58 ('EPSG:27700', "National Grid U.K",
59 "Ordnance Survey National Grid reference system used in Great Britain; EPSG:27700"),
60 ('EPSG:2154', "France (Lambert 93)", "Lambert Projection for France; EPSG:2154"),
61 ('EPSG:5514', "Czech Republic & Slovakia", "Coordinate System for Czech Republic and Slovakia; EPSG:5514"),
62 ('EPSG:5243', "LLC Germany", "Projection for Germany; EPSG:5243"),
63 ('EPSG:28992', "Amersfoort Netherlands", "Amersfoort / RD New -- Netherlands; EPSG:28992"),
64 ('EPSG:21781', "Swiss CH1903 / LV03", "Switzerland and Lichtenstein; EPSG:21781"),
65 ('EPSG:5880', "Brazil Polyconic", "Cartesian 2D; Central, South America; EPSG:5880 "),
66 ('EPSG:42103', "LCC USA", "Lambert Conformal Conic Projection; EPSG:42103"),
67 ('EPSG:3350', "Russia: Pulkovo 1942 / CS63 zone C0", "Russian Federation - onshore and offshore; EPSG:3350"),
68 ('EPSG:22293', "Cape / Lo33 South Africa", "South Africa; EPSG:22293"),
69 ('EPSG:27200', "NZGD49 / New Zealand Map Grid", "NZGD49 / New Zealand Map Grid; EPSG:27200"),
70 ('EPSG:3112', "GDA94 Australia Lambert", "GDA94 / Geoscience Australia Lambert; EPSG:3112"),
71 ('EPSG:24378', "India zone I", "Kalianpur 1975 / India zone I; EPSG:24378"),
72 ('EPSG:2326', "Hong Kong 1980 Grid System", "Hong Kong 1980 Grid System; EPSG:2326"),
73 ('EPSG:3414', "SVY21 / Singapore TM", "SVY21 / Singapore TM; EPSG:3414"),
76 proj_epsg_dict
= {e
[0]: e
[1] for e
in proj_epsg_items
}
78 __version__
= '.'.join([str(s
) for s
in bl_info
['version']])
82 BY_CLOSED_NO_BULGE_POLY
= 2
88 merge_map
= {"BY_LAYER": BY_LAYER
, "BY_TYPE": BY_DXFTYPE
,
89 "BY_CLOSED_NO_BULGE_POLY": BY_CLOSED_NO_BULGE_POLY
, "BY_BLOCKS": BY_BLOCKS
}
96 T_OutlinerGroups
= True
98 T_CreateNewScene
= False
100 T_ThicknessBevel
= True
107 def is_ref_scene(scene
):
108 return "latitude" in scene
and "longitude" in scene
111 def read(report
, filename
, obj_merge
=BY_LAYER
, import_text
=True, import_light
=True, export_acis
=True, merge_lines
=True,
112 do_bbox
=True, block_rep
=LINKED_OBJECTS
, new_scene
=None, recenter
=False, projDXF
=None, projSCN
=None,
113 thicknessWidth
=True, but_group_by_att
=True, dxf_unit_scale
=1.0):
114 # import dxf and export nurbs types to sat/sab files
115 # because that's how autocad stores nurbs types in a dxf...
116 do
= Do(filename
, obj_merge
, import_text
, import_light
, export_acis
, merge_lines
, do_bbox
, block_rep
, recenter
,
117 projDXF
, projSCN
, thicknessWidth
, but_group_by_att
, dxf_unit_scale
)
119 errors
= do
.entities(os
.path
.basename(filename
).replace(".dxf", ""), new_scene
)
123 report({'ERROR', 'INFO'}, error
)
125 # inform the user about the sat/sab files
126 if len(do
.acis_files
) > 0:
127 report({'INFO'}, "Exported %d NURBS objects to sat/sab files next to your DXF file" % len(do
.acis_files
))
130 def display_groups_in_outliner():
131 outliners
= (a
for a
in bpy
.context
.screen
.areas
if a
.type == "OUTLINER")
132 for outliner
in outliners
:
133 outliner
.spaces
[0].display_mode
= "GROUPS"
136 # Update helpers (must be globals to be re-usable).
137 def _update_use_georeferencing_do(self
, context
):
138 if not self
.create_new_scene
:
139 scene
= context
.scene
140 # Try to get Scene SRID (ESPG) data from current scene.
141 srid
= scene
.get("SRID", None)
143 self
.internal_using_scene_srid
= True
146 self
.proj_scene
= 'TMERC'
147 self
.merc_scene_lat
= scene
.get('latitude', 0)
148 self
.merc_scene_lon
= scene
.get('longitude', 0)
150 if srid
in (p
[0] for p
in proj_epsg_items
):
151 self
.proj_scene
= srid
153 self
.proj_scene
= 'USER'
154 self
.epsg_scene_user
= srid
156 self
.internal_using_scene_srid
= False
158 self
.internal_using_scene_srid
= False
161 def _recenter_allowed(self
):
162 scene
= bpy
.context
.scene
163 conditional_requirement
= self
.proj_scene
== 'TMERC' if PYPROJ
else self
.dxf_indi
== "SPHERICAL"
165 self
.use_georeferencing
and
167 conditional_requirement
or
168 (not self
.create_new_scene
and is_ref_scene(scene
))
173 def _set_recenter(self
, value
):
174 self
.recenter
= value
if _recenter_allowed(self
) else False
177 def _update_proj_scene_do(self
, context
):
178 # make sure scene EPSG is not None if DXF EPSG is not None
179 if self
.proj_scene
== 'NONE' and self
.proj_dxf
!= 'NONE':
180 self
.proj_scene
= self
.proj_dxf
183 def _update_import_atts_do(self
, context
):
184 mo
= merge_map
[self
.merge_options
]
185 if mo
== BY_CLOSED_NO_BULGE_POLY
or mo
== BY_BLOCKS
:
186 self
.import_atts
= False
187 self
.represent_thickness_and_width
= False
188 elif self
.represent_thickness_and_width
and self
.merge
:
189 self
.import_atts
= True
191 self
.import_atts
= False
194 class IMPORT_OT_dxf(bpy
.types
.Operator
):
195 """Import from DXF file format (.dxf)"""
196 bl_idname
= "import_scene.dxf"
197 bl_description
= 'Import from DXF file format (.dxf)'
198 bl_label
= "Import DXf v." + __version__
199 bl_space_type
= 'PROPERTIES'
200 bl_region_type
= 'WINDOW'
201 bl_options
= {'UNDO'}
203 filepath
= StringProperty(
208 filename_ext
= ".dxf"
210 filter_glob
= StringProperty(
215 def _update_merge(self
, context
):
216 _update_import_atts_do(self
, context
)
217 merge
= BoolProperty(
218 name
="Merged Objects",
219 description
="Merge DXF entities to Blender objects",
224 def _update_merge_options(self
, context
):
225 _update_import_atts_do(self
, context
)
227 merge_options
= EnumProperty(
229 description
="Merge multiple DXF entities into one Blender object",
230 items
=[('BY_LAYER', "By Layer", "Merge DXF entities of a layer to an object"),
231 ('BY_TYPE', "By Layer AND DXF-Type", "Merge DXF entities by type AND layer"),
232 ('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."),
233 ('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'.")],
235 update
=_update_merge_options
238 merge_lines
= BoolProperty(
239 name
="Combine LINE entities to polygons",
240 description
="Checks if lines are connect on start or end and merges them to a polygon",
244 import_text
= BoolProperty(
246 description
="Import DXF Text Entities MTEXT and TEXT",
247 default
=T_ImportText
,
250 import_light
= BoolProperty(
251 name
="Import Lights",
252 description
="Import DXF Text Entity LIGHT",
253 default
=T_ImportLight
256 export_acis
= BoolProperty(
257 name
="Export ACIS Entities",
258 description
="Export Entities consisting of ACIS code to ACIS .sat/.sab files",
262 outliner_groups
= BoolProperty(
263 name
="Display Groups in Outliner(s)",
264 description
="Make all outliners in current screen layout show groups",
265 default
=T_OutlinerGroups
268 do_bbox
= BoolProperty(
269 name
="Parent Blocks to Bounding Boxes",
270 description
="Create a bounding box for blocks with more than one object (faster without)",
276 block_options
= EnumProperty(
278 description
="Select the representation of DXF blocks: linked objects or group instances",
279 items
=[('LINKED_OBJECTS', "Linked Objects", "Block objects get imported as linked objects"),
280 ('GROUP_INSTANCES', "Group Instances", "Block objects get imported as group instances")],
281 default
='LINKED_OBJECTS',
285 def _update_create_new_scene(self
, context
):
286 _update_use_georeferencing_do(self
, context
)
287 _set_recenter(self
, self
.recenter
)
288 create_new_scene
= BoolProperty(
289 name
="Import DXF to new scene",
290 description
="Creates a new scene with the name of the imported file",
291 default
=T_CreateNewScene
,
292 update
=_update_create_new_scene
,
295 recenter
= BoolProperty(
296 name
="Center geometry to scene",
297 description
="Moves geometry to the center of the scene",
301 def _update_thickness_width(self
, context
):
302 _update_import_atts_do(self
, context
)
303 represent_thickness_and_width
= BoolProperty(
304 name
="Represent line thickness/width",
305 description
="Map thickness and width of lines to Bevel objects and extrusion attribute",
306 default
=T_ThicknessBevel
,
307 update
=_update_thickness_width
310 import_atts
= BoolProperty(
311 name
="Merge by attributes",
312 description
="If 'Merge objects' is on but thickness and width are not chosen to be represented, with this "
313 "option object still can be merged by thickness, with, subd and extrusion attributes "
314 "(extrusion = transformation matrix of DXF objects)",
315 default
=T_import_atts
320 def _update_use_georeferencing(self
, context
):
321 _update_use_georeferencing_do(self
, context
)
322 _set_recenter(self
, self
.recenter
)
323 use_georeferencing
= BoolProperty(
324 name
="Geo Referencing",
325 description
="Project coordinates to a given coordinate system or reference point",
327 update
=_update_use_georeferencing
,
330 def _update_dxf_indi(self
, context
):
331 _set_recenter(self
, self
.recenter
)
332 dxf_indi
= EnumProperty(
333 name
="DXF coordinate type",
334 description
="Indication for spherical or euclidian coordinates",
335 items
=[('EUCLIDEAN', "Euclidean", "Coordinates in x/y"),
336 ('SPHERICAL', "Spherical", "Coordinates in lat/lon")],
338 update
=_update_dxf_indi
,
341 # Note: FloatProperty is not precise enough, e.g. 1.0 becomes 0.999999999. Python is more precise here (it uses
342 # doubles internally), so we store it as string here and convert to number with py's float() func.
343 dxf_scale
= StringProperty(
345 description
="Coordinates are assumed to be in meters; deviation must be indicated here",
349 def _update_proj(self
, context
):
350 _update_proj_scene_do(self
, context
)
351 _set_recenter(self
, self
.recenter
)
353 pitems
= proj_none_items
+ proj_user_items
+ proj_epsg_items
354 proj_dxf
= EnumProperty(
356 description
="The coordinate system for the DXF file (check http://epsg.io)",
362 epsg_dxf_user
= StringProperty(name
="EPSG-Code", default
="EPSG")
363 merc_dxf_lat
= FloatProperty(name
="Geo-Reference Latitude", default
=0.0)
364 merc_dxf_lon
= FloatProperty(name
="Geo-Reference Longitude", default
=0.0)
366 pitems
= proj_none_items
+ ((proj_user_items
+ proj_tmerc_items
+ proj_epsg_items
) if PYPROJ
else proj_tmerc_items
)
367 proj_scene
= EnumProperty(
369 description
="The coordinate system for the Scene (check http://epsg.io)",
375 epsg_scene_user
= StringProperty(name
="EPSG-Code", default
="EPSG")
376 merc_scene_lat
= FloatProperty(name
="Geo-Reference Latitude", default
=0.0)
377 merc_scene_lon
= FloatProperty(name
="Geo-Reference Longitude", default
=0.0)
380 internal_using_scene_srid
= BoolProperty(default
=False, options
={'HIDDEN'})
382 def draw(self
, context
):
384 scene
= context
.scene
387 layout
.label("Merge Options:")
390 #sub.enabled = merge_map[self.merge_options] != BY_BLOCKS
391 sub
.prop(self
, "block_options")
392 box
.prop(self
, "do_bbox")
393 box
.prop(self
, "merge")
395 sub
.enabled
= self
.merge
396 sub
.prop(self
, "merge_options")
397 box
.prop(self
, "merge_lines")
400 layout
.label("Line thickness and width:")
402 box
.enabled
= not merge_map
[self
.merge_options
] == BY_CLOSED_NO_BULGE_POLY
403 box
.prop(self
, "represent_thickness_and_width")
405 sub
.enabled
= (not self
.represent_thickness_and_width
and self
.merge
)
406 sub
.prop(self
, "import_atts")
409 layout
.label("Optional Objects:")
411 box
.prop(self
, "import_text")
412 box
.prop(self
, "import_light")
413 box
.prop(self
, "export_acis")
416 layout
.label("View Options:")
418 box
.prop(self
, "outliner_groups")
419 box
.prop(self
, "create_new_scene")
421 sub
.enabled
= _recenter_allowed(self
)
422 sub
.prop(self
, "recenter")
425 layout
.prop(self
, "use_georeferencing", text
="Geo Referencing:")
427 box
.enabled
= self
.use_georeferencing
428 self
.draw_pyproj(box
, context
.scene
) if PYPROJ
else self
.draw_merc(box
)
430 def draw_merc(self
, box
):
431 box
.label("DXF File:")
432 box
.prop(self
, "dxf_indi")
433 box
.prop(self
, "dxf_scale")
436 sub
.enabled
= not _recenter_allowed(self
)
437 sub
.label("Geo Reference:")
439 sub
.enabled
= not _recenter_allowed(self
)
440 if is_ref_scene(bpy
.context
.scene
):
442 sub
.prop(self
, "merc_scene_lat", text
="Lat")
443 sub
.prop(self
, "merc_scene_lon", text
="Lon")
445 def draw_pyproj(self
, box
, scene
):
446 valid_dxf_srid
= True
449 box
.prop(self
, "dxf_scale")
452 box
.alert
= (self
.proj_scene
!= 'NONE' and (not valid_dxf_srid
or self
.proj_dxf
== 'NONE'))
453 box
.prop(self
, "proj_dxf")
455 if self
.proj_dxf
== 'USER':
457 Proj(init
=self
.epsg_dxf_user
)
460 valid_dxf_srid
= False
461 box
.prop(self
, "epsg_dxf_user")
468 # Only info in case of pre-defined EPSG from current scene.
469 if self
.internal_using_scene_srid
:
472 col
.prop(self
, "proj_scene")
474 if self
.proj_scene
== 'USER':
476 Proj(init
=self
.epsg_scene_user
)
477 except Exception as e
:
479 col
.prop(self
, "epsg_scene_user")
481 col
.label("") # Placeholder.
482 elif self
.proj_scene
== 'TMERC':
483 col
.prop(self
, "merc_scene_lat", text
="Lat")
484 col
.prop(self
, "merc_scene_lon", text
="Lon")
486 col
.label("") # Placeholder.
487 col
.label("") # Placeholder.
490 if self
.proj_scene
!= 'NONE':
491 if not valid_dxf_srid
:
492 box
.label("DXF SRID not valid", icon
="ERROR")
493 if self
.proj_dxf
== 'NONE':
494 box
.label("", icon
='ERROR')
495 box
.label("DXF SRID must be set, otherwise")
496 if self
.proj_scene
== 'USER':
497 code
= self
.epsg_scene_user
499 code
= self
.proj_scene
500 box
.label('Scene SRID %r is ignored!' % code
)
502 def execute(self
, context
):
503 block_map
= {"LINKED_OBJECTS": LINKED_OBJECTS
, "GROUP_INSTANCES": GROUP_INSTANCES
}
504 merge_options
= SEPARATED
506 merge_options
= merge_map
[self
.merge_options
]
507 scene
= bpy
.context
.scene
508 if self
.create_new_scene
:
509 scene
= bpy
.data
.scenes
.new(os
.path
.basename(self
.filepath
).replace(".dxf", ""))
514 if self
.use_georeferencing
:
515 dxf_unit_scale
= float(self
.dxf_scale
.replace(",", "."))
517 if self
.proj_dxf
!= 'NONE':
518 if self
.proj_dxf
== 'USER':
519 proj_dxf
= Proj(init
=self
.epsg_dxf_user
)
521 proj_dxf
= Proj(init
=self
.proj_dxf
)
522 if self
.proj_scene
!= 'NONE':
523 if self
.proj_scene
== 'USER':
524 proj_scn
= Proj(init
=self
.epsg_scene_user
)
525 elif self
.proj_scene
== 'TMERC':
526 proj_scn
= TransverseMercator(lat
=self
.merc_scene_lat
, lon
=self
.merc_scene_lon
)
528 proj_scn
= Proj(init
=self
.proj_scene
)
530 proj_dxf
= Indicator(self
.dxf_indi
)
531 proj_scn
= TransverseMercator(lat
=self
.merc_scene_lat
, lon
=self
.merc_scene_lon
)
534 # for release testing
538 read(self
.report
, self
.filepath
, merge_options
, self
.import_text
, self
.import_light
, self
.export_acis
,
539 self
.merge_lines
, self
.do_bbox
, block_map
[self
.block_options
], scene
, self
.recenter
,
540 proj_dxf
, proj_scn
, self
.represent_thickness_and_width
, self
.import_atts
, dxf_unit_scale
)
542 if self
.outliner_groups
:
543 display_groups_in_outliner()
547 def invoke(self
, context
, event
):
548 # Force first update...
549 self
._update
_use
_georeferencing
(context
)
551 wm
= context
.window_manager
552 wm
.fileselect_add(self
)
553 return {'RUNNING_MODAL'}
556 def menu_func(self
, context
):
557 self
.layout
.operator(IMPORT_OT_dxf
.bl_idname
, text
="AutoCAD DXF")
561 bpy
.utils
.register_module(__name__
)
562 bpy
.types
.TOPBAR_MT_file_import
.append(menu_func
)
566 bpy
.utils
.unregister_module(__name__
)
567 bpy
.types
.TOPBAR_MT_file_import
.remove(menu_func
)
570 if __name__
== "__main__":