AMFReader.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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)
  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) -> MeshData:
  128. tri_faces = tri_node.faces
  129. tri_vertices = tri_node.vertices
  130. indices = []
  131. vertices = []
  132. index_count = 0
  133. face_count = 0
  134. for tri_face in tri_faces:
  135. face = []
  136. for tri_index in tri_face:
  137. vertices.append(tri_vertices[tri_index])
  138. face.append(index_count)
  139. index_count += 1
  140. indices.append(face)
  141. face_count += 1
  142. vertices = numpy.asarray(vertices, dtype = numpy.float32)
  143. indices = numpy.asarray(indices, dtype = numpy.int32)
  144. normals = calculateNormalsFromIndexedVertices(vertices, indices, face_count)
  145. mesh_data = MeshData(vertices = vertices, indices = indices, normals = normals)
  146. return mesh_data