Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Treyer <treyer@arch.ethz.ch>2014-08-19 18:06:16 +0400
committerBastien Montagne <montagne29@wanadoo.fr>2014-08-19 18:11:49 +0400
commit82a00ee2a0d8f2001917ddc4e34de4104200eca3 (patch)
tree7596caf1feaccda00c3db65d689687a4bc60192b /io_import_dxf
parentff4c009b1849d6b7980d589536870ab406c54abd (diff)
New DXF importer, based in DXFGrabber.
This addon replaces the old DXF importer. Written by cnd (Lukas Treyer). It uses dxfgrabber - copyright (C) 2012 by Manfred Moitzi (mozman) (MIT license). Review and some cleanup by mont29 (Bastien Montagne).
Diffstat (limited to 'io_import_dxf')
-rw-r--r--io_import_dxf/Readme.md137
-rw-r--r--io_import_dxf/__init__.py544
-rwxr-xr-xio_import_dxf/dxfgrabber/__init__.py67
-rwxr-xr-xio_import_dxf/dxfgrabber/acdsdata.py88
-rwxr-xr-xio_import_dxf/dxfgrabber/blockssection.py57
-rwxr-xr-xio_import_dxf/dxfgrabber/codepage.py37
-rwxr-xr-xio_import_dxf/dxfgrabber/color.py301
-rwxr-xr-xio_import_dxf/dxfgrabber/const.py110
-rwxr-xr-xio_import_dxf/dxfgrabber/cydxfentity.py7
-rwxr-xr-xio_import_dxf/dxfgrabber/cytags.py7
-rwxr-xr-xio_import_dxf/dxfgrabber/decode.py38
-rwxr-xr-xio_import_dxf/dxfgrabber/defaultchunk.py38
-rwxr-xr-xio_import_dxf/dxfgrabber/drawing.py65
-rwxr-xr-xio_import_dxf/dxfgrabber/dxf12.py202
-rwxr-xr-xio_import_dxf/dxfgrabber/dxf13.py546
-rwxr-xr-xio_import_dxf/dxfgrabber/dxfattr.py47
-rwxr-xr-xio_import_dxf/dxfgrabber/dxfentity.py84
-rwxr-xr-xio_import_dxf/dxfgrabber/entities.py930
-rwxr-xr-xio_import_dxf/dxfgrabber/entitysection.py94
-rwxr-xr-xio_import_dxf/dxfgrabber/headersection.py33
-rwxr-xr-xio_import_dxf/dxfgrabber/juliandate.py73
-rwxr-xr-xio_import_dxf/dxfgrabber/layers.py120
-rwxr-xr-xio_import_dxf/dxfgrabber/linetypes.py74
-rwxr-xr-xio_import_dxf/dxfgrabber/pytags.py385
-rwxr-xr-xio_import_dxf/dxfgrabber/sections.py70
-rwxr-xr-xio_import_dxf/dxfgrabber/styles.py100
-rwxr-xr-xio_import_dxf/dxfgrabber/tablessection.py92
-rwxr-xr-xio_import_dxf/dxfgrabber/tags.py73
-rw-r--r--io_import_dxf/dxfimport/__init__.py0
-rw-r--r--io_import_dxf/dxfimport/convert.py287
-rw-r--r--io_import_dxf/dxfimport/do.py1498
-rw-r--r--io_import_dxf/dxfimport/fake_entities.py45
-rw-r--r--io_import_dxf/dxfimport/groupsort.py83
-rw-r--r--io_import_dxf/dxfimport/is_.py129
-rw-r--r--io_import_dxf/dxfimport/line_merger.py113
-rw-r--r--io_import_dxf/transverse_mercator.py54
36 files changed, 6628 insertions, 0 deletions
diff --git a/io_import_dxf/Readme.md b/io_import_dxf/Readme.md
new file mode 100644
index 00000000..5ea576af
--- /dev/null
+++ b/io_import_dxf/Readme.md
@@ -0,0 +1,137 @@
+_Development Repository of DXF importer for Blender including DXF and .blend testfiles. Help out making the importer more realiable by posting your errors in the [issue tracker](https://bitbucket.org/treyerl/io_import_scene_dxf/issues?status=new&status=open) or in the [BlenderArtists thread](http://blenderartists.org/forum/showthread.php?323358-DXF-Importer)._
+
+# Features v0.8.4
+* __Blocks__ are being imported and are reflected in Blender as linked objects or optionally as group instances. For linked objects sub-blocks get parented to the main block. If a block contains mixed curve / mesh / surface / text / light entities, the different types are being imported to different objects that are being parented to the main block.
+* __Layers__ are being reflected with "Blender-groups". Select an object and type Shift-G to select all objects within the same "Blender-Group" (they should call it category, because that's what it is). Anyhow as Blender supports "only" 20 layers and DXF files can have virtually an infinite amount of layers I think it's best users would select grouped objects as described and move them themselves to layers as they wish.
+* __Speed__: Using as many generators instead of lists as possible minimizes memory consumption. Parts of the the underlying dxf library "dxfgrabber" are written in cython and can be compiled to platform specific modules.
+* __DXF Attributes__: DXF specific attributes (e.g. `thickness`, `width` and `extrusion`) are taken into account to import geometry as precise as possible.
+* __Geo Referencing__: If the pyproj library is available, the scene center will be converted to lat/lon taking into account re-centering of geometry. The origin/DXF coordinate system (SRID) must be specified. If you have a DXF file from QGIS or ArcGIS this option should be most likely set to WGS84. The destination/scene SRID is by default the same as the DXF SRID, but of course you can set it to your local coordinate system. If a scene has a SRID already, this option is not available and the DXF SRID MUST be specified, so that the DXF geometry can be aligned to the scene geometry. For the installation of pyproj see "Installation".
+
+ ### [Options](https://bitbucket.org/repo/5M8eeg/images/616018241-0.8.4.jpg):
+* import `TEXT` entities (`TEXT`, `MTEXT`)
+* import `LIGHT` entity, incl. support for AutoCAD colors
+* merge all entities of a layer into one object per a) Blender geometry type b) DXF geometry type
+* export NURBS 3D geometry (`BODY`, `REGION`, `PLANESURFACE`, `SURFACE`, `3DSOLID`) to ACIS-Sat files, since this is the format AutoCAD stores to DXF. The user is being notified about the amount of stored .sat/.sab files (any comments on experience with other 3D packages than AutoCAD are very welcome).
+* combine `Line` entities to Blender "POLY"-curves (= remove doubles)
+* switch outliner display mode to GROUPS
+* display BLOCK entities with bounding boxes (instead parenting them only to Empties)
+* import BLOCK entities as linked objects or group instances (default = linked objects)
+* import DXF file to a new scene
+* center the imported geometry to the center of the scene; the offset information is stored as a custom property to the scene
+
+ ### DXF entities being mapped to BLENDER CURVES:
+* `LINE` as "POLYLINE"-curve
+* `(LW)POLYLINE`, `(LW)POLYGON` as "POLYLINE"-curve if they have no bulges else as "BEZIER"-curve
+* conversion to Blender's cubic "BEZIER"-curve of
+ * quadratic `SPLINE`s and `(LW)POLYLINE`s
+ * splines with degree > 3 are imported as polylines (so far).
+ * `ARC`s, `CIRCLE`s and `ELLIPSE`s
+ * polys with bulges (`(LW)POLYLINE`, `POLYGON`)
+ * `HELIX`es (__3D__)
+
+ ### DXF entities being mapped to BLENDER MESHES:
+* `MESH` is mapped to an mesh object with a SubD modifier, incl. edge crease
+* `POLYFACE`s and `POLYMESH`es are imported to a mesh object
+* `3DFACE`s, `SOLID`s, `POINT`s are imported into one combined mesh object per layer called _layername_3Dfaces_.
+
+# Installation (User)
+* download the latest `io_import_scene_dxf.zip` file from the [download section](https://bitbucket.org/treyerl/io_import_scene_dxf/downloads)
+* in Blender go to File -> User Preferences -> Addons and click `Install from file` at the bottom and choose the downloaded zip file.
+* optional 'pyproj': Download ([WIN](https://code.google.com/p/pyproj/downloads/list), [MAC](http://www.ia.arch.ethz.ch/wp-content/uploads/2013/11/pyproj.zip)) pyproj and copy it to *your AppData/ApplicationSupport Folder*/Blender/2.70/scripts/modules/
+
+# Roadmap / Release Info
+
+_version 1.0.0 should be able to import `ALL` DXF/AutoCAD information translatable in some way to Blender._
+
+##0.9.x: Materials
+_(surfaces attributes like color, hatches etc.)_
+
+ * color to material map
+ * hatches: dynamic generation of hatch-textures? procedural textures?
+ * line colors and width to freestyle?
+
+##0.8.x: Geometry:
+ * blocks not reference by `INSERT`-entity but name starting with `*` (Dimensions)
+ * text alignment attributes
+ * bsplines with degree > 3? any test-files are welcome!
+ * update option (?):
+ * named entities (blocks) are imported but don't replace existing Blender objects with the same name
+ but only update their geometry if it changed
+
+###0.8.4
+ * proper knot insertion: bsplines are now properly converted to cubic bezier splines
+ * geo-referencing: if pyproj is available, the scene center will be converted to lat/lon taking
+ into account re-centering of geometry. The origin/DXF coordinate system (SRID) must be specified. If you have a
+ DXF file from QGIS or ArcGIS this option should be most likely set to WGS84. The destination/scene SRID
+ is by default the same as the DXF SRID, but of course you can set it to your local coordinate system.
+ If a scene has a SRID already, this option is not available and the DXF SRID MUST be specified, so that the DXF
+ geometry can be aligned to the scene geometry.
+
+###0.8.3
+ * many bug fixes (better testing with test-script)
+ * new option: switch display mode to "GROUPS" in outliner
+ * new option: BLOCK entities with bounding boxes
+ * new option: BLOCK representation as group instances (for INSERTs with sub-inserts and rows and cols)
+ * new option: import dxf to a new scene
+ * new option: center the imported geometry and set the offset information as custom properties in the scene
+ The offset is stored as a custom property to the scene. The key of the property is called like
+ "*name of the dxf file*_recenter" and the value is a [x,y,z] array. "lat"/"lon" georeferencing is not possible
+ since DXF does not store a EPSG coordinate system reference. But if the users know the coordinate system of the
+ DXF file they can convert the x,y,z offset to lat/lon/altitude ([duck it](https://duckduckgo.com)).
+ DXF does not store any information about the projection of x,y to lat,lon = the user needs to convert x, y to lat, lon.
+ * dxf file filter in the file browser
+ * POINT entities get imported as Empties if the merge option is turned off
+ * display errors as pop up message (for known errors)
+ * block objects get copied with obj.copy() = not only the geometry is being cached, but the whole object
+ = skips unnecessary calculation (especially for bounding boxes)
+
+###0.8.2
+ * added support for "width" attribute on curve types.
+ * improved thickness attribute handling on curve types; tilt option is Z_UP (--> check for Blender bug), for better shading a Edge Split Modifier is being added if the curve has width AND thickness
+ * added thickness on solids and traces
+ * improved solids: upon self intersection two faces are created
+ * added support for TRACE entity
+ * better yet not perfect extrusion handling
+ * added option to merge connecting LINE entities into polygons
+ * INSERT col and row attributes are handled with array modifier (needs improvement on BLOCK entities: instead of Empty the parent should be a bounding box)
+ * TEXT entity now uses plain_text() filter (no %%u and %%d symbols anymore)
+
+###0.8.1 (Start of version_info:):
+ * GUI options (for already existing functionality):
+ * import text `True/False`
+ * import lights `True/False`
+ * export ACIS code for NURB types `True/False`
+ * merge entities to Blender objects `True/False`
+ * by layer
+ * by layer and then by DXF entity type
+ * extrusion z-value for 2D types and `INSERT` to x-mirror the entity (which will be excluded from merged entities)
+ * using bmesh-layers/loops for crease information of `MESH`
+ * more clear code structure and doc strings
+
+
+
+###0.8.0:
+ version of APR2014:
+ * Added 3D, text (including attributes, no style), light types. Code restructuring since it grew and grew.
+ * Much improved dxfgrabber library incl. some parts in Cython for speedup. Text, Style, Light, and some 3D types, especially ACIS geometry became feasible only because of the extended capabilities of dxfgrabber.
+ * Tackled DXF Sample Files from [CADKit](http://www.cadkit.net/2012/01/sample-dxf-files.html):
+ * introducing some hacks and bug-fixes; bsplines with order higher > 4 will be most likely be imported as straight polylines
+ * introduced merged entities; especially with `3DFACE`s this lead to massive speed improvements.
+ * added License information: GPL
+
+
+###0.1.0:
+ version of JAN2014 was only able to import 2D curves. no 3D information. But Blocks got mapped to linked objects already and layers to groups.
+
+# Installation (Development)
+* copy/clone this repository to [Blender's addon folder](https://www.google.ch/search?client=safari&rls=en&q=blender+python+modules&ie=UTF-8&oe=UTF-8&gfe_rd=cr&ei=cvJpU6yAI6LC8gfB7IDICA#q=Configuration+%26+Data+Paths+-+Blender+Wiki&rls=en)
+* download the latest version of [dxfgrabber](https://bitbucket.org/mozman/dxfgrabber) and copy its dxfgrabber folder into the this repository.
+* in Blender go to File -> User Preferences -> Addons and search for "dxf" and activate the dxf import addon (there might be an old addon with an exlamation mark, don't activate that one)
+* test it with the supplied testfiles and the [DXF sample files from cadkit.net](http://www.mediafire.com/?pcq6a8pbsiz6paw)
+
+### Development on a mac
+It helps to start Blender from the Terminal because that’s where the python print statements go.
+
+1. right-click on the Blender-icon and select show package contents
+2. navigate to Contents/MacOS/
+3. either double-click on "blender" or drag and drop it to a Terminal window \ No newline at end of file
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()
diff --git a/io_import_dxf/dxfgrabber/__init__.py b/io_import_dxf/dxfgrabber/__init__.py
new file mode 100755
index 00000000..9d3da9d0
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/__init__.py
@@ -0,0 +1,67 @@
+# dxfgrabber - copyright (C) 2012 by Manfred Moitzi (mozman)
+# Purpose: grab information from DXF drawings - all DXF versions supported
+# Created: 21.07.2012
+# License: MIT License
+
+version = (0, 7, 4)
+VERSION = "%d.%d.%d" % version
+
+__author__ = "mozman <mozman@gmx.at>"
+__doc__ = """A Python library to grab information from DXF drawings - all DXF versions supported."""
+
+
+# Python27/3x support should be done here
+import sys
+
+PYTHON3 = sys.version_info.major > 2
+
+if PYTHON3:
+ tostr = str
+else: # PYTHON27
+ tostr = unicode
+
+# end of Python 2/3 adaption
+# if tostr does not work, look at package 'dxfwrite' for escaping unicode chars
+
+from .const import BYBLOCK, BYLAYER
+
+import io
+from .tags import dxfinfo
+from .color import aci_to_true_color
+
+
+def read(stream, options=None):
+ if hasattr(stream, 'readline'):
+ from .drawing import Drawing
+ return Drawing(stream, options)
+ else:
+ raise AttributeError('stream object requires a readline() method.')
+
+
+def readfile(filename, options=None):
+ try: # is it ascii code-page encoded?
+ return readfile_as_asc(filename, options)
+ except UnicodeDecodeError: # try unicode and ignore errors
+ return readfile_as_utf8(filename, options, errors='ignore')
+
+
+def readfile_as_utf8(filename, options=None, errors='strict'):
+ return _read_encoded_file(filename, options, encoding='utf-8', errors=errors)
+
+
+def readfile_as_asc(filename, options=None):
+ def get_encoding():
+ with io.open(filename) as fp:
+ info = dxfinfo(fp)
+ return info.encoding
+
+ return _read_encoded_file(filename, options, encoding=get_encoding())
+
+
+def _read_encoded_file(filename, options=None, encoding='utf-8', errors='strict'):
+ from .drawing import Drawing
+
+ with io.open(filename, encoding=encoding, errors=errors) as fp:
+ dwg = Drawing(fp, options)
+ dwg.filename = filename
+ return dwg
diff --git a/io_import_dxf/dxfgrabber/acdsdata.py b/io_import_dxf/dxfgrabber/acdsdata.py
new file mode 100755
index 00000000..60538d5f
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/acdsdata.py
@@ -0,0 +1,88 @@
+# Purpose: acdsdata section manager
+# Created: 05.05.2014
+# Copyright (C) 2014, Manfred Moitzi
+# License: MIT License
+
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from itertools import islice
+from .tags import TagGroups, DXFStructureError, Tags, binary_encoded_data_to_bytes
+
+
+class AcDsDataSection(object):
+ name = 'acdsdata'
+
+ def __init__(self):
+ # Standard_ACIS_Binary (SAB) data store, key = handle of DXF Entity in the ENTITIES section: BODY, 3DSOLID
+ # SURFACE, PLANESURFACE, REGION
+ self.sab_data = {}
+
+ @classmethod
+ def from_tags(cls, tags, drawing):
+ data_section = cls()
+ data_section._build(tags, drawing.dxfversion)
+ return data_section
+
+ def _build(self, tags, dxfversion):
+ if len(tags) == 3: # empty entities section
+ return
+
+ for group in TagGroups(islice(tags, 2, len(tags)-1)):
+ data_record = AcDsDataRecord(Tags(group))
+ if data_record.dxftype == 'ACDSRECORD':
+ asm_data = data_record.get_section('ASM_Data', None)
+ if asm_data is not None:
+ self.add_asm_data(data_record)
+
+ def add_asm_data(self, acdsrecord):
+ """ Store SAB data as binary string in the sab_data dict, with handle to owner Entity as key.
+ """
+ try:
+ asm_data = acdsrecord.get_section('ASM_Data')
+ entity_id = acdsrecord.get_section('AcDbDs::ID')
+ except ValueError:
+ return
+ else:
+ handle = entity_id[2].value
+ binary_data_text = (tag.value for tag in asm_data if tag.code == 310)
+ binary_data = binary_encoded_data_to_bytes(binary_data_text)
+ self.sab_data[handle] = binary_data
+
+
+class Section(Tags):
+ @property
+ def name(self):
+ return self[0].value
+
+ @property
+ def type(self):
+ return self[1].value
+
+ @property
+ def data(self):
+ return self[2:]
+
+
+class AcDsDataRecord(object):
+ def __init__(self, tags):
+ self.dxftype = tags[0].value
+ start_index = 2
+ while tags[start_index].code != 2:
+ start_index += 1
+ self.sections = [Section(tags) for tags in TagGroups(islice(tags, start_index, None), split_code=2)]
+
+ def has_section(self, name):
+ return self.get_section(name, default=None) is not None
+
+ def get_section(self, name, default=KeyError):
+ for section in self.sections:
+ if section.name == name:
+ return section
+ if default is KeyError:
+ raise KeyError(name)
+ else:
+ return default
+
+ def __getitem__(self, name):
+ return self.get_section(name)
diff --git a/io_import_dxf/dxfgrabber/blockssection.py b/io_import_dxf/dxfgrabber/blockssection.py
new file mode 100755
index 00000000..d8f6b057
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/blockssection.py
@@ -0,0 +1,57 @@
+# Purpose: blocks section
+# Created: 09.08.2012, taken from my package ezdxf
+# Copyright (C) 2011, Manfred Moitzi
+# License: MIT-License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from itertools import islice
+
+from .tags import TagGroups
+from .entitysection import build_entities
+
+
+class BlocksSection(object):
+ name = 'blocks'
+
+ def __init__(self):
+ self._blocks = dict()
+
+ @staticmethod
+ def from_tags(tags, drawing):
+ blocks_section = BlocksSection()
+ if drawing.grab_blocks:
+ blocks_section._build(tags, drawing.dxfversion)
+ return blocks_section
+
+ def _build(self, tags, dxfversion):
+ if len(tags) == 3: # empty block section
+ return
+ groups = list()
+ for group in TagGroups(islice(tags, 2, len(tags)-1)):
+ groups.append(group)
+ if group[0].value == 'ENDBLK':
+ entities = build_entities(groups, dxfversion)
+ block = entities[0]
+ block.set_entities(entities[1:-1])
+ self._add(block)
+ groups = list()
+
+ def _add(self, block):
+ self._blocks[block.name] = block
+
+ # start of public interface
+ def __len__(self):
+ return len(self._blocks)
+
+ def __iter__(self):
+ return iter(self._blocks.values())
+
+ def __contains__(self, name):
+ return name in self._blocks
+
+ def __getitem__(self, name):
+ return self._blocks[name]
+
+ def get(self, name, default=None):
+ return self._blocks.get(name, default)
diff --git a/io_import_dxf/dxfgrabber/codepage.py b/io_import_dxf/dxfgrabber/codepage.py
new file mode 100755
index 00000000..47e8e3ea
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/codepage.py
@@ -0,0 +1,37 @@
+# Purpose: codepage handling
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+codepages = {
+ '874': 'cp874', # Thai,
+ '932': 'cp932', # Japanese
+ '936': 'gbk', # UnifiedChinese
+ '949': 'cp949', # Korean
+ '950': 'cp950', # TradChinese
+ '1250': 'cp1250', # CentralEurope
+ '1251': 'cp1251', # Cyrillic
+ '1252': 'cp1252', # WesternEurope
+ '1253': 'cp1253', # Greek
+ '1254': 'cp1254', # Turkish
+ '1255': 'cp1255', # Hebrew
+ '1256': 'cp1256', # Arabic
+ '1257': 'cp1257', # Baltic
+ '1258': 'cp1258', # Vietnam
+}
+
+
+def toencoding(dxfcodepage):
+ for codepage, encoding in codepages.items():
+ if dxfcodepage.endswith(codepage):
+ return encoding
+ return 'cp1252'
+
+
+def tocodepage(encoding):
+ for codepage, enc in codepages.items():
+ if enc == encoding:
+ return 'ANSI_'+codepage
+ return 'ANSI_1252'
diff --git a/io_import_dxf/dxfgrabber/color.py b/io_import_dxf/dxfgrabber/color.py
new file mode 100755
index 00000000..ae999804
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/color.py
@@ -0,0 +1,301 @@
+__author__ = 'manfred'
+
+
+class TrueColor(int):
+ def rgb(self):
+ return (self >> 16) & 0xFF, (self >> 8) & 0xFF, self & 0xFF
+
+ @property
+ def r(self):
+ return (self >> 16) & 0xFF
+
+ @property
+ def g(self):
+ return (self >> 8) & 0xFF
+
+ @property
+ def b(self):
+ return self & 0xFF
+
+ def __getitem__(self, item):
+ if item == 0:
+ return self.r
+ elif item == 1:
+ return self.g
+ elif item == 2:
+ return self.b
+ raise IndexError(item)
+
+ @staticmethod
+ def from_rgb(r, g, b):
+ return TrueColor(((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff))
+
+ @staticmethod
+ def from_aci(index):
+ if index < 1:
+ raise IndexError(index)
+ return dxf_default_colors[index]
+
+
+def aci_to_true_color(index):
+ return TrueColor.from_aci(index)
+
+
+dxf_default_colors = [
+ TrueColor(0x000000),
+ TrueColor(0xff0000),
+ TrueColor(0xffff00),
+ TrueColor(0x00ff00),
+ TrueColor(0x00ffff),
+ TrueColor(0x0000ff),
+ TrueColor(0xff00ff),
+ TrueColor(0xffffff),
+ TrueColor(0x414141),
+ TrueColor(0x808080),
+ TrueColor(0xff0000),
+ TrueColor(0xffaaaa),
+ TrueColor(0xbd0000),
+ TrueColor(0xbd7e7e),
+ TrueColor(0x810000),
+ TrueColor(0x815656),
+ TrueColor(0x680000),
+ TrueColor(0x684545),
+ TrueColor(0x4f0000),
+ TrueColor(0x4f3535),
+ TrueColor(0xff3f00),
+ TrueColor(0xffbfaa),
+ TrueColor(0xbd2e00),
+ TrueColor(0xbd8d7e),
+ TrueColor(0x811f00),
+ TrueColor(0x816056),
+ TrueColor(0x681900),
+ TrueColor(0x684e45),
+ TrueColor(0x4f1300),
+ TrueColor(0x4f3b35),
+ TrueColor(0xff7f00),
+ TrueColor(0xffd4aa),
+ TrueColor(0xbd5e00),
+ TrueColor(0xbd9d7e),
+ TrueColor(0x814000),
+ TrueColor(0x816b56),
+ TrueColor(0x683400),
+ TrueColor(0x685645),
+ TrueColor(0x4f2700),
+ TrueColor(0x4f4235),
+ TrueColor(0xffbf00),
+ TrueColor(0xffeaaa),
+ TrueColor(0xbd8d00),
+ TrueColor(0xbdad7e),
+ TrueColor(0x816000),
+ TrueColor(0x817656),
+ TrueColor(0x684e00),
+ TrueColor(0x685f45),
+ TrueColor(0x4f3b00),
+ TrueColor(0x4f4935),
+ TrueColor(0xffff00),
+ TrueColor(0xffffaa),
+ TrueColor(0xbdbd00),
+ TrueColor(0xbdbd7e),
+ TrueColor(0x818100),
+ TrueColor(0x818156),
+ TrueColor(0x686800),
+ TrueColor(0x686845),
+ TrueColor(0x4f4f00),
+ TrueColor(0x4f4f35),
+ TrueColor(0xbfff00),
+ TrueColor(0xeaffaa),
+ TrueColor(0x8dbd00),
+ TrueColor(0xadbd7e),
+ TrueColor(0x608100),
+ TrueColor(0x768156),
+ TrueColor(0x4e6800),
+ TrueColor(0x5f6845),
+ TrueColor(0x3b4f00),
+ TrueColor(0x494f35),
+ TrueColor(0x7fff00),
+ TrueColor(0xd4ffaa),
+ TrueColor(0x5ebd00),
+ TrueColor(0x9dbd7e),
+ TrueColor(0x408100),
+ TrueColor(0x6b8156),
+ TrueColor(0x346800),
+ TrueColor(0x566845),
+ TrueColor(0x274f00),
+ TrueColor(0x424f35),
+ TrueColor(0x3fff00),
+ TrueColor(0xbfffaa),
+ TrueColor(0x2ebd00),
+ TrueColor(0x8dbd7e),
+ TrueColor(0x1f8100),
+ TrueColor(0x608156),
+ TrueColor(0x196800),
+ TrueColor(0x4e6845),
+ TrueColor(0x134f00),
+ TrueColor(0x3b4f35),
+ TrueColor(0x00ff00),
+ TrueColor(0xaaffaa),
+ TrueColor(0x00bd00),
+ TrueColor(0x7ebd7e),
+ TrueColor(0x008100),
+ TrueColor(0x568156),
+ TrueColor(0x006800),
+ TrueColor(0x456845),
+ TrueColor(0x004f00),
+ TrueColor(0x354f35),
+ TrueColor(0x00ff3f),
+ TrueColor(0xaaffbf),
+ TrueColor(0x00bd2e),
+ TrueColor(0x7ebd8d),
+ TrueColor(0x00811f),
+ TrueColor(0x568160),
+ TrueColor(0x006819),
+ TrueColor(0x45684e),
+ TrueColor(0x004f13),
+ TrueColor(0x354f3b),
+ TrueColor(0x00ff7f),
+ TrueColor(0xaaffd4),
+ TrueColor(0x00bd5e),
+ TrueColor(0x7ebd9d),
+ TrueColor(0x008140),
+ TrueColor(0x56816b),
+ TrueColor(0x006834),
+ TrueColor(0x456856),
+ TrueColor(0x004f27),
+ TrueColor(0x354f42),
+ TrueColor(0x00ffbf),
+ TrueColor(0xaaffea),
+ TrueColor(0x00bd8d),
+ TrueColor(0x7ebdad),
+ TrueColor(0x008160),
+ TrueColor(0x568176),
+ TrueColor(0x00684e),
+ TrueColor(0x45685f),
+ TrueColor(0x004f3b),
+ TrueColor(0x354f49),
+ TrueColor(0x00ffff),
+ TrueColor(0xaaffff),
+ TrueColor(0x00bdbd),
+ TrueColor(0x7ebdbd),
+ TrueColor(0x008181),
+ TrueColor(0x568181),
+ TrueColor(0x006868),
+ TrueColor(0x456868),
+ TrueColor(0x004f4f),
+ TrueColor(0x354f4f),
+ TrueColor(0x00bfff),
+ TrueColor(0xaaeaff),
+ TrueColor(0x008dbd),
+ TrueColor(0x7eadbd),
+ TrueColor(0x006081),
+ TrueColor(0x567681),
+ TrueColor(0x004e68),
+ TrueColor(0x455f68),
+ TrueColor(0x003b4f),
+ TrueColor(0x35494f),
+ TrueColor(0x007fff),
+ TrueColor(0xaad4ff),
+ TrueColor(0x005ebd),
+ TrueColor(0x7e9dbd),
+ TrueColor(0x004081),
+ TrueColor(0x566b81),
+ TrueColor(0x003468),
+ TrueColor(0x455668),
+ TrueColor(0x00274f),
+ TrueColor(0x35424f),
+ TrueColor(0x003fff),
+ TrueColor(0xaabfff),
+ TrueColor(0x002ebd),
+ TrueColor(0x7e8dbd),
+ TrueColor(0x001f81),
+ TrueColor(0x566081),
+ TrueColor(0x001968),
+ TrueColor(0x454e68),
+ TrueColor(0x00134f),
+ TrueColor(0x353b4f),
+ TrueColor(0x0000ff),
+ TrueColor(0xaaaaff),
+ TrueColor(0x0000bd),
+ TrueColor(0x7e7ebd),
+ TrueColor(0x000081),
+ TrueColor(0x565681),
+ TrueColor(0x000068),
+ TrueColor(0x454568),
+ TrueColor(0x00004f),
+ TrueColor(0x35354f),
+ TrueColor(0x3f00ff),
+ TrueColor(0xbfaaff),
+ TrueColor(0x2e00bd),
+ TrueColor(0x8d7ebd),
+ TrueColor(0x1f0081),
+ TrueColor(0x605681),
+ TrueColor(0x190068),
+ TrueColor(0x4e4568),
+ TrueColor(0x13004f),
+ TrueColor(0x3b354f),
+ TrueColor(0x7f00ff),
+ TrueColor(0xd4aaff),
+ TrueColor(0x5e00bd),
+ TrueColor(0x9d7ebd),
+ TrueColor(0x400081),
+ TrueColor(0x6b5681),
+ TrueColor(0x340068),
+ TrueColor(0x564568),
+ TrueColor(0x27004f),
+ TrueColor(0x42354f),
+ TrueColor(0xbf00ff),
+ TrueColor(0xeaaaff),
+ TrueColor(0x8d00bd),
+ TrueColor(0xad7ebd),
+ TrueColor(0x600081),
+ TrueColor(0x765681),
+ TrueColor(0x4e0068),
+ TrueColor(0x5f4568),
+ TrueColor(0x3b004f),
+ TrueColor(0x49354f),
+ TrueColor(0xff00ff),
+ TrueColor(0xffaaff),
+ TrueColor(0xbd00bd),
+ TrueColor(0xbd7ebd),
+ TrueColor(0x810081),
+ TrueColor(0x815681),
+ TrueColor(0x680068),
+ TrueColor(0x684568),
+ TrueColor(0x4f004f),
+ TrueColor(0x4f354f),
+ TrueColor(0xff00bf),
+ TrueColor(0xffaaea),
+ TrueColor(0xbd008d),
+ TrueColor(0xbd7ead),
+ TrueColor(0x810060),
+ TrueColor(0x815676),
+ TrueColor(0x68004e),
+ TrueColor(0x68455f),
+ TrueColor(0x4f003b),
+ TrueColor(0x4f3549),
+ TrueColor(0xff007f),
+ TrueColor(0xffaad4),
+ TrueColor(0xbd005e),
+ TrueColor(0xbd7e9d),
+ TrueColor(0x810040),
+ TrueColor(0x81566b),
+ TrueColor(0x680034),
+ TrueColor(0x684556),
+ TrueColor(0x4f0027),
+ TrueColor(0x4f3542),
+ TrueColor(0xff003f),
+ TrueColor(0xffaabf),
+ TrueColor(0xbd002e),
+ TrueColor(0xbd7e8d),
+ TrueColor(0x81001f),
+ TrueColor(0x815660),
+ TrueColor(0x680019),
+ TrueColor(0x68454e),
+ TrueColor(0x4f0013),
+ TrueColor(0x4f353b),
+ TrueColor(0x333333),
+ TrueColor(0x505050),
+ TrueColor(0x696969),
+ TrueColor(0x828282),
+ TrueColor(0xbebebe),
+ TrueColor(0xffffff),
+]
diff --git a/io_import_dxf/dxfgrabber/const.py b/io_import_dxf/dxfgrabber/const.py
new file mode 100755
index 00000000..cf8c6613
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/const.py
@@ -0,0 +1,110 @@
+# Purpose: constant values
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+ENV_CYTHON = 'DXFGRABBER_CYTHON'
+
+BYBLOCK = 0
+BYLAYER = 256
+
+XTYPE_NONE = 0
+XTYPE_2D = 1
+XTYPE_3D = 2
+XTYPE_2D_3D = 3
+
+acadrelease = {
+ 'AC1009': 'R12',
+ 'AC1012': 'R13',
+ 'AC1014': 'R14',
+ 'AC1015': 'R2000',
+ 'AC1018': 'R2004',
+ 'AC1021': 'R2007',
+ 'AC1024': 'R2010',
+}
+
+dxfversion = {
+ acad: dxf for dxf, acad in acadrelease.items()
+}
+
+# Entity: Polyline, Polymesh
+# 70 flags
+POLYLINE_CLOSED = 1
+POLYLINE_MESH_CLOSED_M_DIRECTION = POLYLINE_CLOSED
+POLYLINE_CURVE_FIT_VERTICES_ADDED = 2
+POLYLINE_SPLINE_FIT_VERTICES_ADDED = 4
+POLYLINE_3D_POLYLINE = 8
+POLYLINE_3D_POLYMESH = 16
+POLYLINE_MESH_CLOSED_N_DIRECTION = 32
+POLYLINE_POLYFACE = 64
+POLYLINE_GENERATE_LINETYPE_PATTERN =128
+
+# Entity: Polymesh
+# 75 surface smooth type
+POLYMESH_NO_SMOOTH = 0
+POLYMESH_QUADRIC_BSPLINE = 5
+POLYMESH_CUBIC_BSPLINE = 6
+POLYMESH_BEZIER_SURFACE = 8
+
+#Entity: Vertex
+# 70 flags
+VERTEXNAMES = ('vtx0', 'vtx1', 'vtx2', 'vtx3')
+VTX_EXTRA_VERTEX_CREATED = 1 ## Extra vertex created by curve-fitting
+VTX_CURVE_FIT_TANGENT = 2 ## Curve-fit tangent defined for this vertex.
+## A curve-fit tangent direction of 0 may be omitted from the DXF output, but is
+## significant if this bit is set.
+## 4 = unused, never set in dxf files
+VTX_SPLINE_VERTEX_CREATED = 8 ##Spline vertex created by spline-fitting
+VTX_SPLINE_FRAME_CONTROL_POINT = 16
+VTX_3D_POLYLINE_VERTEX = 32
+VTX_3D_POLYGON_MESH_VERTEX = 64
+VTX_3D_POLYFACE_MESH_VERTEX = 128
+
+VERTEX_FLAGS = {
+ 'polyline2d': 0,
+ 'polyline3d': VTX_3D_POLYLINE_VERTEX,
+ 'polymesh': VTX_3D_POLYGON_MESH_VERTEX,
+ 'polyface': VTX_3D_POLYGON_MESH_VERTEX | VTX_3D_POLYFACE_MESH_VERTEX,
+}
+POLYLINE_FLAGS = {
+ 'polyline2d': 0,
+ 'polyline3d': POLYLINE_3D_POLYLINE,
+ 'polymesh': POLYLINE_3D_POLYMESH,
+ 'polyface': POLYLINE_POLYFACE,
+}
+
+#---block-type flags (bit coded values, may be combined):
+# Entity: BLOCK
+# 70 flags
+BLK_ANONYMOUS = 1 # This is an anonymous block generated by hatching, associative dimensioning, other internal operations, or an application
+BLK_NON_CONSTANT_ATTRIBUTES = 2 # This block has non-constant attribute definitions (this bit is not set if the block has any attribute definitions that are constant, or has no attribute definitions at all)
+BLK_XREF = 4 # This block is an external reference (xref)
+BLK_XREF_OVERLAY = 8 # This block is an xref overlay
+BLK_EXTERNAL = 16 # This block is externally dependent
+BLK_RESOLVED = 32 # This is a resolved external reference, or dependent of an external reference (ignored on input)
+BLK_REFERENCED = 64 # This definition is a referenced external reference (ignored on input)
+
+LWPOLYLINE_CLOSED = 1
+LWPOLYLINE_PLINEGEN = 128
+
+SPLINE_CLOSED = 1
+SPLINE_PERIODIC = 2
+SPLINE_RATIONAL = 4
+SPLINE_PLANAR = 8
+SPLINE_LINEAR = 16 # planar bit is also set
+
+MTEXT_TOP_LEFT = 1
+MTEXT_TOP_CENTER = 2
+MTEXT_TOP_RIGHT = 3
+MTEXT_MIDDLE_LEFT = 4
+MTEXT_MIDDLE_CENTER = 5
+MTEXT_MIDDLE_RIGHT = 6
+MTEXT_BOTTOM_LEFT = 7
+MTEXT_BOTTOM_CENTER = 8
+MTEXT_BOTTOM_RIGHT = 9
+
+MTEXT_LEFT_TO_RIGHT = 1
+MTEXT_TOP_TO_BOTTOM = 2
+MTEXT_BY_STYLE = 5
diff --git a/io_import_dxf/dxfgrabber/cydxfentity.py b/io_import_dxf/dxfgrabber/cydxfentity.py
new file mode 100755
index 00000000..be39b48d
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/cydxfentity.py
@@ -0,0 +1,7 @@
+def __bootstrap__():
+ global __bootstrap__, __loader__, __file__
+ import sys, pkg_resources, imp
+ __file__ = pkg_resources.resource_filename(__name__,'cydxfentity.so')
+ __loader__ = None; del __bootstrap__, __loader__
+ imp.load_dynamic(__name__,__file__)
+__bootstrap__()
diff --git a/io_import_dxf/dxfgrabber/cytags.py b/io_import_dxf/dxfgrabber/cytags.py
new file mode 100755
index 00000000..071cea03
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/cytags.py
@@ -0,0 +1,7 @@
+def __bootstrap__():
+ global __bootstrap__, __loader__, __file__
+ import sys, pkg_resources, imp
+ __file__ = pkg_resources.resource_filename(__name__,'cytags.so')
+ __loader__ = None; del __bootstrap__, __loader__
+ imp.load_dynamic(__name__,__file__)
+__bootstrap__()
diff --git a/io_import_dxf/dxfgrabber/decode.py b/io_import_dxf/dxfgrabber/decode.py
new file mode 100755
index 00000000..3187cc8f
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/decode.py
@@ -0,0 +1,38 @@
+# Purpose: decode DXF proprietary data
+# Created: 01.05.2014
+# Copyright (C) 2014, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from . import PYTHON3
+
+_replacement_table = {
+ 0x20: ' ',
+ 0x40: '_',
+ 0x5F: '@',
+}
+for c in range(0x41, 0x5F):
+ _replacement_table[c] = chr(0x41 + (0x5E - c)) # 0x5E -> 'A', 0x5D->'B', ...
+
+
+def decode(text_lines):
+ def _decode(text):
+ s = []
+ skip = False
+ if PYTHON3:
+ text = bytes(text, 'ascii')
+ else:
+ text = map(ord, text)
+
+ for c in text:
+ if skip:
+ skip = False
+ continue
+ if c in _replacement_table:
+ s += _replacement_table[c]
+ skip = (c == 0x5E) # skip space after 'A'
+ else:
+ s += chr(c ^ 0x5F)
+ return ''.join(s)
+ return [_decode(line) for line in text_lines]
diff --git a/io_import_dxf/dxfgrabber/defaultchunk.py b/io_import_dxf/dxfgrabber/defaultchunk.py
new file mode 100755
index 00000000..5a280bd1
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/defaultchunk.py
@@ -0,0 +1,38 @@
+# Purpose: handle default chunk
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from .tags import Tags, DXFTag
+
+
+class DefaultChunk(object):
+ def __init__(self, tags, drawing):
+ assert isinstance(tags, Tags)
+ self.tags = tags
+ self._drawing = drawing
+
+ @staticmethod
+ def from_tags(tags, drawing):
+ return DefaultChunk(tags, drawing)
+
+ @property
+ def name(self):
+ return self.tags[1].value.lower()
+
+
+def iterchunks(tagreader, stoptag='EOF', endofchunk='ENDSEC'):
+ while True:
+ tag = next(tagreader)
+ if tag == DXFTag(0, stoptag):
+ return
+
+ tags = Tags([tag])
+ append = tags.append
+ end_tag = DXFTag(0, endofchunk)
+ while tag != end_tag:
+ tag = next(tagreader)
+ append(tag)
+ yield tags
diff --git a/io_import_dxf/dxfgrabber/drawing.py b/io_import_dxf/dxfgrabber/drawing.py
new file mode 100755
index 00000000..1b275e86
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/drawing.py
@@ -0,0 +1,65 @@
+# Purpose: handle drawing data of DXF files
+# Created: 21.07.12
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+
+__author__ = "mozman <mozman@gmx.at>"
+
+from .tags import TagIterator
+from .sections import Sections
+
+DEFAULT_OPTIONS = {
+ "grab_blocks": True, # import block definitions True=yes, False=No
+ "assure_3d_coords": False, # guarantees (x, y, z) tuples for ALL coordinates
+ "resolve_text_styles": True, # Text, Attrib, Attdef and MText attributes will be set by the associated text style if necessary
+}
+
+
+class Drawing(object):
+ def __init__(self, stream, options=None):
+ if options is None:
+ options = DEFAULT_OPTIONS
+ self.grab_blocks = options.get('grab_blocks', True)
+ self.assure_3d_coords = options.get('assure_3d_coords', False)
+ self.resolve_text_styles = options.get('resolve_text_styles', True)
+
+ tagreader = TagIterator(stream, self.assure_3d_coords)
+ self.dxfversion = 'AC1009'
+ self.encoding = 'cp1252'
+ self.filename = None
+ sections = Sections(tagreader, self)
+ self.header = sections.header
+ self.layers = sections.tables.layers
+ self.styles = sections.tables.styles
+ self.linetypes = sections.tables.linetypes
+ self.blocks = sections.blocks
+ self.entities = sections.entities
+ self.objects = sections.objects if ('objects' in sections) else []
+ if 'acdsdata' in sections:
+ self.acdsdata = sections.acdsdata
+ # sab data introduced with DXF version AC1027 (R2013)
+ if self.dxfversion >= 'AC1027':
+ self.collect_sab_data()
+
+ if self.resolve_text_styles:
+ resolve_text_styles(self.entities, self.styles)
+ for block in self.blocks:
+ resolve_text_styles(block, self.styles)
+
+ def modelspace(self):
+ return (entity for entity in self.entities if not entity.paperspace)
+
+ def paperspace(self):
+ return (entity for entity in self.entities if entity.paperspace)
+
+ def collect_sab_data(self):
+ for entity in self.entities:
+ if hasattr(entity, 'set_sab_data'):
+ sab_data = self.acdsdata.sab_data[entity.handle]
+ entity.set_sab_data(sab_data)
+
+
+def resolve_text_styles(entities, text_styles):
+ for entity in entities:
+ if hasattr(entity, 'resolve_text_style'):
+ entity.resolve_text_style(text_styles) \ No newline at end of file
diff --git a/io_import_dxf/dxfgrabber/dxf12.py b/io_import_dxf/dxfgrabber/dxf12.py
new file mode 100755
index 00000000..7663ae44
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/dxf12.py
@@ -0,0 +1,202 @@
+# Purpose: DXF12 tag wrapper
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+
+from .dxfattr import DXFAttr, DXFAttributes, DefSubclass
+from .dxfentity import DXFEntity
+from . import const
+from .const import XTYPE_3D, XTYPE_2D_3D
+
+def make_attribs(additional=None):
+ dxfattribs = {
+ 'handle': DXFAttr(5),
+ 'layer': DXFAttr(8), # layername as string, default is '0'
+ 'linetype': DXFAttr(6), # linetype as string, special names BYLAYER/BYBLOCK, default is BYLAYER
+ 'thickness': DXFAttr(39),
+ 'color': DXFAttr(62), # dxf color index, 0 .. BYBLOCK, 256 .. BYLAYER, default is 256
+ 'paperspace': DXFAttr(67), # 0 .. modelspace, 1 .. paperspace, default is 0
+ 'extrusion': DXFAttr(210, XTYPE_3D),
+ }
+ if additional:
+ dxfattribs.update(additional)
+ return DXFAttributes(DefSubclass(None, dxfattribs))
+
+
+class Line(DXFEntity):
+ DXFATTRIBS = make_attribs({
+ 'start': DXFAttr(10, XTYPE_2D_3D),
+ 'end': DXFAttr(11, XTYPE_2D_3D),
+ })
+
+
+class Point(DXFEntity):
+ DXFATTRIBS = make_attribs({
+ 'point': DXFAttr(10, XTYPE_2D_3D),
+ })
+
+
+class Circle(DXFEntity):
+ DXFATTRIBS = make_attribs({
+ 'center': DXFAttr(10, XTYPE_2D_3D),
+ 'radius': DXFAttr(40),
+ })
+
+
+class Arc(DXFEntity):
+ DXFATTRIBS = make_attribs({
+ 'center': DXFAttr(10, XTYPE_2D_3D),
+ 'radius': DXFAttr(40),
+ 'startangle': DXFAttr(50),
+ 'endangle': DXFAttr(51),
+ })
+
+
+class Trace(DXFEntity):
+ DXFATTRIBS = make_attribs({
+ 'vtx0': DXFAttr(10, XTYPE_2D_3D),
+ 'vtx1': DXFAttr(11, XTYPE_2D_3D),
+ 'vtx2': DXFAttr(12, XTYPE_2D_3D),
+ 'vtx3': DXFAttr(13, XTYPE_2D_3D),
+ })
+
+
+Solid = Trace
+
+
+class Face(DXFEntity):
+ DXFATTRIBS = make_attribs({
+ 'vtx0': DXFAttr(10, XTYPE_2D_3D),
+ 'vtx1': DXFAttr(11, XTYPE_2D_3D),
+ 'vtx2': DXFAttr(12, XTYPE_2D_3D),
+ 'vtx3': DXFAttr(13, XTYPE_2D_3D),
+ 'invisible_edge': DXFAttr(70),
+ })
+
+
+class Text(DXFEntity):
+ DXFATTRIBS = make_attribs({
+ 'insert': DXFAttr(10, XTYPE_2D_3D),
+ 'height': DXFAttr(40),
+ 'text': DXFAttr(1),
+ 'rotation': DXFAttr(50), # in degrees (circle = 360deg)
+ 'oblique': DXFAttr(51), # in degrees, vertical = 0deg
+ 'style': DXFAttr(7), # text style
+ 'width': DXFAttr(41), # width FACTOR!
+ 'textgenerationflag': DXFAttr(71), # 2 = backward (mirr-x), 4 = upside down (mirr-y)
+ 'halign': DXFAttr(72), # horizontal justification
+ 'valign': DXFAttr(73), # vertical justification
+ 'alignpoint': DXFAttr(11, XTYPE_2D_3D),
+ })
+
+
+class Insert(DXFEntity):
+ DXFATTRIBS = make_attribs({
+ 'attribsfollow': DXFAttr(66),
+ 'name': DXFAttr(2),
+ 'insert': DXFAttr(10, XTYPE_2D_3D),
+ 'xscale': DXFAttr(41),
+ 'yscale': DXFAttr(42),
+ 'zscale': DXFAttr(43),
+ 'rotation': DXFAttr(50),
+ 'colcount': DXFAttr(70),
+ 'rowcount': DXFAttr(71),
+ 'colspacing': DXFAttr(44),
+ 'rowspacing': DXFAttr(45),
+ })
+
+
+class SeqEnd(DXFEntity):
+ DXFATTRIBS = DXFAttributes(DefSubclass(None, {'handle': DXFAttr(5), 'paperspace': DXFAttr(67), }))
+
+
+class Attrib(DXFEntity): # also ATTDEF
+ DXFATTRIBS = make_attribs({
+ 'insert': DXFAttr(10, XTYPE_2D_3D),
+ 'height': DXFAttr(40),
+ 'text': DXFAttr(1),
+ 'prompt': DXFAttr(3), # just in ATTDEF not ATTRIB
+ 'tag': DXFAttr(2),
+ 'flags': DXFAttr(70),
+ 'fieldlength': DXFAttr(73),
+ 'rotation': DXFAttr(50),
+ 'oblique': DXFAttr(51),
+ 'width': DXFAttr(41), # width factor
+ 'style': DXFAttr(7),
+ 'textgenerationflag': DXFAttr(71), # 2 = backward (mirr-x), 4 = upside down (mirr-y)
+ 'halign': DXFAttr(72), # horizontal justification
+ 'valign': DXFAttr(74), # vertical justification
+ 'alignpoint': DXFAttr(11, XTYPE_2D_3D),
+ })
+
+
+class Polyline(DXFEntity):
+ DXFATTRIBS = make_attribs({
+ 'elevation': DXFAttr(10, XTYPE_2D_3D),
+ 'flags': DXFAttr(70),
+ 'defaultstartwidth': DXFAttr(40),
+ 'defaultendwidth': DXFAttr(41),
+ 'mcount': DXFAttr(71),
+ 'ncount': DXFAttr(72),
+ 'msmoothdensity': DXFAttr(73),
+ 'nsmoothdensity': DXFAttr(74),
+ 'smoothtype': DXFAttr(75),
+ })
+
+ def get_vertex_flags(self):
+ return const.VERTEX_FLAGS[self.get_mode()]
+
+ @property
+ def flags(self):
+ return self.get_dxf_attrib('flags', 0)
+
+ def get_mode(self):
+ flags = self.flags
+ if flags & const.POLYLINE_SPLINE_FIT_VERTICES_ADDED:
+ return 'spline2d'
+ elif flags & const.POLYLINE_3D_POLYLINE:
+ return 'polyline3d'
+ elif flags & const.POLYLINE_3D_POLYMESH:
+ return 'polymesh'
+ elif flags & const.POLYLINE_POLYFACE:
+ return 'polyface'
+ else:
+ return 'polyline2d'
+
+ def is_mclosed(self):
+ return bool(self.flags & const.POLYLINE_MESH_CLOSED_M_DIRECTION)
+
+ def is_nclosed(self):
+ return bool(self.flags & const.POLYLINE_MESH_CLOSED_N_DIRECTION)
+
+
+class Vertex(DXFEntity):
+ DXFATTRIBS = make_attribs({
+ 'location': DXFAttr(10, XTYPE_2D_3D),
+ 'startwidth': DXFAttr(40),
+ 'endwidth': DXFAttr(41),
+ 'bulge': DXFAttr(42),
+ 'flags': DXFAttr(70),
+ 'tangent': DXFAttr(50),
+ 'vtx0': DXFAttr(71),
+ 'vtx1': DXFAttr(72),
+ 'vtx2': DXFAttr(73),
+ 'vtx3': DXFAttr(74),
+ })
+
+
+class Block(DXFEntity):
+ DXFATTRIBS = make_attribs({
+ 'name': DXFAttr(2),
+ 'name2': DXFAttr(3),
+ 'flags': DXFAttr(70),
+ 'basepoint': DXFAttr(10, XTYPE_2D_3D),
+ 'xrefpath': DXFAttr(1),
+ })
+
+
+class EndBlk(SeqEnd):
+ DXFATTRIBS = DXFAttributes(DefSubclass(None, {'handle': DXFAttr(5)}))
diff --git a/io_import_dxf/dxfgrabber/dxf13.py b/io_import_dxf/dxfgrabber/dxf13.py
new file mode 100755
index 00000000..d88ada42
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/dxf13.py
@@ -0,0 +1,546 @@
+# Purpose: DXF13 tag wrapper
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+
+from . import dxf12
+from .dxfentity import DXFEntity
+from .dxfattr import DXFAttr, DXFAttributes, DefSubclass
+from . import const
+from .const import XTYPE_2D, XTYPE_3D, XTYPE_2D_3D
+from .tags import Tags
+from .decode import decode
+
+none_subclass = DefSubclass(None, {
+ 'handle': DXFAttr(5),
+ 'block_record': DXFAttr(330), # Soft-pointer ID/handle to owner BLOCK_RECORD object
+})
+
+entity_subclass = DefSubclass('AcDbEntity', {
+ 'paperspace': DXFAttr(67), # 0 .. modelspace, 1 .. paperspace, default is 0
+ 'layer': DXFAttr(8), # layername as string, default is '0'
+ 'linetype': DXFAttr(6), # linetype as string, special names BYLAYER/BYBLOCK, default is BYLAYER
+ 'ltscale': DXFAttr(48), # linetype scale, default is 1.0
+ 'invisible': DXFAttr(60), # invisible .. 1, visible .. 0, default is 0
+ 'color': DXFAttr(62), # dxf color index, 0 .. BYBLOCK, 256 .. BYLAYER, default is 256
+ 'true_color': DXFAttr(420), # true color as 0x00RRGGBB 24-bit value (since AC1018)
+ 'transparency': DXFAttr(440), # transparency value 0x020000TT (since AC1018) 0 = fully transparent / 255 = opaque
+ 'shadow_mode': DXFAttr(284), # shadow_mode (since AC1021)
+ # 0 = Casts and receives shadows
+ # 1 = Casts shadows
+ # 2 = Receives shadows
+ # 3 = Ignores shadows
+})
+
+line_subclass = DefSubclass('AcDbLine', {
+ 'start': DXFAttr(10, XTYPE_2D_3D),
+ 'end': DXFAttr(11, XTYPE_2D_3D),
+ 'thickness': DXFAttr(39),
+ 'extrusion': DXFAttr(210, XTYPE_3D),
+})
+
+
+class Line(dxf12.Line):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, line_subclass)
+
+point_subclass = DefSubclass('AcDbPoint', {
+ 'point': DXFAttr(10, XTYPE_2D_3D),
+ 'thickness': DXFAttr(39),
+ 'extrusion': DXFAttr(210, XTYPE_3D),
+})
+
+
+class Point(dxf12.Point):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, point_subclass)
+
+
+circle_subclass = DefSubclass('AcDbCircle', {
+ 'center': DXFAttr(10, XTYPE_2D_3D),
+ 'radius': DXFAttr(40),
+ 'thickness': DXFAttr(39),
+ 'extrusion': DXFAttr(210, XTYPE_3D),
+})
+
+
+class Circle(dxf12.Circle):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, circle_subclass)
+
+arc_subclass = DefSubclass('AcDbArc', {
+ 'startangle': DXFAttr(50),
+ 'endangle': DXFAttr(51),
+})
+
+
+class Arc(dxf12.Arc):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, circle_subclass, arc_subclass)
+
+
+trace_subclass = DefSubclass('AcDbTrace', {
+ 'vtx0': DXFAttr(10, XTYPE_2D_3D),
+ 'vtx1': DXFAttr(11, XTYPE_2D_3D),
+ 'vtx2': DXFAttr(12, XTYPE_2D_3D),
+ 'vtx3': DXFAttr(13, XTYPE_2D_3D),
+ 'thickness': DXFAttr(39),
+ 'extrusion': DXFAttr(210, XTYPE_3D),
+})
+
+
+class Trace(dxf12.Trace):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, trace_subclass)
+
+
+Solid = Trace
+
+
+face_subclass = DefSubclass('AcDbFace', {
+ 'vtx0': DXFAttr(10, XTYPE_2D_3D),
+ 'vtx1': DXFAttr(11, XTYPE_2D_3D),
+ 'vtx2': DXFAttr(12, XTYPE_2D_3D),
+ 'vtx3': DXFAttr(13, XTYPE_2D_3D),
+ 'invisible_edge': DXFAttr(70),
+})
+
+
+class Face(dxf12.Face):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, face_subclass)
+
+
+text_subclass = (
+ DefSubclass('AcDbText', {
+ 'insert': DXFAttr(10, XTYPE_2D_3D),
+ 'height': DXFAttr(40),
+ 'text': DXFAttr(1),
+ 'rotation': DXFAttr(50), # in degrees (circle = 360deg)
+ 'oblique': DXFAttr(51), # in degrees, vertical = 0deg
+ 'style': DXFAttr(7), # text style
+ 'width': DXFAttr(41), # width FACTOR!
+ 'textgenerationflag': DXFAttr(71), # 2 = backward (mirr-x), 4 = upside down (mirr-y)
+ 'halign': DXFAttr(72), # horizontal justification
+ 'alignpoint': DXFAttr(11, XTYPE_2D_3D),
+ 'thickness': DXFAttr(39),
+ 'extrusion': DXFAttr(210, XTYPE_3D),
+ }),
+ DefSubclass('AcDbText', {'valign': DXFAttr(73)}))
+
+
+class Text(dxf12.Text):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, *text_subclass)
+
+polyline_subclass = DefSubclass('AcDb2dPolyline', {
+ 'elevation': DXFAttr(10, XTYPE_3D),
+ 'flags': DXFAttr(70),
+ 'defaultstartwidth': DXFAttr(40),
+ 'defaultendwidth': DXFAttr(41),
+ 'mcount': DXFAttr(71),
+ 'ncount': DXFAttr(72),
+ 'msmoothdensity': DXFAttr(73),
+ 'nsmoothdensity': DXFAttr(74),
+ 'smoothtype': DXFAttr(75),
+ 'thickness': DXFAttr(39),
+ 'extrusion': DXFAttr(210, XTYPE_3D),
+})
+
+
+class Polyline(dxf12.Polyline):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, polyline_subclass)
+
+
+vertex_subclass = (
+ DefSubclass('AcDbVertex', {}), # subclasses[2]
+ DefSubclass('AcDb2dVertex', { # subclasses[3]
+ 'location': DXFAttr(10, XTYPE_2D_3D),
+ 'startwidth': DXFAttr(40),
+ 'endwidth': DXFAttr(41),
+ 'bulge': DXFAttr(42),
+ 'flags': DXFAttr(70),
+ 'tangent': DXFAttr(50),
+ 'vtx0': DXFAttr(71),
+ 'vtx1': DXFAttr(72),
+ 'vtx2': DXFAttr(73),
+ 'vtx3': DXFAttr(74),
+ })
+)
+
+EMPTY_SUBCLASS = Tags()
+
+
+class Vertex(dxf12.Vertex):
+ VTX3D = const.VTX_3D_POLYFACE_MESH_VERTEX | const.VTX_3D_POLYGON_MESH_VERTEX | const.VTX_3D_POLYLINE_VERTEX
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, *vertex_subclass)
+
+ def post_read_correction(self):
+ if self.tags.subclasses[2][0].value != 'AcDbVertex':
+ self.tags.subclasses.insert(2, EMPTY_SUBCLASS) # create empty AcDbVertex subclass
+
+
+class SeqEnd(dxf12.SeqEnd):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass)
+
+lwpolyline_subclass = DefSubclass('AcDbPolyline', {
+ 'elevation': DXFAttr(38),
+ 'thickness': DXFAttr(39),
+ 'flags': DXFAttr(70),
+ 'const_width': DXFAttr(43),
+ 'count': DXFAttr(90),
+ 'extrusion': DXFAttr(210, XTYPE_3D),
+})
+
+
+LWPOINTCODES = (10, 20, 40, 41, 42)
+
+
+class LWPolyline(DXFEntity):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, lwpolyline_subclass)
+
+ def __iter__(self):
+ subclass = self.tags.subclasses[2] # subclass AcDbPolyline
+
+ def get_vertex():
+ point.append(attribs.get(40, 0))
+ point.append(attribs.get(41, 0))
+ point.append(attribs.get(42, 0))
+ return tuple(point)
+
+ point = None
+ attribs = {}
+ for tag in subclass:
+ if tag.code in LWPOINTCODES:
+ if tag.code == 10:
+ if point is not None:
+ yield get_vertex()
+ point = list(tag.value)
+ attribs = {}
+ else:
+ attribs[tag.code] = tag.value
+ if point is not None:
+ yield get_vertex() # last point
+
+ def data(self):
+ full_points = list(self)
+ points = []
+ width = []
+ bulge = []
+ for point in full_points:
+ x = 2 if len(point) == 5 else 3
+ points.append(point[:x])
+ width.append((point[-3], point[-2]))
+ bulge.append(point[-1])
+ return points, width, bulge
+
+ @property
+ def flags(self):
+ return self.get_dxf_attrib('flags', 0)
+
+ def is_closed(self):
+ return bool(self.flags & const.LWPOLYLINE_CLOSED)
+
+
+insert_subclass = DefSubclass('AcDbBlockReference', {
+ 'attribsfollow': DXFAttr(66),
+ 'name': DXFAttr(2),
+ 'insert': DXFAttr(10, XTYPE_2D_3D),
+ 'xscale': DXFAttr(41),
+ 'yscale': DXFAttr(42),
+ 'zscale': DXFAttr(43),
+ 'rotation': DXFAttr(50),
+ 'colcount': DXFAttr(70),
+ 'rowcount': DXFAttr(71),
+ 'colspacing': DXFAttr(44),
+ 'rowspacing': DXFAttr(45),
+ 'extrusion': DXFAttr(210, XTYPE_3D),
+})
+
+
+class Insert(dxf12.Insert):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, insert_subclass)
+
+
+attrib_subclass = (
+ DefSubclass('AcDbText', {
+ 'insert': DXFAttr(10, XTYPE_2D_3D),
+ 'thickness': DXFAttr(39),
+ 'height': DXFAttr(40),
+ 'text': DXFAttr(1),
+ 'style': DXFAttr(7), # DXF-specs: 'AcDbAttribute'; AutoCAD: 'AcDbText'
+ }),
+ DefSubclass('AcDbAttribute', {
+ 'tag': DXFAttr(2),
+ 'flags': DXFAttr(70),
+ 'fieldlength': DXFAttr(73),
+ 'rotation': DXFAttr(50),
+ 'width': DXFAttr(41),
+ 'oblique': DXFAttr(51),
+ 'textgenerationflag': DXFAttr(71),
+ 'halign': DXFAttr(72),
+ 'valign': DXFAttr(74),
+ 'alignpoint': DXFAttr(11, XTYPE_2D_3D),
+ 'extrusion': DXFAttr(210, XTYPE_3D),
+ })
+)
+
+
+class Attrib(dxf12.Attrib):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, *attrib_subclass)
+
+
+attdef_subclass = (
+ DefSubclass('AcDbText', {
+ 'insert': DXFAttr(10, XTYPE_2D_3D),
+ 'thickness': DXFAttr(39),
+ 'height': DXFAttr(40),
+ 'text': DXFAttr(1),
+ 'rotation': DXFAttr(50),
+ 'width': DXFAttr(41),
+ 'oblique': DXFAttr(51),
+ 'style': DXFAttr(7),
+ 'textgenerationflag': DXFAttr(71),
+ 'halign': DXFAttr(72),
+ 'alignpoint': DXFAttr(11),
+ 'extrusion': DXFAttr(210),
+ }),
+ DefSubclass('AcDbAttributeDefinition', {
+ 'prompt': DXFAttr(3),
+ 'tag': DXFAttr(2),
+ 'flags': DXFAttr(70),
+ 'fieldlength': DXFAttr(73),
+ 'valign': DXFAttr(74),
+ }))
+
+
+class Attdef(dxf12.Attrib):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, *attdef_subclass)
+
+
+ellipse_subclass = DefSubclass('AcDbEllipse', {
+ 'center': DXFAttr(10, XTYPE_2D_3D),
+ 'majoraxis': DXFAttr(11, XTYPE_2D_3D), # relative to the center
+ 'extrusion': DXFAttr(210, XTYPE_3D),
+ 'ratio': DXFAttr(40),
+ 'startparam': DXFAttr(41), # this value is 0.0 for a full ellipse
+ 'endparam': DXFAttr(42), # this value is 2*pi for a full ellipse
+})
+
+
+class Ellipse(DXFEntity):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, ellipse_subclass)
+
+
+ray_subclass = DefSubclass('AcDbRay', {
+ 'start': DXFAttr(10, XTYPE_3D),
+ 'unitvector': DXFAttr(11, XTYPE_3D),
+})
+
+
+class Ray(DXFEntity):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, ray_subclass)
+
+
+xline_subclass = DefSubclass('AcDbXline', {
+ 'start': DXFAttr(10, XTYPE_3D),
+ 'unitvector': DXFAttr(11, XTYPE_3D),
+})
+
+
+class XLine(DXFEntity):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, xline_subclass)
+
+
+spline_subclass = DefSubclass('AcDbSpline', {
+ 'normalvector': DXFAttr(210, XTYPE_3D), # omitted if spline is not planar
+ 'flags': DXFAttr(70),
+ 'degree': DXFAttr(71),
+ 'nknots': DXFAttr(72),
+ 'ncontrolpoints': DXFAttr(73),
+ 'nfitcounts': DXFAttr(74),
+ 'knot_tolerance': DXFAttr(42), # default 0.0000001
+ 'controlpoint_tolerance': DXFAttr(43), # default 0.0000001
+ 'fit_tolerance': DXFAttr(44), # default 0.0000000001
+ 'starttangent': DXFAttr(12, XTYPE_3D), # optional
+ 'endtangent': DXFAttr(13, XTYPE_3D), # optional
+})
+
+
+class Spline(DXFEntity):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, spline_subclass)
+
+ def knots(self):
+ # groupcode 40, multiple values: nknots
+ subclass = self.tags.subclasses[2] # subclass AcDbSpline
+ return (tag.value for tag in subclass if tag.code == 40)
+
+ def weights(self):
+ # groupcode 41, multiple values
+ subclass = self.tags.subclasses[2] # subclass AcDbSpline
+ return (tag.value for tag in subclass if tag.code == 41)
+
+ def controlpoints(self):
+ # groupcode 10,20,30, multiple values: ncontrolpoints
+ return self._get_points(10)
+
+ def fitpoints(self):
+ # groupcode 11,21,31, multiple values: nfitpoints
+ return self._get_points(11)
+
+ def _get_points(self, code):
+ return (tag.value for tag in self.tags.subclasses[2] if tag.code == code)
+
+
+helix_subclass = DefSubclass('AcDbHelix', {
+ 'helix_major_version': DXFAttr(90),
+ 'helix_maintainance_version': DXFAttr(91),
+ 'axis_base_point': DXFAttr(10, XTYPE_3D),
+ 'start_point': DXFAttr(11, XTYPE_3D),
+ 'axis_vector': DXFAttr(12, XTYPE_3D),
+ 'radius': DXFAttr(40),
+ 'turns': DXFAttr(41),
+ 'turn_height': DXFAttr(42),
+ 'handedness': DXFAttr(290), # 0 = left, 1 = right
+ 'constrain': DXFAttr(280), # 0 = Constrain turn height; 1 = Constrain turns; 2 = Constrain height
+})
+
+
+class Helix(Spline):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, spline_subclass, helix_subclass)
+
+mtext_subclass = DefSubclass('AcDbMText', {
+ 'insert': DXFAttr(10, XTYPE_3D),
+ 'height': DXFAttr(40),
+ 'reference_rectangle_width': DXFAttr(41),
+ 'horizontal_width': DXFAttr(42),
+ 'vertical_height': DXFAttr(43),
+ 'attachmentpoint': DXFAttr(71),
+ 'text': DXFAttr(1), # also group code 3, if more than 255 chars
+ 'style': DXFAttr(7), # text style
+ 'extrusion': DXFAttr(210, XTYPE_3D),
+ 'xdirection': DXFAttr(11, XTYPE_3D),
+ 'rotation': DXFAttr(50), # xdirection beats rotation
+ 'linespacing': DXFAttr(44), # valid from 0.25 to 4.00
+})
+
+
+class MText(DXFEntity):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, mtext_subclass)
+
+ def rawtext(self):
+ subclass = self.tags.subclasses[2]
+ lines = [tag.value for tag in subclass.find_all(3)]
+ lines.append(self.get_dxf_attrib('text'))
+ return ''.join(lines)
+
+block_subclass = (
+ DefSubclass('AcDbEntity', {'layer': DXFAttr(8)}),
+ DefSubclass('AcDbBlockBegin', {
+ 'name': DXFAttr(2),
+ 'name2': DXFAttr(3),
+ 'description': DXFAttr(4),
+ 'flags': DXFAttr(70),
+ 'basepoint': DXFAttr(10, XTYPE_2D_3D),
+ 'xrefpath': DXFAttr(1),
+ })
+)
+
+
+class Block(dxf12.Block):
+ DXFATTRIBS = DXFAttributes(none_subclass, *block_subclass)
+
+endblock_subclass = (
+ DefSubclass('AcDbEntity', {'layer': DXFAttr(8)}),
+ DefSubclass('AcDbBlockEnd', {}),
+)
+
+
+class EndBlk(dxf12.EndBlk):
+ DXFATTRIBS = DXFAttributes(none_subclass, *endblock_subclass)
+
+sun_subclass = DefSubclass('AcDbSun', {
+ 'version': DXFAttr(90),
+ 'status': DXFAttr(290),
+ 'sun_color': DXFAttr(63), # ??? DXF Color Index = (1 .. 255), 256 by layer
+ 'intensity': DXFAttr(40),
+ 'shadows': DXFAttr(291),
+ 'date': DXFAttr(91), # Julian day
+ 'time': DXFAttr(92), # Time (in seconds past midnight)
+ 'daylight_savings_time': DXFAttr(292),
+ 'shadow_type': DXFAttr(70), # 0 = Ray traced shadows; 1 = Shadow maps
+ 'shadow_map_size': DXFAttr(71), # 0 = Ray traced shadows; 1 = Shadow maps
+ 'shadow_softness': DXFAttr(280),
+})
+
+
+# SUN resides in the objects section and has no AcDbEntity subclass
+class Sun(DXFEntity):
+ DXFATTRIBS = DXFAttributes(none_subclass, sun_subclass)
+
+mesh_subclass = DefSubclass('AcDbSubDMesh', {
+ 'version': DXFAttr(71),
+ 'blend_crease': DXFAttr(72), # 0 = off, 1 = on
+ 'subdivision_levels': DXFAttr(91), # int >= 1
+})
+
+
+class Mesh(DXFEntity):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, mesh_subclass)
+
+light_subclass = DefSubclass('AcDbLight', {
+ 'version': DXFAttr(90),
+ 'name': DXFAttr(1),
+ 'light_type': DXFAttr(70), # distant = 1; point = 2; spot = 3
+ 'status': DXFAttr(290),
+ 'light_color': DXFAttr(63), # DXF Color Index = (1 .. 255), 256 by layer
+ 'true_color': DXFAttr(421), # 24-bit color 0x00RRGGBB
+ 'plot_glyph': DXFAttr(291),
+ 'intensity': DXFAttr(40),
+ 'position': DXFAttr(10, XTYPE_3D),
+ 'target': DXFAttr(11, XTYPE_3D),
+ 'attenuation_type': DXFAttr(72), # 0 = None; 1 = Inverse Linear; 2 = Inverse Square
+ 'use_attenuation_limits': DXFAttr(292), # bool
+ 'attenuation_start_limit': DXFAttr(41),
+ 'attenuation_end_limit': DXFAttr(42),
+ 'hotspot_angle': DXFAttr(50),
+ 'fall_off_angle': DXFAttr(51),
+ 'cast_shadows': DXFAttr(293),
+ 'shadow_type': DXFAttr(73), # 0 = Ray traced shadows; 1 = Shadow maps
+ 'shadow_map_size': DXFAttr(91),
+ 'shadow_softness': DXFAttr(280),
+})
+
+
+class Light(DXFEntity):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, light_subclass)
+
+
+modeler_geometry_subclass = DefSubclass('AcDbModelerGeometry', {
+ 'version': DXFAttr(70),
+})
+
+
+class Body(DXFEntity):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, modeler_geometry_subclass)
+
+ def get_acis_data(self):
+ # for AC1027 and later - ACIS data is stored in the ACDSDATA section in Standard ACIS Binary format
+ geometry = self.tags.subclasses[2] # AcDbModelerGeometry
+ return decode([tag.value for tag in geometry if tag.code in (1, 3)])
+
+solid3d_subclass = DefSubclass('AcDb3dSolid', {
+ 'handle_to_history_object': DXFAttr(350),
+})
+
+# Region == Body
+
+
+class Solid3d(Body):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, modeler_geometry_subclass, solid3d_subclass)
+
+
+surface_subclass = DefSubclass('AcDbSurface', {
+ 'u_isolines': DXFAttr(71),
+ 'v_isolines': DXFAttr(72),
+})
+
+
+class Surface(Body):
+ DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, modeler_geometry_subclass, surface_subclass)
diff --git a/io_import_dxf/dxfgrabber/dxfattr.py b/io_import_dxf/dxfgrabber/dxfattr.py
new file mode 100755
index 00000000..a98f038d
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/dxfattr.py
@@ -0,0 +1,47 @@
+# Purpose: define dxf attributes
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from collections import namedtuple
+from .const import XTYPE_NONE
+
+
+def DXFAttr(code, xtype=XTYPE_NONE):
+ # assert type(xtype) is int
+ return _DXFAttr(code, xtype)
+
+_DXFAttr = namedtuple('DXFAttr', 'code xtype')
+DXFAttr3 = namedtuple('DXFAttr3', 'code xtype subclass')
+DefSubclass = namedtuple('DefSubclass', 'name attribs')
+
+
+class DXFAttributes(object):
+ def __init__(self, *subclassdefs):
+ self._subclasses = []
+ self._attribs = {}
+ for subclass in subclassdefs:
+ self.add_subclass(subclass)
+
+ def add_subclass(self, subclass):
+ subclass_index = len(self._subclasses)
+ self._subclasses.append(subclass)
+ self._add_subclass_attribs(subclass, subclass_index)
+
+ def _add_subclass_attribs(self, subclass, subclass_index):
+ for name, dxfattrib in subclass.attribs.items():
+ self._attribs[name] = DXFAttr3(dxfattrib.code, dxfattrib.xtype, subclass_index)
+
+ def __getitem__(self, name):
+ return self._attribs[name]
+
+ def __contains__(self, name):
+ return name in self._attribs
+
+ def keys(self):
+ return iter(self._attribs.keys())
+
+ def subclasses(self):
+ return iter(self._subclasses)
diff --git a/io_import_dxf/dxfgrabber/dxfentity.py b/io_import_dxf/dxfgrabber/dxfentity.py
new file mode 100755
index 00000000..26cfc436
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/dxfentity.py
@@ -0,0 +1,84 @@
+# Purpose: generic tag wrapper
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+import os
+
+from .const import ENV_CYTHON, XTYPE_NONE, XTYPE_2D, XTYPE_3D, XTYPE_2D_3D
+
+cyDXFEntity = None
+OPTIMIZE = True
+if ENV_CYTHON in os.environ:
+ if os.environ[ENV_CYTHON].upper() in ('1', 'ON', 'TRUE'):
+ OPTIMIZE = True
+ else:
+ OPTIMIZE = False
+try:
+ if OPTIMIZE:
+ from.cydxfentity import cyDXFEntity
+except ImportError:
+ pass
+
+
+class pyDXFEntity(object):
+ DXFATTRIBS = {}
+
+ def __init__(self, tags):
+ self.tags = tags
+
+ def dxftype(self):
+ return self.tags.noclass[0].value
+
+ def get_dxf_attrib(self, key, default=ValueError):
+ # core function - every optimization is useful
+ try:
+ dxfattr = self.DXFATTRIBS[key]
+ except KeyError:
+ # attribute is not defined - returning the default value is useful
+ # to query newer DXF attributes on older DXF files.
+ # !! Problem: misspelled attributes with default values do not
+ # raise an Exception !!
+ if default is ValueError:
+ raise ValueError("DXFAttrib '%s' is not defined." % key)
+ else:
+ return default
+ try:
+ return self._get_dxf_attrib(dxfattr)
+ except ValueError: # attribute is defined but no value is present
+ if default is ValueError:
+ raise ValueError("DXFAttrib '%s': value is not present." % key)
+ else:
+ return default
+
+ def _get_dxf_attrib(self, dxfattr):
+ # no subclass is subclass index 0
+ subclass_tags = self.tags.subclasses[dxfattr.subclass]
+ xtype = dxfattr.xtype
+ if xtype != XTYPE_NONE and xtype != XTYPE_2D_3D:
+ return self._get_extented_type(subclass_tags, dxfattr.code, xtype)
+ else:
+ return subclass_tags.get_value(dxfattr.code)
+
+ def paperspace(self):
+ return self.get_dxf_attrib('paperspace', default=0) == 1
+
+ def post_read_correction(self):
+ pass
+
+ @staticmethod
+ def _get_extented_type(tags, code, xtype):
+ value = tags.get_value(code)
+ if len(value) == 2:
+ if xtype == XTYPE_3D:
+ return value[0], value[1], 0.
+ elif xtype == XTYPE_2D:
+ return value[0], value[1]
+ return value
+
+if cyDXFEntity is not None:
+ DXFEntity = cyDXFEntity
+else:
+ DXFEntity = pyDXFEntity \ No newline at end of file
diff --git a/io_import_dxf/dxfgrabber/entities.py b/io_import_dxf/dxfgrabber/entities.py
new file mode 100755
index 00000000..937b05bd
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/entities.py
@@ -0,0 +1,930 @@
+# encoding: utf-8
+# Purpose: entity classes
+# Created: 21.07.2012, parts taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from . import dxf12, dxf13
+from . import const
+from .juliandate import calendar_date
+from datetime import datetime
+from .color import TrueColor
+import math
+
+from .styles import default_text_style
+
+SPECIAL_CHARS = {
+ 'd': '°'
+}
+
+
+class SeqEnd(object):
+ def __init__(self, wrapper):
+ self.dxftype = wrapper.dxftype()
+
+
+class Entity(SeqEnd):
+ def __init__(self, wrapper):
+ super(Entity, self).__init__(wrapper)
+ self.paperspace = bool(wrapper.paperspace())
+
+
+class Shape(Entity):
+ def __init__(self, wrapper):
+ super(Shape, self).__init__(wrapper)
+ get_dxf = wrapper.get_dxf_attrib
+ self.layer = get_dxf('layer', '0')
+ self.linetype = get_dxf('linetype', None) # None=BYLAYER
+ self.thickness = get_dxf('thickness', 0.0)
+ self.extrusion = get_dxf('extrusion', (0., 0., 1.))
+ self.ltscale = get_dxf('ltscale', 1.0)
+ self.invisible = get_dxf('invisible', 0) # 0=visible
+ self.color = get_dxf('color', const.BYLAYER) # 256=BYLAYER, 0=BYBLOCK
+ self.true_color = get_dxf('true_color', None) # 0x00RRGGBB
+ if self.true_color is not None:
+ self.true_color = TrueColor(self.true_color)
+ self.transparency = get_dxf('transparency', None) # 0x020000TT
+ if self.transparency is not None:
+ # 0.0 = opaque & 1.0 if fully transparent
+ self.transparency = 1. - float(self.transparency & 0xFF) / 255.
+ self.shadow_mode = get_dxf('shadow_mode', None)
+ # 0 = Casts and receives shadows
+ # 1 = Casts shadows
+ # 2 = Receives shadows
+ # 3 = Ignores shadows
+
+ # if adding additional DXF attributes, do it also for PolyShape
+
+
+class PolyShape(object):
+ """ Base class for Polyface and Polymesh, both are special cases of POLYLINE.
+ """
+ def __init__(self, polyline, dxftype):
+ self.dxftype = dxftype
+ self.paperspace = polyline.paperspace
+ self.layer = polyline.layer
+ self.linetype = polyline.linetype
+ self.ltscale = polyline.ltscale
+ self.invisible = polyline.invisible
+ self.color = polyline.color
+ self.true_color = polyline.true_color
+ self.transparency = polyline.transparency
+ self.shadow_mode = polyline.shadow_mode
+
+
+class Line(Shape):
+ def __init__(self, wrapper):
+ super(Line, self).__init__(wrapper)
+ self.start = wrapper.get_dxf_attrib('start')
+ self.end = wrapper.get_dxf_attrib('end')
+
+
+class Point(Shape):
+ def __init__(self, wrapper):
+ super(Point, self).__init__(wrapper)
+ self.point = wrapper.get_dxf_attrib('point')
+
+
+class Circle(Shape):
+ def __init__(self, wrapper):
+ super(Circle, self).__init__(wrapper)
+ self.center = wrapper.get_dxf_attrib('center')
+ self.radius = wrapper.get_dxf_attrib('radius')
+
+
+class Arc(Shape):
+ def __init__(self, wrapper):
+ super(Arc, self).__init__(wrapper)
+ get_dxf = wrapper.get_dxf_attrib
+ self.center = get_dxf('center')
+ self.radius = get_dxf('radius')
+ self.startangle = get_dxf('startangle')
+ self.endangle = get_dxf('endangle')
+
+
+class Trace(Shape):
+ def __init__(self, wrapper):
+ super(Trace, self).__init__(wrapper)
+ get_dxf = wrapper.get_dxf_attrib
+ self.points = [
+ get_dxf(vname) for vname in const.VERTEXNAMES
+ ]
+
+Solid = Trace
+
+
+class Face(Trace):
+ def __init__(self, wrapper):
+ super(Face, self).__init__(wrapper)
+ self.invisible_edge = wrapper.get_dxf_attrib('invisible_edge', 0)
+
+ def is_edge_invisible(self, edge):
+ # edges 0 .. 3
+ return bool(self.invisible_edge & (1 << edge))
+
+
+class Text(Shape):
+ def __init__(self, wrapper):
+ super(Text, self).__init__(wrapper)
+ get_dxf = wrapper.get_dxf_attrib
+ self.insert = get_dxf('insert')
+ self.text = get_dxf('text')
+ self.height = get_dxf('height', 0)
+ self.width = get_dxf('width', 0)
+ self.oblique = get_dxf('oblique', None)
+ self.rotation = get_dxf('rotation', 0.)
+ self.style = get_dxf('style', "")
+ self.halign = get_dxf('halign', 0)
+ self.valign = get_dxf('valign', 0)
+ self.alignpoint = get_dxf('alignpoint', None)
+ if get_dxf('textgenerationflag', None) is not None:
+ self.is_backwards = bool(get_dxf('textgenerationflag', 0) & 2)
+ self.is_upside_down = bool(get_dxf('textgenerationflag', 0) & 4)
+ else:
+ self.is_backwards = None
+ self.is_upside_down = None
+ self.font = ""
+ self.bigfont = ""
+
+ def resolve_text_style(self, text_styles):
+ style = text_styles.get(self.style, None)
+ if style is None:
+ style = default_text_style
+ if self.height == 0:
+ self.height = style.height
+ if self.width == 0:
+ self.width = style.width
+ if self.oblique is None:
+ self.oblique = style.oblique
+ if self.is_backwards is None:
+ self.is_backwards = style.is_backwards
+ if self.is_upside_down is None:
+ self.is_upside_down = style.is_upside_down
+ if self.font is None:
+ self.font = style.font
+ if self.bigfont is None:
+ self.bigfont = style.bigfont
+
+ def plain_text(self):
+ chars = []
+ raw_chars = list(reversed(self.text)) # text splitted into chars, in reversed order for efficient pop()
+ while len(raw_chars):
+ char = raw_chars.pop()
+ if char == '%': # formatting codes and special characters
+ if len(raw_chars) and raw_chars[-1] == '%':
+ raw_chars.pop() # '%'
+ if len(raw_chars):
+ special_char = raw_chars.pop() # command char
+ chars.append(SPECIAL_CHARS.get(special_char, ""))
+ else: # char is just a single '%'
+ chars.append(char)
+ else: # char is what it is, a character
+ chars.append(char)
+ return "".join(chars)
+
+
+class Insert(Shape):
+ def __init__(self, wrapper):
+ super(Insert, self).__init__(wrapper)
+ get_dxf = wrapper.get_dxf_attrib
+ self.name = get_dxf('name')
+ self.insert = get_dxf('insert')
+ self.rotation = get_dxf('rotation', 0.)
+ self.scale = get_dxf('xscale', 1.), get_dxf('yscale', 1.), get_dxf('zscale', 1.)
+ self.row_count = get_dxf('rowcount', 1)
+ self.row_spacing = get_dxf('rowspacing', 0.)
+ self.col_count = get_dxf('colcount', 1)
+ self.col_spacing = get_dxf('colspacing', 0.)
+ self.attribsfollow = bool(get_dxf('attribsfollow', 0))
+ self.attribs = []
+
+ def find_attrib(self, attrib_tag):
+ for attrib in self.attribs:
+ if attrib.tag == attrib_tag:
+ return attrib
+ return None
+
+ def append_data(self, attribs):
+ self.attribs = attribs
+
+
+class Attrib(Text): # also ATTDEF
+ def __init__(self, wrapper):
+ super(Attrib, self).__init__(wrapper)
+ self.tag = wrapper.get_dxf_attrib('tag')
+
+_LINE_TYPES = frozenset(('spline2d', 'polyline2d', 'polyline3d'))
+
+
+class Polyline(Shape):
+ def __init__(self, wrapper):
+ super(Polyline, self).__init__(wrapper)
+ self.vertices = [] # set in append data
+ self.points = [] # set in append data
+ self.controlpoints = [] # set in append data
+ self.width = [] # set in append data
+ self.bulge = [] # set in append data
+ self.tangents = [] # set in append data
+ self.flags = wrapper.flags
+ self.mode = wrapper.get_mode()
+ get_dxf = wrapper.get_dxf_attrib
+ self.mcount = get_dxf('mcount', 0)
+ self.ncount = get_dxf('ncount', 0)
+ self.default_start_width = get_dxf('defaultstartwidth', 0.)
+ self.default_end_width = get_dxf('defaultendwidth', 0.)
+ self.is_mclosed = wrapper.is_mclosed()
+ self.is_nclosed = wrapper.is_nclosed()
+ self.elevation = get_dxf('elevation', (0., 0., 0.))
+ self.m_smooth_density = get_dxf('msmoothdensity', 0.)
+ self.n_smooth_density = get_dxf('nsmoothdensity', 0.)
+ self.smooth_type = get_dxf('smoothtype', 0)
+ self.spline_type = None
+ if self.mode == 'spline2d':
+ if self.smooth_type == const.POLYMESH_CUBIC_BSPLINE:
+ self.spline_type = 'cubic_bspline'
+ elif self.smooth_type == const.POLYMESH_QUADRIC_BSPLINE:
+ self.spline_type = 'quadratic_bspline'
+ elif self.smooth_type == const.POLYMESH_BEZIER_SURFACE:
+ self.spline_type = 'bezier_curve' # is this a valid spline type for DXF12?
+
+ def __len__(self):
+ return len(self.vertices)
+
+ def __getitem__(self, item):
+ return self.vertices[item]
+
+ def __iter__(self):
+ return iter(self.vertices)
+
+ @property
+ def is_closed(self):
+ return self.is_mclosed
+
+ @is_closed.setter
+ def is_closed(self, status):
+ self.is_mclosed = status
+
+ def append_data(self, vertices):
+ def default_width(start_width, end_width):
+ if start_width == 0.:
+ start_width = self.default_start_width
+ if end_width == 0.:
+ end_width = self.default_end_width
+ return start_width, end_width
+
+ self.vertices = vertices
+ if self.mode in _LINE_TYPES:
+ for vertex in self.vertices:
+ if vertex.flags & const.VTX_SPLINE_FRAME_CONTROL_POINT:
+ self.controlpoints.append(vertex.location)
+ else:
+ self.points.append(vertex.location)
+ self.width.append(default_width(vertex.start_width, vertex.end_width))
+ self.bulge.append(vertex.bulge)
+ self.tangents.append(vertex.tangent if vertex.flags & const.VTX_CURVE_FIT_TANGENT else None)
+
+ def cast(self):
+ if self.mode == 'polyface':
+ return Polyface(self)
+ elif self.mode == 'polymesh':
+ return Polymesh(self)
+ else:
+ return self
+
+
+class SubFace(object):
+ def __init__(self, face_record, vertices):
+ self._vertices = vertices
+ self.face_record = face_record
+
+ def __len__(self):
+ return len(self.face_record.vtx)
+
+ def __getitem__(self, item):
+ return self._vertices[self._vertex_index(item)]
+
+ def __iter__(self):
+ return (self._vertices[index].location for index in self.indices())
+
+ def _vertex_index(self, pos):
+ return abs(self.face_record.vtx[pos]) - 1
+
+ def indices(self):
+ return tuple(abs(i)-1 for i in self.face_record.vtx if i != 0)
+
+ def is_edge_visible(self, pos):
+ return self.face_record.vtx[pos] > 0
+
+
+class Polyface(PolyShape):
+ def __init__(self, polyline):
+ VERTEX_FLAGS = const.VTX_3D_POLYFACE_MESH_VERTEX + const.VTX_3D_POLYGON_MESH_VERTEX
+
+ def is_vertex(flags):
+ return flags & VERTEX_FLAGS == VERTEX_FLAGS
+
+ super(Polyface, self).__init__(polyline, 'POLYFACE')
+ vertices = []
+ face_records = []
+ for vertex in polyline.vertices:
+ (vertices if is_vertex(vertex.flags) else face_records).append(vertex)
+
+ self.vertices = vertices
+ self._face_records = face_records
+
+ def __getitem__(self, item):
+ return SubFace(self._face_records[item], self.vertices)
+
+ def __len__(self):
+ return len(self._face_records)
+
+ def __iter__(self):
+ return (SubFace(f, self.vertices) for f in self._face_records)
+
+
+class Polymesh(PolyShape):
+ def __init__(self, polyline):
+ super(Polymesh, self).__init__(polyline, 'POLYMESH')
+ self.mcount = polyline.mcount
+ self.ncount = polyline.ncount
+ self.is_mclosed = polyline.is_mclosed
+ self.is_nclosed = polyline.is_nclosed
+ self._vertices = polyline.vertices
+ self.m_smooth_density = polyline.m_smooth_density
+ self.n_smooth_density = polyline.n_smooth_density
+ self.smooth_type = polyline.smooth_type
+
+ def __iter__(self):
+ return iter(self._vertices)
+
+ def get_location(self, pos):
+ return self.get_vertex(pos).location
+
+ def get_vertex(self, pos):
+ mcount = self.mcount
+ ncount = self.ncount
+ m, n = pos
+ if 0 <= m < mcount and 0 <= n < ncount:
+ pos = m * ncount + n
+ return self._vertices[pos]
+ else:
+ raise IndexError(repr(pos))
+
+
+class Vertex(Shape):
+ def __init__(self, wrapper):
+ super(Vertex, self).__init__(wrapper)
+ get_dxf = wrapper.get_dxf_attrib
+ self.location = get_dxf('location')
+ self.flags = get_dxf('flags', 0)
+ self.start_width = get_dxf('startwidth', 0)
+ self.end_width = get_dxf('endwidth', 0)
+ self.bulge = get_dxf('bulge', 0)
+ self.tangent = get_dxf('tangent', None)
+ self.vtx = self._get_vtx(wrapper)
+
+ def _get_vtx(self, wrapper):
+ vtx = []
+ get_dxf = wrapper.get_dxf_attrib
+ for vname in const.VERTEXNAMES:
+ try:
+ vtx.append(get_dxf(vname))
+ except ValueError:
+ pass
+ return tuple(vtx)
+
+
+class LWPolyline(Shape):
+ def __init__(self, wrapper):
+ super(LWPolyline, self).__init__(wrapper)
+ self.points, self.width, self.bulge = wrapper.data()
+ self.const_width = wrapper.get_dxf_attrib('const_width', 0)
+ self.is_closed = wrapper.is_closed()
+ self.elevation = wrapper.get_dxf_attrib('elevation', (0., 0., 0.))
+
+ def __len__(self):
+ return len(self.points)
+
+ def __getitem__(self, item):
+ return self.points[item]
+
+ def __iter__(self):
+ return iter(self.points)
+
+
+class Ellipse(Shape):
+ def __init__(self, wrapper):
+ super(Ellipse, self).__init__(wrapper)
+ get_dxf = wrapper.get_dxf_attrib
+ self.center = get_dxf('center')
+ self.majoraxis = get_dxf('majoraxis')
+ self.ratio = get_dxf('ratio', 1.0) # circle
+ self.startparam = get_dxf('startparam', 0.)
+ self.endparam = get_dxf('endparam', 6.283185307179586) # 2*pi
+
+
+class Ray(Shape):
+ def __init__(self, wrapper):
+ super(Ray, self).__init__(wrapper)
+ self.start = wrapper.get_dxf_attrib('start')
+ self.unitvector = wrapper.get_dxf_attrib('unitvector')
+
+XLine = Ray
+
+
+class Spline(Shape):
+ def __init__(self, wrapper):
+ super(Spline, self).__init__(wrapper)
+ get_dxf = wrapper.get_dxf_attrib
+ self.normalvector = get_dxf('normalvector', None)
+ self.flags = get_dxf('flags', 0)
+ self.degree = get_dxf('degree', 3)
+ self.starttangent = get_dxf('starttangent', None)
+ self.endtangent = get_dxf('endtangent', None)
+ self.knots = tuple(wrapper.knots())
+ self.weights = tuple(wrapper.weights())
+ self.tol_knot = get_dxf('knot_tolernace', .0000001)
+ self.tol_controlpoint = get_dxf('controlpoint_tolerance', .0000001)
+ self.tol_fitpoint = get_dxf('fitpoint_tolerance', .0000000001)
+ self.controlpoints = tuple(wrapper.controlpoints())
+ self.fitpoints = tuple(wrapper.fitpoints())
+ if len(self.weights) == 0:
+ self.weights = tuple([1.0] * len(self.controlpoints))
+
+ @property
+ def is_closed(self):
+ return bool(self.flags & const.SPLINE_CLOSED)
+
+ @property
+ def is_periodic(self):
+ return bool(self.flags & const.SPLINE_PERIODIC)
+
+ @property
+ def is_rational(self):
+ return bool(self.flags & const.SPLINE_RATIONAL)
+
+ @property
+ def is_planar(self):
+ return bool(self.flags & const.SPLINE_PLANAR)
+
+ @property
+ def is_linear(self):
+ return bool(self.flags & const.SPLINE_LINEAR)
+
+
+class Helix(Spline):
+ def __init__(self, wrapper):
+ super(Helix, self).__init__(wrapper)
+ get_dxf = wrapper.get_dxf_attrib
+ self.helix_version = (get_dxf('helix_major_version', 1),
+ get_dxf('helix_maintainance_version', 1))
+ self.axis_base_point = get_dxf('axis_base_point', None)
+ self.start_point = get_dxf('start_point', None)
+ self.axis_vector = get_dxf('axis_vector', None)
+ self.radius = get_dxf('radius', 0)
+ self.turns = get_dxf('turns', 0)
+ self.turn_height = get_dxf('turn_height', 0)
+ self.handedness = get_dxf('handedness', 0) # 0 = left, 1 = right
+ self.constrain = get_dxf('constrain', 0)
+ # 0 = Constrain turn height;
+ # 1 = Constrain turns;
+ # 2 = Constrain height
+
+
+def deg2vec(deg):
+ rad = float(deg) * math.pi / 180.0
+ return math.cos(rad), math.sin(rad), 0.
+
+
+def normalized(vector):
+ x, y, z = vector
+ m = (x**2 + y**2 + z**2)**0.5
+ return x/m, y/m, z/m
+
+##################################################
+# MTEXT inline codes
+# \L Start underline
+# \l Stop underline
+# \O Start overstrike
+# \o Stop overstrike
+# \K Start strike-through
+# \k Stop strike-through
+# \P New paragraph (new line)
+# \pxi Control codes for bullets, numbered paragraphs and columns
+# \X Paragraph wrap on the dimension line (only in dimensions)
+# \Q Slanting (obliquing) text by angle - e.g. \Q30;
+# \H Text height - e.g. \H3x;
+# \W Text width - e.g. \W0.8x;
+# \F Font selection
+#
+# e.g. \Fgdt;o - GDT-tolerance
+# e.g. \Fkroeger|b0|i0|c238|p10 - font Kroeger, non-bold, non-italic, codepage 238, pitch 10
+#
+# \S Stacking, fractions
+#
+# e.g. \SA^B:
+# A
+# B
+# e.g. \SX/Y:
+# X
+# -
+# Y
+# e.g. \S1#4:
+# 1/4
+#
+# \A Alignment
+#
+# \A0; = bottom
+# \A1; = center
+# \A2; = top
+#
+# \C Color change
+#
+# \C1; = red
+# \C2; = yellow
+# \C3; = green
+# \C4; = cyan
+# \C5; = blue
+# \C6; = magenta
+# \C7; = white
+#
+# \T Tracking, char.spacing - e.g. \T2;
+# \~ Non-wrapping space, hard space
+# {} Braces - define the text area influenced by the code
+# \ Escape character - e.g. \\ = "\", \{ = "{"
+#
+# Codes and braces can be nested up to 8 levels deep
+
+ESCAPED_CHARS = "\\{}"
+GROUP_CHARS = "{}"
+ONE_CHAR_COMMANDS = "PLlOoKkX"
+
+
+class MText(Shape):
+ def __init__(self, wrapper):
+
+ super(MText, self).__init__(wrapper)
+ self.insert = wrapper.get_dxf_attrib('insert')
+ self.rawtext = wrapper.rawtext()
+ get_dxf = wrapper.get_dxf_attrib
+ self.height = get_dxf('height', 0)
+ self.rect_width = get_dxf('reference_rectangle_width', None)
+ self.horizontal_width = get_dxf('horizontal_width', None)
+ self.vertical_height = get_dxf('vertical_height', None)
+ self.linespacing = get_dxf('linespacing', 1.0)
+ self.attachmentpoint = get_dxf('attachmentpoint', 1)
+ self.style = get_dxf('style', 'STANDARD')
+ self.extrusion = get_dxf('extrusion', (0., 0., 1.))
+ try:
+ xdir = wrapper.get_dxf_attrib('xdirection')
+ except ValueError:
+ xdir = deg2vec(get_dxf('rotation', 0.0))
+ self.xdirection = normalized(xdir)
+ self.font = None
+ self.bigfont = None
+
+ def lines(self):
+ return self.rawtext.split('\P')
+
+ def plain_text(self, split=False):
+ chars = []
+ raw_chars = list(reversed(self.rawtext)) # text splitted into chars, in reversed order for efficient pop()
+ while len(raw_chars):
+ char = raw_chars.pop()
+ if char == '\\': # is a formatting command
+ try:
+ char = raw_chars.pop()
+ except IndexError:
+ break # premature end of text - just ignore
+
+ if char in ESCAPED_CHARS: # \ { }
+ chars.append(char)
+ elif char in ONE_CHAR_COMMANDS:
+ if char == 'P': # new line
+ chars.append('\n')
+ # discard other commands
+ else: # more character commands are terminated by ';'
+ stacking = char == 'S' # stacking command surrounds user data
+ try:
+ while char != ';': # end of format marker
+ char = raw_chars.pop()
+ if stacking and char != ';':
+ chars.append(char) # append user data of stacking command
+ except IndexError:
+ break # premature end of text - just ignore
+ elif char in GROUP_CHARS: # { }
+ pass # discard group markers
+ elif char == '%': # special characters
+ if len(raw_chars) and raw_chars[-1] == '%':
+ raw_chars.pop() # discard next '%'
+ if len(raw_chars):
+ special_char = raw_chars.pop()
+ # replace or discard formatting code
+ chars.append(SPECIAL_CHARS.get(special_char, ""))
+ else: # char is just a single '%'
+ chars.append(char)
+ else: # char is what it is, a character
+ chars.append(char)
+
+ plain_text = "".join(chars)
+ return plain_text.split('\n') if split else plain_text
+
+ def resolve_text_style(self, text_styles):
+ style = text_styles.get(self.style, None)
+ if style is None:
+ style = default_text_style
+ if self.height == 0:
+ self.height = style.height
+ if self.font is None:
+ self.font = style.font
+ if self.bigfont is None:
+ self.bigfont = style.font
+
+
+class Block(Shape):
+ def __init__(self, wrapper):
+ super(Block, self).__init__(wrapper)
+ self.basepoint = wrapper.get_dxf_attrib('basepoint')
+ self.name = wrapper.get_dxf_attrib('name')
+ self.flags = wrapper.get_dxf_attrib('flags', 0)
+ self.xrefpath = wrapper.get_dxf_attrib('xrefpath', "")
+ self._entities = list()
+
+ @property
+ def is_xref(self):
+ return bool(self.flags & const.BLK_XREF)
+
+ @property
+ def is_xref_overlay(self):
+ return bool(self.flags & const.BLK_XREF_OVERLAY)
+
+ @property
+ def is_anonymous(self):
+ return bool(self.flags & const.BLK_ANONYMOUS)
+
+ def set_entities(self, entities):
+ self._entities = entities
+
+ def __iter__(self):
+ return iter(self._entities)
+
+ def __getitem__(self, item):
+ return self._entities[item]
+
+ def __len__(self):
+ return len(self._entities)
+
+
+class BlockEnd(SeqEnd):
+ pass
+
+
+def unpack_seconds(seconds):
+ seconds = int(seconds / 1000) # remove 1/1000 part
+ hours = int(seconds / 3600)
+ seconds = int(seconds % 3600)
+ minutes = int(seconds / 60)
+ seconds = int(seconds % 60)
+ return hours, minutes, seconds
+
+
+class Sun(Entity):
+ def __init__(self, wrapper):
+ super(Sun, self).__init__(wrapper)
+ get_dxf = wrapper.get_dxf_attrib
+ self.version = get_dxf('version', 1)
+ self.status = bool(get_dxf('status', 0)) # on/off ?
+ self.sun_color = get_dxf('sun_color', None) # None is unset
+ self.intensity = get_dxf('intensity', 0)
+ self.shadows = bool(get_dxf('shadows', 0))
+ julian_date = get_dxf('date', 0.)
+ if julian_date > 0.:
+ date = calendar_date(julian_date)
+ else:
+ date = datetime.now()
+ hours, minutes, seconds = unpack_seconds(get_dxf('time', 0))
+ self.date = datetime(date.year, date.month, date.day, hours, minutes, seconds)
+ self.daylight_savings_time = bool(get_dxf('daylight_savings_time', 0))
+ self.shadow_type = get_dxf('shadows_type', 0)
+ self.shadow_map_size = get_dxf('shadow_map_size', 0)
+ self.shadow_softness = get_dxf('shadow_softness', 0)
+
+
+class Mesh(Shape):
+ def __init__(self, wrapper):
+ super(Mesh, self).__init__(wrapper)
+ get_dxf = wrapper.get_dxf_attrib
+ self.version = get_dxf('version', 2)
+ self.blend_crease = bool(get_dxf('blend_crease', 0))
+ self.subdivision_levels = get_dxf('subdivision_levels', 1)
+ # rest are mostly positional tags
+ self.vertices = []
+ self.faces = []
+ self.edges = []
+ self.edge_crease_list = []
+
+ subdmesh_tags = wrapper.tags.get_subclass('AcDbSubDMesh')
+ # for all blocks I ignore the count values, perhaps they are wrong,
+ # but I use the count tags as indicator for the begin of the list
+ try:
+ pos = subdmesh_tags.tag_index(92)
+ except ValueError: # no vertices???
+ return
+ else:
+ self.vertices = Mesh.get_vertices(subdmesh_tags, pos+1)
+ try:
+ pos = subdmesh_tags.tag_index(93)
+ except ValueError: # no faces???
+ pass
+ else:
+ self.faces = Mesh.get_faces(subdmesh_tags, pos+1)
+ try:
+ pos = subdmesh_tags.tag_index(94)
+ except ValueError: # no edges
+ pass
+ else:
+ self.edges = Mesh.get_edges(subdmesh_tags, pos+1)
+ try:
+ pos = subdmesh_tags.tag_index(95)
+ except ValueError: # no edges crease values
+ pass
+ else:
+ self.edge_crease_list = Mesh.get_edge_crease_list(subdmesh_tags, pos+1)
+
+ def get_face(self, index):
+ return tuple(self.vertices[vertex_index] for vertex_index in self.faces[index])
+
+ def get_edge(self, index):
+ return tuple(self.vertices[vertex_index] for vertex_index in self.edges[index])
+
+ @staticmethod
+ def get_vertices(tags, pos):
+ vertices = []
+ itags = iter(tags[pos:])
+ while True:
+ try:
+ tag = next(itags)
+ except StopIteration: # premature end of tags, return what you got
+ break
+ if tag.code == 10:
+ vertices.append(tag.value)
+ else:
+ break
+ return vertices
+
+ @staticmethod
+ def get_faces(tags, pos):
+ faces = []
+ face = []
+ itags = iter(tags[pos:])
+ try:
+ while True:
+ tag = next(itags)
+ # loop until first tag.code != 90
+ if tag.code != 90:
+ break
+ count = tag.value # count of vertex indices
+ while count > 0:
+ tag = next(itags)
+ face.append(tag.value)
+ count -= 1
+ faces.append(tuple(face))
+ del face[:]
+ except StopIteration: # premature end of tags, return what you got
+ pass
+ return faces
+
+ @staticmethod
+ def get_edges(tags, pos):
+ edges = []
+ start_index = None
+ for index in Mesh.get_raw_list(tags, pos, code=90):
+ if start_index is None:
+ start_index = index
+ else:
+ edges.append((start_index, index))
+ start_index = None
+ return edges
+
+ @staticmethod
+ def get_edge_crease_list(tags, pos):
+ return Mesh.get_raw_list(tags, pos, code=140)
+
+ @staticmethod
+ def get_raw_list(tags, pos, code):
+ raw_list = []
+ itags = iter(tags[pos:])
+ while True:
+ try:
+ tag = next(itags)
+ except StopIteration:
+ break
+ if tag.code == code:
+ raw_list.append(tag.value)
+ else:
+ break
+ return raw_list
+
+
+class Light(Shape):
+ def __init__(self, wrapper):
+ super(Light, self).__init__(wrapper)
+ get_dxf = wrapper.get_dxf_attrib
+ self.version = get_dxf('version', 1)
+ self.name = get_dxf('name', "")
+ self.light_type = get_dxf('light_type', 1) # distant = 1; point = 2; spot = 3
+ self.status = bool(get_dxf('status', 0)) # on/off ?
+ self.light_color = get_dxf('light_color', None) # 0 is unset
+ self.true_color = get_dxf('true_color', None) # None is unset
+ self.plot_glyph = bool(get_dxf('plot_glyph', 0))
+ self.intensity = get_dxf('intensity', 0)
+ self.position = get_dxf('position', (0, 0, 1))
+ self.target = get_dxf('target', (0, 0, 0))
+ self.attenuation_type = get_dxf('attenuation_type', 0) # 0 = None; 1 = Inverse Linear; 2 = Inverse Square
+ self.use_attenuation_limits = bool(get_dxf('use_attenuation_limits', 0))
+ self.attenuation_start_limit = get_dxf('attenuation_start_limit', 0)
+ self.attenuation_end_limit = get_dxf('attenuation_end_limit', 0)
+ self.hotspot_angle = get_dxf('hotspot_angle', 0)
+ self.fall_off_angle = get_dxf('fall_off_angle', 0)
+ self.cast_shadows = bool(get_dxf('cast_shadows', 0))
+ self.shadow_type = get_dxf('shadow_type', 0) # 0 = Ray traced shadows; 1 = Shadow maps
+ self.shadow_map_size = get_dxf('shadow_map_size', 0)
+ self.shadow_softness = get_dxf('shadow_softness', 0)
+
+
+class Body(Shape):
+ def __init__(self, wrapper):
+ super(Body, self).__init__(wrapper)
+ # need handle to get SAB data in DXF version AC1027 and later
+ self.handle = wrapper.get_dxf_attrib('handle', None)
+ self.version = wrapper.get_dxf_attrib('version', 1)
+ self.acis = wrapper.get_acis_data()
+
+ def set_sab_data(self, sab_data):
+ self.acis = sab_data
+
+ @property
+ def is_sat(self):
+ return isinstance(self.acis, list) # but could be an empty list
+
+ @property
+ def is_sab(self):
+ return not self.is_sat # has binary encoded ACIS data
+
+Solid3d = Body
+# perhaps reading creation history is needed
+
+
+class Surface(Body):
+ def __init__(self, wrapper):
+ super(Surface, self).__init__(wrapper)
+ self.u_isolines = wrapper.get_dxf_attrib('u_isolines', 0)
+ self.v_isolines = wrapper.get_dxf_attrib('v_isolines', 0)
+
+
+EntityTable = {
+ 'LINE': (Line, dxf12.Line, dxf13.Line),
+ 'POINT': (Point, dxf12.Point, dxf13.Point),
+ 'CIRCLE': (Circle, dxf12.Circle, dxf13.Circle),
+ 'ARC': (Arc, dxf12.Arc, dxf13.Arc),
+ 'TRACE': (Trace, dxf12.Trace, dxf13.Trace),
+ 'SOLID': (Solid, dxf12.Solid, dxf13.Solid),
+ '3DFACE': (Face, dxf12.Face, dxf13.Face),
+ 'TEXT': (Text, dxf12.Text, dxf13.Text),
+ 'INSERT': (Insert, dxf12.Insert, dxf13.Insert),
+ 'SEQEND': (SeqEnd, dxf12.SeqEnd, dxf13.SeqEnd),
+ 'ATTRIB': (Attrib, dxf12.Attrib, dxf13.Attrib),
+ 'ATTDEF': (Attrib, dxf12.Attrib, dxf13.Attdef),
+ 'POLYLINE': (Polyline, dxf12.Polyline, dxf13.Polyline),
+ 'VERTEX': (Vertex, dxf12.Vertex, dxf13.Vertex),
+ 'BLOCK': (Block, dxf12.Block, dxf13.Block),
+ 'ENDBLK': (BlockEnd, dxf12.EndBlk, dxf13.EndBlk),
+ 'LWPOLYLINE': (LWPolyline, None, dxf13.LWPolyline),
+ 'ELLIPSE': (Ellipse, None, dxf13.Ellipse),
+ 'RAY': (Ray, None, dxf13.Ray),
+ 'XLINE': (XLine, None, dxf13.XLine),
+ 'SPLINE': (Spline, None, dxf13.Spline),
+ 'HELIX': (Helix, None, dxf13.Helix),
+ 'MTEXT': (MText, None, dxf13.MText),
+ 'SUN': (Sun, None, dxf13.Sun),
+ 'MESH': (Mesh, None, dxf13.Mesh),
+ 'LIGHT': (Light, None, dxf13.Light),
+ 'BODY': (Body, None, dxf13.Body),
+ 'REGION': (Body, None, dxf13.Body),
+ '3DSOLID': (Solid3d, None, dxf13.Solid3d),
+ 'SURFACE': (Surface, None, dxf13.Surface),
+ 'PLANESURFACE': (Surface, None, dxf13.Surface),
+}
+
+
+def entity_factory(tags, dxfversion):
+ dxftype = tags.get_type()
+ cls, dxf12wrapper, dxf13wrapper = EntityTable[dxftype]
+ wrapper = dxf12wrapper(tags) if dxfversion == "AC1009" else dxf13wrapper(tags)
+ wrapper.post_read_correction()
+ shape = cls(wrapper)
+ return shape
+
+
diff --git a/io_import_dxf/dxfgrabber/entitysection.py b/io_import_dxf/dxfgrabber/entitysection.py
new file mode 100755
index 00000000..e87bbb46
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/entitysection.py
@@ -0,0 +1,94 @@
+# Purpose: handle entity section
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from itertools import islice
+
+from .tags import TagGroups, DXFStructureError
+from .tags import ClassifiedTags
+from .entities import entity_factory
+
+
+class EntitySection(object):
+ name = 'entities'
+
+ def __init__(self):
+ self._entities = list()
+
+ @classmethod
+ def from_tags(cls, tags, drawing):
+ entity_section = cls()
+ entity_section._build(tags, drawing.dxfversion)
+ return entity_section
+
+ def get_entities(self):
+ return self._entities
+
+ # start of public interface
+
+ def __len__(self):
+ return len(self._entities)
+
+ def __iter__(self):
+ return iter(self._entities)
+
+ def __getitem__(self, index):
+ return self._entities[index]
+
+ # end of public interface
+
+ def _build(self, tags, dxfversion):
+ if len(tags) == 3: # empty entities section
+ return
+ groups = TagGroups(islice(tags, 2, len(tags)-1))
+ self._entities = build_entities(groups, dxfversion)
+
+
+class ObjectsSection(EntitySection):
+ name = 'objects'
+
+
+def build_entities(tag_groups, dxfversion):
+ def build_entity(group):
+ try:
+ entity = entity_factory(ClassifiedTags(group), dxfversion)
+ except KeyError:
+ entity = None # ignore unsupported entities
+ return entity
+
+ entities = list()
+ collector = None
+ for group in tag_groups:
+ entity = build_entity(group)
+ if entity is not None:
+ if collector:
+ if entity.dxftype == 'SEQEND':
+ collector.stop()
+ entities.append(collector.entity)
+ collector = None
+ else:
+ collector.append(entity)
+ elif entity.dxftype == 'POLYLINE':
+ collector = _Collector(entity)
+ elif entity.dxftype == 'INSERT' and entity.attribsfollow:
+ collector = _Collector(entity)
+ else:
+ entities.append(entity)
+ return entities
+
+
+class _Collector:
+ def __init__(self, entity):
+ self.entity = entity
+ self._data = list()
+
+ def append(self, entity):
+ self._data.append(entity)
+
+ def stop(self):
+ self.entity.append_data(self._data)
+ if hasattr(self.entity, 'cast'):
+ self.entity = self.entity.cast()
diff --git a/io_import_dxf/dxfgrabber/headersection.py b/io_import_dxf/dxfgrabber/headersection.py
new file mode 100755
index 00000000..a12cee95
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/headersection.py
@@ -0,0 +1,33 @@
+# Purpose: handle header section
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from .tags import TagGroups, DXFTag
+
+class HeaderSection(dict):
+ name = "header"
+
+ def __init__(self):
+ super(HeaderSection, self).__init__()
+ self._create_default_vars()
+
+ @staticmethod
+ def from_tags(tags):
+ header = HeaderSection()
+ if tags[1] == DXFTag(2, 'HEADER'): # DXF12 without a HEADER section is valid!
+ header._build(tags)
+ return header
+
+ def _create_default_vars(self):
+ self['$ACADVER'] = 'AC1009'
+ self['$DWGCODEPAGE'] = 'ANSI_1252'
+
+ def _build(self, tags):
+ if len(tags) == 3: # empty header section!
+ return
+ groups = TagGroups(tags[2:-1], split_code=9)
+ for group in groups:
+ self[group[0].value] = group[1].value
diff --git a/io_import_dxf/dxfgrabber/juliandate.py b/io_import_dxf/dxfgrabber/juliandate.py
new file mode 100755
index 00000000..68c5c8e1
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/juliandate.py
@@ -0,0 +1,73 @@
+# Purpose: julian date
+# Created: 21.03.2011
+# Copyright (C) 2011, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from math import floor
+from datetime import datetime
+
+
+def frac(number):
+ return number - floor(number)
+
+
+class JulianDate:
+ def __init__(self, date):
+ self.date = date
+ self.result = self.julian_date() + self.fractional_day()
+
+ def fractional_day(self):
+ seconds = self.date.hour * 3600. + self.date.minute * 60. + self.date.second
+ return seconds / 86400.
+
+ def julian_date(self):
+ y = self.date.year + (float(self.date.month) - 2.85) / 12.
+ A = floor(367. * y) - 1.75 * floor(y) + self.date.day
+ B = floor(A) - 0.75 * floor(y / 100.)
+ return floor(B) + 1721115.
+
+
+class CalendarDate:
+ def __init__(self, juliandate):
+ self.jdate = juliandate
+ year, month, day = self.get_date()
+ hour, minute, second = frac2time(self.jdate)
+ self.result = datetime(year, month, day, hour, minute, second)
+
+ def get_date(self):
+ Z = floor(self.jdate)
+
+ if Z < 2299161:
+ A = Z # julian calender
+ else:
+ g = floor((Z - 1867216.25) / 36524.25) # gregorian calendar
+ A = Z + 1. + g - floor(g / 4.)
+
+ B = A + 1524.
+ C = floor((B - 122.1) / 365.25)
+ D = floor(365.25 * C)
+ E = floor((B - D) / 30.6001)
+
+ day = B - D - floor(30.6001 * E)
+ month = E - 1 if E < 14 else E - 13
+ year = C - 4716 if month > 2 else C - 4715
+ return int(year), int(month), int(day)
+
+
+def frac2time(jdate):
+ seconds = int(frac(jdate) * 86400.)
+ hour = int(seconds / 3600)
+ seconds = seconds % 3600
+ minute = int(seconds / 60)
+ second = seconds % 60
+ return hour, minute, second
+
+
+def julian_date(date):
+ return JulianDate(date).result
+
+
+def calendar_date(juliandate):
+ return CalendarDate(juliandate).result \ No newline at end of file
diff --git a/io_import_dxf/dxfgrabber/layers.py b/io_import_dxf/dxfgrabber/layers.py
new file mode 100755
index 00000000..de6ae39f
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/layers.py
@@ -0,0 +1,120 @@
+# Purpose: handle layers
+# Created: 21.07.12
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+
+__author__ = "mozman <mozman@gmx.at>"
+
+from .tags import TagGroups
+from .tags import ClassifiedTags
+from .dxfentity import DXFEntity
+
+from .dxfattr import DXFAttr, DXFAttributes, DefSubclass
+
+
+class Layer(object):
+ def __init__(self, wrapper):
+ self.name = wrapper.get_dxf_attrib('name')
+ self.color = wrapper.get_color()
+ self.linetype = wrapper.get_dxf_attrib('linetype')
+ self.locked = wrapper.is_locked()
+ self.frozen = wrapper.is_frozen()
+ self.on = wrapper.is_on()
+
+
+class Table(object):
+
+ def __init__(self):
+ self._table_entries = dict()
+
+ # start public interface
+
+ def get(self, name, default=KeyError):
+ try:
+ return self._table_entries[name]
+ except KeyError:
+ if default is KeyError:
+ raise
+ else:
+ return default
+
+ def __getitem__(self, item):
+ return self.get(item)
+
+ def __contains__(self, name):
+ return name in self._table_entries
+
+ def __iter__(self):
+ return iter(self._table_entries.values())
+
+ def __len__(self):
+ return len(self._table_entries)
+
+ def names(self):
+ return sorted(self._table_entries.keys())
+
+ # end public interface
+
+ def _classified_tags(self, tags):
+ groups = TagGroups(tags)
+ assert groups.get_name(0) == 'TABLE'
+ assert groups.get_name(-1) == 'ENDTAB'
+ for entrytags in groups[1:-1]:
+ yield ClassifiedTags(entrytags)
+
+
+class LayerTable(Table):
+ name = 'layers'
+
+ @staticmethod
+ def from_tags(tags, drawing):
+ dxfversion = drawing.dxfversion
+ layers = LayerTable()
+ for entrytags in layers._classified_tags(tags):
+ dxflayer = layers.wrap(entrytags, dxfversion)
+ layers._table_entries[dxflayer.get_dxf_attrib('name')] = Layer(dxflayer)
+ return layers
+
+ @staticmethod
+ def wrap(tags, dxfversion):
+ return DXF12Layer(tags) if dxfversion == "AC1009" else DXF13Layer(tags)
+
+
+class DXF12Layer(DXFEntity):
+ DXFATTRIBS = DXFAttributes(DefSubclass(None, {
+ 'handle': DXFAttr(5),
+ 'name': DXFAttr(2),
+ 'flags': DXFAttr(70),
+ 'color': DXFAttr(62), # dxf color index, if < 0 layer is off
+ 'linetype': DXFAttr(6),
+ }))
+ LOCK = 0b00000100
+ FROZEN = 0b00000001
+
+ def is_frozen(self):
+ return self.get_dxf_attrib('flags') & DXF12Layer.FROZEN > 0
+
+ def is_locked(self):
+ return self.get_dxf_attrib('flags') & DXF12Layer.LOCK > 0
+
+ def is_off(self):
+ return self.get_dxf_attrib('color') < 0
+
+ def is_on(self):
+ return not self.is_off()
+
+ def get_color(self):
+ return abs(self.get_dxf_attrib('color'))
+
+none_subclass = DefSubclass(None, {'handle': DXFAttr(5)})
+symbol_subclass = DefSubclass('AcDbSymbolTableRecord', {})
+layer_subclass = DefSubclass('AcDbLayerTableRecord', {
+ 'name': DXFAttr(2), # layer name
+ 'flags': DXFAttr(70),
+ 'color': DXFAttr(62), # dxf color index
+ 'linetype': DXFAttr(6), # linetype name
+})
+
+
+class DXF13Layer(DXF12Layer):
+ DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, layer_subclass)
diff --git a/io_import_dxf/dxfgrabber/linetypes.py b/io_import_dxf/dxfgrabber/linetypes.py
new file mode 100755
index 00000000..3e2c877e
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/linetypes.py
@@ -0,0 +1,74 @@
+# Purpose: handle linetypes table
+# Created: 06.01.2014
+# Copyright (C) 2014, Manfred Moitzi
+# License: MIT License
+
+__author__ = "mozman <mozman@gmx.at>"
+
+from .dxfentity import DXFEntity
+from .layers import Table
+from .dxfattr import DXFAttr, DXFAttributes, DefSubclass
+
+
+class Linetype(object):
+ def __init__(self, wrapper):
+ self.name = wrapper.get_dxf_attrib('name')
+ self.description = wrapper.get_dxf_attrib('description')
+ self.length = wrapper.get_dxf_attrib('length') # overall length of the pattern
+ self.pattern = wrapper.get_pattern() # list of floats: value>0: line, value<0: gap, value=0: dot
+
+
+class LinetypeTable(Table):
+ name = 'linetypes'
+
+ @staticmethod
+ def from_tags(tags, drawing):
+ dxfversion = drawing.dxfversion
+ styles = LinetypeTable()
+ for entrytags in styles._classified_tags(tags):
+ dxfstyle = styles.wrap(entrytags, dxfversion)
+ styles._table_entries[dxfstyle.get_dxf_attrib('name')] = Linetype(dxfstyle)
+ return styles
+
+ @staticmethod
+ def wrap(tags, dxfversion):
+ return DXF12Linetype(tags) if dxfversion == "AC1009" else DXF13Linetype(tags)
+
+
+class DXF12Linetype(DXFEntity):
+ DXFATTRIBS = DXFAttributes(DefSubclass(None, {
+ 'handle': DXFAttr(5),
+ 'name': DXFAttr(2),
+ 'description': DXFAttr(3),
+ 'length': DXFAttr(40),
+ 'items': DXFAttr(73),
+ }))
+
+ def get_pattern(self):
+ items = self.get_dxf_attrib('items')
+ if items == 0:
+ return []
+ else:
+ tags = self.tags.noclass
+ return [pattern_tag.value for pattern_tag in tags.find_all(49)]
+
+none_subclass = DefSubclass(None, {'handle': DXFAttr(5)})
+symbol_subclass = DefSubclass('AcDbSymbolTableRecord', {})
+linetype_subclass = DefSubclass('AcDbLinetypeTableRecord', {
+ 'name': DXFAttr(2),
+ 'description': DXFAttr(3),
+ 'length': DXFAttr(40),
+ 'items': DXFAttr(73),
+})
+
+
+class DXF13Linetype(DXF12Linetype):
+ DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, linetype_subclass)
+
+ def get_pattern(self):
+ items = self.get_dxf_attrib('items')
+ if items == 0:
+ return []
+ else:
+ tags = self.tags.get_subclass('AcDbLinetypeTableRecord')
+ return [pattern_tag.value for pattern_tag in tags.find_all(49)]
diff --git a/io_import_dxf/dxfgrabber/pytags.py b/io_import_dxf/dxfgrabber/pytags.py
new file mode 100755
index 00000000..d8d0479b
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/pytags.py
@@ -0,0 +1,385 @@
+# Purpose: tag reader
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from io import StringIO
+from collections import namedtuple
+from itertools import chain, islice
+from . import tostr
+
+
+DXFTag = namedtuple('DXFTag', 'code value')
+NONE_TAG = DXFTag(999999, 'NONE')
+APP_DATA_MARKER = 102
+SUBCLASS_MARKER = 100
+XDATA_MARKER = 1001
+
+
+class DXFStructureError(Exception):
+ pass
+
+
+def point_tuple(value):
+ return tuple(float(f) for f in value)
+
+
+POINT_CODES = frozenset(chain(range(10, 20), (210, ), range(110, 113), range(1010, 1020)))
+
+
+def is_point_tag(tag):
+ return tag[0] in POINT_CODES
+
+
+class TagIterator(object):
+ def __init__(self, textfile, assure_3d_coords=False):
+ self.textfile = textfile
+ self.readline = textfile.readline
+ self.undo = False
+ self.last_tag = NONE_TAG
+ self.undo_coord = None
+ self.eof = False
+ self.assure_3d_coords = assure_3d_coords
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ def undo_tag():
+ self.undo = False
+ tag = self.last_tag
+ return tag
+
+ def read_next_tag():
+ try:
+ code = int(self.readline())
+ value = self.readline().rstrip('\n')
+ except UnicodeDecodeError:
+ raise # because UnicodeDecodeError() is a subclass of ValueError()
+ except (EOFError, ValueError):
+ raise StopIteration()
+ return code, value
+
+ def read_point(code_x, value_x):
+ try:
+ code_y, value_y = read_next_tag() # 2. coordinate is always necessary
+ except StopIteration:
+ code_y = 0 # -> DXF structure error in following if-statement
+
+ if code_y != code_x + 10:
+ raise DXFStructureError("invalid 2D/3D point found")
+
+ value_y = float(value_y)
+ try:
+ code_z, value_z = read_next_tag()
+ except StopIteration: # 2D point at end of file
+ self.eof = True # store reaching end of file
+ if self.assure_3d_coords:
+ value = (value_x, value_y, 0.)
+ else:
+ value = (value_x, value_y)
+ else:
+ if code_z != code_x + 20: # not a Z coordinate -> 2D point
+ self.undo_coord = (code_z, value_z)
+ if self.assure_3d_coords:
+ value = (value_x, value_y, 0.)
+ else:
+ value = (value_x, value_y)
+ else: # is a 3D point
+ value = (value_x, value_y, float(value_z))
+ return value
+
+ def next_tag():
+ code = 999
+ while code == 999: # skip comments
+ if self.undo_coord is not None:
+ code, value = self.undo_coord
+ self.undo_coord = None
+ else:
+ code, value = read_next_tag()
+
+ if code in POINT_CODES: # 2D or 3D point
+ value = read_point(code, float(value)) # returns a tuple of floats, no casting needed
+ else:
+ value = cast_tag_value(code, value)
+ self.last_tag = DXFTag(code, value)
+ return self.last_tag
+
+ if self.eof: # stored end of file
+ raise StopIteration()
+
+ if self.undo:
+ return undo_tag()
+ else:
+ return next_tag()
+ # for Python 2.7
+ next = __next__
+
+ def undo_tag(self):
+ if not self.undo:
+ self.undo = True
+ else:
+ raise ValueError('No tag to undo')
+
+
+class StringIterator(TagIterator):
+ def __init__(self, dxfcontent):
+ super(StringIterator, self).__init__(StringIO(dxfcontent))
+
+
+class TagCaster:
+ def __init__(self):
+ self._cast = self._build()
+
+ def _build(self):
+ table = {}
+ for caster, codes in TYPES:
+ for code in codes:
+ table[code] = caster
+ return table
+
+ def cast(self, tag):
+ code, value = tag
+ typecaster = self._cast.get(code, tostr)
+ try:
+ value = typecaster(value)
+ except ValueError:
+ if typecaster is int: # convert float to int
+ value = int(float(value))
+ else:
+ raise
+ return DXFTag(code, value)
+
+ def cast_value(self, code, value):
+ typecaster = self._cast.get(code, tostr)
+ try:
+ return typecaster(value)
+ except ValueError:
+ if typecaster is int: # convert float to int
+ return int(float(value))
+ else:
+ raise
+
+TYPES = [
+ (tostr, range(0, 10)),
+ (point_tuple, range(10, 20)),
+ (float, range(20, 60)),
+ (int, range(60, 100)),
+ (tostr, range(100, 106)),
+ (point_tuple, range(110, 113)),
+ (float, range(113, 150)),
+ (int, range(170, 180)),
+ (point_tuple, [210]),
+ (float, range(211, 240)),
+ (int, range(270, 290)),
+ (int, range(290, 300)), # bool 1=True 0=False
+ (tostr, range(300, 370)),
+ (int, range(370, 390)),
+ (tostr, range(390, 400)),
+ (int, range(400, 410)),
+ (tostr, range(410, 420)),
+ (int, range(420, 430)),
+ (tostr, range(430, 440)),
+ (int, range(440, 460)),
+ (float, range(460, 470)),
+ (tostr, range(470, 480)),
+ (tostr, range(480, 482)),
+ (tostr, range(999, 1010)),
+ (point_tuple, range(1010, 1020)),
+ (float, range(1020, 1060)),
+ (int, range(1060, 1072)),
+]
+
+_TagCaster = TagCaster()
+cast_tag = _TagCaster.cast
+cast_tag_value = _TagCaster.cast_value
+
+
+class Tags(list):
+ """ DXFTag() chunk as flat list. """
+ def find_all(self, code):
+ """ Returns a list of DXFTag(code, ...). """
+ return [tag for tag in self if tag.code == code]
+
+ def tag_index(self, code, start=0, end=None):
+ """ Return first index of DXFTag(code, ...). """
+ if end is None:
+ end = len(self)
+ for index, tag in enumerate(islice(self, start, end)):
+ if tag.code == code:
+ return start+index
+ raise ValueError(code)
+
+ def get_value(self, code):
+ for tag in self:
+ if tag.code == code:
+ return tag.value
+ raise ValueError(code)
+
+ @staticmethod
+ def from_text(text):
+ return Tags(StringIterator(text))
+
+ def get_type(self):
+ return self.__getitem__(0).value
+
+
+class TagGroups(list):
+ """
+ Group of tags starting with a SplitTag and ending before the next SplitTag.
+
+ A SplitTag is a tag with code == splitcode, like (0, 'SECTION') for splitcode=0.
+
+ """
+ def __init__(self, tags, split_code=0):
+ super(TagGroups, self).__init__()
+ self._buildgroups(tags, split_code)
+
+ def _buildgroups(self, tags, split_code):
+ def push_group():
+ if len(group) > 0:
+ self.append(group)
+
+ def start_tag(itags):
+ tag = next(itags)
+ while tag.code != split_code:
+ tag = next(itags)
+ return tag
+
+ itags = iter(tags)
+ group = Tags([start_tag(itags)])
+
+ for tag in itags:
+ if tag.code == split_code:
+ push_group()
+ group = Tags([tag])
+ else:
+ group.append(tag)
+ push_group()
+
+ def get_name(self, index):
+ return self[index][0].value
+
+ @staticmethod
+ def from_text(text, split_code=0):
+ return TagGroups(Tags.from_text(text), split_code)
+
+
+class ClassifiedTags:
+ """ Manage Subclasses, AppData and Extended Data """
+
+ def __init__(self, iterable=None):
+ self.appdata = list() # code == 102, keys are "{<arbitrary name>", values are Tags()
+ self.subclasses = list() # code == 100, keys are "subclassname", values are Tags()
+ self.xdata = list() # code >= 1000, keys are "APPNAME", values are Tags()
+ if iterable is not None:
+ self._setup(iterable)
+
+ @property
+ def noclass(self):
+ return self.subclasses[0]
+
+ def _setup(self, iterable):
+ tagstream = iter(iterable)
+
+ def collect_subclass(start_tag):
+ """ a subclass can contain appdata, but not xdata, ends with
+ SUBCLASSMARKER or XDATACODE.
+ """
+ data = Tags() if start_tag is None else Tags([start_tag])
+ try:
+ while True:
+ tag = next(tagstream)
+ if tag.code == APP_DATA_MARKER and tag.value[0] == '{':
+ app_data_pos = len(self.appdata)
+ data.append(DXFTag(tag.code, app_data_pos))
+ collect_appdata(tag)
+ elif tag.code in (SUBCLASS_MARKER, XDATA_MARKER):
+ self.subclasses.append(data)
+ return tag
+ else:
+ data.append(tag)
+ except StopIteration:
+ pass
+ self.subclasses.append(data)
+ return NONE_TAG
+
+ def collect_appdata(starttag):
+ """ appdata, can not contain xdata or subclasses """
+ data = Tags([starttag])
+ while True:
+ try:
+ tag = next(tagstream)
+ except StopIteration:
+ raise DXFStructureError("Missing closing DXFTag(102, '}') for appdata structure.")
+ data.append(tag)
+ if tag.code == APP_DATA_MARKER:
+ break
+ self.appdata.append(data)
+
+ def collect_xdata(starttag):
+ """ xdata are always at the end of the entity and can not contain
+ appdata or subclasses
+ """
+ data = Tags([starttag])
+ try:
+ while True:
+ tag = next(tagstream)
+ if tag.code == XDATA_MARKER:
+ self.xdata.append(data)
+ return tag
+ else:
+ data.append(tag)
+ except StopIteration:
+ pass
+ self.xdata.append(data)
+ return NONE_TAG
+
+ tag = collect_subclass(None) # preceding tags without a subclass
+ while tag.code == SUBCLASS_MARKER:
+ tag = collect_subclass(tag)
+ while tag.code == XDATA_MARKER:
+ tag = collect_xdata(tag)
+
+ if tag is not NONE_TAG:
+ raise DXFStructureError("Unexpected tag '%r' at end of entity." % tag)
+
+ def __iter__(self):
+ for subclass in self.subclasses:
+ for tag in subclass:
+ if tag.code == APP_DATA_MARKER and isinstance(tag.value, int):
+ for subtag in self.appdata[tag.value]:
+ yield subtag
+ else:
+ yield tag
+
+ for xdata in self.xdata:
+ for tag in xdata:
+ yield tag
+
+ def get_subclass(self, name):
+ for subclass in self.subclasses:
+ if len(subclass) and subclass[0].value == name:
+ return subclass
+ raise KeyError("Subclass '%s' does not exist." % name)
+
+ def get_xdata(self, appid):
+ for xdata in self.xdata:
+ if xdata[0].value == appid:
+ return xdata
+ raise ValueError("No extended data for APPID '%s'" % appid)
+
+ def get_appdata(self, name):
+ for appdata in self.appdata:
+ if appdata[0].value == name:
+ return appdata
+ raise ValueError("Application defined group '%s' does not exist." % name)
+
+ def get_type(self):
+ return self.noclass[0].value
+
+ @staticmethod
+ def from_text(text):
+ return ClassifiedTags(StringIterator(text))
diff --git a/io_import_dxf/dxfgrabber/sections.py b/io_import_dxf/dxfgrabber/sections.py
new file mode 100755
index 00000000..286ebf09
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/sections.py
@@ -0,0 +1,70 @@
+# Purpose: handle dxf sections
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from .codepage import toencoding
+from .defaultchunk import DefaultChunk, iterchunks
+from .headersection import HeaderSection
+from .tablessection import TablesSection
+from .entitysection import EntitySection, ObjectsSection
+from .blockssection import BlocksSection
+from .acdsdata import AcDsDataSection
+
+
+class Sections(object):
+ def __init__(self, tagreader, drawing):
+ self._sections = {}
+ self._create_default_sections()
+ self._setup_sections(tagreader, drawing)
+
+ def __contains__(self, name):
+ return name in self._sections
+
+ def _create_default_sections(self):
+ self._sections['header'] = HeaderSection()
+ for cls in SECTIONMAP.values():
+ section = cls()
+ self._sections[section.name] = section
+
+ def _setup_sections(self, tagreader, drawing):
+ def name(section):
+ return section[1].value
+
+ bootstrap = True
+ for section in iterchunks(tagreader, stoptag='EOF', endofchunk='ENDSEC'):
+ if bootstrap:
+ new_section = HeaderSection.from_tags(section)
+ drawing.dxfversion = new_section.get('$ACADVER', 'AC1009')
+ codepage = new_section.get('$DWGCODEPAGE', 'ANSI_1252')
+ drawing.encoding = toencoding(codepage)
+ bootstrap = False
+ else:
+ section_name = name(section)
+ if section_name in SECTIONMAP:
+ section_class = get_section_class(section_name)
+ new_section = section_class.from_tags(section, drawing)
+ else:
+ new_section = None
+ if new_section is not None:
+ self._sections[new_section.name] = new_section
+
+ def __getattr__(self, key):
+ try:
+ return self._sections[key]
+ except KeyError:
+ raise AttributeError(key)
+
+SECTIONMAP = {
+ 'TABLES': TablesSection,
+ 'ENTITIES': EntitySection,
+ 'OBJECTS': ObjectsSection,
+ 'BLOCKS': BlocksSection,
+ 'ACDSDATA': AcDsDataSection,
+}
+
+
+def get_section_class(name):
+ return SECTIONMAP.get(name, DefaultChunk)
diff --git a/io_import_dxf/dxfgrabber/styles.py b/io_import_dxf/dxfgrabber/styles.py
new file mode 100755
index 00000000..8553bc4f
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/styles.py
@@ -0,0 +1,100 @@
+# Purpose: handle text styles
+# Created: 06.01.2014
+# Copyright (C) 2014, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from .dxfentity import DXFEntity
+from .layers import Table
+from .dxfattr import DXFAttr, DXFAttributes, DefSubclass
+from .tags import ClassifiedTags
+
+
+class Style(object):
+ def __init__(self, wrapper):
+ self.name = wrapper.get_dxf_attrib('name')
+ self.height = wrapper.get_dxf_attrib('height')
+ self.width = wrapper.get_dxf_attrib('width')
+ self.oblique = wrapper.get_dxf_attrib('oblique')
+ # backward & mirror_y was first and stays for compatibility
+ self.backward = bool(wrapper.get_dxf_attrib('generation_flags') & 2)
+ self.mirror_y = bool(wrapper.get_dxf_attrib('generation_flags') & 4)
+ self.is_backwards = self.backward
+ self.is_upside_down = self.mirror_y
+ self.font = wrapper.get_dxf_attrib('font')
+ self.bigfont = wrapper.get_dxf_attrib('bigfont', "")
+
+
+class StyleTable(Table):
+ name = 'styles'
+
+ @staticmethod
+ def from_tags(tags, drawing):
+ dxfversion = drawing.dxfversion
+ styles = StyleTable()
+ for entrytags in styles._classified_tags(tags):
+ dxfstyle = styles.wrap(entrytags, dxfversion)
+ styles._table_entries[dxfstyle.get_dxf_attrib('name')] = Style(dxfstyle)
+ return styles
+
+ @staticmethod
+ def wrap(tags, dxfversion):
+ return DXF12Style(tags) if dxfversion == "AC1009" else DXF13Style(tags)
+
+
+class DXF12Style(DXFEntity):
+ DXFATTRIBS = DXFAttributes(DefSubclass(None, {
+ 'handle': DXFAttr(5),
+ 'name': DXFAttr(2),
+ 'flags': DXFAttr(70),
+ 'height': DXFAttr(40), # fixed height, 0 if not fixed
+ 'width': DXFAttr(41), # width factor
+ 'oblique': DXFAttr(50), # oblique angle in degree, 0 = vertical
+ 'generation_flags': DXFAttr(71), # 2 = backward, 4 = mirrored in Y
+ 'last_height': DXFAttr(42), # last height used
+ 'font': DXFAttr(3), # primary font file name
+ 'bigfont': DXFAttr(4), # big font name, blank if none
+ }))
+
+none_subclass = DefSubclass(None, {'handle': DXFAttr(5)})
+symbol_subclass = DefSubclass('AcDbSymbolTableRecord', {})
+style_subclass = DefSubclass('AcDbTextStyleTableRecord', {
+ 'name': DXFAttr(2),
+ 'flags': DXFAttr(70),
+ 'height': DXFAttr(40), # fixed height, 0 if not fixed
+ 'width': DXFAttr(41), # width factor
+ 'oblique': DXFAttr(50), # oblique angle in degree, 0 = vertical
+ 'generation_flags': DXFAttr(71), # 2 = backward, 4 = mirrored in Y
+ 'last_height': DXFAttr(42), # last height used
+ 'font': DXFAttr(3), # primary font file name
+ 'bigfont': DXFAttr(4), # big font name, blank if none
+})
+
+
+class DXF13Style(DXF12Style):
+ DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, style_subclass)
+
+DEFAULT_STYLE = """ 0
+STYLE
+ 2
+STANDARD
+ 70
+0
+ 40
+0.0
+ 41
+1.0
+ 50
+0.0
+ 71
+0
+ 42
+1.0
+ 3
+Arial
+ 4
+
+"""
+
+default_text_style = Style(DXF12Style(ClassifiedTags.from_text(DEFAULT_STYLE))) \ No newline at end of file
diff --git a/io_import_dxf/dxfgrabber/tablessection.py b/io_import_dxf/dxfgrabber/tablessection.py
new file mode 100755
index 00000000..3737138a
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/tablessection.py
@@ -0,0 +1,92 @@
+# Purpose: handle tables section
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+from .defaultchunk import iterchunks, DefaultChunk
+from .layers import LayerTable
+from .styles import StyleTable
+from .linetypes import LinetypeTable
+
+TABLENAMES = {
+ 'layer': 'layers',
+ 'ltype': 'linetypes',
+ 'appid': 'appids',
+ 'dimstyle': 'dimstyles',
+ 'style': 'styles',
+ 'ucs': 'ucs',
+ 'view': 'views',
+ 'vport': 'viewports',
+ 'block_record': 'block_records',
+ }
+
+
+def tablename(dxfname):
+ """ Translate DXF-table-name to attribute-name. ('LAYER' -> 'layers') """
+ name = dxfname.lower()
+ return TABLENAMES.get(name, name+'s')
+
+
+class GenericTable(DefaultChunk):
+ @property
+ def name(self):
+ return tablename(self.tags[1].value)
+
+
+class DefaultDrawing(object):
+ dxfversion = 'AC1009'
+ encoding = 'cp1252'
+
+
+class TablesSection(object):
+ name = 'tables'
+
+ def __init__(self, drawing=DefaultDrawing()):
+ self._tables = dict()
+ self._drawing = drawing
+ self._create_default_tables()
+
+ def _create_default_tables(self):
+ for cls in TABLESMAP.values():
+ table = cls()
+ self._tables[table.name] = table
+
+ @staticmethod
+ def from_tags(tags, drawing):
+ tables_section = TablesSection(drawing)
+ tables_section._setup_tables(tags)
+ return tables_section
+
+ def _setup_tables(self, tags):
+ def name(table):
+ return table[1].value
+
+ def skiptags(tags, count):
+ for i in range(count):
+ next(tags)
+ return tags
+
+ itertags = skiptags(iter(tags), 2) # (0, 'SECTION'), (2, 'TABLES')
+ for table in iterchunks(itertags, stoptag='ENDSEC', endofchunk='ENDTAB'):
+ table_class = table_factory(name(table))
+ new_table = table_class.from_tags(table, self._drawing)
+ self._tables[new_table.name] = new_table
+
+ def __getattr__(self, key):
+ try:
+ return self._tables[key]
+ except KeyError:
+ raise AttributeError(key)
+
+# support for further tables types are possible
+TABLESMAP = {
+ 'LAYER': LayerTable,
+ 'STYLE': StyleTable,
+ 'LTYPE': LinetypeTable,
+}
+
+
+def table_factory(name):
+ return TABLESMAP.get(name, GenericTable)
diff --git a/io_import_dxf/dxfgrabber/tags.py b/io_import_dxf/dxfgrabber/tags.py
new file mode 100755
index 00000000..fed6aa88
--- /dev/null
+++ b/io_import_dxf/dxfgrabber/tags.py
@@ -0,0 +1,73 @@
+# Purpose: tag reader
+# Created: 21.07.2012, taken from my ezdxf project
+# Copyright (C) 2012, Manfred Moitzi
+# License: MIT License
+from __future__ import unicode_literals
+__author__ = "mozman <mozman@gmx.at>"
+
+import os
+from .const import ENV_CYTHON
+
+OPTIMIZE = True
+if ENV_CYTHON in os.environ:
+ if os.environ[ENV_CYTHON].upper() in ('1', 'ON', 'TRUE'):
+ OPTIMIZE = True
+ else:
+ OPTIMIZE = False
+try:
+ if not OPTIMIZE:
+ raise ImportError
+ CYTHON_EXT = True
+ from.cytags import TagIterator, Tags, TagGroups, DXFTag, NONE_TAG
+ from.cytags import DXFStructureError, StringIterator, ClassifiedTags
+except ImportError:
+ CYTHON_EXT = False
+ from.pytags import TagIterator, Tags, TagGroups, DXFTag, NONE_TAG
+ from.pytags import DXFStructureError, StringIterator, ClassifiedTags
+
+
+import sys
+from .codepage import toencoding
+from .const import acadrelease
+from array import array
+
+
+class DXFInfo(object):
+ def __init__(self):
+ self.release = 'R12'
+ self.version = 'AC1009'
+ self.encoding = 'cp1252'
+ self.handseed = '0'
+
+ def DWGCODEPAGE(self, value):
+ self.encoding = toencoding(value)
+
+ def ACADVER(self, value):
+ self.version = value
+ self.release = acadrelease.get(value, 'R12')
+
+ def HANDSEED(self, value):
+ self.handseed = value
+
+
+def dxfinfo(stream):
+ info = DXFInfo()
+ tag = DXFTag(999999, '')
+ tagreader = TagIterator(stream)
+ while tag != DXFTag(0, 'ENDSEC'):
+ tag = next(tagreader)
+ if tag.code != 9:
+ continue
+ name = tag.value[1:]
+ method = getattr(info, name, None)
+ if method is not None:
+ method(next(tagreader).value)
+ return info
+
+
+def binary_encoded_data_to_bytes(data):
+ PY3 = sys.version_info[0] >= 3
+ byte_array = array('B' if PY3 else b'B')
+ for text in data:
+ byte_array.extend(int(text[index:index+2], 16) for index in range(0, len(text), 2))
+ return byte_array.tobytes() if PY3 else byte_array.tostring()
diff --git a/io_import_dxf/dxfimport/__init__.py b/io_import_dxf/dxfimport/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/io_import_dxf/dxfimport/__init__.py
diff --git a/io_import_dxf/dxfimport/convert.py b/io_import_dxf/dxfimport/convert.py
new file mode 100644
index 00000000..64df5c35
--- /dev/null
+++ b/io_import_dxf/dxfimport/convert.py
@@ -0,0 +1,287 @@
+# ##### 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 itertools
+from . import is_
+from .fake_entities import ArcEntity
+from mathutils import Vector, Matrix, Euler, Color
+from math import pi, radians, floor, ceil, degrees
+from copy import deepcopy
+
+
+class ShortVec(Vector):
+ def __str__(self):
+ return "Vec" + str((round(self.x, 2), round(self.y, 2), round(self.z, 2)))
+
+ def __repr__(self):
+ return self.__str__()
+
+
+def bspline_to_cubic(do, en, curve, errors=None):
+ """
+ do: an instance of Do()
+ en: a DXF entity
+ curve: Blender geometry data of type "CURVE"
+ inserts knots until every knot has multiplicity of 3; returns new spline controlpoints
+ if degree of the spline is > 3 "None" is returned
+ """
+ def clean_knots():
+ start = knots[:degree + 1]
+ end = knots[-degree - 1:]
+
+ if start.count(start[0]) < degree + 1:
+ maxa = max(start)
+ for i in range(degree + 1):
+ knots[i] = maxa
+
+ if end.count(end[0]) < degree + 1:
+ mina = min(end)
+ lenk = len(knots)
+ for i in range(lenk - degree - 1, lenk):
+ knots[i] = mina
+
+ def insert_knot(t, k, p):
+ """ http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/NURBS-knot-insert.html """
+ def a(t, ui, uip):
+ if uip == ui:
+ print("zero!")
+ return 0
+ return (t - ui) / (uip - ui)
+
+ new_spline = spline.copy()
+ for pp in range(p, 1, -1):
+ i = k - pp + 1
+ ai = a(t, knots[i], knots[i + p])
+ new_spline[i] = (1 - ai) * spline[i - 1] + ai * spline[i]
+
+ ai = a(t, knots[k], knots[k + p])
+ new_spline.insert(k, (1 - ai) * spline[k - 1] + ai * spline[k % len(spline)])
+ knots.insert(k, t)
+
+ return new_spline
+
+ knots = list(en.knots)
+ spline = [ShortVec(cp) for cp in en.controlpoints]
+ degree = len(knots) - len(spline) - 1
+ if degree <= 3:
+ clean_knots()
+ k = 1
+ st = 1
+ while k < len(knots) - 1:
+ t = knots[k]
+ multilen = knots[st:-st].count(t)
+ if multilen < degree:
+ before = multilen
+ while multilen < degree:
+ spline = insert_knot(t, k, degree)
+ multilen += 1
+ k += 1
+ k += before
+ else:
+ k += degree
+
+ if degree <= 2:
+ return quad_to_cube(spline)
+
+ # the ugly truth
+ if len(spline) % 3 == 0:
+ spline.append([spline[-1]])
+ errors.add("Cubic spline: Something went wrong with knot insertion")
+ return spline
+
+
+def quad_to_cube(spline):
+ """
+ spline: list of (x,y,z)-tuples)
+ Converts quad bezier to cubic bezier curve.
+ """
+ s = []
+ for i, p in enumerate(spline):
+ if i % 2 == 1:
+ before = Vector(spline[i - 1])
+ after = Vector(spline[(i + 1) % len(spline)])
+ s.append(before + 2 / 3 * (Vector(p) - before))
+ s.append(after + 2 / 3 * (Vector(p) - after))
+ else:
+ s.append(p)
+
+ # degree == 1
+ if len(spline) == 2:
+ s.append(spline[-1])
+ return s
+
+
+def bulge_to_arc(point, next, bulge):
+ """
+ point: start point of segment in lwpolyline
+ next: end point of segment in lwpolyline
+ bulge: number between 0 and 1
+ Converts a bulge of lwpolyline to an arc with a bulge describing the amount of how much a straight segment should
+ be bended to an arc. With the bulge one can find the center point of the arc that replaces the segment.
+ """
+
+ rot = Matrix(((0, -1, 0), (1, 0, 0), (0, 0, 1)))
+ section = next - point
+ section_length = section.length / 2
+ direction = -bulge / abs(bulge)
+ correction = 1
+ sagitta_len = section_length * abs(bulge)
+ radius = (sagitta_len**2 + section_length**2) / (2 * sagitta_len)
+ if sagitta_len < radius:
+ cosagitta_len = radius - sagitta_len
+ else:
+ cosagitta_len = sagitta_len - radius
+ direction *= -1
+ correction *= -1
+ center = point + section / 2 + section.normalized() * cosagitta_len * rot * direction
+ cp = point - center
+ cn = next - center
+ cr = cp.to_3d().cross(cn.to_3d()) * correction
+ start = Vector((1, 0))
+ if cr[2] > 0:
+ angdir = 0
+ startangle = -start.angle_signed(cp.to_2d())
+ endangle = -start.angle_signed(cn.to_2d())
+ else:
+ angdir = 1
+ startangle = start.angle_signed(cp.to_2d())
+ endangle = start.angle_signed(cn.to_2d())
+ return ArcEntity(startangle, endangle, center.to_3d(), radius, angdir)
+
+
+def bulgepoly_to_cubic(do, lwpolyline):
+ """
+ do: instance of Do()
+ lwpolyline: DXF entity of type polyline
+ Bulges define how much a straight segment of a polyline should be transformed to an arc. Hence do.arc() is called
+ for segments with a bulge and all segments are being connected to a cubic bezier curve in the end.
+ Reference: http://www.afralisp.net/archive/lisp/Bulges1.htm
+ """
+ def handle_segment(last, point, bulge):
+ if bulge != 0 and (point - last).length != 0:
+ arc = bulge_to_arc(last, point, bulge)
+ cubic_bezier = do.arc(arc, None, aunits=1, angdir=arc.angdir, angbase=0)
+ else:
+ la = last.to_3d()
+ po = point.to_3d()
+ section = point - last
+ cubic_bezier = [la, la + section * 1 / 3, la + section * 2 / 3, po]
+ return cubic_bezier
+
+ points = lwpolyline.points
+ bulges = lwpolyline.bulge
+ lenpo = len(points)
+ spline = []
+ for i in range(1, lenpo):
+ spline += handle_segment(Vector(points[i - 1]), Vector(points[i]), bulges[i - 1])[:-1]
+
+ if lwpolyline.is_closed:
+ spline += handle_segment(Vector(points[-1]), Vector(points[0]), bulges[-1])
+ else:
+ spline.append(points[-1])
+ return spline
+
+
+def bulgepoly_to_lenlist(lwpolyline):
+ """
+ returns a list with the segment lengths of a lwpolyline
+ """
+ def handle_segment(last, point, bulge):
+ seglen = (point - last).length
+ if bulge != 0 and seglen != 0:
+ arc = bulge_to_arc(last, point, bulge)
+ if arc.startangle > arc.endangle:
+ arc.endangle += 2 * pi
+ angle = arc.endangle - arc.startangle
+ lenlist.append(abs(arc.radius * angle))
+ else:
+ lenlist.append(seglen)
+
+ points = lwpolyline.points
+ bulges = lwpolyline.bulge
+ lenpo = len(points)
+ lenlist = []
+ for i in range(1, lenpo):
+ handle_segment(Vector(points[i - 1][:2]), Vector(points[i][:2]), bulges[i - 1])
+
+ if lwpolyline.is_closed:
+ handle_segment(Vector(points[-1][:2]), Vector(points[0][:2]), bulges[-1])
+
+ return lenlist
+
+
+def extrusion_to_matrix(entity):
+ """
+ Converts an extrusion vector to a rotation matrix that denotes the transformation between world coordinate system
+ and the entity's own coordinate system (described by the extrusion vector).
+ """
+ def arbitrary_x_axis(extrusion_normal):
+ world_y = Vector((0, 1, 0))
+ world_z = Vector((0, 0, 1))
+ if abs(extrusion_normal[0]) < 1 / 64 and abs(extrusion_normal[1]) < 1 / 64:
+ a_x = world_y.cross(extrusion_normal)
+ else:
+ a_x = world_z.cross(extrusion_normal)
+ a_x.normalize()
+ return a_x, extrusion_normal.cross(a_x)
+
+ az = Vector(entity.extrusion)
+ ax, ay = arbitrary_x_axis(az)
+ return Matrix((ax, ay, az)).inverted()
+
+
+def split_by_width(entity):
+ """
+ Used to split a curve (polyline, lwpolyline) into smaller segments if their width is varying in the overall curve.
+ """
+ class WidthTuple:
+ def __init__(self, w):
+ self.w1 = w[0]
+ self.w2 = w[1]
+
+ def __eq__(self, other):
+ return self.w1 == other.w1 and self.w2 == other.w2 and self.w1 == self.w2
+
+ if is_.varying_width(entity):
+ entities = []
+ en_template = deepcopy(entity)
+ en_template.points = []
+ en_template.bulge = []
+ en_template.width = []
+ en_template.tangents = []
+ en_template.is_closed = False
+
+ i = 0
+ for pair, same_width in itertools.groupby(entity.width, key=lambda w: WidthTuple(w)):
+ en = deepcopy(en_template)
+ for segment in same_width:
+ en.points.append(entity.points[i])
+ en.points.append(entity.points[(i + 1) % len(entity.points)])
+ en.bulge.append(entity.bulge[i])
+ en.width.append(entity.width[i])
+ i += 1
+ entities.append(en)
+
+ if not entity.is_closed:
+ entities.pop(-1)
+ return entities
+
+ else:
+ return [entity]
diff --git a/io_import_dxf/dxfimport/do.py b/io_import_dxf/dxfimport/do.py
new file mode 100644
index 00000000..0e39c5a9
--- /dev/null
+++ b/io_import_dxf/dxfimport/do.py
@@ -0,0 +1,1498 @@
+# ##### 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
+import re
+from mathutils import Vector, Matrix, Euler, Color, geometry
+from math import pi, radians
+
+import bmesh
+from .. import dxfgrabber
+from . import convert, is_, groupsort
+from .line_merger import line_merger
+from ..transverse_mercator import TransverseMercator
+
+
+try:
+ from pyproj import Proj, transform as proj_transform
+ PYPROJ = True
+except:
+ PYPROJ = False
+
+BY_LAYER = 0
+BY_DXFTYPE = 1
+SEPARATED = 2
+LINKED_OBJECTS = 3
+GROUP_INSTANCES = 4
+
+
+def transform(p1, p2, c1, c2, c3):
+ if PYPROJ:
+ if type(p1) is Proj and type(p2) is Proj:
+ if p1.srs != p2.srs:
+ return proj_transform(p1, p2, c1, c2, c3)
+ else:
+ return (c1, c2, c3)
+ elif type(p2) is TransverseMercator:
+ wgs84 = Proj(init="EPSG:4326")
+ if p1.srs != wgs84.srs:
+ t2, t1, t3 = proj_transform(p1, wgs84, c1, c2, c3)
+ else:
+ t1, t2, t3 = c2, c1, c3 # mind c2, c1 inversion
+ tm1, tm2 = p2.fromGeographic(t1, t2)
+ return (tm1, tm2, t3)
+ else:
+ if p1.spherical:
+ t1, t2 = p2.fromGeographic(c2, c1) # mind c2, c1 inversion
+ return (t1, t2, c3)
+ else:
+ return (c1, c2, c3)
+
+
+def float_len(f):
+ s = str(f)
+ if 'e' in s:
+ return int(s[3:5])
+ else:
+ return len(s[2:])
+
+
+class Indicator:
+ spherical = False
+ euclidean = False
+
+ def __init__(self, i):
+ if i == "spherical":
+ self.spherical = True
+ elif i == "euclidean":
+ self.euclidean = True
+ else:
+ raise AttributeError("Indication must be 'spherical' or 'planar'.")
+
+
+class Do:
+ __slots__ = (
+ "dwg", "combination", "known_blocks", "import_text", "import_light", "export_acis", "merge_lines",
+ "do_bounding_boxes", "acis_files", "errors", "block_representation", "recenter", "did_group_instance",
+ "objects_before", "pDXF", "pScene", "thickness_and_width", "but_group_by_att", "current_scene",
+ "dxf_unit_scale",
+ )
+
+ def __init__(self, dxf_filename, c=BY_LAYER, import_text=True, import_light=True, export_acis=True,
+ merge_lines=True, do_bbox=True, block_rep=LINKED_OBJECTS, recenter=False, pDXF=None, pScene=None,
+ thicknessWidth=True, but_group_by_att=True, dxf_unit_scale=1.0):
+ self.dwg = dxfgrabber.readfile(dxf_filename, {"assure_3d_coords": True})
+ self.combination = c
+ self.known_blocks = {}
+ self.import_text = import_text
+ self.import_light = import_light
+ self.export_acis = export_acis
+ self.merge_lines = merge_lines
+ self.do_bounding_boxes = do_bbox
+ self.acis_files = []
+ self.errors = set([])
+ self.block_representation = block_rep
+ self.recenter = recenter
+ self.did_group_instance = False
+ self.objects_before = []
+ self.pDXF = pDXF
+ self.pScene = pScene
+ self.thickness_and_width = thicknessWidth
+ self.but_group_by_att = but_group_by_att
+ self.current_scene = None
+ self.dxf_unit_scale = dxf_unit_scale
+
+ def proj(self, co):
+ """
+ :param co: coordinate
+ :return: transformed coordinate if self.pScene is defined
+ """
+ if self.pScene is not None and self.pDXF is not None:
+ u = self.dxf_unit_scale
+ if len(co) == 3:
+ c1, c2, c3 = co
+ else:
+ c1, c2 = co
+ c3 = 0
+ if u != 1.0:
+ c1 *= u
+ c2 *= u
+ c3 *= u
+
+ # add
+ add = Vector((0, 0, 0))
+ if "latitude" in self.current_scene and "longitude" in self.current_scene:
+ if PYPROJ and type(self.pScene) not in (TransverseMercator, Indicator):
+ wgs84 = Proj(init="EPSG:4326")
+ cscn_lat = self.current_scene.get('latitude', 0)
+ cscn_lon = self.current_scene.get('longitude', 0)
+ cscn_alt = self.current_scene.get('altitude', 0)
+ add = Vector(transform(wgs84, self.pScene, cscn_lat, cscn_lon, cscn_alt))
+
+ # projection
+ newco = Vector(transform(self.pDXF, self.pScene, c1, c2, c3))
+ newco = newco - add
+ if any((c == float("inf") or c == float("-inf") for c in newco)):
+ self.errors.add("Projection results in +/- infinity coordinates.")
+ return newco
+ else:
+ u = self.dxf_unit_scale
+ if u != 1:
+ if len(co) == 3:
+ c1, c2, c3 = co
+ else:
+ c1, c2 = co
+ c3 = 0
+ c1 *= u
+ c2 *= u
+ c3 *= u
+ return Vector((c1, c2, c3))
+ else:
+ return Vector((co[0], co[1], co[2] if len(co) == 3 else 0))
+
+ def georeference(self, scene, center):
+ if "latitude" not in scene and "longitude" not in scene:
+ if type(self.pScene) is TransverseMercator:
+ scene['latitude'] = self.pScene.lat
+ scene['longitude'] = self.pScene.lon
+ scene['altitude'] = 0
+ elif type(self.pScene) is not None:
+ wgs84 = Proj(init="EPSG:4326")
+ latlon = transform(self.pScene, wgs84, center[0], center[1], center[2])
+ scene['latitude'] = latlon[0]
+ scene['longitude'] = latlon[1]
+ scene['altitude'] = latlon[2]
+
+ """ GEOMETRY DXF TYPES TO BLENDER CURVES FILTERS"""
+ # type(self, dxf entity, blender curve data)
+
+ def _cubic_bezier_closed(self, ptuple, curve):
+ count = (len(ptuple)-1)/3
+ points = [ptuple[-2]]
+ ptuples = ptuple[:-2]
+ points += [p for p in ptuples]
+
+ spl = curve.splines.new('BEZIER')
+ spl.use_cyclic_u = True
+ b = spl.bezier_points
+ b.add(count - 1)
+ for i, j in enumerate(range(1, len(points), 3)):
+ b[i].handle_left = self.proj(points[j - 1])
+ b[i].co = self.proj(points[j])
+ b[i].handle_right = self.proj(points[j + 1])
+
+ def _cubic_bezier_open(self, points, curve):
+ count = (len(points) - 1) / 3 + 1
+ spl = curve.splines.new('BEZIER')
+ b = spl.bezier_points
+ b.add(count - 1)
+
+ b[0].co = self.proj(points[0])
+ b[0].handle_left = self.proj(points[0])
+ b[0].handle_right = self.proj(points[1])
+
+ b[-1].co = self.proj(points[-1])
+ b[-1].handle_right = self.proj(points[-1])
+ b[-1].handle_left = self.proj(points[-2])
+
+ for i, j in enumerate(range(3, len(points) - 2, 3), 1):
+ b[i].handle_left = self.proj(points[j - 1])
+ b[i].co = self.proj(points[j])
+ b[i].handle_right = self.proj(points[j + 1])
+
+ def _cubic_bezier(self, points, curve, is_closed):
+ """
+ points: control points; list of (x,y,z) tuples
+ curve: Blender curve data of type "CURVE" (object.data) where the bezier should be added to
+ is_closed: True / False to indicate if the curve is open or closed
+ """
+ if is_closed:
+ self._cubic_bezier_closed(points, curve)
+ else:
+ self._cubic_bezier_open(points, curve)
+
+ def _poly(self, points, curve, is_closed=False):
+ """
+ points: list of (x,y,z)
+ curve: Blender curve data of type "CURVE" (object.data) to which the poly should be added to
+ is_closed: True / False to indicate if the polygon is open or closed
+ """
+ p = curve.splines.new("POLY")
+ p.use_smooth = False
+ p.use_cyclic_u = is_closed
+ p.points.add(len(points) - 1)
+
+ for i, pt in enumerate(points):
+ p.points[i].co = self.proj(pt).to_4d()
+
+ def _gen_poly(self, en, curve):
+ if any([b != 0 for b in en.bulge]):
+ self._cubic_bezier(convert.bulgepoly_to_cubic(self, en), curve, en.is_closed)
+ else:
+ self._poly(en.points, curve, en.is_closed)
+
+ def polyline(self, en, curve):
+ """
+ en: DXF entity of type `POLYLINE`
+ curve: Blender data structure of type `CURVE`
+ """
+ self._gen_poly(en, curve)
+
+ def polygon(self, en, curve):
+ """
+ en: DXF entity of type `POLYGON`
+ curve: Blender data structure of type `CURVE`
+ """
+ self._gen_poly(en, curve)
+
+ def lwpolyline(self, en, curve):
+ """
+ en: DXF entity of type `LWPOLYLINE`
+ curve: Blender data structure of type `CURVE`
+ """
+ self._gen_poly(en, curve)
+
+ def line(self, en, curve):
+ """
+ en: DXF entity of type `LINE`
+ curve: Blender data structure of type `CURVE`
+ """
+ self._poly([en.start, en.end], curve, False)
+
+ def arc(self, en, curve=None, aunits=None, angdir=None, angbase=None):
+ """
+ en: dxf entity (en.startangle, en.endangle, en.center, en.radius)
+ curve: optional; Blender curve data of type "CURVE" (object.data) to which the arc should be added to
+ return control points of a cubic spline (do be used in a spline with bulges / series of arcs)
+ note: en.startangle + en.endangle: angles measured from the angle base (angbase) in the direction of
+ angdir (1 = clockwise, 0 = counterclockwise)
+ """
+ treshold = 0.005
+
+ if aunits is None:
+ aunits = self.dwg.header.get('$AUNITS', 0)
+ if angbase is None:
+ angbase = self.dwg.header.get('$ANGBASE', 0)
+ if angdir is None:
+ angdir = self.dwg.header.get('$ANGDIR', 0)
+ kappa = 0.5522848
+
+ if aunits == 0:
+ s = radians(en.startangle+angbase)
+ e = radians(en.endangle+angbase)
+ else:
+ s = en.startangle+angbase
+ e = en.endangle+angbase
+
+ if s > e:
+ e += 2 * pi
+ angle = e - s
+
+ # curve == None means arc is called from bulge conversion
+ # nothing should be projected at this stage, since the
+ # lwpolyline (the only entity with bulges) will be projected
+ # as a whole afterwars (small little error; took ages to debug)
+ if curve is not None:
+ vc = self.proj(en.center)
+ else:
+ vc = en.center
+ x_vec = Vector((1, 0, 0))
+ radius = en.radius
+
+ # turn clockwise
+ if angdir == 0:
+ rot = Matrix.Rotation(radians(-90), 3, "Z")
+ start = x_vec * radius * Matrix.Rotation(-s, 3, "Z")
+ end = x_vec * radius * Matrix.Rotation(-e, 3, "Z")
+
+ # turn counter-clockwise
+ else:
+ rot = Matrix.Rotation(radians(90), 3, "Z")
+ start = x_vec * radius * Matrix.Rotation(s, 3, "Z")
+ end = x_vec * radius * Matrix.Rotation(e, 3, "Z")
+
+ # start
+ spline = list()
+ spline.append(vc + start)
+ if abs(angle) - pi / 2 > treshold: # if angle is more than pi/2 incl. treshold
+ spline.append(vc + start + start * kappa * rot)
+ else:
+ spline.append(vc + start + start * kappa * angle / (pi / 2) * rot)
+
+ # fill if angle is larger than 90 degrees
+ a = pi / 2
+ if abs(angle) - treshold > a:
+ fill = start
+
+ while abs(angle) - a > treshold:
+ fillnext = fill * rot
+ spline.append(vc + fillnext + fill * kappa)
+ spline.append(vc + fillnext)
+ # if this was the last fill control point
+ if abs(angle) - a - pi / 2 < treshold:
+ end_angle = (abs(angle) - a) * abs(angle) / angle
+ spline.append(vc + fillnext + fillnext * kappa * end_angle / (pi / 2) * rot)
+ else:
+ spline.append(vc + fillnext + fillnext * kappa * rot)
+ fill = fillnext
+ a += pi / 2
+
+ else:
+ end_angle = angle
+
+ # end
+ spline.append(vc + end + end * -kappa * end_angle / (pi / 2) * rot)
+ spline.append(vc + end)
+
+ if len(spline) % 3 != 1:
+ print("DXF-IMPORT: DO ARC: CHECK PLEASE: ", len(spline), spline)
+
+ if curve is not None:
+ self._cubic_bezier_open(spline, curve)
+ return spline
+ else:
+ return spline
+
+ def circle(self, en, curve, major=Vector((1, 0, 0))):
+ """
+ en: dxf entity
+ curve: Blender curve data of type "CURVE" (object.data) to which the circle should be added to
+ major: optional; if the circle is used as a base for an ellipse, major denotes the ellipse's major direction
+ """
+ c = curve.splines.new("BEZIER")
+ c.use_cyclic_u = True
+ b = c.bezier_points
+ b.add(3)
+
+ for i in range(4):
+ b[i].handle_left_type = 'AUTO'
+ b[i].handle_right_type = 'AUTO'
+
+ vc = self.proj(en.center)
+ clockwise = Matrix(((0, -1, 0), (1, 0, 0), (0, 0, 1)))
+
+ r = major
+ if len(r) == 2:
+ r = r.to_3d()
+ r = r * en.radius
+
+ try:
+ b[0].co = vc + r
+ b[1].co = vc + r * clockwise
+ b[2].co = vc + r * clockwise * clockwise
+ b[3].co = vc + r * clockwise * clockwise * clockwise
+ except:
+ print("Circle center: ", vc, "radius: ", r)
+ raise
+
+ return c
+
+ def scale_controlpoint(self, p, factor):
+ """
+ p: Blender control-point
+ factor: (Float)
+ Repositions left and right handle of a bezier control point.
+ """
+ p.handle_left = p.co + (p.handle_left - p.co) * factor
+ p.handle_right = p.co + (p.handle_right - p.co) * factor
+
+ def ellipse(self, en, curve):
+ """
+ center: (x,y,z) of circle center
+ major: the ellipse's major direction
+ ratio: ratio between major and minor axis lengths (always < 1)
+ curve: Blender curve data of type "CURVE" (object.data) to which the ellipse should be added to
+ """
+ major = Vector(en.majoraxis)
+ en.__dict__["radius"] = major.length
+ c = self.circle(en, curve, major.normalized())
+ b = c.bezier_points
+
+ if en.ratio < 1:
+ for i in range(4):
+ b[i].handle_left_type = 'ALIGNED'
+ b[i].handle_right_type = 'ALIGNED'
+
+ vc = self.proj(en.center)
+ clockwise = Matrix(((0, -1, 0), (1, 0, 0), (0, 0, 1)))
+ if len(major) == 2:
+ major = major.to_3d()
+ minor = major * en.ratio * clockwise
+
+ lh = b[1].handle_left - b[1].co
+ rh = b[1].handle_right - b[1].co
+ b[1].co = vc + minor
+ b[1].handle_left = b[1].co + lh
+ b[1].handle_right = b[1].co + rh
+ b[3].co = vc + minor * clockwise * clockwise
+ b[3].handle_left = b[3].co + rh
+ b[3].handle_right = b[3].co + lh
+
+ self.scale_controlpoint(b[0], en.ratio)
+ self.scale_controlpoint(b[2], en.ratio)
+
+ def spline(self, en, curve, _3D=True):
+ """
+ en: DXF entity of type `SPLINE`
+ curve: Blender data structure of type `CURVE`
+ """
+ if _3D:
+ curve.dimensions = "3D"
+ spline = convert.bspline_to_cubic(self, en, curve, self.errors)
+ if spline is None:
+ self.errors.add("Not able to import bspline with degree > 3")
+ else:
+ self._cubic_bezier(spline, curve, en.is_closed)
+
+ def helix(self, en, curve):
+ """
+ en: DXF entity of type `HELIX`
+ curve: Blender data structure of type `CURVE`
+ """
+ self.spline(en, curve, not en.is_planar)
+
+ """ GEOMETRY DXF TYPES TO BLENDER MESHES FILTERS"""
+ # type(self, dxf entity, blender bmesh data)
+
+ def _gen_meshface(self, points, bm):
+ """
+ points: list of (x,y,z) tuples
+ bm: bmesh to add the (face-) points
+ Used by the3dface() and solid()
+ """
+
+ def _is_on_edge(point):
+ return abs(sum((e - point).length for e in (edge1, edge2)) - (edge1 - edge2).length) < 0.01
+
+ points = list(points)
+
+ i = 0
+ while i < len(points):
+ if points.count(points[i]) > 1:
+ points.pop(i)
+ else:
+ i += 1
+
+ verts = []
+ for p in points:
+ verts.append(bm.verts.new(self.proj(p)))
+ face = bm.faces.new(verts)
+
+ if len(points) == 4:
+ for i in range(2):
+ edge1 = verts[i].co
+ edge2 = verts[i + 1].co
+ opposite1 = verts[i + 2].co
+ opposite2 = verts[(i + 3) % 4].co
+ ii = geometry.intersect_line_line(edge1, edge2, opposite1, opposite2)
+ if ii is not None:
+ if _is_on_edge(ii[0]):
+ bm.faces.remove(face)
+ iv = bm.verts.new(ii[0])
+ bm.faces.new((verts[i], iv, verts[(i + 3) % 4]))
+ bm.faces.new((verts[i + 1], iv, verts[i + 2]))
+
+ def the3dface(self, en, bm):
+ """ f: dxf entity
+ bm: Blender bmesh data to which the 3DFACE should be added to.
+ <-? bm.from_mesh(object.data) ?->
+ """
+ if en.points[-1] == en.points[-2]:
+ points = en.points[:3]
+ else:
+ points = en.points
+ self._gen_meshface(points, bm)
+
+ def solid(self, en, bm):
+ """ f: dxf entity
+ bm: Blender bmesh data to which the SOLID should be added to.
+ <-? bm.from_mesh(object.data) ?->
+ """
+ p = en.points
+ points = (p[0], p[1], p[3], p[2])
+ self._gen_meshface(points, bm)
+
+ def trace(self, en, bm):
+ self.solid(en, bm)
+
+ def point(self, en, bm):
+ """
+ en: DXF entity of type `POINT`
+ bm: Blender bmesh instance
+ """
+ bm.verts.new(en.point)
+
+ def polyface(self, en, bm):
+ """
+ pf: polyface
+ bm: Blender bmesh data to which the POLYFACE should be added to.
+ <-? bm.from_mesh(object.data) ?->
+ """
+ for v in en.vertices:
+ bm.verts.new(v.location)
+ for subface in en:
+ idx = subface.indices()
+ points = []
+ for p in idx:
+ if p not in points:
+ points.append(p)
+ if len(points) in (3, 4):
+ bm.faces.new([bm.verts[i] for i in points])
+
+ def polymesh(self, en, bm):
+ """
+ en: POLYMESH entitiy
+ bm: Blender bmesh instance
+ """
+ mc = en.mcount if not en.is_mclosed else en.mcount + 1
+ nc = en.ncount if not en.is_nclosed else en.ncount + 1
+ for i in range(1, mc):
+ i = i % en.mcount
+ i_ = (i - 1) % en.mcount
+ for j in range(1, nc):
+ j = j % en.ncount
+ j_ = (j - 1) % en.ncount
+ face = []
+ face.append(bm.verts.new(en.get_location((i_, j_))))
+ face.append(bm.verts.new(en.get_location((i, j_))))
+ face.append(bm.verts.new(en.get_location((i, j))))
+ face.append(bm.verts.new(en.get_location((i_, j))))
+
+ bm.faces.new(face)
+
+ def mesh(self, en, bm):
+ """
+ mesh: dxf entity
+ m: Blender MESH data (object.data) to which the dxf-mesh should be added
+ """
+ # verts:
+ for v in en.vertices:
+ bm.verts.new(v)
+
+ # edges:
+ if any((c < 0 for c in en.edge_crease_list)):
+ layerkey = bm.edges.layers.crease.new("SubsurfCrease")
+ for i, edge in enumerate(en.edges):
+ bme = bm.edges.new([bm.verts[edge[0]], bm.verts[edge[1]]])
+ bme[layerkey] = -en.edge_crease_list[i]
+ else:
+ for i, edge in enumerate(en.edges):
+ bm.edges.new([bm.verts[edge[0]], bm.verts[edge[1]]])
+
+ # faces:
+ for face in en.faces:
+ bm.faces.new([bm.verts[i] for i in face])
+
+ """ SEPARATE BLENDER OBJECTS FROM (CON)TEXT / STRUCTURE DXF TYPES """
+ # type(self, dxf entity, name string)
+ # returns blender object
+
+ def _extrusion(self, obj, entity):
+ """
+ extrusion describes the normal vector of the entity
+ """
+ if entity.dxftype not in {"LINE", "POINT"}:
+ if Vector(entity.extrusion) != Vector((0, 0, 1)):
+ transformation = convert.extrusion_to_matrix(entity)
+ obj.location = transformation * obj.location
+ obj.rotation_euler.rotate(transformation)
+
+ def _bbox(self, objects, scene):
+ xmin = ymin = zmin = float('+inf')
+ xmax = ymax = zmax = float('-inf')
+ scene.update()
+
+ for obj in objects:
+ om = obj.matrix_basis
+ for v in obj.bound_box:
+ p = om * Vector(v)
+ if p.x < xmin:
+ xmin = p.x
+ if p.x > xmax:
+ xmax = p.x
+ if p.y < ymin:
+ ymin = p.y
+ if p.y > ymax:
+ ymax = p.y
+ if p.z < zmin:
+ zmin = p.z
+ if p.z > zmax:
+ zmax = p.z
+
+ if xmin == float('+inf'):
+ xmin = 0
+ if ymin == float('+inf'):
+ ymin = 0
+ if zmin == float('+inf'):
+ zmin = 0
+ if xmax == float('-inf'):
+ xmax = 0
+ if ymax == float('-inf'):
+ ymax = 0
+ if zmax == float('-inf'):
+ zmax = 0
+
+ return xmin, ymin, zmin, xmax, ymax, zmax
+
+ def _object_bbox(self, objects, scene, name, do_widgets=True):
+ xmin, ymin, zmin, xmax, ymax, zmax = self._bbox(objects, scene)
+
+ # creating bbox geometry
+ bm = bmesh.new()
+ verts = []
+ # left side vertices
+ verts.append(bm.verts.new((xmin, ymin, zmin)))
+ verts.append(bm.verts.new((xmin, ymin, zmax)))
+ verts.append(bm.verts.new((xmin, ymax, zmax)))
+ verts.append(bm.verts.new((xmin, ymax, zmin)))
+ # right side vertices
+ verts.append(bm.verts.new((xmax, ymin, zmin)))
+ verts.append(bm.verts.new((xmax, ymin, zmax)))
+ verts.append(bm.verts.new((xmax, ymax, zmax)))
+ verts.append(bm.verts.new((xmax, ymax, zmin)))
+
+ # creating the widgets
+ if do_widgets:
+ for i in range(2):
+ for j in range(4):
+ point = verts[j + (i * 4)]
+ prevp = verts[((j - 1) % 4) + (i * 4)]
+ nextp = verts[((j + 1) % 4) + (i * 4)]
+ neigh = verts[(j + (i * 4) + 4) % 8]
+
+ for con in (prevp, nextp, neigh):
+ vec = Vector(con.co - point.co) / 10
+ bm.edges.new((point, bm.verts.new(point.co + vec)))
+
+ d = bpy.data.meshes.new(name + "BBOX")
+ bm.to_mesh(d)
+ o = bpy.data.objects.new(name, d)
+ return o
+
+ def _vertex_duplication(self, x, y, x_count, y_count):
+ bm = bmesh.new()
+ for i in range(x_count):
+ for j in range(y_count):
+ bm.verts.new(Vector((x * i, y * j, 0.)))
+
+ d = bpy.data.meshes.new("vertex_duplicator")
+ bm.to_mesh(d)
+ return d
+
+ def point_object(self, en, scene, name=None):
+ if name is None:
+ name = en.dxftype
+ o = bpy.data.objects.new("Point", None)
+ o.location = self.proj(en.point)
+ self._extrusion(o, en)
+ scene.objects.link(o)
+
+ group = self._get_group(en.layer)
+ group.objects.link(o)
+
+ def light(self, en, scene, name):
+ """
+ en: dxf entity
+ name: ignored; exists to make separate and merged objects methods universally callable from _call_types()
+ Creates, links and returns a new light object depending on the type and color of the dxf entity.
+ """
+ # light_type : distant = 1; point = 2; spot = 3
+ if self.import_light:
+ type_map = ["NONE", "SUN", "POINT", "SPOT"]
+ layer = self.dwg.layers[en.layer]
+ lamp = bpy.data.lamps.new(en.name, type_map[en.light_type])
+ if en.color != 256:
+ aci = en.color
+ else:
+ aci = layer.color
+ c = dxfgrabber.aci_to_true_color(aci)
+ lamp.color = Color(c.rgb())
+ if en.light_type == 3:
+ lamp.spot_size = en.hotspot_angle
+ o = bpy.data.objects.new(en.name, lamp)
+ o.location = self.proj(en.position)
+ dir = self.proj(en.target) - self.proj(en.position)
+ o.rotation_quaternion = dir.rotation_difference(Vector((0, 0, -1)))
+ scene.objects.link(o)
+ return o
+
+ def mtext(self, en, scene, name):
+ """
+ en: dxf entity
+ name: ignored; exists to make separate and merged objects methods universally callable from _call_types()
+ Returns a new multi-line text object.
+ """
+ if self.import_text:
+ text = en.plain_text()
+ name = text[:8]
+ d = bpy.data.curves.new(name, "FONT")
+ o = bpy.data.objects.new(name, d)
+ d.body = text
+ d.size = en.height
+ if en.rect_width is not None:
+ if en.rect_width > 50:
+ width = 50
+ ratio = 50 / en.rect_width
+ d.size = en.height * ratio * 1.4 # XXX HACK
+ scale = (1 / ratio, 1 / ratio, 1 / ratio)
+ d.space_line = en.linespacing
+ else:
+ width = en.rect_width
+ scale = (1, 1, 1)
+ d.text_boxes[0].width = width
+ o.scale = scale
+
+ # HACK
+ d.space_line *= 1.5
+
+ o.rotation_euler = Vector((1, 0, 0)).rotation_difference(en.xdirection).to_euler()
+ o.location = en.insert
+ return o
+
+ def text(self, en, scene, name):
+ """
+ en: dxf entity
+ name: ignored; exists to make separate and merged objects methods universally callable from _call_types()
+ Returns a new single line text object.
+ """
+ if self.import_text:
+ name = en.text[:8]
+ d = bpy.data.curves.new(name, "FONT")
+ d.body = en.plain_text()
+ d.size = en.height
+ o = bpy.data.objects.new(name, d)
+ o.rotation_euler = Euler((0, 0, radians(en.rotation)), 'XYZ')
+ basepoint = self.proj(en.basepoint) if hasattr(en, "basepoint") else self.proj((0, 0, 0))
+ o.location = self.proj((en.insert)) + basepoint
+ if hasattr(en, "thickness"):
+ et = en.thickness / 2
+ d.extrude = abs(et)
+ if et > 0:
+ o.location.z += et
+ elif et < 0:
+ o.location.z -= et
+ return o
+
+ def block_linked_object(self, entity, scene, name=None, override_group=None, invisible=None, recursion_level=0):
+ """
+ entity: DXF entity of type `BLOCK`
+ name: to be consistent with all functions that get mapped to a DXF type; is set when called from insert()
+ override_group: is set when called from insert() (bpy_types.Group)
+ invisible: boolean to control the visibility of the returned object
+ Returns an object. All blocks with the same name share the same geometry (Linked Blender Objects). If the
+ entities in a block have to mapped to different Blender-types (like curve, mesh, surface, text, light) a list of
+ sub-objects is being created. `INSERT`s inside a block are being added to the same list. If the list has more
+ than one element they are being parented to a newly created Blender-Empty which is return instead of the
+ single object in the sub-objects-list that is being returned only if it is the only object in the list.
+ """
+ def _recursive_copy_inserts(parent, known_inserts, inserts, group, invisible):
+ for ki in known_inserts:
+ new_insert = ki.copy()
+ _recursive_copy_inserts(new_insert, ki.children, None, group, invisible)
+ if new_insert.name not in group.objects:
+ group.objects.link(new_insert)
+ if invisible is not None:
+ new_insert.hide = bool(invisible)
+ if inserts is not None:
+ inserts.append(new_insert)
+ new_insert.parent = parent
+ scene.objects.link(new_insert)
+
+ if name is None:
+ name = entity.name
+ # get group
+ if override_group is not None:
+ group = override_group
+ else:
+ group = self._get_group(entity.layer)
+
+ # get object(s)
+ objects = []
+ inserts = []
+ if name not in self.known_blocks.keys():
+ block_inserts = [en for en in entity if is_.insert(en.dxftype)]
+ bc = (en for en in entity if is_.combined_entity(en))
+ bs = (en for en in entity if is_.separated_entity(en) and not is_.insert(en.dxftype))
+
+ if self.combination != SEPARATED:
+ objects += self.combined_objects(bc, scene, "BL|" + name, group)
+ else:
+ bs = (en for en in entity if (is_.combined_entity(en) or is_.separated_entity(en)) and
+ not is_.insert(en.dxftype))
+ objects += self.separated_entities(bs, scene, "BL|" + name, group)
+
+ # create inserts - RECURSION
+ insert_bounding_boxes = []
+ for INSERT in block_inserts:
+ insert = self.insert(INSERT, scene, None, group, invisible, recursion_level + 1)
+ if len(insert.children) > 0:
+ i_copy = bpy.data.objects.new(insert.name, None)
+ i_copy.matrix_basis = insert.matrix_basis
+ scene.objects.link(i_copy)
+ group.objects.link(i_copy)
+ kids = insert.children[:]
+ for child in kids:
+ child.parent = i_copy
+ inserts.append(i_copy)
+ insert_bounding_boxes.append(insert)
+ else:
+ inserts.append(insert)
+
+ # determining the main object o
+ if len(objects) > 1 or len(insert_bounding_boxes) > 0:
+ if self.do_bounding_boxes:
+ o = self._object_bbox(objects + insert_bounding_boxes, scene, name, recursion_level == 0)
+ scene.objects.link(o)
+ else:
+ o = bpy.data.objects.new(name, None)
+ scene.objects.link(o)
+ if len(objects) > 0:
+ for obj in objects:
+ obj.parent = o
+
+ elif len(objects) == 1:
+ o = objects.pop(0)
+ else:
+ # strange case but possible according to the testfiles
+ o = bpy.data.objects.new(name, None)
+ scene.objects.link(o)
+
+ # unlink bounding boxes of inserts
+ for ib in insert_bounding_boxes:
+ if ib.name in group.objects:
+ group.objects.unlink(ib)
+ scene.objects.unlink(ib)
+
+ # parent inserts to this block before any transformation on the block is being applied
+ for obj in inserts:
+ obj.parent = o
+
+ # put a copy of the retreived objects into the known_blocks dict, so that the attributes being added to
+ # the object from this point onwards (from INSERT attributes) are not being copied to new/other INSERTs
+ self.known_blocks[name] = [[o.copy() for o in objects], inserts]
+
+ # so that it gets assigned to the group and inherits visibility too
+ objects.append(o)
+
+ self.known_blocks[name].append(o.copy())
+ else:
+ known_objects, known_inserts, known_o = self.known_blocks[name]
+
+ for known_object in known_objects:
+ oc = known_object.copy()
+ scene.objects.link(oc)
+ objects.append(oc)
+
+ o = known_o.copy()
+ scene.objects.link(o)
+
+ _recursive_copy_inserts(o, known_inserts, inserts, group, invisible)
+
+ # parent objects to o
+ for obj in objects:
+ obj.parent = o
+
+ objects.append(o)
+
+ # link group
+ for obj in objects:
+ if obj.name not in group.objects:
+ group.objects.link(obj)
+
+ # visibility
+ if invisible is not None:
+ for obj in objects:
+ obj.hide = bool(invisible)
+
+ # block transformations
+ o.location = self.proj(entity.basepoint)
+
+ return o
+
+ def block_group_instances(self, entity, scene, name=None, override_group=None, invisible=None, recursion_level=0):
+ self.did_group_instance = True
+
+ if name is None:
+ name = entity.name
+ # get group
+ if override_group is not None:
+ group = override_group
+ else:
+ group = self._get_group(entity.layer)
+
+ block_group = self._get_group(entity.name+"_BLOCK")
+
+ if "Blocks" not in bpy.data.scenes:
+ block_scene = bpy.data.scenes.new("Blocks")
+ else:
+ block_scene = bpy.data.scenes["Blocks"]
+
+ # create the block
+ if len(block_group.objects) == 0 or name not in self.known_blocks.keys():
+ bpy.context.screen.scene = block_scene
+ block_inserts = [en for en in entity if is_.insert(en.dxftype)]
+ bc = (en for en in entity if is_.combined_entity(en))
+ bs = (en for en in entity if is_.separated_entity(en) and not is_.insert(en.dxftype))
+
+ objects = []
+ if self.combination != SEPARATED:
+ objects += self.combined_objects(bc, block_scene, "BL|" + name, block_group)
+ else:
+ bs = (en for en in entity if (is_.combined_entity(en) or is_.separated_entity(en)) and
+ not is_.insert(en.dxftype))
+ objects += self.separated_entities(bs, block_scene, "BL|" + name, block_group)
+
+ # create inserts - RECURSION
+ inserts = []
+ for INSERT in block_inserts:
+ i = self.insert(INSERT, block_scene, None, block_group, invisible, recursion_level + 1, True)
+ inserts.append(i)
+
+ bbox = self._object_bbox(objects + inserts, block_scene, name, True)
+
+ for i in inserts:
+ sub_group = i.dupli_group
+ block_scene.objects.unlink(i)
+ block_group.objects.unlink(i)
+ i_empty = bpy.data.objects.new(i.name, None)
+ i_empty.matrix_basis = i.matrix_basis
+ i_empty.dupli_type = "GROUP"
+ i_empty.dupli_group = sub_group
+ block_group.objects.link(i_empty)
+ block_scene.objects.link(i_empty)
+
+ self.known_blocks[name] = [objects, inserts, bbox]
+ else:
+ bbox = self.known_blocks[name][2]
+
+ bpy.context.screen.scene = scene
+ o = bbox.copy()
+ # o.empty_draw_size = 0.3
+ o.dupli_type = "GROUP"
+ o.dupli_group = block_group
+ group.objects.link(o)
+ if invisible is not None:
+ o.hide = invisible
+ o.location = self.proj(entity.basepoint)
+ scene.objects.link(o)
+ # block_scene.update()
+
+ return o
+
+ def insert(self, entity, scene, name, group=None, invisible=None, recursion_level=0, need_group_inst=None):
+ """
+ entity: DXF entity
+ name: String; not used but required to be consistent with the methods being called from _call_type()
+ group: Blender group of type (bpy_types.group) being set if called from block()
+ invisible: boolean to control visibility; being set if called from block()
+ """
+ aunits = self.dwg.header.get('$AUNITS', 0)
+
+ # check if group instances are needed
+ kids = sum(1 for i in self.dwg.blocks[entity.name] if i.dxftype == "INSERT")
+ sep = sum(1 for sep in self.dwg.blocks[entity.name] if is_.separated_entity(sep))
+ objtypes = sum(1 for ot, ens in groupsort.by_blender_type(en for en in self.dwg.blocks[entity.name]
+ if is_.combined_entity(en))
+ if ot in {"object_mesh", "object_curve"})
+ if need_group_inst is None:
+ need_group_inst = (entity.row_count or entity.col_count) > 1 and \
+ (kids > 0 or objtypes > 1 or sep > 1 or (objtypes > 0 and sep > 0))
+
+ if group is None:
+ group = self._get_group(entity.layer)
+
+ if self.block_representation == GROUP_INSTANCES or need_group_inst:
+ o = self.block_group_instances(self.dwg.blocks[entity.name], scene, entity.name, group,
+ entity.invisible, recursion_level)
+ else:
+ o = self.block_linked_object(self.dwg.blocks[entity.name], scene, entity.name, group,
+ entity.invisible, recursion_level)
+
+ # column & row
+ if (entity.row_count or entity.col_count) > 1:
+ if len(o.children) == 0 and self.block_representation == LINKED_OBJECTS and not need_group_inst:
+ arr_row = o.modifiers.new("ROW", "ARRAY")
+ arr_col = o.modifiers.new("COL", "ARRAY")
+ arr_row.show_expanded = False
+ arr_row.count = entity.row_count
+ arr_row.relative_offset_displace = (0, entity.row_spacing, 0)
+ arr_col.show_expanded = False
+ arr_col.count = entity.col_count
+ arr_col.relative_offset_displace = (entity.col_spacing / 2, 0, 0)
+
+ else:
+ instance = o
+ x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length
+ y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
+ dm = self._vertex_duplication(x * entity.col_spacing / 2, y * entity.row_spacing,
+ entity.col_count, entity.row_count)
+ o = bpy.data.objects.new(entity.name, dm)
+ instance.parent = o
+ o.dupli_type = "VERTS"
+
+ # insert transformations
+ rot = radians(entity.rotation) if aunits == 0 else entity.rotation
+ o.location += self.proj(entity.insert)
+ o.rotation_euler = Euler((0, 0, rot))
+ o.scale = entity.scale
+
+ # mirror (extrusion value of an INSERT ENTITY)
+ self._extrusion(o, entity)
+
+ # visibility
+ if invisible is None:
+ o.hide = bool(entity.invisible)
+ else:
+ o.hide = bool(invisible)
+
+ # attributes
+ if self.import_text:
+ if entity.attribsfollow:
+ for a in entity.attribs:
+ # Blender custom property
+ o[a.tag] = a.text
+ attname = entity.name + "_" + a.tag
+ scene.objects.link(self.text(a, scene, attname))
+
+ return o
+
+ """ COMBINED BLENDER OBJECT FROM GEOMETRY DXF TYPES """
+ # type(self, dxf entities, object name string)
+ # returns blender object
+
+ def _check3D_object(self, curve):
+ """
+ Checks if a curve object has coordinates that are elevated from the z-plane.
+ If so the curve.dimensions are set to 3D.
+ """
+ if any((p.co.z != 0 for spline in curve.splines for p in spline.points)) or \
+ any((p.co.z != 0 for spline in curve.splines for p in spline.bezier_points)):
+ curve.dimensions = '3D'
+
+ def _merge_lines(self, lines, curve):
+ """
+ lines: list of LINE entities
+ curve: Blender curve data
+ merges a list of LINE entities to a polygon-point-list and adds it to the Blender curve
+ """
+ polylines = line_merger(lines)
+ for polyline in polylines:
+ self._poly(polyline, curve, polyline[0] == polyline[-1])
+
+ def _thickness(self, bm, thickness):
+ """
+ Used for mesh types
+ """
+ if not self.thickness_and_width:
+ return
+ original_faces = [face for face in bm.faces]
+ bm.normal_update()
+ for face in original_faces:
+ normal = face.normal.copy()
+ if normal.z < 0:
+ normal *= -1
+ ret = bmesh.ops.extrude_face_region(bm, geom=[face])
+ new_geom = ret["geom"]
+ verts = (g for g in new_geom if type(g) == bmesh.types.BMVert)
+ for v in verts:
+ v.co += normal * thickness
+ del ret
+ del original_faces
+
+ def _thickness_and_width(self, obj, entity, scene):
+ """
+ Used for curve types
+ """
+ if not self.thickness_and_width:
+ return
+ has_varying_width = is_.varying_width(entity)
+ th = entity.thickness
+ w = entity.width[0][0] if hasattr(entity, "width") else 0
+
+ if w == 0 and not has_varying_width:
+ if th != 0:
+ obj.data.extrude = abs(th / 2)
+ if th > 0:
+ obj.location.z += th / 2
+ else:
+ obj.location.z -= th / 2
+ obj.data.dimensions = "3D"
+ obj.data.twist_mode = "Z_UP"
+
+ else:
+ # CURVE BEVEL
+ ew = entity.width
+ max_w = max((w for w_pair in ew for w in w_pair))
+
+ bevd = bpy.data.curves.new("BEVEL", "CURVE")
+ bevdp = bevd.splines.new("POLY")
+ bevdp.points.add(1)
+ bevdp.points[0].co = Vector((-max_w / 2, 0, 0, 0))
+ bevdp.points[1].co = Vector((max_w / 2, 0, 0, 0))
+
+ bevel = bpy.data.objects.new("BEVEL", bevd)
+ obj.data.bevel_object = bevel
+ scene.objects.link(bevel)
+
+ # CURVE TAPER
+ if has_varying_width and len(ew) == 1:
+ tapd = bpy.data.curves.new("TAPER", "CURVE")
+ tapdp = tapd.splines.new("POLY")
+ # lenlist = convert.bulgepoly_to_lenlist(entity)
+ # amount = len(ew) if entity.is_closed else len(ew) - 1
+
+ tapdp.points[0].co = Vector((0, ew[0][0] / max_w, 0, 0))
+ tapdp.points.add(1)
+ tapdp.points[1].co = Vector((1, ew[0][1] / max_w, 0, 0))
+
+ # for i in range(1, amount):
+ # start_w = ew[i][0]
+ # end_w = ew[i][1]
+ # tapdp.points.add(2)
+ # tapdp.points[-2].co = Vector((sum(lenlist[:i]), start_w / max_w, 0, 0))
+ # tapdp.points[-1].co = Vector((sum(lenlist[:i + 1]), end_w / max_w, 0, 0))
+
+ taper = bpy.data.objects.new("TAPER", tapd)
+ obj.data.taper_object = taper
+ scene.objects.link(taper)
+
+ # THICKNESS FOR CURVES HAVING A WIDTH
+ if th != 0:
+ solidify = obj.modifiers.new("THICKNESS", "SOLIDIFY")
+ solidify.thickness = th
+ solidify.use_even_offset = True
+ solidify.offset = 1
+ solidify.show_expanded = False
+
+ # make the shading look good
+ esp = obj.modifiers.new("EdgeSplit", "EDGE_SPLIT")
+ esp.show_expanded = False
+
+ def _subdivision(self, obj, entity):
+ if entity.subdivision_levels > 0:
+ subd = obj.modifiers.new("SubD", "SUBSURF")
+ subd.levels = entity.subdivision_levels
+ subd.show_expanded = False
+
+ def object_mesh(self, entities, scene, name):
+ """
+ entities: list of DXF entities
+ name: name of the returned Blender object (String)
+ Accumulates all entities into a Blender bmesh and returns a Blender object containing it.
+ """
+ d = bpy.data.meshes.new(name)
+ bm = bmesh.new()
+
+ i = 0
+ for en in entities:
+ i += 1
+ if en.dxftype == "3DFACE":
+ self.the3dface(en, bm)
+ else:
+ dxftype = getattr(self, en.dxftype.lower(), None)
+ if dxftype is not None:
+ dxftype(en, bm)
+ else:
+ self.errors.add(en.dxftype.lower() + " - unknown dxftype")
+ if i > 0:
+ if hasattr(en, "thickness"):
+ if en.thickness != 0:
+ self._thickness(bm, en.thickness)
+ bm.to_mesh(d)
+ o = bpy.data.objects.new(name, d)
+ # for POLYFACE
+ if hasattr(en, "extrusion"):
+ self._extrusion(o, en)
+ if hasattr(en, "subdivision_levels"):
+ self._subdivision(o, en)
+ return o
+ return None
+
+ def object_curve(self, entities, scene, name):
+ """
+ entities: list of DXF entities
+ name: name of the returned Blender object (String)
+ Accumulates all entities in the list into a Blender curve and returns a Blender object containing it.
+ """
+ d = bpy.data.curves.new(name, "CURVE")
+
+ i = 0
+ lines = []
+ for en in entities:
+ i += 1
+ TYPE = en.dxftype
+ if TYPE == "LINE" and self.merge_lines:
+ lines.append(en)
+ continue
+ typefunc = getattr(self, TYPE.lower(), None)
+ if typefunc is not None:
+ typefunc(en, d)
+ else:
+ self.errors.add(en.dxftype.lower() + " - unknown dxftype")
+
+ if len(lines) > 0:
+ self._merge_lines(lines, d)
+
+ if i > 0:
+ self._check3D_object(d)
+ o = bpy.data.objects.new(name, d)
+ self._thickness_and_width(o, en, scene)
+ self._extrusion(o, en)
+ return o
+
+ return None
+
+ def object_surface(self, entities, scene, name):
+ """
+ entities: list of DXF entities
+ name: name of the returned Blender object (String) (for future use and also to make it callable from
+ _call_types()
+ Returns None. Exports all NURB entities to ACIS files if the GUI option for it is set.
+ """
+ def _get_acis_filename(name, ending):
+ df = self.dwg.filename
+ dir = os.path.dirname(df)
+ filename = bpy.path.display_name(df)
+ return os.path.join(dir, "{}_{}.{}".format(filename, name, ending))
+
+ if self.export_acis:
+ for en in entities:
+ if name in self.acis_files:
+ name = name + "." + str(len([n for n in self.acis_files if name in n])).zfill(3)
+ # store SAB files
+ if self.dwg.header.get("$ACADVER", "AC1024") > "AC1024":
+ filename = _get_acis_filename(name, "sab")
+ self.acis_files.append(name)
+ with open(filename, 'wb') as f:
+ f.write(en.acis)
+ # store SAT files
+ else:
+ filename = _get_acis_filename(name, "sat")
+ self.acis_files.append(name)
+ with open(filename, 'w') as f:
+ f.write('\n'.join(en.acis))
+ return None
+
+ """ ITERATE OVER DXF ENTITIES AND CREATE BLENDER OBJECTS """
+
+ def _get_group(self, name):
+ """
+ name: name of group (String)
+ Finds group by name or creates it if it does not exist.
+ """
+ groups = bpy.data.groups
+ if name in groups.keys():
+ group = groups[name]
+ else:
+ group = bpy.data.groups.new(name)
+ return group
+
+ def _call_object_types(self, TYPE, entities, group, name, scene, separated=False):
+ """
+ TYPE: DXF type
+ entities: list of DXF entities
+ group: Blender group (type: bpy_types.Group)
+ name: name of the object that is being created and returned (String)
+ separated: flag to make _call_types uniformly available for combined_objects() and separated_objects()
+ """
+ if separated:
+ entity = entities[0]
+ else:
+ entity = entities
+
+ # call merged geometry methods
+ if is_.mesh(TYPE):
+ o = self.object_mesh(entities, scene, name)
+ elif is_.curve(TYPE):
+ o = self.object_curve(entities, scene, name)
+ elif is_.nurbs(TYPE):
+ o = self.object_surface(entities, scene, name)
+
+ # call separate object methods (or merged geometry if TYPE depending on type)
+ else:
+ try:
+ type_func = getattr(self, TYPE.lower(), None)
+ o = type_func(entity, scene, name)
+ except (AttributeError, TypeError):
+ # don't call self.light(en), self.mtext(o, en), self.text(o, en) with a list of entities
+ if is_.separated(TYPE) and not separated:
+ self.errors.add("DXF-Import: multiple %ss cannot be merged into a Blender object." % TYPE)
+ elif TYPE == "not_mergeable":
+ self.errors.add("DXF-Import: Not mergeable dxf type '%s' should not be called in merge-mode" % TYPE)
+ else:
+ self.errors.add("DXF-import: Unsupported dxftype: %s" % TYPE)
+ raise
+
+ if type(o) == bpy.types.Object:
+ if o.name not in scene.objects:
+ scene.objects.link(o)
+
+ if o.name not in group.objects:
+ group.objects.link(o)
+ return o
+
+ def _recenter(self, scene, name):
+ bpy.context.screen.scene = scene
+ scene.update()
+ bpy.ops.object.select_all(action='DESELECT')
+
+ recenter_objects = (o for o in scene.objects if "BEVEL" not in o.name and "TAPER" not in o.name
+ and o not in self.objects_before)
+ xmin, ymin, zmin, xmax, ymax, zmax = self._bbox(recenter_objects, scene)
+ vmin = Vector((xmin, ymin, zmin))
+ vmax = Vector((xmax, ymax, zmax))
+ center = vmin + (vmax - vmin) / 2
+ for o in (o for o in scene.objects if "BEVEL" not in o.name and "TAPER" not in o.name
+ and o not in self.objects_before and o.parent is None):
+ o.location = o.location - center
+ o.select = True
+
+ if not self.did_group_instance:
+ bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
+
+ if self.pDXF is not None:
+ self.georeference(scene, center)
+ else:
+ scene[name + "_recenter"] = center
+
+ def combined_objects(self, entities, scene, override_name=None, override_group=None):
+ """
+ entities: list of dxf entities
+ override_group & override_name: for use within insert() and block()
+ Adds multiple dxf entities to one Blender object (per blender or dxf type).
+ """
+ objects = []
+ for layer_name, layer_ents in groupsort.by_layer(entities):
+ # group and name
+ if override_group is None:
+ group = self._get_group(layer_name)
+ else:
+ group = override_group
+ if override_name is not None:
+ layer_name = override_name
+
+ # sort
+ if self.combination == BY_LAYER:
+ group_sorted = groupsort.by_blender_type(layer_ents)
+ elif self.combination == BY_DXFTYPE:
+ group_sorted = groupsort.by_dxftype(layer_ents)
+ else:
+ break
+
+ for TYPE, grouped_entities in group_sorted:
+ if self.but_group_by_att:
+ for atts, by_att in groupsort.by_attributes(grouped_entities):
+ thickness, subd, width, extrusion = atts
+ att = ""
+ if thickness != 0:
+ att += "thickness" + str(thickness) + ", "
+ if subd > 0:
+ att += "subd" + str(subd) + ", "
+ if width != [(0, 0)]:
+ att += "width" + str(width) + ", "
+ if extrusion != (0, 0, 1):
+ att += "extrusion" + str([str(round(c, 1)) + ".." + str(c)[-1:] for c in extrusion]) + ", "
+ name = layer_name + "_" + TYPE.replace("object_", "") + "_" + att
+
+ o = self._call_object_types(TYPE, by_att, group, name, scene, False)
+ if o is not None:
+ objects.append(o)
+ else:
+ name = layer_name + "_" + TYPE.replace("object_", "")
+ o = self._call_object_types(TYPE, grouped_entities, group, name, scene, False)
+ if o is not None:
+ objects.append(o)
+ return objects
+
+ def separated_entities(self, entities, scene, override_name=None, override_group=None):
+ """
+ entities: list of dxf entities
+ override_group & override_name: for use within insert() and block()
+ Adds multiple DXF entities to multiple Blender objects.
+ """
+ def _do_it(en):
+ # group and name
+ if override_group is None:
+ group = self._get_group(en.layer)
+ else:
+ group = override_group
+
+ if override_name is None:
+ name = en.dxftype
+ else:
+ name = override_name
+
+ if en.dxftype == "POINT":
+ o = self.point_object(en)
+ else:
+ o = self._call_object_types(en.dxftype, [en], group, name, scene, separated=True)
+
+ if o is not None:
+ objects.append(o)
+
+ objects = []
+ split_entities = []
+
+ for en in entities:
+ split = convert.split_by_width(en)
+ if len(split) > 1:
+ split_entities.extend(split)
+ continue
+ _do_it(en)
+
+ for en in split_entities:
+ _do_it(en)
+
+ return objects
+
+ def entities(self, name, scene=None):
+ """
+ Iterates over all DXF entities according to the options set by user.
+ """
+ if scene is None:
+ scene = bpy.context.scene
+
+ self.current_scene = scene
+
+ if self.recenter:
+ self.objects_before += scene.objects[:]
+
+ if self.combination != SEPARATED:
+ self.combined_objects((en for en in self.dwg.modelspace() if is_.combined_entity(en)), scene)
+ self.separated_entities((en for en in self.dwg.modelspace() if is_.separated_entity(en)), scene)
+ else:
+ self.separated_entities((en for en in self.dwg.modelspace() if en.dxftype != "ATTDEF"), scene)
+
+ if self.recenter:
+ self._recenter(scene, name)
+ elif self.pDXF is not None:
+ self.georeference(scene, Vector((0, 0, 0)))
+
+ if type(self.pScene) is TransverseMercator:
+ scene['SRID'] = "tmerc"
+ elif self.pScene is not None: # assume Proj
+ scene['SRID'] = re.findall("\+init=(.+)\s", self.pScene.srs)[0]
+
+ bpy.context.screen.scene = scene
+
+ return self.errors
+ # trying to import dimensions:
+ # self.separated_objects((block for block in self.dwg.blocks if block.name.startswith("*")))
diff --git a/io_import_dxf/dxfimport/fake_entities.py b/io_import_dxf/dxfimport/fake_entities.py
new file mode 100644
index 00000000..a3df42f0
--- /dev/null
+++ b/io_import_dxf/dxfimport/fake_entities.py
@@ -0,0 +1,45 @@
+# ##### 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>
+
+
+class ArcEntity:
+ """
+ Used in convert.bulge_to_cubic() since bulges define how much a straight polyline segment should be transformed to
+ an arc. ArcEntity is just used to call do.arc() without having a real Arc-Entity from dxfgrabber.
+ """
+ def __init__(self, start, end, center, radius, angdir):
+ self.startangle = start
+ self.endangle = end
+ self.center = center
+ self.radius = radius
+ self.angdir = angdir
+
+ def __str__(self):
+ return "startangle: %s, endangle: %s, center: %s, radius: %s, angdir: %s" % \
+ (str(self.startangle), str(self.endangle), str(self.center), str(self.radius), str(self.angdir))
+
+
+class LineEntity:
+ """
+ Used in do._gen_meshface()
+ """
+ def __init__(self, start, end):
+ self.start = start
+ self.end = end
diff --git a/io_import_dxf/dxfimport/groupsort.py b/io_import_dxf/dxfimport/groupsort.py
new file mode 100644
index 00000000..9722d05e
--- /dev/null
+++ b/io_import_dxf/dxfimport/groupsort.py
@@ -0,0 +1,83 @@
+# ##### 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 itertools
+from . import is_
+from mathutils import Vector
+
+
+def map_dxf_to_blender_type(TYPE):
+ """
+ TYPE: DXF entity type (String)
+ """
+ if is_.mesh(TYPE):
+ return "object_mesh"
+ if is_.curve(TYPE):
+ return "object_curve"
+ if is_.nurbs(TYPE):
+ return "object_surface"
+ else:
+ print("groupsort: not mergeable type ", TYPE)
+ return "not_mergeable"
+
+
+def by_blender_type(entities):
+ """
+ entities: list of DXF entities
+ """
+ keyf = lambda e: map_dxf_to_blender_type(e.dxftype)
+ return itertools.groupby(sorted(entities, key=keyf), key=keyf)
+
+
+def by_layer(entities):
+ """
+ entities: list of DXF entities
+ """
+ keyf = lambda e: e.layer
+ return itertools.groupby(sorted(entities, key=keyf), key=keyf)
+
+
+def by_dxftype(entities):
+ """
+ entities: list of DXF entities
+ """
+ keyf = lambda e: e.dxftype
+ return itertools.groupby(sorted(entities, key=keyf), key=keyf)
+
+
+def by_attributes(entities):
+ """
+ entities: list of DXF entities
+ attributes: thickness and width occuring in curve types; subdivision_levels occuring in MESH dxf types
+ """
+ def attributes(entity):
+ width = [(0, 0)]
+ subd = -1
+ extrusion = entity.extrusion
+ if hasattr(entity, "width"):
+ if any((w != 0 for ww in entity.width for w in ww)):
+ width = entity.width
+ if hasattr(entity, "subdivision_levels"):
+ subd = entity.subdivision_levels
+ if entity.dxftype in {"LINE", "POINT"}:
+ extrusion = (0.0, 0.0, 1.0)
+ return entity.thickness, subd, width, extrusion
+
+ return itertools.groupby(sorted(entities, key=attributes), key=attributes)
diff --git a/io_import_dxf/dxfimport/is_.py b/io_import_dxf/dxfimport/is_.py
new file mode 100644
index 00000000..38928aa0
--- /dev/null
+++ b/io_import_dxf/dxfimport/is_.py
@@ -0,0 +1,129 @@
+# ##### 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>
+
+_MESH_ENTITIES = frozenset(["POLYFACE", "POLYMESH", "MESH", "POINT", "3DFACE", "SOLID", "TRACE"])
+
+
+def mesh_entity(entity):
+ return entity.dxftype in _MESH_ENTITIES
+
+
+def mesh(typestr):
+ return typestr in _MESH_ENTITIES
+
+
+_CURVE_ENTITIES = frozenset(("POLYLINE", "POLYGON", "LWPOLYLINE", "SPLINE",
+ "CIRCLE", "ARC", "ELLIPSE", "LINE", "HELIX"))
+
+
+def curve_entity(entity):
+ return entity.dxftype in _CURVE_ENTITIES
+
+
+def curve(typestr):
+ return typestr in _CURVE_ENTITIES
+
+
+_NURBS_ENTITIES = frozenset(("BODY", "REGION", "PLANESURFACE", "SURFACE", "3DSOLID"))
+
+
+def nurbs_entity(entity):
+ return entity.dxftype in _NURBS_ENTITIES
+
+
+def nurbs(typestr):
+ return typestr in _NURBS_ENTITIES
+
+
+_TEXT_ENTITIES = frozenset(("MTEXT", "TEXT"))
+
+
+def text_entity(entity):
+ return entity.dxftype in _TEXT_ENTITIES
+
+
+def text(typestr):
+ return typestr in _TEXT_ENTITIES
+
+
+def insert_entity(entity):
+ return entity.dxftype == "INSERT"
+
+
+def insert(typestr):
+ return typestr == "INSERT"
+
+
+def light_entity(entity):
+ return entity.dxftype == "LIGHT"
+
+
+def light(typestr):
+ return typestr == "LIGHT"
+
+
+def attrib(entity):
+ return entity.dxftype == "ATTDEF"
+
+
+def attrib(typestr):
+ return typestr == "ATTDEF"
+
+
+_2D_ENTITIES = frozenset(("CIRCLE", "ARC", "SOLID", "TRACE", "TEXT", "ATTRIB", "ATTDEF", "SHAPE",
+ "INSERT", "LWPOLYLINE", "HATCH", "IMAGE", "ELLIPSE"))
+
+
+def _2D_entity(entity):
+ return entity.dxftype in _2D_ENTITIES or (entity.dxftype == "POLYGON" and entity.mode == "spline2d")
+
+
+def varying_width(entity):
+ if hasattr(entity, "width"):
+ ew = entity.width
+ if hasattr(ew, "__iter__"):
+ return ew.count(ew[0]) != len(ew) or ew[0][0] != ew[0][1]
+ return False
+
+
+_SEPERATED_ENTITIES = frozenset(("POLYFACE", "POLYMESH", "LIGHT", "MTEXT", "TEXT", "INSERT", "BLOCK"))
+
+
+def separated_entity(entity):
+ """
+ Indicates if the entity should be imported to one single Blender object or if it can be merged with other entities.
+ This depends not only on the type of a dxf-entity but also whether the width values are varying or all the same.
+ """
+ return entity.dxftype in _SEPERATED_ENTITIES or varying_width(entity)
+
+
+def separated(typestr):
+ return typestr in _SEPERATED_ENTITIES
+
+
+_NOT_COMBINED_ENTITIES = frozenset(tuple(_SEPERATED_ENTITIES) + ("ATTDEF",))
+
+
+def combined_entity(entity):
+ return not separated_entity(entity) and not entity.dxftype == "ATTDEF"
+
+
+def combined(typestr):
+ return typestr not in _NOT_COMBINED_ENTITIES
diff --git a/io_import_dxf/dxfimport/line_merger.py b/io_import_dxf/dxfimport/line_merger.py
new file mode 100644
index 00000000..78e78cc6
--- /dev/null
+++ b/io_import_dxf/dxfimport/line_merger.py
@@ -0,0 +1,113 @@
+# ##### 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>
+
+
+def line_merger(lines, precision=6):
+ merger = _LineMerger(lines, precision)
+ return merger.polylines
+
+
+def _round_point(point, precision):
+ return tuple(round(c, precision) for c in point)
+
+
+class _LineMerger:
+ def __init__(self, lines, precision):
+ self.segments = set() # single lines as tuples: ((sx, sy[, sz]), (ex, ey[, ez]))
+ self.used_segments = set()
+ self.points = dict() # key: point -> value: list of segments with this point as start or end point
+ self.precision = precision
+ self.setup(lines)
+ self.polylines = self.merge_lines() # result of merging process
+
+ def setup(self, lines):
+ for line in lines:
+ s = _round_point(line.start, self.precision)
+ e = _round_point(line.end, self.precision)
+ self.add_segment(s, e)
+
+ def add_segment(self, start, end):
+ if start == end:
+ return # this is not a segment
+ if end < start: # order start and end points to detect all doubles
+ segment = (end, start)
+ else:
+ segment = (start, end)
+ if segment in self.segments:
+ return # this segment already exist
+ self.segments.add(segment)
+ self.add_point(start, segment)
+ self.add_point(end, segment)
+
+ def add_point(self, point, segment):
+ segments = self.points.get(point)
+ if segments is None:
+ segments = list()
+ self.points[point] = segments
+ segments.append(segment)
+
+ def get_segment_with_point(self, point):
+ segments = self.points.get(point)
+ if segments is None:
+ return None
+
+ # Very important: do not return already used segments
+ for segment in segments:
+ if segment not in self.used_segments:
+ return segment
+ return None
+
+ def mark_as_used_segment(self, segment):
+ self.used_segments.add(segment)
+ self.segments.discard(segment)
+
+ def merge_lines(self):
+ def get_extension_point(point):
+ extension = self.get_segment_with_point(point)
+ if extension is not None:
+ self.mark_as_used_segment(extension)
+ if extension[0] == point:
+ return extension[1]
+ else:
+ return extension[0]
+ return None
+
+ polylines = []
+ while len(self.segments):
+ segment = self.segments.pop() # take an arbitrary segment
+ self.mark_as_used_segment(segment)
+ polyline = list(segment) # start a new polyline
+ extend_start = True
+ extend_end = True
+ while extend_start or extend_end:
+ if extend_start:
+ extension_point = get_extension_point(polyline[0]) # extend start of polyline
+ if extension_point is not None:
+ polyline.insert(0, extension_point)
+ else:
+ extend_start = False
+ if extend_end:
+ extension_point = get_extension_point(polyline[-1]) # extend end of polyline
+ if extension_point is not None:
+ polyline.append(extension_point)
+ else:
+ extend_end = False
+ polylines.append(polyline)
+ return polylines
diff --git a/io_import_dxf/transverse_mercator.py b/io_import_dxf/transverse_mercator.py
new file mode 100644
index 00000000..84b155b3
--- /dev/null
+++ b/io_import_dxf/transverse_mercator.py
@@ -0,0 +1,54 @@
+# ##### 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>
+
+from math import sin, cos, atan, atanh, radians, tan, sinh, asin, cosh, degrees
+
+# see conversion formulas at
+# http://en.wikipedia.org/wiki/Transverse_Mercator_projection
+# http://mathworld.wolfram.com/MercatorProjection.html
+
+
+class TransverseMercator:
+ radius = 6378137
+
+ def __init__(self, lat=0, lon=0):
+ self.lat = lat # in degrees
+ self.lon = lon # in degrees
+ self.lat_rad = radians(self.lat)
+ self.lon_rad = radians(self.lon)
+
+ def fromGeographic(self, lat, lon):
+ lat_rad = radians(lat)
+ lon_rad = radians(lon)
+ B = cos(lat_rad) * sin(lon_rad - self.lon_rad)
+ x = self.radius * atanh(B)
+ y = self.radius * (atan(tan(lat_rad) / cos(lon_rad - self.lon_rad)) - self.lat_rad)
+ return x, y
+
+ def toGeographic(self, x, y):
+ x /= self.radius
+ y /= self.radius
+ D = y + self.lat_rad
+ lon = atan(sinh(x) / cos(D))
+ lat = asin(sin(D) / cosh(x))
+
+ lon = self.lon + degrees(lon)
+ lat = degrees(lat)
+ return lat, lon