1# Copyright (c) 2019-2020 Manfred Moitzi 2# License: MIT License 3from typing import TYPE_CHECKING, List 4from ezdxf.lldxf import validator 5from ezdxf.lldxf.attributes import ( 6 DXFAttr, DXFAttributes, DefSubclass, XType, RETURN_DEFAULT, 7 group_code_mapping, 8) 9from ezdxf.lldxf.const import DXF12, SUBCLASS_MARKER, VERTEXNAMES 10from ezdxf.math import Matrix44, Z_AXIS, NULLVEC, Vec3 11from ezdxf.math.transformtools import OCSTransform 12from .dxfentity import base_class, SubclassProcessor 13from .dxfgfx import DXFGraphic, acdb_entity, elevation_to_z_axis 14from .factory import register_entity 15 16if TYPE_CHECKING: 17 from ezdxf.eztypes import TagWriter, DXFNamespace 18 19__all__ = ['Solid', 'Trace', 'Face3d'] 20 21acdb_trace = DefSubclass('AcDbTrace', { 22 # 1. corner Solid WCS; Trace OCS 23 'vtx0': DXFAttr(10, xtype=XType.point3d, default=NULLVEC), 24 25 # 2. corner Solid WCS; Trace OCS 26 'vtx1': DXFAttr(11, xtype=XType.point3d, default=NULLVEC), 27 28 # 3. corner Solid WCS; Trace OCS 29 'vtx2': DXFAttr(12, xtype=XType.point3d, default=NULLVEC), 30 31 # 4. corner Solid WCS; Trace OCS: 32 # If only three corners are entered to define the SOLID, then the fourth 33 # corner coordinate is the same as the third. 34 'vtx3': DXFAttr(13, xtype=XType.point3d, default=NULLVEC), 35 36 # Elevation is a legacy feature from R11 and prior, do not use this 37 # attribute, store the entity elevation in the z-axis of the vertices. 38 # ezdxf does not export the elevation attribute! 39 'elevation': DXFAttr(38, default=0, optional=True), 40 41 # Thickness could be negative: 42 'thickness': DXFAttr(39, default=0, optional=True), 43 'extrusion': DXFAttr( 44 210, xtype=XType.point3d, default=Z_AXIS, optional=True, 45 validator=validator.is_not_null_vector, 46 fixer=RETURN_DEFAULT, 47 ), 48}) 49acdb_trace_group_codes = group_code_mapping(acdb_trace) 50 51 52class _Base(DXFGraphic): 53 def __getitem__(self, num): 54 return self.dxf.get(VERTEXNAMES[num]) 55 56 def __setitem__(self, num, value): 57 return self.dxf.set(VERTEXNAMES[num], value) 58 59 60@register_entity 61class Solid(_Base): 62 """ DXF SHAPE entity """ 63 DXFTYPE = 'SOLID' 64 DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_trace) 65 66 def load_dxf_attribs( 67 self, processor: SubclassProcessor = None) -> 'DXFNamespace': 68 """ Loading interface. (internal API) """ 69 dxf = super().load_dxf_attribs(processor) 70 if processor: 71 processor.fast_load_dxfattribs( 72 dxf, acdb_trace_group_codes, subclass=2, recover=True) 73 if processor.r12: 74 # Transform elevation attribute from R11 to z-axis values: 75 elevation_to_z_axis(dxf, VERTEXNAMES) 76 return dxf 77 78 def export_entity(self, tagwriter: 'TagWriter') -> None: 79 """ Export entity specific data as DXF tags. (internal API) """ 80 super().export_entity(tagwriter) 81 if tagwriter.dxfversion > DXF12: 82 tagwriter.write_tag2(SUBCLASS_MARKER, acdb_trace.name) 83 if not self.dxf.hasattr('vtx3'): 84 self.dxf.vtx3 = self.dxf.vtx2 85 self.dxf.export_dxf_attribs(tagwriter, [ 86 'vtx0', 'vtx1', 'vtx2', 'vtx3', 'thickness', 87 'extrusion', 88 ]) 89 90 def transform(self, m: Matrix44) -> 'Solid': 91 """ Transform the SOLID/TRACE entity by transformation matrix `m` inplace. 92 """ 93 # SOLID and TRACE are OCS entities. 94 dxf = self.dxf 95 ocs = OCSTransform(self.dxf.extrusion, m) 96 for name in VERTEXNAMES: 97 if dxf.hasattr(name): 98 dxf.set(name, ocs.transform_vertex(dxf.get(name))) 99 if dxf.hasattr('thickness'): 100 dxf.thickness = ocs.transform_length( 101 (0, 0, dxf.thickness), reflection=dxf.thickness) 102 dxf.extrusion = ocs.new_extrusion 103 return self 104 105 def wcs_vertices(self, close: bool = False) -> List[Vec3]: 106 """ Returns WCS vertices in correct order, 107 if argument `close` is ``True``, last vertex == first vertex. 108 Does **not** return duplicated last vertex if represents a triangle. 109 110 .. versionadded:: 0.15 111 112 """ 113 ocs = self.ocs() 114 return list(ocs.points_to_wcs(self.vertices(close))) 115 116 def vertices(self, close: bool = False) -> List[Vec3]: 117 """ Returns OCS vertices in correct order, 118 if argument `close` is ``True``, last vertex == first vertex. 119 Does **not** return duplicated last vertex if represents a triangle. 120 121 .. versionadded:: 0.15 122 123 """ 124 dxf = self.dxf 125 vertices = [dxf.vtx0, dxf.vtx1, dxf.vtx2] 126 if dxf.vtx3 != dxf.vtx2: # when the face is a triangle, vtx2 == vtx3 127 vertices.append(dxf.vtx3) 128 129 # adjust weird vertex order of SOLID and TRACE: 130 # 0, 1, 2, 3 -> 0, 1, 3, 2 131 if len(vertices) > 3: 132 vertices[2], vertices[3] = vertices[3], vertices[2] 133 134 if close and not vertices[0].isclose(vertices[-1]): 135 vertices.append(vertices[0]) 136 return vertices 137 138 139@register_entity 140class Trace(Solid): 141 """ DXF TRACE entity """ 142 DXFTYPE = 'TRACE' 143 144 145acdb_face = DefSubclass('AcDbFace', { 146 # 1. corner WCS: 147 'vtx0': DXFAttr(10, xtype=XType.point3d, default=NULLVEC), 148 149 # 2. corner WCS: 150 'vtx1': DXFAttr(11, xtype=XType.point3d, default=NULLVEC), 151 152 # 3. corner WCS: 153 'vtx2': DXFAttr(12, xtype=XType.point3d, default=NULLVEC), 154 155 # 4. corner WCS: 156 # If only three corners are entered to define the SOLID, then the fourth 157 # corner coordinate is the same as the third. 158 'vtx3': DXFAttr(13, xtype=XType.point3d, default=NULLVEC), 159 160 # invisible: 161 # 1 = First edge is invisible 162 # 2 = Second edge is invisible 163 # 4 = Third edge is invisible 164 # 8 = Fourth edge is invisible 165 'invisible': DXFAttr(70, default=0, optional=True), 166}) 167acdb_face_group_codes = group_code_mapping(acdb_face) 168 169 170@register_entity 171class Face3d(_Base): 172 """ DXF 3DFACE entity """ 173 DXFTYPE = '3DFACE' 174 DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_face) 175 176 def is_invisible_edge(self, num) -> bool: 177 """ Returns True if edge `num` is an invisible edge. """ 178 return bool(self.dxf.invisible & (1 << num)) 179 180 def set_edge_visibility(self, num, status=False): 181 """ Set visibility of edge `num`, status `True` for visible, status 182 `False` for invisible. 183 """ 184 if not status: 185 self.dxf.invisible = self.dxf.invisible | (1 << num) 186 else: 187 self.dxf.invisible = self.dxf.invisible & ~(1 << num) 188 189 def load_dxf_attribs(self, 190 processor: SubclassProcessor = None) -> 'DXFNamespace': 191 dxf = super().load_dxf_attribs(processor) 192 if processor: 193 processor.fast_load_dxfattribs( 194 dxf, acdb_face_group_codes, subclass=2, recover=True) 195 return dxf 196 197 def export_entity(self, tagwriter: 'TagWriter') -> None: 198 super().export_entity(tagwriter) 199 if tagwriter.dxfversion > DXF12: 200 tagwriter.write_tag2(SUBCLASS_MARKER, acdb_face.name) 201 if not self.dxf.hasattr('vtx3'): 202 self.dxf.vtx3 = self.dxf.vtx2 203 self.dxf.export_dxf_attribs(tagwriter, [ 204 'vtx0', 'vtx1', 'vtx2', 'vtx3', 'invisible' 205 ]) 206 207 def transform(self, m: Matrix44) -> 'Face3d': 208 """ Transform the 3DFACE entity by transformation matrix `m` inplace. 209 """ 210 dxf = self.dxf 211 # 3DFACE is a real 3d entity 212 dxf.vtx0, dxf.vtx1, dxf.vtx2, dxf.vtx3 = m.transform_vertices( 213 (dxf.vtx0, dxf.vtx1, dxf.vtx2, dxf.vtx3)) 214 return self 215 216 def wcs_vertices(self, close: bool = False) -> List[Vec3]: 217 """ Returns WCS vertices, if argument `close` is 218 ``True``, last vertex == first vertex. 219 Does **not** return duplicated last vertex if represents a triangle. 220 221 Compatibility interface to SOLID and TRACE. The 3DFACE entity returns 222 already WCS vertices. 223 224 """ 225 dxf = self.dxf 226 vertices = [dxf.vtx0, dxf.vtx1, dxf.vtx2] 227 if dxf.vtx3 != dxf.vtx2: # when the face is a triangle, vtx2 == vtx3 228 vertices.append(dxf.vtx3) 229 230 if close and not vertices[0].isclose(vertices[-1]): 231 vertices.append(vertices[0]) 232 return vertices 233