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