# 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))