mesh.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. # Created: 24.05.2015
  2. # Copyright (c) 2015-2018, Manfred Moitzi
  3. # License: MIT License
  4. from typing import TYPE_CHECKING, Iterable, List, Sequence, Tuple, Union, Dict
  5. from contextlib import contextmanager
  6. import array
  7. from itertools import chain
  8. from ezdxf.lldxf.attributes import DXFAttr, DXFAttributes, DefSubclass
  9. from ezdxf.lldxf.types import DXFTag
  10. from ezdxf.lldxf.extendedtags import ExtendedTags
  11. from ezdxf.lldxf.const import DXFStructureError, DXFValueError
  12. from ezdxf.lldxf.packedtags import TagArray, VertexArray, TagList
  13. from ezdxf.tools import take2
  14. from ezdxf.lldxf import loader
  15. from .graphics import none_subclass, entity_subclass, ModernGraphicEntity
  16. if TYPE_CHECKING:
  17. from ezdxf.eztypes import Tags, Vertex
  18. class MeshVertexArray(VertexArray):
  19. code = -92
  20. def dxftags(self) -> Iterable[DXFTag]:
  21. yield DXFTag(92, len(self))
  22. # python 2.7 compatible
  23. for tag in super(MeshVertexArray, self).dxftags():
  24. yield tag
  25. def set_data(self, vertices: Iterable['Vertex']) -> None:
  26. self.value = array.array('d', chain.from_iterable(vertices))
  27. def create_vertex_array(tags: 'Tags', start_index: int) -> 'MeshVertexArray':
  28. vertex_tags = tags.collect_consecutive_tags(codes=(10,), start=start_index)
  29. return MeshVertexArray(data=chain.from_iterable(t.value for t in vertex_tags))
  30. class FaceList(TagList):
  31. code = -93
  32. def __len__(self) -> int:
  33. return len(self.value)
  34. def __iter__(self) -> Iterable[array.array]:
  35. return iter(self.value)
  36. def dxftags(self) -> List[DXFTag]:
  37. # count = count of tags not faces!
  38. yield DXFTag(93, self.tag_count())
  39. for face in self.value:
  40. yield DXFTag(90, len(face))
  41. for index in face:
  42. yield DXFTag(90, index)
  43. def tag_count(self) -> int:
  44. return len(self.value) + sum(len(f) for f in self.value)
  45. def set_data(self, faces: Iterable[Sequence[int]]) -> None:
  46. _faces = []
  47. for face in faces:
  48. _faces.append(face_to_array(face))
  49. self.value = _faces
  50. def face_to_array(face: Sequence[int]) -> array.array:
  51. max_index = max(face)
  52. if max_index < 256:
  53. dtype = 'B'
  54. elif max_index < 65536:
  55. dtype = 'I'
  56. else:
  57. dtype = 'L'
  58. return array.array(dtype, face)
  59. def create_face_list(tags: 'Tags', start_index: int) -> 'FaceList':
  60. faces = FaceList()
  61. faces_list = faces.value
  62. face = []
  63. counter = 0
  64. for tag in tags.collect_consecutive_tags(codes=(90,), start=start_index):
  65. if not counter:
  66. # leading counter tag
  67. counter = tag.value
  68. if face:
  69. # group code 90 = 32 bit integer
  70. faces_list.append(face_to_array(face))
  71. face = []
  72. else:
  73. # followed by count face tags
  74. counter -= 1
  75. face.append(tag.value)
  76. # add last face
  77. if face:
  78. # group code 90 = 32 bit integer
  79. faces_list.append(face_to_array(face))
  80. return faces
  81. class EdgeArray(TagArray):
  82. code = -94
  83. VALUE_CODE = 90 # 32 bit integer
  84. DTYPE = 'L'
  85. def dxftags(self) -> Iterable[DXFTag]:
  86. # count = count of edges not tags!
  87. yield DXFTag(94, len(self.value) // 2)
  88. # python 2.7 compatible
  89. for v in super(EdgeArray, self).dxftags():
  90. yield v
  91. def __len__(self) -> int:
  92. return len(self.value) // 2
  93. def __iter__(self) -> Iterable[Tuple[int, int]]:
  94. for edge in take2(self.value):
  95. yield edge
  96. def set_data(self, edges: Iterable[Tuple[int, int]]) -> None:
  97. self.value = array.array('L', chain.from_iterable(edges))
  98. def create_edge_array(tags: 'Tags', start_index: int) -> 'EdgeArray':
  99. return EdgeArray(data=collect_values(tags, start_index, code=90)) # int values
  100. def collect_values(tags: 'Tags', start_index: int, code: int) -> Iterable[Union[float, int]]:
  101. values = tags.collect_consecutive_tags(codes=(code,), start=start_index)
  102. return (t.value for t in values)
  103. def create_crease_array(tags: 'Tags', start_index: int) -> 'CreaseArray':
  104. return CreaseArray(data=collect_values(tags, start_index, code=140)) # float values
  105. class CreaseArray(TagArray):
  106. code = -95
  107. VALUE_CODE = 140 # double precision
  108. DTYPE = 'd'
  109. def dxftags(self) -> Iterable[DXFTag]:
  110. yield DXFTag(95, len(self.value))
  111. # python 2.7 compatible
  112. for v in super(CreaseArray, self).dxftags():
  113. yield v
  114. def __len__(self) -> int:
  115. return len(self.value)
  116. def __iter__(self) -> Iterable[float]:
  117. return iter(self.value)
  118. def set_data(self, creases: Iterable[float]) -> None:
  119. self.value = array.array('d', creases)
  120. COUNT_ERROR_MSG = "'MESH (#{}) without {} count.'"
  121. def convert_and_replace_tags(tags: 'Tags', handle: str) -> None:
  122. def process_vertices():
  123. try:
  124. vertex_count_index = tags.tag_index(92)
  125. except DXFValueError:
  126. raise DXFStructureError(COUNT_ERROR_MSG.format(handle, 'vertex'))
  127. vertices = create_vertex_array(tags, vertex_count_index + 1)
  128. # replace vertex count tag and all vertex tags by MeshVertexArray()
  129. end_index = vertex_count_index + 1 + len(vertices)
  130. tags[vertex_count_index:end_index] = [vertices]
  131. def process_faces():
  132. try:
  133. face_count_index = tags.tag_index(93)
  134. except DXFValueError:
  135. raise DXFStructureError(COUNT_ERROR_MSG.format(handle, 'face'))
  136. else:
  137. # replace face count tag and all face tags by FaceList()
  138. faces = create_face_list(tags, face_count_index + 1)
  139. end_index = face_count_index + 1 + faces.tag_count()
  140. tags[face_count_index:end_index] = [faces]
  141. def process_edges():
  142. try:
  143. edge_count_index = tags.tag_index(94)
  144. except DXFValueError:
  145. raise DXFStructureError(COUNT_ERROR_MSG.format(handle, 'edge'))
  146. else:
  147. edges = create_edge_array(tags, edge_count_index + 1)
  148. # replace edge count tag and all edge tags by EdgeArray()
  149. end_index = edge_count_index + 1 + len(edges.value)
  150. tags[edge_count_index:end_index] = [edges]
  151. def process_creases():
  152. try:
  153. crease_count_index = tags.tag_index(95)
  154. except DXFValueError:
  155. raise DXFStructureError(COUNT_ERROR_MSG.format(handle, 'crease'))
  156. else:
  157. creases = create_crease_array(tags, crease_count_index + 1)
  158. # replace crease count tag and all crease tags by CreaseArray()
  159. end_index = crease_count_index + 1 + len(creases.value)
  160. tags[crease_count_index:end_index] = [creases]
  161. process_vertices()
  162. process_faces()
  163. process_edges()
  164. process_creases()
  165. @loader.register('MESH', legacy=False)
  166. def tag_processor(tags: ExtendedTags) -> ExtendedTags:
  167. subclass = tags.get_subclass('AcDbSubDMesh')
  168. handle = tags.get_handle()
  169. convert_and_replace_tags(subclass, handle)
  170. return tags
  171. _MESH_TPL = """0
  172. MESH
  173. 5
  174. 0
  175. 330
  176. 1F
  177. 100
  178. AcDbEntity
  179. 8
  180. 0
  181. 100
  182. AcDbSubDMesh
  183. 71
  184. 2
  185. 72
  186. 0
  187. 91
  188. 0
  189. 92
  190. 0
  191. 93
  192. 0
  193. 94
  194. 0
  195. 95
  196. 0
  197. 90
  198. 0
  199. """
  200. mesh_subclass = DefSubclass('AcDbSubDMesh', {
  201. 'version': DXFAttr(71),
  202. 'blend_crease': DXFAttr(72), # 0 = off, 1 = on
  203. 'subdivision_levels': DXFAttr(91), # int >= 0, 0 is no smoothing
  204. # 92: Vertex count of level 0
  205. # 10: Vertex position, multiple entries
  206. # 93: Size of face list of level 0
  207. # 90: Face list item, >=3 possible
  208. # 90: length of face list
  209. # 90: 1st vertex index
  210. # 90: 2nd vertex index ...
  211. # 94: Edge count of level 0
  212. # 90: Vertex index of 1st edge
  213. # 90: Vertex index of 2nd edge
  214. # 95: Edge crease count of level 0
  215. # 95 same as 94, or how is the 'edge create value' associated to edge index
  216. # 140: Edge create value
  217. #
  218. # Overriding properties: how does this work?
  219. # 90: Count of sub-entity which property has been overridden
  220. # 91: Sub-entity marker
  221. # 92: Count of property was overridden
  222. # 90: Property type
  223. # 0 = Color
  224. # 1 = Material
  225. # 2 = Transparency
  226. # 3 = Material mapper
  227. })
  228. class Mesh(ModernGraphicEntity):
  229. __slots__ = ()
  230. TEMPLATE = tag_processor(ExtendedTags.from_text(_MESH_TPL))
  231. DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, mesh_subclass)
  232. @property
  233. def AcDbSubDMesh(self) -> 'Tags':
  234. return self.tags.subclasses[2]
  235. @property
  236. def vertices(self) -> MeshVertexArray:
  237. return self.AcDbSubDMesh.get_first_tag(MeshVertexArray.code)
  238. @property
  239. def faces(self) -> FaceList:
  240. return self.AcDbSubDMesh.get_first_tag(FaceList.code)
  241. @property
  242. def edges(self) -> EdgeArray:
  243. return self.AcDbSubDMesh.get_first_tag(EdgeArray.code)
  244. @property
  245. def creases(self) -> CreaseArray:
  246. return self.AcDbSubDMesh.get_first_tag(CreaseArray.code)
  247. def get_data(self) -> 'MeshData':
  248. return MeshData(self)
  249. def set_data(self, data: 'MeshData') -> None:
  250. self.vertices.set_data(data.vertices)
  251. self.faces.set_data(data.faces)
  252. self.edges.set_data(data.edges)
  253. self.creases.set_data(data.edge_crease_values)
  254. @contextmanager
  255. def edit_data(self) -> 'MeshData':
  256. data = self.get_data()
  257. yield data
  258. self.set_data(data)
  259. class MeshData:
  260. def __init__(self, mesh):
  261. self.vertices = list(mesh.vertices) # type: List[array.array]
  262. self.faces = list(mesh.faces) # type: List[array.array]
  263. self.edges = list(mesh.edges) # type: List[Tuple[int, int]]
  264. self.edge_crease_values = list(mesh.creases) # type: List[float]
  265. def add_face(self, vertices: Iterable[Sequence[float]]) -> Sequence[int]:
  266. return self.add_entity(vertices, self.faces)
  267. def add_edge(self, vertices: Sequence[Sequence[float]]) -> Sequence[int]:
  268. if len(vertices) != 2:
  269. raise DXFValueError("Parameter vertices has to be a list/tuple of 2 vertices [(x1, y1, z1), (x2, y2, z2)].")
  270. return self.add_entity(vertices, self.edges)
  271. def add_entity(self, vertices: Iterable[Sequence[float]], entity_list: List) -> Sequence[int]:
  272. indices = [self.add_vertex(vertex) for vertex in vertices]
  273. entity_list.append(indices)
  274. return indices
  275. def add_vertex(self, vertex: Sequence[float]) -> int:
  276. if len(vertex) != 3:
  277. raise DXFValueError('Parameter vertex has to be a 3-tuple (x, y, z).')
  278. index = len(self.vertices)
  279. self.vertices.append(vertex)
  280. return index
  281. def optimize(self, precision: int = 6):
  282. def remove_doublette_vertices() -> Dict[int, int]:
  283. def prepare_vertices() -> Iterable[Tuple[float, float, float]]:
  284. for index, vertex in enumerate(self.vertices):
  285. x, y, z = vertex
  286. yield round(x, precision), round(y, precision), round(z, precision), index
  287. sorted_vertex_list = list(sorted(prepare_vertices()))
  288. original_vertices = self.vertices
  289. self.vertices = []
  290. index_map = {} # type: Dict[int, int]
  291. cmp_vertex = None
  292. index = 0
  293. while len(sorted_vertex_list):
  294. vertex_entry = sorted_vertex_list.pop()
  295. original_index = vertex_entry[3]
  296. vertex = original_vertices[original_index]
  297. if vertex != cmp_vertex: # this is not a doublette
  298. index = len(self.vertices)
  299. self.vertices.append(vertex)
  300. index_map[original_index] = index
  301. cmp_vertex = vertex
  302. else: # it is a doublette
  303. index_map[original_index] = index
  304. return index_map
  305. def remap_faces() -> None:
  306. self.faces = remap_indices(self.faces)
  307. def remap_edges() -> None:
  308. self.edges = remap_indices(self.edges)
  309. def remap_indices(entity_list: Sequence[Sequence[int]]) -> List[Tuple]:
  310. mapped_indices = [] # type: List[Tuple]
  311. for entity in entity_list:
  312. index_list = [index_map[index] for index in entity]
  313. mapped_indices.append(tuple(index_list))
  314. return mapped_indices
  315. index_map = remove_doublette_vertices()
  316. remap_faces()
  317. remap_edges()