diff options
author | Lukas Treyer <treyer@arch.ethz.ch> | 2014-08-19 18:06:16 +0400 |
---|---|---|
committer | Bastien Montagne <montagne29@wanadoo.fr> | 2014-08-19 18:11:49 +0400 |
commit | 82a00ee2a0d8f2001917ddc4e34de4104200eca3 (patch) | |
tree | 7596caf1feaccda00c3db65d689687a4bc60192b /io_import_dxf/dxfgrabber | |
parent | ff4c009b1849d6b7980d589536870ab406c54abd (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')
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() |