mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-04-22 05:39:37 +08:00
180 lines
6.7 KiB
Python
180 lines
6.7 KiB
Python
# Copyright (c) 2019 fieldOfView, Ultimaker B.V.
|
|
# Cura is released under the terms of the LGPLv3 or higher.
|
|
|
|
# This AMF parser is based on the AMF parser in legacy cura:
|
|
# https://github.com/daid/LegacyCura/blob/ad7641e059048c7dcb25da1f47c0a7e95e7f4f7c/Cura/util/meshLoaders/amf.py
|
|
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
|
from cura.CuraApplication import CuraApplication
|
|
from UM.Logger import Logger
|
|
|
|
from UM.Mesh.MeshData import MeshData, calculateNormalsFromIndexedVertices
|
|
from UM.Mesh.MeshReader import MeshReader
|
|
|
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
|
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
|
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
|
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
|
|
from UM.Scene.GroupDecorator import GroupDecorator
|
|
|
|
import numpy
|
|
import trimesh
|
|
import os.path
|
|
import zipfile
|
|
|
|
MYPY = False
|
|
try:
|
|
if not MYPY:
|
|
import xml.etree.cElementTree as ET
|
|
except ImportError:
|
|
import xml.etree.ElementTree as ET
|
|
|
|
from typing import Dict
|
|
|
|
|
|
class AMFReader(MeshReader):
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
self._supported_extensions = [".amf"]
|
|
self._namespaces = {} # type: Dict[str, str]
|
|
|
|
MimeTypeDatabase.addMimeType(
|
|
MimeType(
|
|
name = "application/x-amf",
|
|
comment = "AMF",
|
|
suffixes = ["amf"]
|
|
)
|
|
)
|
|
|
|
# Main entry point
|
|
# Reads the file, returns a SceneNode (possibly with nested ones), or None
|
|
def _read(self, file_name):
|
|
base_name = os.path.basename(file_name)
|
|
try:
|
|
zipped_file = zipfile.ZipFile(file_name)
|
|
xml_document = zipped_file.read(zipped_file.namelist()[0])
|
|
zipped_file.close()
|
|
except zipfile.BadZipfile:
|
|
raw_file = open(file_name, "r")
|
|
xml_document = raw_file.read()
|
|
raw_file.close()
|
|
|
|
try:
|
|
amf_document = ET.fromstring(xml_document)
|
|
except ET.ParseError:
|
|
Logger.log("e", "Could not parse XML in file %s" % base_name)
|
|
return None
|
|
|
|
if "unit" in amf_document.attrib:
|
|
unit = amf_document.attrib["unit"].lower()
|
|
else:
|
|
unit = "millimeter"
|
|
if unit == "millimeter":
|
|
scale = 1.0
|
|
elif unit == "meter":
|
|
scale = 1000.0
|
|
elif unit == "inch":
|
|
scale = 25.4
|
|
elif unit == "feet":
|
|
scale = 304.8
|
|
elif unit == "micron":
|
|
scale = 0.001
|
|
else:
|
|
Logger.log("w", "Unknown unit in amf: %s. Using mm instead." % unit)
|
|
scale = 1.0
|
|
|
|
nodes = []
|
|
for amf_object in amf_document.iter("object"):
|
|
for amf_mesh in amf_object.iter("mesh"):
|
|
amf_mesh_vertices = []
|
|
for vertices in amf_mesh.iter("vertices"):
|
|
for vertex in vertices.iter("vertex"):
|
|
for coordinates in vertex.iter("coordinates"):
|
|
v = [0.0, 0.0, 0.0]
|
|
for t in coordinates:
|
|
if t.tag == "x":
|
|
v[0] = float(t.text) * scale
|
|
elif t.tag == "y":
|
|
v[2] = -float(t.text) * scale
|
|
elif t.tag == "z":
|
|
v[1] = float(t.text) * scale
|
|
amf_mesh_vertices.append(v)
|
|
if not amf_mesh_vertices:
|
|
continue
|
|
|
|
indices = []
|
|
for volume in amf_mesh.iter("volume"):
|
|
for triangle in volume.iter("triangle"):
|
|
f = [0, 0, 0]
|
|
for t in triangle:
|
|
if t.tag == "v1":
|
|
f[0] = int(t.text)
|
|
elif t.tag == "v2":
|
|
f[1] = int(t.text)
|
|
elif t.tag == "v3":
|
|
f[2] = int(t.text)
|
|
indices.append(f)
|
|
|
|
mesh = trimesh.base.Trimesh(vertices = numpy.array(amf_mesh_vertices, dtype = numpy.float32), faces = numpy.array(indices, dtype = numpy.int32))
|
|
mesh.merge_vertices()
|
|
mesh.remove_unreferenced_vertices()
|
|
mesh.fix_normals()
|
|
mesh_data = self._toMeshData(mesh, file_name)
|
|
|
|
new_node = CuraSceneNode()
|
|
new_node.setSelectable(True)
|
|
new_node.setMeshData(mesh_data)
|
|
new_node.setName(base_name if len(nodes) == 0 else "%s %d" % (base_name, len(nodes)))
|
|
new_node.addDecorator(BuildPlateDecorator(CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate))
|
|
new_node.addDecorator(SliceableObjectDecorator())
|
|
|
|
nodes.append(new_node)
|
|
|
|
if not nodes:
|
|
Logger.log("e", "No meshes in file %s" % base_name)
|
|
return None
|
|
|
|
if len(nodes) == 1:
|
|
return nodes[0]
|
|
|
|
# Add all scenenodes to a group so they stay together
|
|
group_node = CuraSceneNode()
|
|
group_node.addDecorator(GroupDecorator())
|
|
group_node.addDecorator(ConvexHullDecorator())
|
|
group_node.addDecorator(BuildPlateDecorator(CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate))
|
|
|
|
for node in nodes:
|
|
node.setParent(group_node)
|
|
|
|
return group_node
|
|
|
|
def _toMeshData(self, tri_node: trimesh.base.Trimesh, file_name: str = "") -> MeshData:
|
|
"""Converts a Trimesh to Uranium's MeshData.
|
|
|
|
:param tri_node: A Trimesh containing the contents of a file that was just read.
|
|
:param file_name: The full original filename used to watch for changes
|
|
:return: Mesh data from the Trimesh in a way that Uranium can understand it.
|
|
"""
|
|
tri_faces = tri_node.faces
|
|
tri_vertices = tri_node.vertices
|
|
|
|
indices = []
|
|
vertices = []
|
|
|
|
index_count = 0
|
|
face_count = 0
|
|
for tri_face in tri_faces:
|
|
face = []
|
|
for tri_index in tri_face:
|
|
vertices.append(tri_vertices[tri_index])
|
|
face.append(index_count)
|
|
index_count += 1
|
|
indices.append(face)
|
|
face_count += 1
|
|
|
|
vertices = numpy.asarray(vertices, dtype = numpy.float32)
|
|
indices = numpy.asarray(indices, dtype = numpy.int32)
|
|
normals = calculateNormalsFromIndexedVertices(vertices, indices, face_count)
|
|
|
|
mesh_data = MeshData(vertices = vertices, indices = indices, normals = normals,file_name = file_name)
|
|
return mesh_data
|