1# Copyright (c) 2020, Manfred Moitzi 2# License: MIT License 3import logging 4import math 5from typing import TYPE_CHECKING, Iterable, Union, cast 6 7from ezdxf.entities import factory 8from ezdxf.lldxf.const import VERTEXNAMES 9from ezdxf.math import Vec3, bulge_to_arc, OCS 10 11logger = logging.getLogger('ezdxf') 12 13if TYPE_CHECKING: 14 from ezdxf.eztypes import ( 15 LWPolyline, Polyline, Line, Arc, Face3d 16 ) 17 18 19def virtual_lwpolyline_entities( 20 lwpolyline: 'LWPolyline') -> Iterable[Union['Line', 'Arc']]: 21 """ Yields 'virtual' entities of LWPOLYLINE as LINE or ARC objects. 22 23 This entities are located at the original positions, but are not stored in 24 the entity database, have no handle and are not assigned to any layout. 25 26 (internal API) 27 28 """ 29 assert lwpolyline.dxftype() == 'LWPOLYLINE' 30 31 points = lwpolyline.get_points('xyb') 32 if len(points) < 2: 33 return 34 35 if lwpolyline.closed: 36 points.append(points[0]) 37 38 yield from _virtual_polyline_entities( 39 points=points, 40 elevation=lwpolyline.dxf.elevation, 41 extrusion=lwpolyline.dxf.get('extrusion', None), 42 dxfattribs=lwpolyline.graphic_properties(), 43 doc=lwpolyline.doc, 44 ) 45 46 47def virtual_polyline_entities( 48 polyline: 'Polyline') -> Iterable[Union['Line', 'Arc', 'Face3d']]: 49 """ Yields 'virtual' entities of POLYLINE as LINE, ARC or 3DFACE objects. 50 51 This entities are located at the original positions, but are not stored in 52 the entity database, have no handle and are not assigned to any layout. 53 54 (internal API) 55 56 """ 57 assert polyline.dxftype() == 'POLYLINE' 58 if polyline.is_2d_polyline: 59 return virtual_polyline2d_entities(polyline) 60 elif polyline.is_3d_polyline: 61 return virtual_polyline3d_entities(polyline) 62 elif polyline.is_polygon_mesh: 63 return virtual_polymesh_entities(polyline) 64 elif polyline.is_poly_face_mesh: 65 return virtual_polyface_entities(polyline) 66 return [] 67 68 69def virtual_polyline2d_entities( 70 polyline: 'Polyline') -> Iterable[Union['Line', 'Arc']]: 71 """ Yields 'virtual' entities of 2D POLYLINE as LINE or ARC objects. 72 73 This entities are located at the original positions, but are not stored in 74 the entity database, have no handle and are not assigned to any layout. 75 76 (internal API) 77 78 """ 79 assert polyline.dxftype() == 'POLYLINE' 80 assert polyline.is_2d_polyline 81 if len(polyline.vertices) < 2: 82 return 83 84 points = [(v.dxf.location.x, v.dxf.location.y, v.dxf.bulge) for v in 85 polyline.vertices] 86 if polyline.is_closed: 87 points.append(points[0]) 88 89 yield from _virtual_polyline_entities( 90 points=points, 91 elevation=Vec3(polyline.dxf.get('elevation', (0, 0, 0))).z, 92 extrusion=polyline.dxf.get('extrusion', None), 93 dxfattribs=polyline.graphic_properties(), 94 doc=polyline.doc, 95 ) 96 97 98def _virtual_polyline_entities( 99 points, elevation: float, extrusion: Vec3, 100 dxfattribs: dict, doc) -> Iterable[Union['Line', 'Arc']]: 101 ocs = OCS(extrusion) if extrusion else OCS() 102 prev_point = None 103 prev_bulge = None 104 105 for x, y, bulge in points: 106 point = Vec3(x, y, elevation) 107 if prev_point is None: 108 prev_point = point 109 prev_bulge = bulge 110 continue 111 112 attribs = dict(dxfattribs) 113 if prev_bulge != 0: 114 center, start_angle, end_angle, radius = bulge_to_arc( 115 prev_point, point, prev_bulge) 116 if radius > 0: 117 attribs['center'] = Vec3(center.x, center.y, elevation) 118 attribs['radius'] = radius 119 attribs['start_angle'] = math.degrees(start_angle) 120 attribs['end_angle'] = math.degrees(end_angle) 121 if extrusion: 122 attribs['extrusion'] = extrusion 123 yield factory.new(dxftype='ARC', dxfattribs=attribs, doc=doc) 124 else: 125 attribs['start'] = ocs.to_wcs(prev_point) 126 attribs['end'] = ocs.to_wcs(point) 127 yield factory.new(dxftype='LINE', dxfattribs=attribs, doc=doc) 128 prev_point = point 129 prev_bulge = bulge 130 131 132def virtual_polyline3d_entities(polyline: 'Polyline') -> Iterable['Line']: 133 """ Yields 'virtual' entities of 3D POLYLINE as LINE objects. 134 135 This entities are located at the original positions, but are not stored in 136 the entity database, have no handle and are not assigned to any layout. 137 138 (internal API) 139 140 """ 141 assert polyline.dxftype() == 'POLYLINE' 142 assert polyline.is_3d_polyline 143 if len(polyline.vertices) < 2: 144 return 145 doc = polyline.doc 146 vertices = polyline.vertices 147 dxfattribs = polyline.graphic_properties() 148 start = -1 if polyline.is_closed else 0 149 for index in range(start, len(vertices) - 1): 150 dxfattribs['start'] = vertices[index].dxf.location 151 dxfattribs['end'] = vertices[index + 1].dxf.location 152 yield factory.new(dxftype='LINE', dxfattribs=dxfattribs, doc=doc) 153 154 155def virtual_polymesh_entities(polyline: 'Polyline') -> Iterable['Face3d']: 156 """ Yields 'virtual' entities of POLYMESH as 3DFACE objects. 157 158 This entities are located at the original positions, but are not stored in 159 the entity database, have no handle and are not assigned to any layout. 160 161 (internal API) 162 163 """ 164 polymesh = cast('Polymesh', polyline) 165 assert polymesh.dxftype() == 'POLYLINE' 166 assert polymesh.is_polygon_mesh 167 168 doc = polymesh.doc 169 mesh = polymesh.get_mesh_vertex_cache() 170 dxfattribs = polymesh.graphic_properties() 171 m_count = polymesh.dxf.m_count 172 n_count = polymesh.dxf.n_count 173 m_range = m_count - int(not polymesh.is_m_closed) 174 n_range = n_count - int(not polymesh.is_n_closed) 175 176 for m in range(m_range): 177 for n in range(n_range): 178 next_m = (m + 1) % m_count 179 next_n = (n + 1) % n_count 180 181 dxfattribs['vtx0'] = mesh[m, n] 182 dxfattribs['vtx1'] = mesh[next_m, n] 183 dxfattribs['vtx2'] = mesh[next_m, next_n] 184 dxfattribs['vtx3'] = mesh[m, next_n] 185 yield factory.new(dxftype='3DFACE', dxfattribs=dxfattribs, doc=doc) 186 187 188def virtual_polyface_entities(polyline: 'Polyline') -> Iterable['Face3d']: 189 """ Yields 'virtual' entities of POLYFACE as 3DFACE objects. 190 191 This entities are located at the original positions, but are not stored in 192 the entity database, have no handle and are not assigned to any layout. 193 194 (internal API) 195 196 """ 197 assert polyline.dxftype() == 'POLYLINE' 198 assert polyline.is_poly_face_mesh 199 200 doc = polyline.doc 201 vertices = polyline.vertices 202 base_attribs = polyline.graphic_properties() 203 204 face_records = (v for v in vertices if v.is_face_record) 205 for face in face_records: 206 face3d_attribs = dict(base_attribs) 207 face3d_attribs.update(face.graphic_properties()) 208 invisible = 0 209 pos = 1 210 211 indices = ((face.dxf.get(name), name) for name in VERTEXNAMES if 212 face.dxf.hasattr(name)) 213 for index, name in indices: 214 # vertex indices are 1-based, negative indices indicate invisible edges 215 if index < 0: 216 index = abs(index) 217 invisible += pos 218 # python list `vertices` is 0-based 219 face3d_attribs[name] = vertices[index - 1].dxf.location 220 # vertex index bit encoded: 1=0b0001, 2=0b0010, 3=0b0100, 4=0b1000 221 pos <<= 1 222 223 face3d_attribs['invisible'] = invisible 224 yield factory.new(dxftype='3DFACE', dxfattribs=face3d_attribs, doc=doc) 225