diff options
author | fieldOfView <aldo@fieldofview.com> | 2019-03-14 18:40:02 +0300 |
---|---|---|
committer | fieldOfView <aldo@fieldofview.com> | 2019-03-14 18:40:02 +0300 |
commit | 28eca820750da9d0b3e0d6396e4131ed7bf6f1d2 (patch) | |
tree | 88164f0b87a6ca9079e0f37e6a21f07d45e86055 /plugins/AMFReader | |
parent | f73dabdc3fcb56ea38c29367faa61fd191c65a85 (diff) |
Add AMF reader plugin
Diffstat (limited to 'plugins/AMFReader')
-rw-r--r-- | plugins/AMFReader/AMFReader.py | 164 | ||||
-rw-r--r-- | plugins/AMFReader/__init__.py | 10 | ||||
-rw-r--r-- | plugins/AMFReader/plugin.json | 8 |
3 files changed, 182 insertions, 0 deletions
diff --git a/plugins/AMFReader/AMFReader.py b/plugins/AMFReader/AMFReader.py new file mode 100644 index 0000000000..4507cbdab2 --- /dev/null +++ b/plugins/AMFReader/AMFReader.py @@ -0,0 +1,164 @@ +# Copyright (c) 2019 fieldOfView +# The 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 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] + + # 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 = zfile.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) + + 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) -> MeshData: + 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) + return mesh_data diff --git a/plugins/AMFReader/__init__.py b/plugins/AMFReader/__init__.py new file mode 100644 index 0000000000..e76bb782c3 --- /dev/null +++ b/plugins/AMFReader/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) 2019 fieldOfView +# Cura is released under the terms of the LGPLv3 or higher. + +from . import AMFReader + +def getMetaData(): + return {} + +def register(app): + return {"mesh_reader": AMFReader.AMFReader()} diff --git a/plugins/AMFReader/plugin.json b/plugins/AMFReader/plugin.json new file mode 100644 index 0000000000..5483fab479 --- /dev/null +++ b/plugins/AMFReader/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "AMF Reader", + "author": "fieldOfView", + "version": "3.5.0", + "description": "Provides support for reading AMF files.", + "api": 5, + "supported_sdk_versions": ["5.0.0", "6.0.0"] +} |