diff options
Diffstat (limited to 'io_import_dxf/__init__.py')
-rw-r--r-- | io_import_dxf/__init__.py | 544 |
1 files changed, 544 insertions, 0 deletions
diff --git a/io_import_dxf/__init__.py b/io_import_dxf/__init__.py new file mode 100644 index 00000000..9c539634 --- /dev/null +++ b/io_import_dxf/__init__.py @@ -0,0 +1,544 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +import bpy +import os +from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty +from .dxfimport.do import Do, Indicator +from .transverse_mercator import TransverseMercator + + +try: + from pyproj import Proj, transform + PYPROJ = True +except: + PYPROJ = False + +bl_info = { + "name": "Import AutoCAD DXF Format (.dxf)", + "author": "Lukas Treyer, Manfred Moitzi (support + dxfgrabber library), Vladimir Elistratov, Bastien Montagne", + "version": (0, 8, 5), + "blender": (2, 7, 1), + "location": "File > Import > AutoCAD DXF", + "description": "Import files in the Autocad DXF format (.dxf)", + "wiki_url": "https://bitbucket.org/treyerl/io_import_scene_dxf/overview", + "tracker_url": "https://bitbucket.org/treyerl/io_import_scene_dxf/issues?status=new&status=open", + "category": "Import-Export", +} + + +proj_none_items = ( + ('NONE', "None", "No Coordinate System is available / will be set"), +) +proj_user_items = ( + ('USER', "User Defined", "Define the EPSG code"), +) +proj_tmerc_items = ( + ('TMERC', "Transverse Mercator", "Mercator Projection using a lat/lon coordinate as its geo-reference"), +) +proj_epsg_items = ( + ('EPSG:4326', "WGS84", "World Geodetic System 84; default for lat / lon; EPSG:4326"), + ('EPSG:3857', "Spherical Mercator", "Webbrowser mapping service standard (Google, OpenStreetMap, ESRI); EPSG:3857"), + ('EPSG:27700', "National Grid U.K", + "Ordnance Survey National Grid reference system used in Great Britain; EPSG:27700"), + ('EPSG:2154', "France (Lambert 93)", "Lambert Projection for France; EPSG:2154"), + ('EPSG:5514', "Czech Republic & Slovakia", "Coordinate System for Czech Republic and Slovakia; EPSG:5514"), + ('EPSG:5243', "LLC Germany", "Projection for Germany; EPSG:5243"), + ('EPSG:28992', "Amersfoort Netherlands", "Amersfoort / RD New -- Netherlands; EPSG:28992"), + ('EPSG:21781', "Swiss CH1903 / LV03", "Switzerland and Lichtenstein; EPSG:21781"), + ('EPSG:5880', "Brazil Polyconic", "Cartesian 2D; Central, South America; EPSG:5880 "), + ('EPSG:42103', "LCC USA", "Lambert Conformal Conic Projection; EPSG:42103"), + ('EPSG:3350', "Russia: Pulkovo 1942 / CS63 zone C0", "Russian Federation - onshore and offshore; EPSG:3350"), + ('EPSG:22293', "Cape / Lo33 South Africa", "South Africa; EPSG:22293"), + ('EPSG:27200', "NZGD49 / New Zealand Map Grid", "NZGD49 / New Zealand Map Grid; EPSG:27200"), + ('EPSG:3112', "GDA94 Australia Lambert", "GDA94 / Geoscience Australia Lambert; EPSG:3112"), + ('EPSG:24378', "India zone I", "Kalianpur 1975 / India zone I; EPSG:24378"), + ('EPSG:2326', "Hong Kong 1980 Grid System", "Hong Kong 1980 Grid System; EPSG:2326"), + ('EPSG:3414', "SVY21 / Singapore TM", "SVY21 / Singapore TM; EPSG:3414"), +) + +proj_epsg_dict = {e[0]: e[1] for e in proj_epsg_items} + +__version__ = '.'.join([str(s) for s in bl_info['version']]) + +BY_LAYER = 0 +BY_DXFTYPE = 1 +SEPARATED = 2 +LINKED_OBJECTS = 3 +GROUP_INSTANCES = 4 + +T_Merge = True +T_ImportText = True +T_ImportLight = True +T_ExportAcis = False +T_MergeLines = True +T_OutlinerGroups = True +T_Bbox = True +T_CreateNewScene = False +T_Recenter = False +T_ThicknessBevel = False +T_import_atts = True + +RELEASE_TEST = False +DEBUG = False + + +def is_ref_scene(scene): + return "latitude" in scene and "longitude" in scene + + +def read(report, filename, obj_merge=BY_LAYER, import_text=True, import_light=True, export_acis=True, merge_lines=True, + do_bbox=True, block_rep=LINKED_OBJECTS, new_scene=None, recenter=False, projDXF=None, projSCN=None, + thicknessWidth=True, but_group_by_att=True, dxf_unit_scale=1.0): + # import dxf and export nurbs types to sat/sab files + # because that's how autocad stores nurbs types in a dxf... + do = Do(filename, obj_merge, import_text, import_light, export_acis, merge_lines, do_bbox, block_rep, recenter, + projDXF, projSCN, thicknessWidth, but_group_by_att, dxf_unit_scale) + errors = do.entities(os.path.basename(filename).replace(".dxf", ""), new_scene) + + # display errors + for error in errors: + report('ERROR', error) + + # inform the user about the sat/sab files + if len(do.acis_files) > 0: + report('INFO', "Exported %d NURBS objects to sat/sab files next to your DXF file" % len(do.acis_files)) + + +def display_groups_in_outliner(): + outliners = (a for a in bpy.context.screen.areas if a.type == "OUTLINER") + for outliner in outliners: + outliner.spaces[0].display_mode = "GROUPS" + + +# Update helpers (must be globals to be re-usable). +def _update_use_georeferencing_do(self, context): + if not self.create_new_scene: + scene = context.scene + # Try to get Scene SRID (ESPG) data from current scene. + srid = scene.get("SRID", None) + if srid is not None: + self.internal_using_scene_srid = True + srid = srid.upper() + if srid == 'TMERC': + self.proj_scene = 'TMERC' + self.merc_scene_lat = scene.get('latitude', 0) + self.merc_scene_lon = scene.get('longitude', 0) + else: + if srid in (p[0] for p in proj_epsg_items): + self.proj_scene = srid + else: + self.proj_scene = 'USER' + self.epsg_scene_user = srid + else: + self.internal_using_scene_srid = False + else: + self.internal_using_scene_srid = False + + +def _recenter_allowed(self): + scene = bpy.context.scene + return (not (self.use_georeferencing and (self.proj_scene == 'TMERC' + or (not self.create_new_scene and is_ref_scene(scene)))) + or (not PYPROJ and self.dxf_indi == "EUCLIDEAN")) + + +def _set_recenter(self, value): + self.recenter = value if _recenter_allowed(self) else False + + +def _update_proj_scene_do(self, context): + # make sure scene EPSG is not None if DXF EPSG is not None + if self.proj_scene == 'NONE' and self.proj_dxf != 'NONE': + self.proj_scene = self.proj_dxf + + +def _update_import_atts_do(self, context): + if self.represent_thickness_and_width and self.merge: + self.import_atts = True + elif not self.merge: + self.import_atts = False + + +class IMPORT_OT_dxf(bpy.types.Operator): + """Import from DXF file format (.dxf)""" + bl_idname = "import_scene.dxf" + bl_description = 'Import from DXF file format (.dxf)' + bl_label = "Import DXf v." + __version__ + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_options = {'UNDO'} + + filepath = StringProperty( + name="input file", + subtype='FILE_PATH' + ) + + filename_ext = ".dxf" + + filter_glob = StringProperty( + default="*.dxf", + options={'HIDDEN'}, + ) + + def _update_merge(self, context): + _update_import_atts_do(self, context) + merge = BoolProperty( + name="Merged Objects", + description="Merge DXF entities to Blender objects", + default=T_Merge, + update=_update_merge + ) + + merge_options = EnumProperty( + name="Merge", + description="Merge multiple DXF entities into one Blender object", + items=[('BY_TYPE', "By Layer AND Dxf-Type", "Merge DXF entities by type AND layer"), + ('BY_LAYER', "By Layer", "Merge DXF entities of a layer to an object")], + default='BY_LAYER', + ) + + merge_lines = BoolProperty( + name="Combine LINE entities to polygons", + description="Checks if lines are connect on start or end and merges them to a polygon", + default=T_MergeLines + ) + + import_text = BoolProperty( + name="Import Text", + description="Import DXF Text Entities MTEXT and TEXT", + default=T_ImportText, + ) + + import_light = BoolProperty( + name="Import Lights", + description="Import DXF Text Entity LIGHT", + default=T_ImportLight + ) + + export_acis = BoolProperty( + name="Export ACIS Entities", + description="Export Entities consisting of ACIS code to ACIS .sat/.sab files", + default=T_ExportAcis + ) + + outliner_groups = BoolProperty( + name="Display Groups in Outliner(s)", + description="Make all outliners in current screen layout show groups", + default=T_OutlinerGroups + ) + + do_bbox = BoolProperty( + name="Parent Blocks to Bounding Boxes", + description="Create a bounding box for blocks with more than one object (faster without)", + default=T_Bbox + ) + + block_options = EnumProperty( + name="Blocks As", + description="Select the representation of DXF blocks: linked objects or group instances", + items=[('LINKED_OBJECTS', "Linked Objects", "Block objects get imported as linked objects"), + ('GROUP_INSTANCES', "Group Instances", "Block objects get imported as group instances")], + default='LINKED_OBJECTS', + ) + + def _update_create_new_scene(self, context): + _update_use_georeferencing_do(self, context) + _set_recenter(self, self.recenter) + create_new_scene = BoolProperty( + name="Import DXF to new scene", + description="Creates a new scene with the name of the imported file", + default=T_CreateNewScene, + update=_update_create_new_scene, + ) + + recenter = BoolProperty( + name="Center geometry to scene", + description="Moves geometry to the center of the scene", + default=T_Recenter, + ) + + def _update_thickness_width(self, context): + _update_import_atts_do(self, context) + represent_thickness_and_width = BoolProperty( + name="Represent line thickness/width", + description="Map thickness and width of lines to Bevel objects and extrusion attribute", + default=T_ThicknessBevel, + update=_update_thickness_width + ) + + import_atts = BoolProperty( + name="Merge by attributes", + description="If 'Merge objects' is on but thickness and width are not chosen to be represented, with this " + "option object still can be merged by thickness, with, subd and extrusion attributes " + "(extrusion = transformation matrix of DXF objects)", + default=T_import_atts + ) + + # geo referencing + + def _update_use_georeferencing(self, context): + _update_use_georeferencing_do(self, context) + _set_recenter(self, self.recenter) + use_georeferencing = BoolProperty( + name="Geo Referencing", + description="Project coordinates to a given coordinate system or reference point", + default=True, + update=_update_use_georeferencing, + ) + + def _update_dxf_indi(self, context): + _set_recenter(self, self.recenter) + dxf_indi = EnumProperty( + name="DXF coordinate type", + description="Indication for spherical or euclidian coordinates", + items=[('EUCLIDEAN', "Euclidean", "Coordinates in x/y"), + ('SPHERICAL', "Spherical", "Coordinates in lat/lon")], + default='EUCLIDEAN', + update=_update_dxf_indi, + ) + + # Note: FloatProperty is not precise enough, e.g. 1.0 becomes 0.999999999. Python is more precise here (it uses + # doubles internally), so we store it as string here and convert to number with py's float() func. + dxf_scale = StringProperty( + name="Unit Scale", + description="Coordinates are assumed to be in meters; deviation must be indicated here", + default="1.0" + ) + + def _update_proj(self, context): + _update_proj_scene_do(self, context) + _set_recenter(self, self.recenter) + if PYPROJ: + pitems = proj_none_items + proj_user_items + proj_epsg_items + proj_dxf = EnumProperty( + name="DXF SRID", + description="The coordinate system for the DXF file (check http://epsg.io)", + items=pitems, + default='NONE', + update=_update_proj, + ) + + epsg_dxf_user = StringProperty(name="EPSG-Code", default="EPSG") + merc_dxf_lat = FloatProperty(name="Geo-Reference Latitude", default=0.0) + merc_dxf_lon = FloatProperty(name="Geo-Reference Longitude", default=0.0) + + pitems = proj_none_items + ((proj_user_items + proj_tmerc_items + proj_epsg_items) if PYPROJ else proj_tmerc_items) + proj_scene = EnumProperty( + name="Scn SRID", + description="The coordinate system for the Scene (check http://epsg.io)", + items=pitems, + default='NONE', + update=_update_proj, + ) + + epsg_scene_user = StringProperty(name="EPSG-Code", default="EPSG") + merc_scene_lat = FloatProperty(name="Geo-Reference Latitude", default=0.0) + merc_scene_lon = FloatProperty(name="Geo-Reference Longitude", default=0.0) + + # internal use only! + internal_using_scene_srid = BoolProperty(default=False, options={'HIDDEN'}) + + def draw(self, context): + layout = self.layout + scene = context.scene + + # merge options + layout.label("Merge Options:") + box = layout.box() + box.prop(self, "block_options") + box.prop(self, "do_bbox") + box.prop(self, "merge") + sub = box.row() + sub.enabled = self.merge + sub.prop(self, "merge_options") + box.prop(self, "merge_lines") + + # general options + layout.label("Line thickness and width:") + box = layout.box() + box.prop(self, "represent_thickness_and_width") + sub = box.row() + sub.enabled = (not self.represent_thickness_and_width and self.merge) + sub.prop(self, "import_atts") + + # optional objects + layout.label("Optional Objects:") + box = layout.box() + box.prop(self, "import_text") + box.prop(self, "import_light") + box.prop(self, "export_acis") + + # view options + layout.label("View Options:") + box = layout.box() + box.prop(self, "outliner_groups") + box.prop(self, "create_new_scene") + sub = box.row() + sub.enabled = _recenter_allowed(self) + sub.prop(self, "recenter") + + # geo referencing + layout.prop(self, "use_georeferencing", text="Geo Referencing:") + box = layout.box() + box.enabled = self.use_georeferencing + self.draw_pyproj(box, context.scene) if PYPROJ else self.draw_merc(box) + + def draw_merc(self, box): + box.label("DXF File:") + box.prop(self, "dxf_indi") + box.prop(self, "dxf_scale") + + sub = box.column() + sub.enabled = not _recenter_allowed(self) + sub.label("Geo Reference:") + sub = box.column() + sub.enabled = not _recenter_allowed(self) and self.create_new_scene + sub.prop(self, "merc_scene_lat", text="Lat") + sub.prop(self, "merc_scene_lon", text="Lon") + + def draw_pyproj(self, box, scene): + valid_dxf_srid = True + + # DXF SCALE + box.prop(self, "dxf_scale") + + # EPSG DXF + box.alert = (self.proj_scene != 'NONE' and (not valid_dxf_srid or self.proj_dxf == 'NONE')) + box.prop(self, "proj_dxf") + box.alert = False + if self.proj_dxf == 'USER': + try: + Proj(init=self.epsg_dxf_user) + except: + box.alert = True + valid_dxf_srid = False + box.prop(self, "epsg_dxf_user") + box.alert = False + + box.separator() + + # EPSG SCENE + col = box.column() + # Only info in case of pre-defined EPSG from current scene. + if self.internal_using_scene_srid: + col.enabled = False + + col.prop(self, "proj_scene") + + if self.proj_scene == 'USER': + try: + Proj(init=self.epsg_scene_user) + except Exception as e: + col.alert = True + col.prop(self, "epsg_scene_user") + col.alert = False + col.label("") # Placeholder. + elif self.proj_scene == 'TMERC': + col.prop(self, "merc_scene_lat", text="Lat") + col.prop(self, "merc_scene_lon", text="Lon") + else: + col.label("") # Placeholder. + col.label("") # Placeholder. + + # user info + if self.proj_scene != 'NONE': + if not valid_dxf_srid: + box.label("DXF SRID not valid", icon="ERROR") + if self.proj_dxf == 'NONE': + box.label("", icon='ERROR') + box.label("DXF SRID must be set, otherwise") + if self.proj_scene == 'USER': + code = self.epsg_scene_user + else: + code = self.proj_scene + box.label('Scene SRID %r is ignored!' % code) + + def execute(self, context): + merge_map = {"BY_LAYER": BY_LAYER, "BY_TYPE": BY_DXFTYPE} + block_map = {"LINKED_OBJECTS": LINKED_OBJECTS, "GROUP_INSTANCES": GROUP_INSTANCES} + merge_options = SEPARATED + if self.merge: + merge_options = merge_map[self.merge_options] + scene = bpy.context.scene + if self.create_new_scene: + scene = bpy.data.scenes.new(os.path.basename(self.filepath).replace(".dxf", "")) + + proj_dxf = None + proj_scn = None + dxf_unit_scale = 1.0 + if self.use_georeferencing: + dxf_unit_scale = float(self.dxf_scale) + if PYPROJ: + if self.proj_dxf != 'NONE': + if self.proj_dxf == 'USER': + proj_dxf = Proj(init=self.epsg_dxf_user) + else: + proj_dxf = Proj(init=self.proj_dxf) + if self.proj_scene != 'NONE': + if self.proj_scene == 'USER': + proj_scn = Proj(init=self.epsg_scene_user) + elif self.proj_scene == 'TMERC': + proj_scn = TransverseMercator(lat=self.merc_scene_lat, lon=self.merc_scene_lon) + else: + proj_scn = Proj(init=self.proj_scene) + else: + proj_dxf = Indicator(self.dxf_indi) + proj_scn = TransverseMercator(lat=self.merc_scene_lat, lon=self.merc_scene_lon) + + if RELEASE_TEST: + # for release testing + from . import test + test.test() + else: + read(self.report, self.filepath, merge_options, self.import_text, self.import_light, self.export_acis, + self.merge_lines, self.do_bbox, block_map[self.block_options], scene, self.recenter, + proj_dxf, proj_scn, self.represent_thickness_and_width, self.import_atts, dxf_unit_scale) + + if self.outliner_groups: + display_groups_in_outliner() + + return {'FINISHED'} + + def invoke(self, context, event): + # Force first update... + self._update_use_georeferencing(context) + + wm = context.window_manager + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + + +def menu_func(self, context): + self.layout.operator(IMPORT_OT_dxf.bl_idname, text="AutoCAD DXF") + + +def register(): + bpy.utils.register_module(__name__) + bpy.types.INFO_MT_file_import.append(menu_func) + + +def unregister(): + bpy.utils.unregister_module(__name__) + bpy.types.INFO_MT_file_import.remove(menu_func) + + +if __name__ == "__main__": + register() |