mesh.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. # Purpose: simple mesh builders
  2. # Copyright (c) 2018 Manfred Moitzi
  3. # License: MIT License
  4. from typing import List, Sequence, Tuple, Iterable, TYPE_CHECKING
  5. from ezdxf.math.vector import Vector
  6. from ezdxf.lldxf.const import DXFValueError
  7. if TYPE_CHECKING:
  8. from ezdxf.eztypes import Vertex, Matrix44, GenericLayoutType
  9. class MeshBuilder:
  10. """
  11. A simple Mesh builder. Stores a list of vertices, a list of edges where an edge is a list of indices into the
  12. vertices list, and a faces list where each face is a list of indices into the vertices list.
  13. The render() method, renders the mesh into a DXF MESH entity. The MESH entity supports ngons in AutoCAD, ngons are
  14. polygons with more than 4 vertices.
  15. Can only create new meshes.
  16. """
  17. def __init__(self):
  18. self.vertices = [] # type: list[Vector] # vertex storage, list of (x, y, z) tuples or Vector() objects
  19. self.faces = [] # type: List[Sequence[int]] # face storage, each face is a list/tuple of vertex indices (v0, v1, v2, v3, ....), AutoCAD supports ngons
  20. self.edges = [] # type: list[Tuple[int, int]] # edge storage, each edge is a 2-tuple of vertex indices (v0, v1)
  21. def add_face(self, vertices: Iterable['Vertex']) -> None:
  22. """
  23. Add a face as vertices list to the mesh. A face requires at least 3 vertices, each vertex is a (x, y, z) tuple.
  24. A face is stored as index list, which means, a face does not contain the vertex itself, but the indices of the
  25. vertices in the vertex list.
  26. list [index v1, index v2, index v3, ...].
  27. Args:
  28. vertices: list of at least 3 vertices [(x1, y1, z1), (x2, y2, z2), (x3, y3, y3), ...]
  29. """
  30. self.faces.append(self.add_vertices(vertices))
  31. def add_edge(self, vertices: Iterable['Vertex']) -> None:
  32. """
  33. An edge consist of two vertices [v1, v2]. Each vertex is a (x, y, z) tuple and will be added to the mesh
  34. and the resulting vertex indices will be added to the mesh edges list. The stored edge is [index v1, index v2]
  35. Args:
  36. vertices: list of 2 vertices : [(x1, y1, z1), (x2, y2, z2)]
  37. """
  38. vertices = list(vertices)
  39. if len(vertices) == 2:
  40. self.edges.append(self.add_vertices(vertices))
  41. else:
  42. raise DXFValueError('Invalid vertices count, expected two vertices.')
  43. def add_vertices(self, vertices: Iterable['Vertex']) -> Tuple:
  44. """
  45. Add new vertices to the mesh.
  46. e.g. adding 4 vertices to an empty mesh, returns the indices (0, 1, 2, 3), adding additional 4 vertices
  47. return s the indices (4, 5, 6, 7)
  48. Args:
  49. vertices: list of vertices, vertex as (x, y, z) tuple
  50. Returns: a tuple of vertex indices.
  51. """
  52. start_index = len(self.vertices)
  53. self.vertices.extend(vertices)
  54. return tuple(range(start_index, len(self.vertices)))
  55. def add_mesh(self,
  56. vertices: List[Vector] = None,
  57. faces: List[Sequence[int]] = None,
  58. edges: List[Tuple[int, int]] = None,
  59. mesh: 'MeshBuilder' = None) -> None:
  60. """
  61. Add another mesh to this mesh.
  62. Args:
  63. vertices: list of vertices, a vertex is a (x, y, z)
  64. faces: list of faces, a face is a list of vertex indices
  65. edges: list of edges, an edge is a list of vertex indices
  66. mesh: another mesh entity, mesh overrides vertices, faces and edges
  67. """
  68. if mesh is not None:
  69. vertices = mesh.vertices
  70. faces = mesh.faces
  71. edges = mesh.edges
  72. if vertices is None:
  73. raise ValueError("Requires vertices or another mesh.")
  74. if faces is None:
  75. faces = []
  76. if edges is None:
  77. edges = []
  78. indices = self.add_vertices(vertices)
  79. for v1, v2 in edges:
  80. self.edges.append((indices[v1], indices[v2]))
  81. for face_vertices in faces:
  82. self.faces.append(tuple(indices[vi] for vi in face_vertices))
  83. def transform(self, matrix: 'Matrix44') -> 'MeshBuilder':
  84. """
  85. Transform actual mesh into a new mesh by applying the transformation matrix to vertices.
  86. Args:
  87. matrix: 4x4 transformation matrix as Matrix44() object
  88. Returns: new Mesh() object
  89. """
  90. mesh = self.__class__()
  91. mesh.add_mesh(
  92. vertices=matrix.transform_vectors(self.vertices),
  93. faces=self.faces,
  94. edges=self.edges,
  95. )
  96. return mesh
  97. def translate(self, x: float = 0, y: float = 0, z: float = 0) -> None:
  98. """
  99. Translate mesh inplace.
  100. """
  101. if isinstance(x, (float, int)):
  102. t = Vector(x, y, z)
  103. else:
  104. t = Vector(x)
  105. for index, vertex in enumerate(self.vertices):
  106. self.vertices[index] = t + vertex
  107. def scale(self, sx: float = 1, sy: float = 1, sz: float = 1) -> None:
  108. """
  109. Scale mesh inplace.
  110. """
  111. self.vertices = [Vector(v[0] * sx, v[1] * sy, v[2] * sz) for v in self.vertices]
  112. for index, vertex in enumerate(self.vertices):
  113. self.vertices[index] = Vector(vertex[0] * sx, vertex[1] * sy, vertex[2] * sz)
  114. def render(self, layout: 'GenericLayoutType', dxfattribs: dict = None, matrix: 'Matrix44' = None):
  115. """
  116. Render mesh as MESH entity into layout.
  117. Args:
  118. layout: ezdxf Layout() object
  119. dxfattribs: dict of DXF attributes e.g. {'layer': 'mesh', 'color': 7}
  120. matrix: transformation matrix, requires a .transform_vectors() method
  121. """
  122. mesh = layout.add_mesh(dxfattribs=dxfattribs)
  123. with mesh.edit_data() as data:
  124. if matrix is not None:
  125. data.vertices = matrix.transform_vectors(self.vertices)
  126. else:
  127. data.vertices = self.vertices
  128. data.edges = self.edges
  129. data.faces = self.faces
  130. @classmethod
  131. def from_mesh(cls, other: 'MeshBuilder') -> 'MeshBuilder':
  132. mesh = cls()
  133. mesh.add_mesh(mesh=other)
  134. return mesh
  135. class MeshVertexMerger(MeshBuilder):
  136. """
  137. Mesh with unique vertices. Resulting meshes have no doublets, but MeshVertexMerger() needs extra memory for
  138. bookkeeping.
  139. Can only create new meshes.
  140. """
  141. def __init__(self, precision: int = 6):
  142. super().__init__()
  143. self.ledger = {}
  144. self.precision = precision
  145. def key(self, vertex: 'Vertex') -> 'Vertex':
  146. p = self.precision
  147. return round(vertex[0], p), round(vertex[1], p), round(vertex[2], p)
  148. def add_vertices(self, vertices: Iterable['Vertex']) -> Sequence[int]:
  149. """
  150. Add new vertices only, if no vertex with identical x, y, z coordinates already exists, else the index of the
  151. existing vertex is returned as index of the new (not added) vertex.
  152. Args:
  153. vertices: list of vertices, vertex as (x, y, z) tuple
  154. Returns:
  155. A tuples of the vertex indices.
  156. """
  157. indices = []
  158. for vertex in vertices:
  159. key = self.key(vertex)
  160. try:
  161. indices.append(self.ledger[key])
  162. except KeyError:
  163. index = len(self.vertices)
  164. self.vertices.append(vertex)
  165. self.ledger[key] = index
  166. indices.append(index)
  167. return tuple(indices)
  168. def index(self, vertex: 'Vertex') -> int:
  169. """
  170. Get index of vertex, raise KeyError if not found.
  171. Args:
  172. vertex: (x, y, z) vertex
  173. Returns: index of vertex as int
  174. """
  175. try:
  176. return self.ledger[self.key(vertex)]
  177. except KeyError:
  178. raise IndexError("vertex {} not found.".format(vertex))