123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- # Created: 24.05.2015
- # Copyright (c) 2015-2018, Manfred Moitzi
- # License: MIT License
- from typing import TYPE_CHECKING, Iterable, List, Sequence, Tuple, Union, Dict
- from contextlib import contextmanager
- import array
- from itertools import chain
- from ezdxf.lldxf.attributes import DXFAttr, DXFAttributes, DefSubclass
- from ezdxf.lldxf.types import DXFTag
- from ezdxf.lldxf.extendedtags import ExtendedTags
- from ezdxf.lldxf.const import DXFStructureError, DXFValueError
- from ezdxf.lldxf.packedtags import TagArray, VertexArray, TagList
- from ezdxf.tools import take2
- from ezdxf.lldxf import loader
- from .graphics import none_subclass, entity_subclass, ModernGraphicEntity
- if TYPE_CHECKING:
- from ezdxf.eztypes import Tags, Vertex
- class MeshVertexArray(VertexArray):
- code = -92
- def dxftags(self) -> Iterable[DXFTag]:
- yield DXFTag(92, len(self))
- # python 2.7 compatible
- for tag in super(MeshVertexArray, self).dxftags():
- yield tag
- def set_data(self, vertices: Iterable['Vertex']) -> None:
- self.value = array.array('d', chain.from_iterable(vertices))
- def create_vertex_array(tags: 'Tags', start_index: int) -> 'MeshVertexArray':
- vertex_tags = tags.collect_consecutive_tags(codes=(10,), start=start_index)
- return MeshVertexArray(data=chain.from_iterable(t.value for t in vertex_tags))
- class FaceList(TagList):
- code = -93
- def __len__(self) -> int:
- return len(self.value)
- def __iter__(self) -> Iterable[array.array]:
- return iter(self.value)
- def dxftags(self) -> List[DXFTag]:
- # count = count of tags not faces!
- yield DXFTag(93, self.tag_count())
- for face in self.value:
- yield DXFTag(90, len(face))
- for index in face:
- yield DXFTag(90, index)
- def tag_count(self) -> int:
- return len(self.value) + sum(len(f) for f in self.value)
- def set_data(self, faces: Iterable[Sequence[int]]) -> None:
- _faces = []
- for face in faces:
- _faces.append(face_to_array(face))
- self.value = _faces
- def face_to_array(face: Sequence[int]) -> array.array:
- max_index = max(face)
- if max_index < 256:
- dtype = 'B'
- elif max_index < 65536:
- dtype = 'I'
- else:
- dtype = 'L'
- return array.array(dtype, face)
- def create_face_list(tags: 'Tags', start_index: int) -> 'FaceList':
- faces = FaceList()
- faces_list = faces.value
- face = []
- counter = 0
- for tag in tags.collect_consecutive_tags(codes=(90,), start=start_index):
- if not counter:
- # leading counter tag
- counter = tag.value
- if face:
- # group code 90 = 32 bit integer
- faces_list.append(face_to_array(face))
- face = []
- else:
- # followed by count face tags
- counter -= 1
- face.append(tag.value)
- # add last face
- if face:
- # group code 90 = 32 bit integer
- faces_list.append(face_to_array(face))
- return faces
- class EdgeArray(TagArray):
- code = -94
- VALUE_CODE = 90 # 32 bit integer
- DTYPE = 'L'
- def dxftags(self) -> Iterable[DXFTag]:
- # count = count of edges not tags!
- yield DXFTag(94, len(self.value) // 2)
- # python 2.7 compatible
- for v in super(EdgeArray, self).dxftags():
- yield v
- def __len__(self) -> int:
- return len(self.value) // 2
- def __iter__(self) -> Iterable[Tuple[int, int]]:
- for edge in take2(self.value):
- yield edge
- def set_data(self, edges: Iterable[Tuple[int, int]]) -> None:
- self.value = array.array('L', chain.from_iterable(edges))
- def create_edge_array(tags: 'Tags', start_index: int) -> 'EdgeArray':
- return EdgeArray(data=collect_values(tags, start_index, code=90)) # int values
- def collect_values(tags: 'Tags', start_index: int, code: int) -> Iterable[Union[float, int]]:
- values = tags.collect_consecutive_tags(codes=(code,), start=start_index)
- return (t.value for t in values)
- def create_crease_array(tags: 'Tags', start_index: int) -> 'CreaseArray':
- return CreaseArray(data=collect_values(tags, start_index, code=140)) # float values
- class CreaseArray(TagArray):
- code = -95
- VALUE_CODE = 140 # double precision
- DTYPE = 'd'
- def dxftags(self) -> Iterable[DXFTag]:
- yield DXFTag(95, len(self.value))
- # python 2.7 compatible
- for v in super(CreaseArray, self).dxftags():
- yield v
- def __len__(self) -> int:
- return len(self.value)
- def __iter__(self) -> Iterable[float]:
- return iter(self.value)
- def set_data(self, creases: Iterable[float]) -> None:
- self.value = array.array('d', creases)
- COUNT_ERROR_MSG = "'MESH (#{}) without {} count.'"
- def convert_and_replace_tags(tags: 'Tags', handle: str) -> None:
- def process_vertices():
- try:
- vertex_count_index = tags.tag_index(92)
- except DXFValueError:
- raise DXFStructureError(COUNT_ERROR_MSG.format(handle, 'vertex'))
- vertices = create_vertex_array(tags, vertex_count_index + 1)
- # replace vertex count tag and all vertex tags by MeshVertexArray()
- end_index = vertex_count_index + 1 + len(vertices)
- tags[vertex_count_index:end_index] = [vertices]
- def process_faces():
- try:
- face_count_index = tags.tag_index(93)
- except DXFValueError:
- raise DXFStructureError(COUNT_ERROR_MSG.format(handle, 'face'))
- else:
- # replace face count tag and all face tags by FaceList()
- faces = create_face_list(tags, face_count_index + 1)
- end_index = face_count_index + 1 + faces.tag_count()
- tags[face_count_index:end_index] = [faces]
- def process_edges():
- try:
- edge_count_index = tags.tag_index(94)
- except DXFValueError:
- raise DXFStructureError(COUNT_ERROR_MSG.format(handle, 'edge'))
- else:
- edges = create_edge_array(tags, edge_count_index + 1)
- # replace edge count tag and all edge tags by EdgeArray()
- end_index = edge_count_index + 1 + len(edges.value)
- tags[edge_count_index:end_index] = [edges]
- def process_creases():
- try:
- crease_count_index = tags.tag_index(95)
- except DXFValueError:
- raise DXFStructureError(COUNT_ERROR_MSG.format(handle, 'crease'))
- else:
- creases = create_crease_array(tags, crease_count_index + 1)
- # replace crease count tag and all crease tags by CreaseArray()
- end_index = crease_count_index + 1 + len(creases.value)
- tags[crease_count_index:end_index] = [creases]
- process_vertices()
- process_faces()
- process_edges()
- process_creases()
- @loader.register('MESH', legacy=False)
- def tag_processor(tags: ExtendedTags) -> ExtendedTags:
- subclass = tags.get_subclass('AcDbSubDMesh')
- handle = tags.get_handle()
- convert_and_replace_tags(subclass, handle)
- return tags
- _MESH_TPL = """0
- MESH
- 5
- 0
- 330
- 1F
- 100
- AcDbEntity
- 8
- 0
- 100
- AcDbSubDMesh
- 71
- 2
- 72
- 0
- 91
- 0
- 92
- 0
- 93
- 0
- 94
- 0
- 95
- 0
- 90
- 0
- """
- mesh_subclass = DefSubclass('AcDbSubDMesh', {
- 'version': DXFAttr(71),
- 'blend_crease': DXFAttr(72), # 0 = off, 1 = on
- 'subdivision_levels': DXFAttr(91), # int >= 0, 0 is no smoothing
- # 92: Vertex count of level 0
- # 10: Vertex position, multiple entries
- # 93: Size of face list of level 0
- # 90: Face list item, >=3 possible
- # 90: length of face list
- # 90: 1st vertex index
- # 90: 2nd vertex index ...
- # 94: Edge count of level 0
- # 90: Vertex index of 1st edge
- # 90: Vertex index of 2nd edge
- # 95: Edge crease count of level 0
- # 95 same as 94, or how is the 'edge create value' associated to edge index
- # 140: Edge create value
- #
- # Overriding properties: how does this work?
- # 90: Count of sub-entity which property has been overridden
- # 91: Sub-entity marker
- # 92: Count of property was overridden
- # 90: Property type
- # 0 = Color
- # 1 = Material
- # 2 = Transparency
- # 3 = Material mapper
- })
- class Mesh(ModernGraphicEntity):
- __slots__ = ()
- TEMPLATE = tag_processor(ExtendedTags.from_text(_MESH_TPL))
- DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, mesh_subclass)
- @property
- def AcDbSubDMesh(self) -> 'Tags':
- return self.tags.subclasses[2]
- @property
- def vertices(self) -> MeshVertexArray:
- return self.AcDbSubDMesh.get_first_tag(MeshVertexArray.code)
- @property
- def faces(self) -> FaceList:
- return self.AcDbSubDMesh.get_first_tag(FaceList.code)
- @property
- def edges(self) -> EdgeArray:
- return self.AcDbSubDMesh.get_first_tag(EdgeArray.code)
- @property
- def creases(self) -> CreaseArray:
- return self.AcDbSubDMesh.get_first_tag(CreaseArray.code)
- def get_data(self) -> 'MeshData':
- return MeshData(self)
- def set_data(self, data: 'MeshData') -> None:
- self.vertices.set_data(data.vertices)
- self.faces.set_data(data.faces)
- self.edges.set_data(data.edges)
- self.creases.set_data(data.edge_crease_values)
- @contextmanager
- def edit_data(self) -> 'MeshData':
- data = self.get_data()
- yield data
- self.set_data(data)
- class MeshData:
- def __init__(self, mesh):
- self.vertices = list(mesh.vertices) # type: List[array.array]
- self.faces = list(mesh.faces) # type: List[array.array]
- self.edges = list(mesh.edges) # type: List[Tuple[int, int]]
- self.edge_crease_values = list(mesh.creases) # type: List[float]
- def add_face(self, vertices: Iterable[Sequence[float]]) -> Sequence[int]:
- return self.add_entity(vertices, self.faces)
- def add_edge(self, vertices: Sequence[Sequence[float]]) -> Sequence[int]:
- if len(vertices) != 2:
- raise DXFValueError("Parameter vertices has to be a list/tuple of 2 vertices [(x1, y1, z1), (x2, y2, z2)].")
- return self.add_entity(vertices, self.edges)
- def add_entity(self, vertices: Iterable[Sequence[float]], entity_list: List) -> Sequence[int]:
- indices = [self.add_vertex(vertex) for vertex in vertices]
- entity_list.append(indices)
- return indices
- def add_vertex(self, vertex: Sequence[float]) -> int:
- if len(vertex) != 3:
- raise DXFValueError('Parameter vertex has to be a 3-tuple (x, y, z).')
- index = len(self.vertices)
- self.vertices.append(vertex)
- return index
- def optimize(self, precision: int = 6):
- def remove_doublette_vertices() -> Dict[int, int]:
- def prepare_vertices() -> Iterable[Tuple[float, float, float]]:
- for index, vertex in enumerate(self.vertices):
- x, y, z = vertex
- yield round(x, precision), round(y, precision), round(z, precision), index
- sorted_vertex_list = list(sorted(prepare_vertices()))
- original_vertices = self.vertices
- self.vertices = []
- index_map = {} # type: Dict[int, int]
- cmp_vertex = None
- index = 0
- while len(sorted_vertex_list):
- vertex_entry = sorted_vertex_list.pop()
- original_index = vertex_entry[3]
- vertex = original_vertices[original_index]
- if vertex != cmp_vertex: # this is not a doublette
- index = len(self.vertices)
- self.vertices.append(vertex)
- index_map[original_index] = index
- cmp_vertex = vertex
- else: # it is a doublette
- index_map[original_index] = index
- return index_map
- def remap_faces() -> None:
- self.faces = remap_indices(self.faces)
- def remap_edges() -> None:
- self.edges = remap_indices(self.edges)
- def remap_indices(entity_list: Sequence[Sequence[int]]) -> List[Tuple]:
- mapped_indices = [] # type: List[Tuple]
- for entity in entity_list:
- index_list = [index_map[index] for index in entity]
- mapped_indices.append(tuple(index_list))
- return mapped_indices
- index_map = remove_doublette_vertices()
- remap_faces()
- remap_edges()
|