facemixins.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. # Created: 2011-04-30
  2. # Copyright (c) 2011-2018, Manfred Moitzi
  3. # License: MIT License
  4. from typing import Tuple, TYPE_CHECKING, Iterable, Dict, Sequence, List, cast
  5. from itertools import chain
  6. from ezdxf.lldxf import const
  7. from .polyfacebuilder import PolyfaceBuilder
  8. if TYPE_CHECKING:
  9. from ezdxf.eztypes import Vertex, FaceType, DXFVertex, Polyline
  10. class PolymeshMixin:
  11. __slots__ = ()
  12. def set_mesh_vertex(self, pos: Tuple[int, int], point: 'Vertex', dxfattribs: dict = None):
  13. """
  14. Set location and DXF attributes of a single mesh vertex.
  15. Args:
  16. pos: 0-based (row, col)-tuple, position of mesh vertex
  17. point: (x, y, z)-tuple, new 3D coordinates of the mesh vertex
  18. dxfattribs: dict of DXF attributes
  19. """
  20. dxfattribs = dxfattribs or {}
  21. dxfattribs['location'] = point
  22. vertex = self.get_mesh_vertex(pos)
  23. vertex.update_dxf_attribs(dxfattribs)
  24. def get_mesh_vertex(self, pos: Tuple[int, int]) -> 'DXFVertex':
  25. """
  26. Get location of a single mesh vertex.
  27. Args:
  28. pos: 0-based (row, col)-tuple, position of mesh vertex
  29. """
  30. polyline = cast('Polyline', self)
  31. m_count = polyline.dxf.m_count
  32. n_count = polyline.dxf.n_count
  33. m, n = pos
  34. if 0 <= m < m_count and 0 <= n < n_count:
  35. pos = m * n_count + n
  36. return polyline.__getitem__(pos)
  37. else:
  38. raise const.DXFIndexError(repr(pos))
  39. def get_mesh_vertex_cache(self) -> 'MeshVertexCache':
  40. """
  41. Get a MeshVertexCache() object for this Polymesh. The caching object provides fast access to the location
  42. attributes of the mesh vertices.
  43. """
  44. return MeshVertexCache(cast('Polyline', self))
  45. class MeshVertexCache:
  46. __slots__ = ('vertices',)
  47. """
  48. Cache mesh vertices in a dict, keys are 0-based (row, col)-tuples.
  49. vertices:
  50. Dict of mesh vertices, keys are 0-based (row, col)-tuples. Writing to this dict doesn't change the DXF entity.
  51. """
  52. def __init__(self, mesh: 'Polyline'):
  53. self.vertices = self._setup(mesh, mesh.dxf.m_count, mesh.dxf.n_count) # type: Dict[Tuple[int, int], DXFVertex]
  54. def _setup(self, mesh: 'Polyline', m_count: int, n_count: int) -> dict:
  55. cache = {} # type: Dict[Tuple[int, int], DXFVertex]
  56. vertices = iter(mesh.vertices())
  57. for m in range(m_count):
  58. for n in range(n_count):
  59. cache[(m, n)] = next(vertices)
  60. return cache
  61. def __getitem__(self, pos: Tuple[int, int]) -> 'Vertex':
  62. """
  63. Get mesh vertex location as (x, y, z)-tuple.
  64. """
  65. try:
  66. return self.vertices[pos].dxf.location
  67. except KeyError:
  68. raise const.DXFIndexError(repr(pos))
  69. def __setitem__(self, pos: Tuple[int, int], location: 'Vertex') -> None:
  70. """
  71. Get mesh vertex location as (x, y, z)-tuple.
  72. """
  73. try:
  74. self.vertices[pos].dxf.location = location
  75. except KeyError:
  76. raise const.DXFIndexError(repr(pos))
  77. class PolyfaceMixin:
  78. __slots__ = ()
  79. """
  80. Order of mesh_vertices and face_records is important (DXF R2010):
  81. 1. mesh_vertices: the polyface mesh vertex locations
  82. 2. face_records: indices of the face forming vertices
  83. """
  84. def append_face(self, face: 'FaceType', dxfattribs: dict = None) -> None:
  85. """
  86. Appends a single face. Appending single faces is very inefficient, try collecting single faces and use
  87. Polyface.append_faces().
  88. Args:
  89. face: list of (x, y, z)-tuples
  90. dxfattribs: dict of DXF attributes
  91. """
  92. self.append_faces([face], dxfattribs)
  93. def append_faces(self, faces: Iterable['FaceType'], dxfattribs: dict = None) -> None:
  94. """
  95. Append multiple *faces*. *faces* is a list of single faces and a single face is a list of (x, y, z)-tuples.
  96. Args:
  97. faces: list of (list of (x, y, z)-tuples)
  98. dxfattribs: dict of DXF attributes
  99. """
  100. def new_face_record() -> 'DXFVertex':
  101. dxfattribs['flags'] = const.VTX_3D_POLYFACE_MESH_VERTEX
  102. return self._new_entity('VERTEX', dxfattribs)
  103. dxfattribs = dxfattribs or {}
  104. existing_vertices, existing_faces = self.indexed_faces()
  105. # existing_faces is a generator, can't append new data
  106. new_faces = [] # type: List[FaceProxy]
  107. for face in faces:
  108. # convert face point coordinates to DXF Vertex() objects.
  109. face_mesh_vertices = cast('Polyline', self)._points_to_dxf_vertices(face, {}) # type: List[DXFVertex]
  110. # index of first new vertex
  111. index = len(existing_vertices)
  112. existing_vertices.extend(face_mesh_vertices)
  113. # create a new face_record with all indices set to 0
  114. face_record = FaceProxy(new_face_record(), existing_vertices)
  115. # set correct indices
  116. face_record.indices = tuple(range(index, index + len(face_mesh_vertices)))
  117. new_faces.append(face_record)
  118. self._rebuild(chain(existing_faces, new_faces))
  119. def _rebuild(self, faces: Iterable['FaceProxy'], precision: int = 6) -> None:
  120. """
  121. Build a valid Polyface structure out of *faces*.
  122. Args:
  123. faces: iterable of FaceProxy objects.
  124. """
  125. polyface_builder = PolyfaceBuilder(faces, precision=precision)
  126. polyline = cast('Polyline', self)
  127. polyline._unlink_all_vertices() # but don't remove it from database
  128. polyline._append_vertices(polyface_builder.get_vertices())
  129. self.update_count(polyface_builder.nvertices, polyface_builder.nfaces)
  130. def update_count(self, nvertices: int, nfaces: int) -> None:
  131. polyline = cast('Polyline', self)
  132. polyline.dxf.m_count = nvertices
  133. polyline.dxf.n_count = nfaces
  134. def optimize(self, precision: int = 6) -> None:
  135. """
  136. Rebuilds polyface with vertex optimization. Merges vertices with nearly same vertex locations.
  137. Polyfaces created by *ezdxf* are optimized automatically.
  138. Args:
  139. precision: decimal precision for determining identical vertex locations
  140. """
  141. vertices, faces = self.indexed_faces()
  142. self._rebuild(faces, precision)
  143. def faces(self) -> Iterable['DXFVertex']:
  144. """
  145. Iterate over all faces, a face is a tuple of vertices.
  146. result is a list: vertex, vertex, vertex, [vertex,] face_record
  147. """
  148. _, faces = self.indexed_faces() # just need the faces generator
  149. for face in faces:
  150. face_vertices = list(face)
  151. face_vertices.append(face.face_record)
  152. yield face_vertices
  153. def indexed_faces(self) -> Tuple[List['DXFVertex'], Iterable['FaceProxy']]:
  154. """
  155. Returns a list of all vertices and a generator of FaceProxy() objects.
  156. """
  157. polyline = cast('Polyline', self)
  158. vertices = []
  159. face_records = []
  160. for vertex in polyline.vertices(): # type: DXFVertex
  161. (vertices if vertex.is_poly_face_mesh_vertex else face_records).append(vertex)
  162. faces = (FaceProxy(face_record, vertices) for face_record in face_records)
  163. return vertices, faces
  164. class FaceProxy:
  165. __slots__ = ('vertices', 'face_record', 'indices')
  166. """
  167. Represents a single face of a polyface structure.
  168. vertices:
  169. List of all polyface vertices.
  170. face_record:
  171. The face forming vertex of type ``AcDbFaceRecord``, contains the indices to the face building vertices. Indices
  172. of the DXF structure are 1-based and a negative index indicates the beginning of an invisible edge.
  173. Face.face_record.dxf.color determines the color of the face.
  174. indices:
  175. Indices to the face building vertices as tuple. This indices are 0-base and are used to get vertices from the
  176. list *Face.vertices*.
  177. """
  178. def __init__(self, face_record: 'DXFVertex', vertices: Sequence['DXFVertex']):
  179. self.vertices = vertices # type: Sequence[DXFVertex]
  180. self.face_record = face_record # type: DXFVertex
  181. self.indices = self._indices() # type: Sequence[int]
  182. def __len__(self) -> int:
  183. return len(self.indices)
  184. def __getitem__(self, pos: int) -> 'DXFVertex':
  185. return self.vertices[self.indices[pos]]
  186. def __iter__(self) -> Iterable['DXFVertex']:
  187. return (self.vertices[index] for index in self.indices)
  188. def points(self) -> Iterable['Vertex']:
  189. return (vertex.dxf.location for vertex in self)
  190. def _raw_indices(self) -> Iterable[int]:
  191. return (self.face_record.get_dxf_attrib(name, 0) for name in const.VERTEXNAMES)
  192. def _indices(self) -> Sequence[int]:
  193. return tuple(abs(index) - 1 for index in self._raw_indices() if index != 0)
  194. def is_edge_visible(self, pos: int) -> bool:
  195. name = const.VERTEXNAMES[pos]
  196. return self.face_record.get_dxf_attrib(name) > 0