diff options
Diffstat (limited to 'io_import_dxf')
-rw-r--r-- | io_import_dxf/dxfgrabber/dxfentities.py | 1264 | ||||
-rw-r--r-- | io_import_dxf/dxfgrabber/dxfobjects.py | 99 | ||||
-rw-r--r-- | io_import_dxf/dxfimport/is_.py | 2 |
3 files changed, 1365 insertions, 0 deletions
diff --git a/io_import_dxf/dxfgrabber/dxfentities.py b/io_import_dxf/dxfgrabber/dxfentities.py new file mode 100644 index 00000000..5c83a882 --- /dev/null +++ b/io_import_dxf/dxfgrabber/dxfentities.py @@ -0,0 +1,1264 @@ +# encoding: utf-8 +# Purpose: entity classes, new implementation without dxf12/dxf13 layer +# Created: 17.04.2016 +# Copyright (C) 2016, Manfred Moitzi +# License: MIT License +from __future__ import unicode_literals +__author__ = "mozman <mozman@gmx.at>" + +import math + +from . import const +from .color import TrueColor +from .styles import default_text_style +from .decode import decode + +SPECIAL_CHARS = { + 'd': '°' +} + + +basic_attribs = { + 5: 'handle', + 6: 'linetype', + 8: 'layer', + 39: 'thickness', + 48: 'ltscale', + 62: 'color', + 67: 'paperspace', + 210: 'extrusion', + 284: 'shadow_mode', + 330: 'owner', + 370: 'line_weight', + 410: 'layout_tab_name', +} + + +class DXFEntity(object): + def __init__(self): + self.dxftype = 'ENTITY' + self.handle = None + self.owner = None + self.paperspace = None + self.layer = '0' + self.linetype = None + self.thickness = 0.0 + self.extrusion = None + self.ltscale = 1.0 + self.line_weight = 0 + self.invisible = 0 + self.color = const.BYLAYER + self.true_color = None + self.transparency = None + self.shadow_mode = None + self.layout_tab_name = None + + def setup_attributes(self, tags): + self.dxftype = tags.get_type() + for code, value in tags.plain_tags(): + if code in basic_attribs: + self.__setattr__(basic_attribs[code], value) + elif code == 420: + self.true_color = TrueColor(value) + elif code == 440: + self.transparency = 1. - float(value & 0xFF) / 255. + else: + yield code, value # chain of generators + + def set_default_extrusion(self): # call only for 2d entities with extrusion vector + if self.extrusion is None: + self.extrusion = (0., 0., 1.) + + def __str__(self): + return "{} [{}]".format(self.dxftype, self.handle) + + +class Point(DXFEntity): + def __init__(self): + super(Point, self).__init__() + self.point = (0, 0, 0) + + def setup_attributes(self, tags): + for code, value in super(Point, self).setup_attributes(tags): + if code == 10: + self.point = value + else: + yield code, value # chain of generators + self.set_default_extrusion() + + +class Line(DXFEntity): + def __init__(self): + super(Line, self).__init__() + self.start = (0, 0, 0) + self.end = (0, 0, 0) + + def setup_attributes(self, tags): + for code, value in super(Line, self).setup_attributes(tags): + if code == 10: + self.start = value + elif code == 11: + self.end = value + else: + yield code, value # chain of generators + + +class Circle(DXFEntity): + def __init__(self): + super(Circle, self).__init__() + self.center = (0, 0, 0) + self.radius = 1.0 + + def setup_attributes(self, tags): + for code, value in super(Circle, self).setup_attributes(tags): + if code == 10: + self.center = value + elif code == 40: + self.radius = value + else: + yield code, value # chain of generators + self.set_default_extrusion() + + +class Arc(Circle): + def __init__(self): + super(Arc, self).__init__() + self.start_angle = 0. + self.end_angle = 360. + + def setup_attributes(self, tags): + for code, value in super(Arc, self).setup_attributes(tags): + if code == 50: + self.start_angle = value + elif code == 51: + self.end_angle = value + else: + yield code, value # chain of generators + self.set_default_extrusion() + +TRACE_CODES = frozenset((10, 11, 12, 13)) + + +class Trace(DXFEntity): + def __init__(self): + super(Trace, self).__init__() + self.points = [] + + def setup_attributes(self, tags): + for code, value in super(Trace, self).setup_attributes(tags): + if code in TRACE_CODES: + self.points.append(value) + else: + yield code, value # chain of generators + self.set_default_extrusion() + + +Solid = Trace + + +class Face(Trace): + def __init__(self): + super(Face, self).__init__() + self.points = [] + self.invisible_edge = 0 + + def setup_attributes(self, tags): + for code, value in super(Face, self).setup_attributes(tags): + if code == 70: + self.invisible_edge = value + else: + yield code, value # chain of generators + self.set_default_extrusion() + + def is_edge_invisible(self, edge): + # edges 0 .. 3 + return bool(self.invisible_edge & (1 << edge)) + + +class Text(DXFEntity): + def __init__(self): + super(Text, self).__init__() + self.insert = (0., 0.) + self.height = 1.0 + self.text = "" + self.rotation = 0. + self.oblique = 0. + self.style = "STANDARD" + self.width = 1. + self.is_backwards = False + self.is_upside_down = False + self.halign = 0 + self.valign = 0 + self.align_point = None + self.font = "" + self.big_font = "" + + def setup_attributes(self, tags): + for code, value in super(Text, self).setup_attributes(tags): + if code == 10: + self.insert = value + elif code == 11: + self.align_point = value + elif code == 1: + self.text = value + elif code == 7: + self.style = value + elif code == 40: + self.height = value + elif code == 41: + self.width = value + elif code == 50: + self.rotation = value + elif code == 51: + self.oblique = value + elif code == 71: + self.is_backwards = bool(value & 2) + self.is_upside_down = bool(value & 4) + elif code == 72: + self.halign = value + elif code == 73: + self.valign = value + else: + yield code, value # chain of generators + self.set_default_extrusion() + + 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.big_font is None: + self.big_font = style.big_font + + 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 Attrib(Text): + def __init__(self): + super(Attrib, self).__init__() + self.field_length = 0 + self.tag = "" + + def setup_attributes(self, tags): + for code, value in super(Attrib, self).setup_attributes(tags): + if code == 2: + self.tag = value + elif code == 73: + self.field_length = value + else: + yield code, value + + +class Insert(DXFEntity): + def __init__(self): + super(Insert, self).__init__() + self.name = "" + self.insert = (0., 0., 0.) + self.rotation = 0. + self.scale = (1., 1., 1.) + self.row_count = 1 + self.row_spacing = 0. + self.col_count = 1 + self.col_spacing = 0. + self.attribsfollow = False + self.attribs = [] + + def setup_attributes(self, tags): + xscale = 1. + yscale = 1. + zscale = 1. + for code, value in super(Insert, self).setup_attributes(tags): + if code == 2: + self.name = value + elif code == 10: + self.insert = value + elif code == 41: + xscale = value + elif code == 42: + yscale = value + elif code == 43: + zscale = value + elif code == 44: + self.col_spacing = value + elif code == 45: + self.row_spacing = value + elif code == 50: + self.rotation = value + elif code == 66: + self.attribsfollow = bool(value) + elif code == 70: + self.col_count = value + elif code == 71: + self.row_count = value + else: + yield code, value # chain of generators + self.scale = (xscale, yscale, zscale) + self.set_default_extrusion() + + 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 Polyline(DXFEntity): + LINE_TYPES = frozenset(('spline2d', 'polyline2d', 'polyline3d')) + + def __init__(self): + super(Polyline, self).__init__() + self.vertices = [] # set in append data + self.points = [] # set in append data + self.control_points = [] # set in append data + self.width = [] # set in append data + self.bulge = [] # set in append data + self.tangents = [] # set in append data + self.flags = 0 + self.mode = 'polyline2d' + self.mcount = 0 + self.ncount = 0 + self.default_start_width = 0. + self.default_end_width = 0. + self.is_mclosed = False + self.is_nclosed = False + self.is_closed = False + self.elevation = (0., 0., 0.) + self.m_smooth_density = 0. + self.n_smooth_density = 0. + self.smooth_type = 0 + self.spline_type = None + + def setup_attributes(self, tags): + def get_mode(): + 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' + + for code, value in super(Polyline, self).setup_attributes(tags): + if code == 10: + self.elevation = value + elif code == 40: + self.default_start_width = value + elif code == 41: + self.default_end_width = value + elif code == 70: + self.flags = value + elif code == 71: + self.mcount = value + elif code == 72: + self.ncount = value + elif code == 73: + self.m_smooth_density = value + elif code == 73: + self.n_smooth_density = value + elif code == 75: + self.smooth_type = value + else: + yield code, value # chain of generators + + self.mode = get_mode() + 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? + self.is_mclosed = bool(self.flags & const.POLYLINE_MESH_CLOSED_M_DIRECTION) + self.is_nclosed = bool(self.flags & const.POLYLINE_MESH_CLOSED_N_DIRECTION) + self.is_closed = self.is_mclosed + self.set_default_extrusion() + + def __len__(self): + return len(self.vertices) + + def __getitem__(self, item): + return self.vertices[item] + + def __iter__(self): + return iter(self.vertices) + + 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 Polyline.LINE_TYPES: + for vertex in self.vertices: + if vertex.flags & const.VTX_SPLINE_FRAME_CONTROL_POINT: + self.control_points.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 PolyShape(object): + def __init__(self, polyline, dxftype): + # copy all dxf attributes from polyline + for key, value in polyline.__dict__.items(): + self.__dict__[key] = value + self.dxftype = dxftype + + def __str__(self): + return "{} [{}]".format(self.dxftype, self.handle) + + +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._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') + + def __iter__(self): + return iter(self.vertices) + + def get_location(self, pos): + return self.get_vertex(pos).location + + def get_vertex(self, pos): + m, n = pos + if 0 <= m < self.mcount and 0 <= n < self.ncount: + pos = m * self.ncount + n + return self.vertices[pos] + else: + raise IndexError(repr(pos)) + + +class Vertex(DXFEntity): + def __init__(self): + super(Vertex, self).__init__() + self.location = (0., 0., 0.) + self.flags = 0 + self.start_width = 0. + self.end_width = 0. + self.bulge = 0. + self.tangent = None + self.vtx = None + + def setup_attributes(self, tags): + vtx0 = 0 + vtx1 = 0 + vtx2 = 0 + vtx3 = 0 + for code, value in super(Vertex, self).setup_attributes(tags): + if code == 10: + self.location = value + elif code == 40: + self.start_width = value + elif code == 41: + self.end_width = value + elif code == 42: + self.bulge = value + elif code == 50: + self.tangent = value + elif code == 70: + self.flags = value + elif code == 71: + vtx0 = value + elif code == 72: + vtx1 = value + elif code == 73: + vtx2 = value + elif code == 74: + vtx3 = value + else: + yield code, value # chain of generators + indices = (vtx0, vtx1, vtx2, vtx3) + if any(indices): + self.vtx = indices + + def __getitem__(self, item): + return self.location[item] + + def __iter__(self): + return iter(self.location) + + +class Block(DXFEntity): + def __init__(self): + super(Block, self).__init__() + self.basepoint = (0, 0, 0) + self.name = '' + self.description = '' + self.flags = 0 + self.xrefpath = "" + self._entities = [] + + def setup_attributes(self, tags): + for code, value in super(Block, self).setup_attributes(tags): + if code == 2: + self.name = value + elif code == 4: + self.description = value + elif code == 1: + self.xrefpath = value + elif code == 10: + self.basepoint = value + elif code == 70: + self.flags = value + else: + yield code, value # chain of generators + + @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 LWPolyline(DXFEntity): + def __init__(self): + super(LWPolyline, self).__init__() + self.points = [] + self.width = [] + self.bulge = [] + self.elevation = 0. + self.const_width = 0. + self.flags = 0 + + def setup_attributes(self, tags): + bulge, start_width, end_width = 0., 0., 0. + init = True + + for code, value in super(LWPolyline, self).setup_attributes(tags): + if code == 10: + if not init: + self.bulge.append(bulge) + self.width.append((start_width, end_width)) + bulge, start_width, end_width = 0., 0., 0. + self.points.append(value) + init = False + elif code == 40: + start_width = value + elif code == 41: + end_width = value + elif code == 42: + bulge = value + elif code == 38: + self.elevation = value + elif code == 39: + self.thickness = value + elif code == 43: + self.const_width = value + elif code == 70: + self.flags = value + elif code == 210: + self.extrusion = value + else: + yield code, value # chain of generators + + # add values for the last point + self.bulge.append(bulge) + self.width.append((start_width, end_width)) + + if self.const_width != 0.: + self.width = [] + self.set_default_extrusion() + + @property + def is_closed(self): + return bool(self.flags & 1) + + def __len__(self): + return len(self.points) + + def __getitem__(self, item): + return self.points[item] + + def __iter__(self): + return iter(self.points) + + +class Ellipse(DXFEntity): + def __init__(self): + super(Ellipse, self).__init__() + self.center = (0., 0., 0.) + self.major_axis = (1., 0., 0.) + self.ratio = 1.0 + self.start_param = 0. + self.end_param = 6.283185307179586 + + def setup_attributes(self, tags): + for code, value in super(Ellipse, self).setup_attributes(tags): + if code == 10: + self.center = value + elif code == 11: + self.major_axis = value + elif code == 40: + self.ratio = value + elif code == 41: + self.start_param = value + elif code == 42: + self.end_param = value + else: + yield code, value # chain of generators + self.set_default_extrusion() + + +class Ray(DXFEntity): + def __init__(self): + super(Ray, self).__init__() + self.start = (0, 0, 0) + self.unit_vector = (1, 0, 0) + + def setup_attributes(self, tags): + for code, value in super(Ray, self).setup_attributes(tags): + if code == 10: + self.start = value + elif code == 11: + self.unit_vector = value + else: + yield code, value # chain of generators + + +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(DXFEntity): + def __init__(self): + super(MText, self).__init__() + self.insert = (0., 0., 0.) + self.raw_text = "" + self.height = 0. + self.rect_width = None + self.horizontal_width = None + self.vertical_height = None + self.line_spacing = 1. + self.attachment_point = 1 + self.style = 'STANDARD' + self.xdirection = (1., 0., 0.) + self.font = None + self.big_font = None + + def setup_attributes(self, tags): + text = "" + lines = [] + rotation = 0. + xdir = None + for code, value in super(MText, self).setup_attributes(tags): + if code == 10: + self.insert = value + elif code == 11: + xdir = value + elif code == 1: + text = value + elif code == 3: + lines.append(value) + elif code == 7: + self.style = value + elif code == 40: + self.height = value + elif code == 41: + self.rect_width = value + elif code == 42: + self.horizontal_width = value + elif code == 43: + self.vertical_height = value + elif code == 44: + self.line_spacing = value + elif code == 50: + rotation = value + elif code == 71: + self.attachment_point = value + else: + yield code, value # chain of generators + + lines.append(text) + self.raw_text = "".join(lines) + if xdir is None: + xdir = deg2vec(rotation) + self.xdirection = normalized(xdir) + self.set_default_extrusion() + + def lines(self): + return self.raw_text.split('\P') + + def plain_text(self, split=False): + chars = [] + raw_chars = list(reversed(self.raw_text)) # 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.big_font is None: + self.big_font = style.font + + +class Light(DXFEntity): + def __init__(self): + super(Light, self).__init__() + self.version = 1 + self.name = "" + self.light_type = 1 # distant = 1; point = 2; spot = 3 + self.status = False # on/off ? + self.light_color = 0 # 0 is unset + self.true_color = None # None is unset + self.plot_glyph = 0 + self.intensity = 0 + self.position = (0., 0., 1.) + self.target = (0., 0., 0.) + self.attenuation_type = 0 # 0 = None; 1 = Inverse Linear; 2 = Inverse Square + self.use_attenuation_limits = False + self.attenuation_start_limit = 0 + self.attenuation_end_limit = 0 + self.hotspot_angle = 0 + self.fall_off_angle = 0. + self.cast_shadows = False + self.shadow_type = 0 # 0 = Ray traced shadows; 1 = Shadow maps + self.shadow_map_size = 0 + self.shadow_softness = 0 + + def setup_attributes(self, tags): + for code, value in super(Light, self).setup_attributes(tags): + if code == 1: + self.name = value + elif code == 10: + self.position = value + elif code == 11: + self.target = value + elif code == 40: + self.intensity = value + elif code == 41: + self.attenuation_start_limit = value + elif code == 42: + self.attenuation_end_limit = value + elif code == 50: + self.hotspot_angle = value + elif code == 51: + self.fall_off_angle = value + elif code == 63: + self.light_color = value + elif code == 70: + self.light_type = value + elif code == 72: + self.attenuation_type = value + elif code == 73: + self.shadow_type = value + elif code == 90: + self.version = value + elif code == 91: + self.shadow_map_size = value + elif code == 280: + self.shadow_softness = value + elif code == 290: + self.status = value + elif code == 291: + self.plot_glyph = value + elif code == 292: + self.use_attenuation_limits = value + elif code == 293: + self.cast_shadows = value + elif code == 421: + self.true_color = value + else: + yield code, value # chain of generators + + +class Body(DXFEntity): + def __init__(self): + super(Body, self).__init__() + # need handle to get SAB data in DXF version AC1027 and later + self.version = 1 + self.acis = [] + + def setup_attributes(self, tags): + sat = [] + for code, value in super(Body, self).setup_attributes(tags): + if code == 70: + self.version = value + elif code in (1, 3): + sat.append(value) + else: + yield code, value # chain of generators + self.acis = decode(sat) + + 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 + + +class Surface(Body): + def __init__(self): + super(Body, self).__init__() + self.u_isolines = 0 + self.v_isolines = 0 + + def setup_attributes(self, tags): + for code, value in super(Surface, self).setup_attributes(tags): + if code == 71: + self.u_isolines = value + elif code == 72: + self.v_isolines = value + else: + yield code, value # chain of generators + + +class Mesh(DXFEntity): + def __init__(self): + super(Mesh, self).__init__() + self.version = 2 + self.blend_crease = False + self.subdivision_levels = 1 + # rest are mostly positional tags + self.vertices = [] + self.faces = [] + self.edges = [] + self.edge_crease_list = [] + + def setup_attributes(self, tags): + status = 0 + count = 0 + index_tags = [] + for code, value in super(Mesh, self).setup_attributes(tags): + if code == 10: + self.vertices.append(value) + elif status == -1: # ignore overridden properties at the end of the mesh entity + pass # property override uses also group codes 90, 91, 92 but only at the end of the MESH entity + elif code == 71: + self.version = value + elif code == 72: + self.blend_crease = bool(value) + elif code == 91: + self.subdivision_levels = value + elif 92 <= code <= 95: # 92 = vertices, 93 = faces; 94 = edges 95 = edge creases + if code in (92, 95): + continue # ignore vertices and edge creases count + status = code + count = value + if status == 94: # edge count + count *= 2 + elif code == 140: + self.edge_crease_list.append(value) + elif code == 90 and count > 0: # faces or edges + count -= 1 + index_tags.append(value) + if count < 1: + if status == 93: + self.setup_faces(index_tags) + elif status == 94: + self.setup_edges(index_tags) + index_tags = [] + elif code == 90: # count == 0; start of overridden properties (group code 90 after face or edge list) + status = -1 + else: + yield code, value # chain of generators + + 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]) + + def setup_faces(self, tags): + face = [] + count = 0 + for value in tags: + if count == 0: + if len(face): + self.faces.append(tuple(face)) + del face[:] + count = value + else: + count -= 1 + face.append(value) + + if len(face): + self.faces.append(tuple(face)) + + def setup_edges(self, tags): + self.edges = list(zip(tags[::2], tags[1::2])) + + +class Spline(DXFEntity): + def __init__(self): + super(Spline, self).__init__() + self.normal_vector = None + self.flags = 0 + self.degree = 3 + self.start_tangent = None + self.end_tangent = None + self.knots = [] + self.weights = [] + self.tol_knot = .0000001 + self.tol_control_point = .0000001 + self.tol_fit_point = .0000000001 + self.control_points = [] + self.fit_points = [] + + def setup_attributes(self, tags): + subclass = 'AcDbSpline' + for code, value in super(Spline, self).setup_attributes(tags): + if subclass == 'AcDbHelix': + yield code, value # chain of generators + elif code == 10: + self.control_points.append(value) + elif code == 11: + self.fit_points.append(value) + elif code == 12: + self.start_tangent = value + elif code == 13: + self.end_tangent = value + elif code == 40: + self.knots.append(value) + elif code == 41: + self.weights.append(value) + elif code == 42: + self.tol_knot = value + elif code == 43: + self.tol_control_point = value + elif code == 44: + self.tol_fit_point = value + elif code == 70: + self.flags = value + elif code == 71: + self.degree = value + elif 72 <= code < 75: + pass # ignore knot-, control- and fit point count + elif code == 100: + subclass = value + self.normal_vector = self.extrusion + if len(self.weights) == 0: + self.weights = [1.0] * len(self.control_points) + + @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): + super(Helix, self).__init__() + self.helix_version = (1, 1) + self.axis_base_point = None + self.start_point = None + self.axis_vector = None + self.radius = 0 + self.turns = 0 + self.turn_height = 0 + self.handedness = 0 # 0 = left, 1 = right + self.constrain = 0 + # 0 = Constrain turn height; + # 1 = Constrain turns; + # 2 = Constrain height + + def setup_attributes(self, tags): + helix_major_version = 1 + helix_maintainance_version = 1 + for code, value in super(Helix, self).setup_attributes(tags): + if code == 10: + self.axis_base_point = value + elif code == 11: + self.start_point = value + elif code == 12: + self.axis_vector = value + elif code == 90: + helix_major_version = value + elif code == 91: + helix_maintainance_version = value + elif code == 91: + helix_maintainance_version = value + elif code == 40: + self.radius = value + elif code == 41: + self.turns = value + elif code == 42: + self.turn_height = value + elif code == 290: + self.handedness = value + elif code == 280: + self.constrain = value + else: + yield code, value # chain of generators + self.helix_version = (helix_major_version, helix_maintainance_version) + + +EntityTable = { + 'LINE': Line, + 'POINT': Point, + 'CIRCLE': Circle, + 'ARC': Arc, + 'TRACE': Trace, + 'SOLID': Solid, + '3DFACE': Face, + 'TEXT': Text, + 'INSERT': Insert, + 'SEQEND': DXFEntity, + 'ATTRIB': Attrib, + 'ATTDEF': Attrib, + 'POLYLINE': Polyline, + 'VERTEX': Vertex, + 'BLOCK': Block, + 'ENDBLK': DXFEntity, + 'LWPOLYLINE': LWPolyline, + 'ELLIPSE': Ellipse, + 'RAY': Ray, + 'XLINE': Ray, + 'SPLINE': Spline, + 'HELIX': Helix, + 'MTEXT': MText, + 'MESH': Mesh, + 'LIGHT': Light, + 'BODY': Body, + 'REGION': Body, + '3DSOLID': Body, + 'SURFACE': Surface, + 'PLANESURFACE': Surface, +} + + +def entity_factory(tags): + dxftype = tags.get_type() + cls = EntityTable[dxftype] # get entity class or raise KeyError + entity = cls() # call constructor + list(entity.setup_attributes(tags)) # setup dxf attributes - chain of generators + return entity + diff --git a/io_import_dxf/dxfgrabber/dxfobjects.py b/io_import_dxf/dxfgrabber/dxfobjects.py new file mode 100644 index 00000000..c7ee320b --- /dev/null +++ b/io_import_dxf/dxfgrabber/dxfobjects.py @@ -0,0 +1,99 @@ +# encoding: utf-8 +# Purpose: objects classes +# Created: 17.04.2016 +# Copyright (C) 2016, Manfred Moitzi +# License: MIT License +from __future__ import unicode_literals +__author__ = "mozman <mozman@gmx.at>" + +from datetime import datetime +from .juliandate import calendar_date + + +class DXFObject(object): + def __init__(self): + self.dxftype = 'ENTITY' + self.handle = None + self.owner = None + + def setup_attributes(self, tags): + self.dxftype = tags.get_type() + for code, value in tags.plain_tags(): + if code == 5: # special case 105 handled by STYLE TABLE + self.handle = value + elif code == 330: + self.owner = value + else: + yield code, value # chain of generators + + +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(DXFObject): + def __init__(self): + super(Sun, self).__init__() + self.version = 1 + self.status = False + self.sun_color = None + self.intensity = 0. + self.shadows = False + self.date = datetime.now() + self.daylight_savings_time = False + self.shadow_type = 0 + self.shadow_map_size = 0 + self.shadow_softness = 0. + + def setup_attributes(self, tags): + date = 0 + time = 0 + for code, value in super(Sun, self).setup_attributes(tags): + if code == 40: + self.intensity = value + elif code == 63: + self.sun_color = value + elif code == 70: + self.shadow_type = value + elif code == 71: + self.shadow_map_size = value + elif code == 90: + self.version = value + elif code == 91: + date = value + elif code == 92: + time = value + elif code == 280: + self.shadow_softness = value + elif code == 290: + self.status = bool(value) + elif code == 291: + self.shadows = bool(value) + elif code == 292: + self.daylight_savings_time = bool(value) + else: + yield code, value # chain of generators + + if date > 0: + date = calendar_date(date) + else: + date = datetime.now() + hours, minutes, seconds = unpack_seconds(time) + self.date = datetime(date.year, date.month, date.day, hours, minutes, seconds) + +ObjectsTable = { + 'SUN': Sun, +} + + +def objects_factory(tags): + dxftype = tags.get_type() + cls = ObjectsTable.get(dxftype, DXFObject) # get object class + entity = cls() # call constructor + list(entity.setup_attributes(tags)) # setup dxf attributes - chain of generators + return entity diff --git a/io_import_dxf/dxfimport/is_.py b/io_import_dxf/dxfimport/is_.py index 6a8977a5..c6777530 100644 --- a/io_import_dxf/dxfimport/is_.py +++ b/io_import_dxf/dxfimport/is_.py @@ -140,5 +140,7 @@ def combined(typestr): def extrusion(entity): + if entity.extrusion is None: + return False return Vector(entity.extrusion) != Vector((0, 0, 1)) \ or (hasattr(entity, "elevation") and entity.elevation != 0)
\ No newline at end of file |