camera_dolly_crane_rigs: update for 2.8
[blender-addons.git] / io_import_dxf / __init__.py
blob46e84fd553818b44042fd79aa69e5d6971f73fd2
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
21 import bpy
22 import os
23 from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty
24 from .dxfimport.do import Do, Indicator
25 from .transverse_mercator import TransverseMercator
28 try:
29 from pyproj import Proj, transform
30 PYPROJ = True
31 except:
32 PYPROJ = False
34 bl_info = {
35 "name": "Import AutoCAD DXF Format (.dxf)",
36 "author": "Lukas Treyer, Manfred Moitzi (support + dxfgrabber library), Vladimir Elistratov, Bastien Montagne",
37 "version": (0, 8, 6),
38 "blender": (2, 7, 1),
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",
46 proj_none_items = (
47 ('NONE', "None", "No Coordinate System is available / will be set"),
49 proj_user_items = (
50 ('USER', "User Defined", "Define the EPSG code"),
52 proj_tmerc_items = (
53 ('TMERC', "Transverse Mercator", "Mercator Projection using a lat/lon coordinate as its geo-reference"),
55 proj_epsg_items = (
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']])
80 BY_LAYER = 0
81 BY_DXFTYPE = 1
82 BY_CLOSED_NO_BULGE_POLY = 2
83 SEPARATED = 3
84 LINKED_OBJECTS = 4
85 GROUP_INSTANCES = 5
86 BY_BLOCKS = 6
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}
91 T_Merge = True
92 T_ImportText = True
93 T_ImportLight = True
94 T_ExportAcis = False
95 T_MergeLines = True
96 T_OutlinerGroups = True
97 T_Bbox = True
98 T_CreateNewScene = False
99 T_Recenter = False
100 T_ThicknessBevel = True
101 T_import_atts = True
103 RELEASE_TEST = False
104 DEBUG = False
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)
121 # display errors
122 for error in errors:
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)
142 if srid is not None:
143 self.internal_using_scene_srid = True
144 srid = srid.upper()
145 if srid == 'TMERC':
146 self.proj_scene = 'TMERC'
147 self.merc_scene_lat = scene.get('latitude', 0)
148 self.merc_scene_lon = scene.get('longitude', 0)
149 else:
150 if srid in (p[0] for p in proj_epsg_items):
151 self.proj_scene = srid
152 else:
153 self.proj_scene = 'USER'
154 self.epsg_scene_user = srid
155 else:
156 self.internal_using_scene_srid = False
157 else:
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"
164 return not (
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
190 elif not self.merge:
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(
204 name="input file",
205 subtype='FILE_PATH'
208 filename_ext = ".dxf"
210 filter_glob = StringProperty(
211 default="*.dxf",
212 options={'HIDDEN'},
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",
220 default=T_Merge,
221 update=_update_merge
224 def _update_merge_options(self, context):
225 _update_import_atts_do(self, context)
227 merge_options = EnumProperty(
228 name="Merge",
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'.")],
234 default='BY_LAYER',
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",
241 default=T_MergeLines
244 import_text = BoolProperty(
245 name="Import Text",
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",
259 default=T_ExportAcis
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)",
271 default=T_Bbox
276 block_options = EnumProperty(
277 name="Blocks As",
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",
298 default=T_Recenter,
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
318 # geo referencing
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",
326 default=True,
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")],
337 default='EUCLIDEAN',
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(
344 name="Unit Scale",
345 description="Coordinates are assumed to be in meters; deviation must be indicated here",
346 default="1.0"
349 def _update_proj(self, context):
350 _update_proj_scene_do(self, context)
351 _set_recenter(self, self.recenter)
352 if PYPROJ:
353 pitems = proj_none_items + proj_user_items + proj_epsg_items
354 proj_dxf = EnumProperty(
355 name="DXF SRID",
356 description="The coordinate system for the DXF file (check http://epsg.io)",
357 items=pitems,
358 default='NONE',
359 update=_update_proj,
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(
368 name="Scn SRID",
369 description="The coordinate system for the Scene (check http://epsg.io)",
370 items=pitems,
371 default='NONE',
372 update=_update_proj,
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)
379 # internal use only!
380 internal_using_scene_srid = BoolProperty(default=False, options={'HIDDEN'})
382 def draw(self, context):
383 layout = self.layout
384 scene = context.scene
386 # merge options
387 layout.label("Merge Options:")
388 box = layout.box()
389 sub = box.row()
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")
394 sub = box.row()
395 sub.enabled = self.merge
396 sub.prop(self, "merge_options")
397 box.prop(self, "merge_lines")
399 # general options
400 layout.label("Line thickness and width:")
401 box = layout.box()
402 box.enabled = not merge_map[self.merge_options] == BY_CLOSED_NO_BULGE_POLY
403 box.prop(self, "represent_thickness_and_width")
404 sub = box.row()
405 sub.enabled = (not self.represent_thickness_and_width and self.merge)
406 sub.prop(self, "import_atts")
408 # optional objects
409 layout.label("Optional Objects:")
410 box = layout.box()
411 box.prop(self, "import_text")
412 box.prop(self, "import_light")
413 box.prop(self, "export_acis")
415 # view options
416 layout.label("View Options:")
417 box = layout.box()
418 box.prop(self, "outliner_groups")
419 box.prop(self, "create_new_scene")
420 sub = box.row()
421 sub.enabled = _recenter_allowed(self)
422 sub.prop(self, "recenter")
424 # geo referencing
425 layout.prop(self, "use_georeferencing", text="Geo Referencing:")
426 box = layout.box()
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")
435 sub = box.column()
436 sub.enabled = not _recenter_allowed(self)
437 sub.label("Geo Reference:")
438 sub = box.column()
439 sub.enabled = not _recenter_allowed(self)
440 if is_ref_scene(bpy.context.scene):
441 sub.enabled = False
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
448 # DXF SCALE
449 box.prop(self, "dxf_scale")
451 # EPSG DXF
452 box.alert = (self.proj_scene != 'NONE' and (not valid_dxf_srid or self.proj_dxf == 'NONE'))
453 box.prop(self, "proj_dxf")
454 box.alert = False
455 if self.proj_dxf == 'USER':
456 try:
457 Proj(init=self.epsg_dxf_user)
458 except:
459 box.alert = True
460 valid_dxf_srid = False
461 box.prop(self, "epsg_dxf_user")
462 box.alert = False
464 box.separator()
466 # EPSG SCENE
467 col = box.column()
468 # Only info in case of pre-defined EPSG from current scene.
469 if self.internal_using_scene_srid:
470 col.enabled = False
472 col.prop(self, "proj_scene")
474 if self.proj_scene == 'USER':
475 try:
476 Proj(init=self.epsg_scene_user)
477 except Exception as e:
478 col.alert = True
479 col.prop(self, "epsg_scene_user")
480 col.alert = False
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")
485 else:
486 col.label("") # Placeholder.
487 col.label("") # Placeholder.
489 # user info
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
498 else:
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
505 if self.merge:
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", ""))
511 proj_dxf = None
512 proj_scn = None
513 dxf_unit_scale = 1.0
514 if self.use_georeferencing:
515 dxf_unit_scale = float(self.dxf_scale.replace(",", "."))
516 if PYPROJ:
517 if self.proj_dxf != 'NONE':
518 if self.proj_dxf == 'USER':
519 proj_dxf = Proj(init=self.epsg_dxf_user)
520 else:
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)
527 else:
528 proj_scn = Proj(init=self.proj_scene)
529 else:
530 proj_dxf = Indicator(self.dxf_indi)
531 proj_scn = TransverseMercator(lat=self.merc_scene_lat, lon=self.merc_scene_lon)
533 if RELEASE_TEST:
534 # for release testing
535 from . import test
536 test.test()
537 else:
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()
545 return {'FINISHED'}
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")
560 def register():
561 bpy.utils.register_module(__name__)
562 bpy.types.TOPBAR_MT_file_import.append(menu_func)
565 def unregister():
566 bpy.utils.unregister_module(__name__)
567 bpy.types.TOPBAR_MT_file_import.remove(menu_func)
570 if __name__ == "__main__":
571 register()