Sun position: fix HDRI mouse wheel exposure setting alpha
[blender-addons.git] / io_import_dxf / __init__.py
blobc24f6a82565a6b4b5fc8f1639f828aad2fff613a
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, Remigiusz Fiedler (AKA migius)",
37 "version": (0, 9, 6),
38 "blender": (2, 80, 0),
39 "location": "File > Import > AutoCAD DXF",
40 "description": "Import files in the Autocad DXF format (.dxf)",
41 "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/scene_dxf.html",
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 pass
134 #outliner.spaces[0].display_mode = "GROUPS"
137 # Update helpers (must be globals to be re-usable).
138 def _update_use_georeferencing_do(self, context):
139 if not self.create_new_scene:
140 scene = context.scene
141 # Try to get Scene SRID (ESPG) data from current scene.
142 srid = scene.get("SRID", None)
143 if srid is not None:
144 self.internal_using_scene_srid = True
145 srid = srid.upper()
146 if srid == 'TMERC':
147 self.proj_scene = 'TMERC'
148 self.merc_scene_lat = scene.get('latitude', 0)
149 self.merc_scene_lon = scene.get('longitude', 0)
150 else:
151 if srid in (p[0] for p in proj_epsg_items):
152 self.proj_scene = srid
153 else:
154 self.proj_scene = 'USER'
155 self.epsg_scene_user = srid
156 else:
157 self.internal_using_scene_srid = False
158 else:
159 self.internal_using_scene_srid = False
162 def _recenter_allowed(self):
163 scene = bpy.context.scene
164 conditional_requirement = self.proj_scene == 'TMERC' if PYPROJ else self.dxf_indi == "SPHERICAL"
165 return not (
166 self.use_georeferencing and
168 conditional_requirement or
169 (not self.create_new_scene and is_ref_scene(scene))
174 def _set_recenter(self, value):
175 self.recenter = value if _recenter_allowed(self) else False
178 def _update_proj_scene_do(self, context):
179 # make sure scene EPSG is not None if DXF EPSG is not None
180 if self.proj_scene == 'NONE' and self.proj_dxf != 'NONE':
181 self.proj_scene = self.proj_dxf
184 def _update_import_atts_do(self, context):
185 mo = merge_map[self.merge_options]
186 if mo == BY_CLOSED_NO_BULGE_POLY or mo == BY_BLOCKS:
187 self.import_atts = False
188 self.represent_thickness_and_width = False
189 elif self.represent_thickness_and_width and self.merge:
190 self.import_atts = True
191 elif not self.merge:
192 self.import_atts = False
195 class IMPORT_OT_dxf(bpy.types.Operator):
196 """Import from DXF file format (.dxf)"""
197 bl_idname = "import_scene.dxf"
198 bl_description = 'Import from DXF file format (.dxf)'
199 bl_label = "Import DXf v." + __version__
200 bl_space_type = 'PROPERTIES'
201 bl_region_type = 'WINDOW'
202 bl_options = {'UNDO'}
204 filepath: StringProperty(
205 name="input file",
206 subtype='FILE_PATH'
209 filename_ext = ".dxf"
211 filter_glob: StringProperty(
212 default="*.dxf",
213 options={'HIDDEN'},
216 def _update_merge(self, context):
217 _update_import_atts_do(self, context)
218 merge: BoolProperty(
219 name="Merged Objects",
220 description="Merge DXF entities to Blender objects",
221 default=T_Merge,
222 update=_update_merge
225 def _update_merge_options(self, context):
226 _update_import_atts_do(self, context)
228 merge_options: EnumProperty(
229 name="Merge",
230 description="Merge multiple DXF entities into one Blender object",
231 items=[('BY_LAYER', "By Layer", "Merge DXF entities of a layer to an object"),
232 ('BY_TYPE', "By Layer AND DXF-Type", "Merge DXF entities by type AND layer"),
233 ('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."),
234 ('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 default='BY_LAYER',
236 update=_update_merge_options
239 merge_lines: BoolProperty(
240 name="Combine LINE entities to polygons",
241 description="Checks if lines are connect on start or end and merges them to a polygon",
242 default=T_MergeLines
245 import_text: BoolProperty(
246 name="Import Text",
247 description="Import DXF Text Entities MTEXT and TEXT",
248 default=T_ImportText,
251 import_light: BoolProperty(
252 name="Import Lights",
253 description="Import DXF Text Entity LIGHT",
254 default=T_ImportLight
257 export_acis: BoolProperty(
258 name="Export ACIS Entities",
259 description="Export Entities consisting of ACIS code to ACIS .sat/.sab files",
260 default=T_ExportAcis
263 outliner_groups: BoolProperty(
264 name="Display Groups in Outliner(s)",
265 description="Make all outliners in current screen layout show groups",
266 default=T_OutlinerGroups
269 do_bbox: BoolProperty(
270 name="Parent Blocks to Bounding Boxes",
271 description="Create a bounding box for blocks with more than one object (faster without)",
272 default=T_Bbox
277 block_options: EnumProperty(
278 name="Blocks As",
279 description="Select the representation of DXF blocks: linked objects or group instances",
280 items=[('LINKED_OBJECTS', "Linked Objects", "Block objects get imported as linked objects"),
281 ('GROUP_INSTANCES', "Group Instances", "Block objects get imported as group instances")],
282 default='LINKED_OBJECTS',
286 def _update_create_new_scene(self, context):
287 _update_use_georeferencing_do(self, context)
288 _set_recenter(self, self.recenter)
289 create_new_scene: BoolProperty(
290 name="Import DXF to new scene",
291 description="Creates a new scene with the name of the imported file",
292 default=T_CreateNewScene,
293 update=_update_create_new_scene,
296 recenter: BoolProperty(
297 name="Center geometry to scene",
298 description="Moves geometry to the center of the scene",
299 default=T_Recenter,
302 def _update_thickness_width(self, context):
303 _update_import_atts_do(self, context)
304 represent_thickness_and_width: BoolProperty(
305 name="Represent line thickness/width",
306 description="Map thickness and width of lines to Bevel objects and extrusion attribute",
307 default=T_ThicknessBevel,
308 update=_update_thickness_width
311 import_atts: BoolProperty(
312 name="Merge by attributes",
313 description="If 'Merge objects' is on but thickness and width are not chosen to be represented, with this "
314 "option object still can be merged by thickness, with, subd and extrusion attributes "
315 "(extrusion = transformation matrix of DXF objects)",
316 default=T_import_atts
319 # geo referencing
321 def _update_use_georeferencing(self, context):
322 _update_use_georeferencing_do(self, context)
323 _set_recenter(self, self.recenter)
324 use_georeferencing: BoolProperty(
325 name="Geo Referencing",
326 description="Project coordinates to a given coordinate system or reference point",
327 default=True,
328 update=_update_use_georeferencing,
331 def _update_dxf_indi(self, context):
332 _set_recenter(self, self.recenter)
333 dxf_indi: EnumProperty(
334 name="DXF coordinate type",
335 description="Indication for spherical or euclidian coordinates",
336 items=[('EUCLIDEAN', "Euclidean", "Coordinates in x/y"),
337 ('SPHERICAL', "Spherical", "Coordinates in lat/lon")],
338 default='EUCLIDEAN',
339 update=_update_dxf_indi,
342 # Note: FloatProperty is not precise enough, e.g. 1.0 becomes 0.999999999. Python is more precise here (it uses
343 # doubles internally), so we store it as string here and convert to number with py's float() func.
344 dxf_scale: StringProperty(
345 name="Unit Scale",
346 description="Coordinates are assumed to be in meters; deviation must be indicated here",
347 default="1.0"
350 def _update_proj(self, context):
351 _update_proj_scene_do(self, context)
352 _set_recenter(self, self.recenter)
353 if PYPROJ:
354 pitems = proj_none_items + proj_user_items + proj_epsg_items
355 proj_dxf: EnumProperty(
356 name="DXF SRID",
357 description="The coordinate system for the DXF file (check http://epsg.io)",
358 items=pitems,
359 default='NONE',
360 update=_update_proj,
363 epsg_dxf_user: StringProperty(name="EPSG-Code", default="EPSG")
364 merc_dxf_lat: FloatProperty(name="Geo-Reference Latitude", default=0.0)
365 merc_dxf_lon: FloatProperty(name="Geo-Reference Longitude", default=0.0)
367 pitems = proj_none_items + ((proj_user_items + proj_tmerc_items + proj_epsg_items) if PYPROJ else proj_tmerc_items)
368 proj_scene: EnumProperty(
369 name="Scn SRID",
370 description="The coordinate system for the Scene (check http://epsg.io)",
371 items=pitems,
372 default='NONE',
373 update=_update_proj,
376 epsg_scene_user: StringProperty(name="EPSG-Code", default="EPSG")
377 merc_scene_lat: FloatProperty(name="Geo-Reference Latitude", default=0.0)
378 merc_scene_lon: FloatProperty(name="Geo-Reference Longitude", default=0.0)
380 # internal use only!
381 internal_using_scene_srid: BoolProperty(default=False, options={'HIDDEN'})
383 def draw(self, context):
384 layout = self.layout
385 scene = context.scene
387 # merge options
388 layout.label(text="Merge Options:")
389 box = layout.box()
390 sub = box.row()
391 #sub.enabled = merge_map[self.merge_options] != BY_BLOCKS
392 sub.prop(self, "block_options")
393 box.prop(self, "do_bbox")
394 box.prop(self, "merge")
395 sub = box.row()
396 sub.enabled = self.merge
397 sub.prop(self, "merge_options")
398 box.prop(self, "merge_lines")
400 # general options
401 layout.label(text="Line thickness and width:")
402 box = layout.box()
403 box.enabled = not merge_map[self.merge_options] == BY_CLOSED_NO_BULGE_POLY
404 box.prop(self, "represent_thickness_and_width")
405 sub = box.row()
406 sub.enabled = (not self.represent_thickness_and_width and self.merge)
407 sub.prop(self, "import_atts")
409 # optional objects
410 layout.label(text="Optional Objects:")
411 box = layout.box()
412 box.prop(self, "import_text")
413 box.prop(self, "import_light")
414 box.prop(self, "export_acis")
416 # view options
417 layout.label(text="View Options:")
418 box = layout.box()
419 box.prop(self, "outliner_groups")
420 box.prop(self, "create_new_scene")
421 sub = box.row()
422 sub.enabled = _recenter_allowed(self)
423 sub.prop(self, "recenter")
425 # geo referencing
426 layout.prop(self, "use_georeferencing", text="Geo Referencing:")
427 box = layout.box()
428 box.enabled = self.use_georeferencing
429 self.draw_pyproj(box, context.scene) if PYPROJ else self.draw_merc(box)
431 def draw_merc(self, box):
432 box.label(text="DXF File:")
433 box.prop(self, "dxf_indi")
434 box.prop(self, "dxf_scale")
436 sub = box.column()
437 sub.enabled = not _recenter_allowed(self)
438 sub.label(text="Geo Reference:")
439 sub = box.column()
440 sub.enabled = not _recenter_allowed(self)
441 if is_ref_scene(bpy.context.scene):
442 sub.enabled = False
443 sub.prop(self, "merc_scene_lat", text="Lat")
444 sub.prop(self, "merc_scene_lon", text="Lon")
446 def draw_pyproj(self, box, scene):
447 valid_dxf_srid = True
449 # DXF SCALE
450 box.prop(self, "dxf_scale")
452 # EPSG DXF
453 box.alert = (self.proj_scene != 'NONE' and (not valid_dxf_srid or self.proj_dxf == 'NONE'))
454 box.prop(self, "proj_dxf")
455 box.alert = False
456 if self.proj_dxf == 'USER':
457 try:
458 Proj(init=self.epsg_dxf_user)
459 except:
460 box.alert = True
461 valid_dxf_srid = False
462 box.prop(self, "epsg_dxf_user")
463 box.alert = False
465 box.separator()
467 # EPSG SCENE
468 col = box.column()
469 # Only info in case of pre-defined EPSG from current scene.
470 if self.internal_using_scene_srid:
471 col.enabled = False
473 col.prop(self, "proj_scene")
475 if self.proj_scene == 'USER':
476 try:
477 Proj(init=self.epsg_scene_user)
478 except Exception as e:
479 col.alert = True
480 col.prop(self, "epsg_scene_user")
481 col.alert = False
482 col.label(text="") # Placeholder.
483 elif self.proj_scene == 'TMERC':
484 col.prop(self, "merc_scene_lat", text="Lat")
485 col.prop(self, "merc_scene_lon", text="Lon")
486 else:
487 col.label(text="") # Placeholder.
488 col.label(text="") # Placeholder.
490 # user info
491 if self.proj_scene != 'NONE':
492 if not valid_dxf_srid:
493 box.label(text="DXF SRID not valid", icon="ERROR")
494 if self.proj_dxf == 'NONE':
495 box.label(text="", icon='ERROR')
496 box.label(text="DXF SRID must be set, otherwise")
497 if self.proj_scene == 'USER':
498 code = self.epsg_scene_user
499 else:
500 code = self.proj_scene
501 box.label(text='Scene SRID %r is ignored!' % code)
503 def execute(self, context):
504 block_map = {"LINKED_OBJECTS": LINKED_OBJECTS, "GROUP_INSTANCES": GROUP_INSTANCES}
505 merge_options = SEPARATED
506 if self.merge:
507 merge_options = merge_map[self.merge_options]
508 scene = bpy.context.scene
509 if self.create_new_scene:
510 scene = bpy.data.scenes.new(os.path.basename(self.filepath).replace(".dxf", ""))
512 proj_dxf = None
513 proj_scn = None
514 dxf_unit_scale = 1.0
515 if self.use_georeferencing:
516 dxf_unit_scale = float(self.dxf_scale.replace(",", "."))
517 if PYPROJ:
518 if self.proj_dxf != 'NONE':
519 if self.proj_dxf == 'USER':
520 proj_dxf = Proj(init=self.epsg_dxf_user)
521 else:
522 proj_dxf = Proj(init=self.proj_dxf)
523 if self.proj_scene != 'NONE':
524 if self.proj_scene == 'USER':
525 proj_scn = Proj(init=self.epsg_scene_user)
526 elif self.proj_scene == 'TMERC':
527 proj_scn = TransverseMercator(lat=self.merc_scene_lat, lon=self.merc_scene_lon)
528 else:
529 proj_scn = Proj(init=self.proj_scene)
530 else:
531 proj_dxf = Indicator(self.dxf_indi)
532 proj_scn = TransverseMercator(lat=self.merc_scene_lat, lon=self.merc_scene_lon)
534 if RELEASE_TEST:
535 # for release testing
536 from . import test
537 test.test()
538 else:
539 read(self.report, self.filepath, merge_options, self.import_text, self.import_light, self.export_acis,
540 self.merge_lines, self.do_bbox, block_map[self.block_options], scene, self.recenter,
541 proj_dxf, proj_scn, self.represent_thickness_and_width, self.import_atts, dxf_unit_scale)
543 if self.outliner_groups:
544 display_groups_in_outliner()
546 return {'FINISHED'}
548 def invoke(self, context, event):
549 # Force first update...
550 self._update_use_georeferencing(context)
552 wm = context.window_manager
553 wm.fileselect_add(self)
554 return {'RUNNING_MODAL'}
557 def menu_func(self, context):
558 self.layout.operator(IMPORT_OT_dxf.bl_idname, text="AutoCAD DXF")
561 def register():
562 bpy.utils.register_class(IMPORT_OT_dxf)
563 bpy.types.TOPBAR_MT_file_import.append(menu_func)
566 def unregister():
567 bpy.utils.unregister_class(IMPORT_OT_dxf)
568 bpy.types.TOPBAR_MT_file_import.remove(menu_func)
571 if __name__ == "__main__":
572 register()