AMFReader.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. # Copyright (c) 2019 fieldOfView, Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. # This AMF parser is based on the AMF parser in legacy cura:
  4. # https://github.com/daid/LegacyCura/blob/ad7641e059048c7dcb25da1f47c0a7e95e7f4f7c/Cura/util/meshLoaders/amf.py
  5. from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
  6. from cura.CuraApplication import CuraApplication
  7. from UM.Logger import Logger
  8. from UM.Mesh.MeshData import MeshData, calculateNormalsFromIndexedVertices
  9. from UM.Mesh.MeshReader import MeshReader
  10. from cura.Scene.CuraSceneNode import CuraSceneNode
  11. from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
  12. from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
  13. from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
  14. from UM.Scene.GroupDecorator import GroupDecorator
  15. import numpy
  16. import trimesh
  17. import os.path
  18. import zipfile
  19. MYPY = False
  20. try:
  21. if not MYPY:
  22. import xml.etree.cElementTree as ET
  23. except ImportError:
  24. import xml.etree.ElementTree as ET
  25. from typing import Dict
  26. class AMFReader(MeshReader):
  27. def __init__(self) -> None:
  28. super().__init__()
  29. self._supported_extensions = [".amf"]
  30. self._namespaces = {} # type: Dict[str, str]
  31. MimeTypeDatabase.addMimeType(
  32. MimeType(
  33. name = "application/x-amf",
  34. comment = "AMF",
  35. suffixes = ["amf"]
  36. )
  37. )
  38. # Main entry point
  39. # Reads the file, returns a SceneNode (possibly with nested ones), or None
  40. def _read(self, file_name):
  41. base_name = os.path.basename(file_name)
  42. try:
  43. zipped_file = zipfile.ZipFile(file_name)
  44. xml_document = zipped_file.read(zipped_file.namelist()[0])
  45. zipped_file.close()
  46. except zipfile.BadZipfile:
  47. raw_file = open(file_name, "r")
  48. xml_document = raw_file.read()
  49. raw_file.close()
  50. try:
  51. amf_document = ET.fromstring(xml_document)
  52. except ET.ParseError:
  53. Logger.log("e", "Could not parse XML in file %s" % base_name)
  54. return None
  55. if "unit" in amf_document.attrib:
  56. unit = amf_document.attrib["unit"].lower()
  57. else:
  58. unit = "millimeter"
  59. if unit == "millimeter":
  60. scale = 1.0
  61. elif unit == "meter":
  62. scale = 1000.0
  63. elif unit == "inch":
  64. scale = 25.4
  65. elif unit == "feet":
  66. scale = 304.8
  67. elif unit == "micron":
  68. scale = 0.001
  69. else:
  70. Logger.log("w", "Unknown unit in amf: %s. Using mm instead." % unit)
  71. scale = 1.0
  72. nodes = []
  73. for amf_object in amf_document.iter("object"):
  74. for amf_mesh in amf_object.iter("mesh"):
  75. amf_mesh_vertices = []
  76. for vertices in amf_mesh.iter("vertices"):
  77. for vertex in vertices.iter("vertex"):
  78. for coordinates in vertex.iter("coordinates"):
  79. v = [0.0, 0.0, 0.0]
  80. for t in coordinates:
  81. if t.tag == "x":
  82. v[0] = float(t.text) * scale
  83. elif t.tag == "y":
  84. v[2] = -float(t.text) * scale
  85. elif t.tag == "z":
  86. v[1] = float(t.text) * scale
  87. amf_mesh_vertices.append(v)
  88. if not amf_mesh_vertices:
  89. continue
  90. indices = []
  91. for volume in amf_mesh.iter("volume"):
  92. for triangle in volume.iter("triangle"):
  93. f = [0, 0, 0]
  94. for t in triangle:
  95. if t.tag == "v1":
  96. f[0] = int(t.text)
  97. elif t.tag == "v2":
  98. f[1] = int(t.text)
  99. elif t.tag == "v3":
  100. f[2] = int(t.text)
  101. indices.append(f)
  102. mesh = trimesh.base.Trimesh(vertices = numpy.array(amf_mesh_vertices, dtype = numpy.float32), faces = numpy.array(indices, dtype = numpy.int32))
  103. mesh.merge_vertices()
  104. mesh.remove_unreferenced_vertices()
  105. mesh.fix_normals()
  106. mesh_data = self._toMeshData(mesh, file_name)
  107. new_node = CuraSceneNode()
  108. new_node.setSelectable(True)
  109. new_node.setMeshData(mesh_data)
  110. new_node.setName(base_name if len(nodes) == 0 else "%s %d" % (base_name, len(nodes)))
  111. new_node.addDecorator(BuildPlateDecorator(CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate))
  112. new_node.addDecorator(SliceableObjectDecorator())
  113. nodes.append(new_node)
  114. if not nodes:
  115. Logger.log("e", "No meshes in file %s" % base_name)
  116. return None
  117. if len(nodes) == 1:
  118. return nodes[0]
  119. # Add all scenenodes to a group so they stay together
  120. group_node = CuraSceneNode()
  121. group_node.addDecorator(GroupDecorator())
  122. group_node.addDecorator(ConvexHullDecorator())
  123. group_node.addDecorator(BuildPlateDecorator(CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate))
  124. for node in nodes:
  125. node.setParent(group_node)
  126. return group_node
  127. def _toMeshData(self, tri_node: trimesh.base.Trimesh, file_name: str = "") -> MeshData:
  128. """Converts a Trimesh to Uranium's MeshData.
  129. :param tri_node: A Trimesh containing the contents of a file that was just read.
  130. :param file_name: The full original filename used to watch for changes
  131. :return: Mesh data from the Trimesh in a way that Uranium can understand it.
  132. """
  133. tri_faces = tri_node.faces
  134. tri_vertices = tri_node.vertices
  135. indices = []
  136. vertices = []
  137. index_count = 0
  138. face_count = 0
  139. for tri_face in tri_faces:
  140. face = []
  141. for tri_index in tri_face:
  142. vertices.append(tri_vertices[tri_index])
  143. face.append(index_count)
  144. index_count += 1
  145. indices.append(face)
  146. face_count += 1
  147. vertices = numpy.asarray(vertices, dtype = numpy.float32)
  148. indices = numpy.asarray(indices, dtype = numpy.int32)
  149. normals = calculateNormalsFromIndexedVertices(vertices, indices, face_count)
  150. mesh_data = MeshData(vertices = vertices, indices = indices, normals = normals,file_name = file_name)
  151. return mesh_data