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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'io_import_dxf/dxfgrabber/tags.py')
-rw-r--r--[-rwxr-xr-x]io_import_dxf/dxfgrabber/tags.py422
1 files changed, 400 insertions, 22 deletions
diff --git a/io_import_dxf/dxfgrabber/tags.py b/io_import_dxf/dxfgrabber/tags.py
index fed6aa88..b406bd02 100755..100644
--- a/io_import_dxf/dxfgrabber/tags.py
+++ b/io_import_dxf/dxfgrabber/tags.py
@@ -5,32 +5,410 @@
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
+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 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
+
+
+def stream_tagger(stream, assure_3d_coords=False):
+ """ Generates DXFTag() from a stream (untrusted external source). Does not skip comment tags 999.
+ """
+ class Counter:
+ def __init__(self):
+ self.counter = 0
+
+ undo_tag = None
+ line = Counter() # writeable line counter for next_tag(), Python 2.7 does not support the nonlocal statement
+
+ def next_tag():
+ code = stream.readline()
+ value = stream.readline()
+ line.counter += 2
+ if code and value: # StringIO(): empty strings indicates EOF
+ return DXFTag(int(code[:-1]), value[:-1]) # without '\n'
+ else: # StringIO(): missing '\n' indicates EOF
+ raise EOFError()
+
+ while True:
+ try:
+ if undo_tag is not None:
+ x = undo_tag
+ undo_tag = None
+ else:
+ x = next_tag()
+ code = x.code
+ if code == 999: # skip comments
+ continue
+ if code in POINT_CODES:
+ y = next_tag() # y coordinate is mandatory
+ if y.code != code + 10:
+ raise DXFStructureError("Missing required y coordinate near line: {}.".format(line.counter))
+ z = next_tag() # z coordinate just for 3d points
+ try:
+ if z.code == code + 20:
+ point = (float(x.value), float(y.value), float(z.value))
+ else:
+ if assure_3d_coords:
+ point = (float(x.value), float(y.value), 0.)
+ else:
+ point = (float(x.value), float(y.value))
+ undo_tag = z
+ except ValueError:
+ raise DXFStructureError('Invalid floating point values near line: {}.'.format(line.counter))
+ yield DXFTag(code, point)
+ else: # just a single tag
+ try:
+ yield cast_tag(x)
+ except ValueError:
+ raise DXFStructureError('Invalid tag (code={code}, value="{value}") near line: {line}.'.format(
+ line=line.counter,
+ code=x.code,
+ value=x.value,
+ ))
+ except EOFError:
+ return
+
+
+def string_tagger(s):
+ return stream_tagger(StringIO(s))
+
+
+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, value).
+ """
+ if end is None:
+ end = len(self)
+ index = start
+ while index < end:
+ if self[index].code == code:
+ return index
+ index += 1
+ 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(string_tagger(text))
+
+ def get_type(self):
+ return self.__getitem__(0).value
+
+ def plain_tags(self): # yield no app data and no xdata
+ is_app_data = False
+ for tag in self:
+ if tag.code >= 1000: # skip xdata
+ continue
+ if tag.code == APP_DATA_MARKER:
+ if is_app_data: # tag.value == '}'
+ is_app_data = False # end of app data
+ else: # value == '{APPID'
+ is_app_data = True # start of app data
+ continue
+ if not is_app_data:
+ yield tag
+
+ def xdata(self):
+ index = 0
+ end = len(self)
+ while index < end:
+ if self[index].code > 999: # all xdata tag codes are >= 1000
+ return self[index:] # xdata is always at the end of the DXF entity
+ index += 1
+ return []
+
+ def app_data(self):
+ app_data = {}
+ app_tags = None
+ for tag in self:
+ if tag.code == APP_DATA_MARKER:
+ if tag.value == '}': # end of app data
+ app_tags.append(tag)
+ app_data[app_tags[0].value] = app_tags
+ app_tags = None
+ else:
+ app_tags = [tag]
+ else:
+ if app_tags is not None: # collection app data
+ app_tags.append(tag)
+ return app_data
+
+ def subclasses(self):
+ classes = {}
+ name = 'noname'
+ tags = []
+ for tag in self.plain_tags():
+ if tag.code == SUBCLASS_MARKER:
+ classes[name] = tags
+ tags = []
+ name = tag.value
+ else:
+ tags.append(tag)
+ classes[name] = tags
+ return classes
+
+ def get_subclass(self, name):
+ classes = self.subclasses()
+ return classes.get(name, 'noname')
+
+
+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._build_groups(tags, split_code)
+
+ def _build_groups(self, tags, splitcode):
+ def append(tag): # first do nothing, skip tags in front of the first split tag
+ pass
+ group = None
+ for tag in tags: # has to work with iterators/generators
+ if tag.code == splitcode:
+ if group is not None:
+ self.append(group)
+ group = Tags([tag])
+ append = group.append # redefine append: add tags to this group
+ else:
+ append(tag)
+ if group is not None:
+ self.append(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(string_tagger(text))
+
class DXFInfo(object):
def __init__(self):
@@ -53,7 +431,7 @@ class DXFInfo(object):
def dxfinfo(stream):
info = DXFInfo()
tag = DXFTag(999999, '')
- tagreader = TagIterator(stream)
+ tagreader = stream_tagger(stream)
while tag != DXFTag(0, 'ENDSEC'):
tag = next(tagreader)
if tag.code != 9: