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/dxfgrabber
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/dxfgrabber')
-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
26 files changed, 3738 insertions, 0 deletions
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()