123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- # Purpose: simple mesh builders
- # Copyright (c) 2018 Manfred Moitzi
- # License: MIT License
- from typing import List, Sequence, Tuple, Iterable, TYPE_CHECKING
- from ezdxf.math.vector import Vector
- from ezdxf.lldxf.const import DXFValueError
- if TYPE_CHECKING:
- from ezdxf.eztypes import Vertex, Matrix44, GenericLayoutType
- class MeshBuilder:
- """
- A simple Mesh builder. Stores a list of vertices, a list of edges where an edge is a list of indices into the
- vertices list, and a faces list where each face is a list of indices into the vertices list.
- The render() method, renders the mesh into a DXF MESH entity. The MESH entity supports ngons in AutoCAD, ngons are
- polygons with more than 4 vertices.
- Can only create new meshes.
- """
- def __init__(self):
- self.vertices = [] # type: list[Vector] # vertex storage, list of (x, y, z) tuples or Vector() objects
- self.faces = [] # type: List[Sequence[int]] # face storage, each face is a list/tuple of vertex indices (v0, v1, v2, v3, ....), AutoCAD supports ngons
- self.edges = [] # type: list[Tuple[int, int]] # edge storage, each edge is a 2-tuple of vertex indices (v0, v1)
- def add_face(self, vertices: Iterable['Vertex']) -> None:
- """
- Add a face as vertices list to the mesh. A face requires at least 3 vertices, each vertex is a (x, y, z) tuple.
- A face is stored as index list, which means, a face does not contain the vertex itself, but the indices of the
- vertices in the vertex list.
- list [index v1, index v2, index v3, ...].
- Args:
- vertices: list of at least 3 vertices [(x1, y1, z1), (x2, y2, z2), (x3, y3, y3), ...]
- """
- self.faces.append(self.add_vertices(vertices))
- def add_edge(self, vertices: Iterable['Vertex']) -> None:
- """
- An edge consist of two vertices [v1, v2]. Each vertex is a (x, y, z) tuple and will be added to the mesh
- and the resulting vertex indices will be added to the mesh edges list. The stored edge is [index v1, index v2]
- Args:
- vertices: list of 2 vertices : [(x1, y1, z1), (x2, y2, z2)]
- """
- vertices = list(vertices)
- if len(vertices) == 2:
- self.edges.append(self.add_vertices(vertices))
- else:
- raise DXFValueError('Invalid vertices count, expected two vertices.')
- def add_vertices(self, vertices: Iterable['Vertex']) -> Tuple:
- """
- Add new vertices to the mesh.
- e.g. adding 4 vertices to an empty mesh, returns the indices (0, 1, 2, 3), adding additional 4 vertices
- return s the indices (4, 5, 6, 7)
- Args:
- vertices: list of vertices, vertex as (x, y, z) tuple
- Returns: a tuple of vertex indices.
- """
- start_index = len(self.vertices)
- self.vertices.extend(vertices)
- return tuple(range(start_index, len(self.vertices)))
- def add_mesh(self,
- vertices: List[Vector] = None,
- faces: List[Sequence[int]] = None,
- edges: List[Tuple[int, int]] = None,
- mesh: 'MeshBuilder' = None) -> None:
- """
- Add another mesh to this mesh.
- Args:
- vertices: list of vertices, a vertex is a (x, y, z)
- faces: list of faces, a face is a list of vertex indices
- edges: list of edges, an edge is a list of vertex indices
- mesh: another mesh entity, mesh overrides vertices, faces and edges
- """
- if mesh is not None:
- vertices = mesh.vertices
- faces = mesh.faces
- edges = mesh.edges
- if vertices is None:
- raise ValueError("Requires vertices or another mesh.")
- if faces is None:
- faces = []
- if edges is None:
- edges = []
- indices = self.add_vertices(vertices)
- for v1, v2 in edges:
- self.edges.append((indices[v1], indices[v2]))
- for face_vertices in faces:
- self.faces.append(tuple(indices[vi] for vi in face_vertices))
- def transform(self, matrix: 'Matrix44') -> 'MeshBuilder':
- """
- Transform actual mesh into a new mesh by applying the transformation matrix to vertices.
- Args:
- matrix: 4x4 transformation matrix as Matrix44() object
- Returns: new Mesh() object
- """
- mesh = self.__class__()
- mesh.add_mesh(
- vertices=matrix.transform_vectors(self.vertices),
- faces=self.faces,
- edges=self.edges,
- )
- return mesh
- def translate(self, x: float = 0, y: float = 0, z: float = 0) -> None:
- """
- Translate mesh inplace.
- """
- if isinstance(x, (float, int)):
- t = Vector(x, y, z)
- else:
- t = Vector(x)
- for index, vertex in enumerate(self.vertices):
- self.vertices[index] = t + vertex
- def scale(self, sx: float = 1, sy: float = 1, sz: float = 1) -> None:
- """
- Scale mesh inplace.
- """
- self.vertices = [Vector(v[0] * sx, v[1] * sy, v[2] * sz) for v in self.vertices]
- for index, vertex in enumerate(self.vertices):
- self.vertices[index] = Vector(vertex[0] * sx, vertex[1] * sy, vertex[2] * sz)
- def render(self, layout: 'GenericLayoutType', dxfattribs: dict = None, matrix: 'Matrix44' = None):
- """
- Render mesh as MESH entity into layout.
- Args:
- layout: ezdxf Layout() object
- dxfattribs: dict of DXF attributes e.g. {'layer': 'mesh', 'color': 7}
- matrix: transformation matrix, requires a .transform_vectors() method
- """
- mesh = layout.add_mesh(dxfattribs=dxfattribs)
- with mesh.edit_data() as data:
- if matrix is not None:
- data.vertices = matrix.transform_vectors(self.vertices)
- else:
- data.vertices = self.vertices
- data.edges = self.edges
- data.faces = self.faces
- @classmethod
- def from_mesh(cls, other: 'MeshBuilder') -> 'MeshBuilder':
- mesh = cls()
- mesh.add_mesh(mesh=other)
- return mesh
- class MeshVertexMerger(MeshBuilder):
- """
- Mesh with unique vertices. Resulting meshes have no doublets, but MeshVertexMerger() needs extra memory for
- bookkeeping.
- Can only create new meshes.
- """
- def __init__(self, precision: int = 6):
- super().__init__()
- self.ledger = {}
- self.precision = precision
- def key(self, vertex: 'Vertex') -> 'Vertex':
- p = self.precision
- return round(vertex[0], p), round(vertex[1], p), round(vertex[2], p)
- def add_vertices(self, vertices: Iterable['Vertex']) -> Sequence[int]:
- """
- Add new vertices only, if no vertex with identical x, y, z coordinates already exists, else the index of the
- existing vertex is returned as index of the new (not added) vertex.
- Args:
- vertices: list of vertices, vertex as (x, y, z) tuple
- Returns:
- A tuples of the vertex indices.
- """
- indices = []
- for vertex in vertices:
- key = self.key(vertex)
- try:
- indices.append(self.ledger[key])
- except KeyError:
- index = len(self.vertices)
- self.vertices.append(vertex)
- self.ledger[key] = index
- indices.append(index)
- return tuple(indices)
- def index(self, vertex: 'Vertex') -> int:
- """
- Get index of vertex, raise KeyError if not found.
- Args:
- vertex: (x, y, z) vertex
- Returns: index of vertex as int
- """
- try:
- return self.ledger[self.key(vertex)]
- except KeyError:
- raise IndexError("vertex {} not found.".format(vertex))
|