From 28eca820750da9d0b3e0d6396e4131ed7bf6f1d2 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 14 Mar 2019 16:40:02 +0100 Subject: [PATCH 1/5] Add AMF reader plugin --- plugins/AMFReader/AMFReader.py | 164 +++++++++++++++++++++++++++++++++ plugins/AMFReader/__init__.py | 10 ++ plugins/AMFReader/plugin.json | 8 ++ 3 files changed, 182 insertions(+) create mode 100644 plugins/AMFReader/AMFReader.py create mode 100644 plugins/AMFReader/__init__.py create mode 100644 plugins/AMFReader/plugin.json 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"] +} From 211bf46974c052d0b307fc1e648bb65189cdbe0f Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 14 Mar 2019 16:50:52 +0100 Subject: [PATCH 2/5] Change API version to semantic version --- plugins/AMFReader/plugin.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/AMFReader/plugin.json b/plugins/AMFReader/plugin.json index 5483fab479..599dc03c76 100644 --- a/plugins/AMFReader/plugin.json +++ b/plugins/AMFReader/plugin.json @@ -1,8 +1,7 @@ { "name": "AMF Reader", "author": "fieldOfView", - "version": "3.5.0", + "version": "1.0.0", "description": "Provides support for reading AMF files.", - "api": 5, - "supported_sdk_versions": ["5.0.0", "6.0.0"] + "api": "6.0.0" } From 331c8c52fdffa39f9747855f11355879850193a0 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 14 Mar 2019 16:53:36 +0100 Subject: [PATCH 3/5] Add AMDReader to bundled packages --- resources/bundled_packages/cura.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/resources/bundled_packages/cura.json b/resources/bundled_packages/cura.json index 9e126ee028..bbe5304b52 100644 --- a/resources/bundled_packages/cura.json +++ b/resources/bundled_packages/cura.json @@ -33,6 +33,23 @@ } } }, + "AMFReader": { + "package_info": { + "package_id": "AMFReader", + "package_type": "plugin", + "display_name": "AMF Reader", + "description": "Provides support for reading AMF files.", + "package_version": "1.0.0", + "sdk_version": "6.0.0", + "website": "https://ultimaker.com", + "author": { + "author_id": "fieldOfView", + "display_name": "fieldOfView", + "email": "plugins@ultimaker.com", + "website": "https://ultimaker.com" + } + } + }, "ChangeLogPlugin": { "package_info": { "package_id": "ChangeLogPlugin", From 8276cf235287ced64e3340a77d1e54fcb4770cad Mon Sep 17 00:00:00 2001 From: Aldo Hoeben Date: Fri, 26 Apr 2019 11:53:50 +0200 Subject: [PATCH 4/5] Fix merge issue Merge accidentally reverted a cosmetic change. --- resources/bundled_packages/cura.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/bundled_packages/cura.json b/resources/bundled_packages/cura.json index 6c61cfffb4..259ac05201 100644 --- a/resources/bundled_packages/cura.json +++ b/resources/bundled_packages/cura.json @@ -23,7 +23,7 @@ "display_name": "3MF Writer", "description": "Provides support for writing 3MF files.", "package_version": "1.0.1", - "sdk_version": "6.0", + "sdk_version": "6.0.0", "website": "https://ultimaker.com", "author": { "author_id": "UltimakerPackages", @@ -1636,4 +1636,4 @@ } } } -} \ No newline at end of file +} From b3bc3243d131c97bacbec61bc0bc1b1e89fd3feb Mon Sep 17 00:00:00 2001 From: Aldo Hoeben Date: Fri, 26 Apr 2019 11:54:44 +0200 Subject: [PATCH 5/5] Update copyright --- plugins/AMFReader/AMFReader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/AMFReader/AMFReader.py b/plugins/AMFReader/AMFReader.py index 4507cbdab2..2132cf6a0e 100644 --- a/plugins/AMFReader/AMFReader.py +++ b/plugins/AMFReader/AMFReader.py @@ -1,5 +1,5 @@ # Copyright (c) 2019 fieldOfView -# The Cura is released under the terms of the LGPLv3 or higher. +# 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