Merge branch '3mf_speedup'

This commit is contained in:
Ghostkeeper 2017-03-06 17:03:42 +01:00
commit d1fada78e6
No known key found for this signature in database
GPG key ID: C5F96EE2BC0F7E75
2 changed files with 170 additions and 243 deletions

View file

@ -18,6 +18,10 @@ from cura.QualityManager import QualityManager
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
MYPY = False MYPY = False
import Savitar
import numpy
try: try:
if not MYPY: if not MYPY:
import xml.etree.cElementTree as ET import xml.etree.cElementTree as ET
@ -38,98 +42,10 @@ class ThreeMFReader(MeshReader):
self._base_name = "" self._base_name = ""
self._unit = None self._unit = None
def _createNodeFromObject(self, object, name = ""):
node = SceneNode()
node.setName(name)
mesh_builder = MeshBuilder()
vertex_list = []
components = object.find(".//3mf:components", self._namespaces)
if components:
for component in components:
id = component.get("objectid")
new_object = self._root.find("./3mf:resources/3mf:object[@id='{0}']".format(id), self._namespaces)
new_node = self._createNodeFromObject(new_object, self._base_name + "_" + str(id))
node.addChild(new_node)
transform = component.get("transform")
if transform is not None:
new_node.setTransformation(self._createMatrixFromTransformationString(transform))
# for vertex in entry.mesh.vertices.vertex:
for vertex in object.findall(".//3mf:vertex", self._namespaces):
vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")])
Job.yieldThread()
xml_settings = list(object.findall(".//cura:setting", self._namespaces))
# Add the setting override decorator, so we can add settings to this node.
if xml_settings:
node.addDecorator(SettingOverrideDecorator())
global_container_stack = Application.getInstance().getGlobalContainerStack()
# Ensure the correct next container for the SettingOverride decorator is set.
if global_container_stack:
multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
# Ensure that all extruder data is reset
if not multi_extrusion:
default_stack_id = global_container_stack.getId()
else:
default_stack = ExtruderManager.getInstance().getExtruderStack(0)
if default_stack:
default_stack_id = default_stack.getId()
else:
default_stack_id = global_container_stack.getId()
node.callDecoration("setActiveExtruder", default_stack_id)
# Get the definition & set it
definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom())
node.callDecoration("getStack").getTop().setDefinition(definition)
setting_container = node.callDecoration("getStack").getTop()
for setting in xml_settings:
setting_key = setting.get("key")
setting_value = setting.text
# Extruder_nr is a special case.
if setting_key == "extruder_nr":
extruder_stack = ExtruderManager.getInstance().getExtruderStack(int(setting_value))
if extruder_stack:
node.callDecoration("setActiveExtruder", extruder_stack.getId())
else:
Logger.log("w", "Unable to find extruder in position %s", setting_value)
continue
setting_container.setProperty(setting_key,"value", setting_value)
if len(node.getChildren()) > 0:
group_decorator = GroupDecorator()
node.addDecorator(group_decorator)
triangles = object.findall(".//3mf:triangle", self._namespaces)
mesh_builder.reserveFaceCount(len(triangles))
for triangle in triangles:
v1 = int(triangle.get("v1"))
v2 = int(triangle.get("v2"))
v3 = int(triangle.get("v3"))
mesh_builder.addFaceByPoints(vertex_list[v1][0], vertex_list[v1][1], vertex_list[v1][2],
vertex_list[v2][0], vertex_list[v2][1], vertex_list[v2][2],
vertex_list[v3][0], vertex_list[v3][1], vertex_list[v3][2])
Job.yieldThread()
# TODO: We currently do not check for normals and simply recalculate them.
mesh_builder.calculateNormals(fast=True)
mesh_builder.setFileName(name)
mesh_data = mesh_builder.build()
if len(mesh_data.getVertices()):
node.setMeshData(mesh_data)
node.setSelectable(True)
return node
def _createMatrixFromTransformationString(self, transformation): def _createMatrixFromTransformationString(self, transformation):
if transformation == "":
return Matrix()
splitted_transformation = transformation.split() splitted_transformation = transformation.split()
## Transformation is saved as: ## Transformation is saved as:
## M00 M01 M02 0.0 ## M00 M01 M02 0.0
@ -156,51 +72,96 @@ class ThreeMFReader(MeshReader):
return temp_mat return temp_mat
## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a Uranium scenenode.
# \returns Uranium Scenen node.
def _convertSavitarNodeToUMNode(self, savitar_node):
um_node = SceneNode()
transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation())
um_node.setTransformation(transformation)
mesh_builder = MeshBuilder()
data = numpy.fromstring(savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32)
vertices = numpy.resize(data, (int(data.size / 3), 3))
mesh_builder.setVertices(vertices)
mesh_builder.calculateNormals(fast=True)
mesh_data = mesh_builder.build()
if len(mesh_data.getVertices()):
um_node.setMeshData(mesh_data)
for child in savitar_node.getChildren():
um_node.addChild(self._convertSavitarNodeToUMNode(child))
settings = savitar_node.getSettings()
# Add the setting override decorator, so we can add settings to this node.
if settings:
um_node.addDecorator(SettingOverrideDecorator())
global_container_stack = Application.getInstance().getGlobalContainerStack()
# Ensure the correct next container for the SettingOverride decorator is set.
if global_container_stack:
multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
# Ensure that all extruder data is reset
if not multi_extrusion:
default_stack_id = global_container_stack.getId()
else:
default_stack = ExtruderManager.getInstance().getExtruderStack(0)
if default_stack:
default_stack_id = default_stack.getId()
else:
default_stack_id = global_container_stack.getId()
um_node.callDecoration("setActiveExtruder", default_stack_id)
# Get the definition & set it
definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom())
um_node.callDecoration("getStack").getTop().setDefinition(definition)
setting_container = um_node.callDecoration("getStack").getTop()
for key in settings:
setting_value = settings[key]
# Extruder_nr is a special case.
if key == "extruder_nr":
extruder_stack = ExtruderManager.getInstance().getExtruderStack(int(setting_value))
if extruder_stack:
um_node.callDecoration("setActiveExtruder", extruder_stack.getId())
else:
Logger.log("w", "Unable to find extruder in position %s", setting_value)
continue
setting_container.setProperty(key,"value", setting_value)
if len(um_node.getChildren()) > 0:
group_decorator = GroupDecorator()
um_node.addDecorator(group_decorator)
um_node.setSelectable(True)
return um_node
def read(self, file_name): def read(self, file_name):
result = [] result = []
# The base object of 3mf is a zipped archive. # The base object of 3mf is a zipped archive.
try:
archive = zipfile.ZipFile(file_name, "r") archive = zipfile.ZipFile(file_name, "r")
self._base_name = os.path.basename(file_name) self._base_name = os.path.basename(file_name)
try: parser = Savitar.ThreeMFParser()
self._root = ET.parse(archive.open("3D/3dmodel.model")) scene_3mf = parser.parse(archive.open("3D/3dmodel.model").read())
self._unit = self._root.getroot().get("unit") self._unit = scene_3mf.getUnit()
for node in scene_3mf.getSceneNodes():
build_items = self._root.findall("./3mf:build/3mf:item", self._namespaces) um_node = self._convertSavitarNodeToUMNode(node)
for build_item in build_items:
id = build_item.get("objectid")
object = self._root.find("./3mf:resources/3mf:object[@id='{0}']".format(id), self._namespaces)
if "type" in object.attrib:
if object.attrib["type"] == "support" or object.attrib["type"] == "other":
# Ignore support objects, as cura does not support these.
# We can't guarantee that they wont be made solid.
# We also ignore "other", as I have no idea what to do with them.
Logger.log("w", "3MF file contained an object of type %s which is not supported by Cura", object.attrib["type"])
continue
elif object.attrib["type"] == "solidsupport" or object.attrib["type"] == "model":
pass # Load these as normal
else:
# We should technically fail at this point because it's an invalid 3MF, but try to continue anyway.
Logger.log("e", "3MF file contained an object of type %s which is not supported by the 3mf spec",
object.attrib["type"])
continue
build_item_node = self._createNodeFromObject(object, self._base_name + "_" + str(id))
# compensate for original center position, if object(s) is/are not around its zero position # compensate for original center position, if object(s) is/are not around its zero position
transform_matrix = Matrix() transform_matrix = Matrix()
mesh_data = build_item_node.getMeshData() mesh_data = um_node.getMeshData()
if mesh_data is not None: if mesh_data is not None:
extents = mesh_data.getExtents() extents = mesh_data.getExtents()
center_vector = Vector(extents.center.x, extents.center.y, extents.center.z) center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
transform_matrix.setByTranslation(center_vector) transform_matrix.setByTranslation(center_vector)
transform_matrix.multiply(um_node.getLocalTransformation())
# offset with transform from 3mf um_node.setTransformation(transform_matrix)
transform = build_item.get("transform")
if transform is not None:
transform_matrix.multiply(self._createMatrixFromTransformationString(transform))
build_item_node.setTransformation(transform_matrix)
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
@ -228,12 +189,13 @@ class ThreeMFReader(MeshReader):
transformation_matrix.multiply(scale_matrix) transformation_matrix.multiply(scale_matrix)
# Pre multiply the transformation with the loaded transformation, so the data is handled correctly. # Pre multiply the transformation with the loaded transformation, so the data is handled correctly.
build_item_node.setTransformation(build_item_node.getLocalTransformation().preMultiply(transformation_matrix)) um_node.setTransformation(um_node.getLocalTransformation().preMultiply(transformation_matrix))
result.append(build_item_node) result.append(um_node)
except Exception as e: except Exception:
Logger.log("e", "An exception occurred in 3mf reader: %s", e) Logger.logException("e", "An exception occurred in 3mf reader.")
return []
return result return result

View file

@ -6,6 +6,11 @@ from UM.Math.Vector import Vector
from UM.Logger import Logger from UM.Logger import Logger
from UM.Math.Matrix import Matrix from UM.Math.Matrix import Matrix
from UM.Application import Application from UM.Application import Application
import UM.Scene.SceneNode
import Savitar
import numpy
MYPY = False MYPY = False
try: try:
@ -55,6 +60,48 @@ class ThreeMFWriter(MeshWriter):
def setStoreArchive(self, store_archive): def setStoreArchive(self, store_archive):
self._store_archive = store_archive self._store_archive = store_archive
## Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode
# \returns Uranium Scenen node.
def _convertUMNodeToSavitarNode(self, um_node, transformation = Matrix()):
if type(um_node) is not UM.Scene.SceneNode.SceneNode:
return None
savitar_node = Savitar.SceneNode()
node_matrix = um_node.getLocalTransformation()
matrix_string = self._convertMatrixToString(node_matrix.preMultiply(transformation))
savitar_node.setTransformation(matrix_string)
mesh_data = um_node.getMeshData()
if mesh_data is not None:
savitar_node.getMeshData().setVerticesFromBytes(mesh_data.getVerticesAsByteArray())
indices_array = mesh_data.getIndicesAsByteArray()
if indices_array is not None:
savitar_node.getMeshData().setFacesFromBytes(indices_array)
else:
savitar_node.getMeshData().setFacesFromBytes(numpy.arange(mesh_data.getVertices().size / 3, dtype=numpy.int32).tostring())
# Handle per object settings (if any)
stack = um_node.callDecoration("getStack")
if stack is not None:
changed_setting_keys = set(stack.getTop().getAllKeys())
# Ensure that we save the extruder used for this object.
if stack.getProperty("machine_extruder_count", "value") > 1:
changed_setting_keys.add("extruder_nr")
# Get values for all changed settings & save them.
for key in changed_setting_keys:
savitar_node.setSetting(key, str(stack.getProperty(key, "value")))
for child_node in um_node.getChildren():
savitar_child_node = self._convertUMNodeToSavitarNode(child_node)
if savitar_child_node is not None:
savitar_node.addChild(savitar_child_node)
return savitar_node
def getArchive(self): def getArchive(self):
return self._archive return self._archive
@ -79,98 +126,7 @@ class ThreeMFWriter(MeshWriter):
relations_element = ET.Element("Relationships", xmlns = self._namespaces["relationships"]) relations_element = ET.Element("Relationships", xmlns = self._namespaces["relationships"])
model_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/3D/3dmodel.model", Id = "rel0", Type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel") model_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/3D/3dmodel.model", Id = "rel0", Type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel")
model = ET.Element("model", unit = "millimeter", xmlns = self._namespaces["3mf"]) savitar_scene = Savitar.Scene()
model.set("xmlns:cura", self._namespaces["cura"])
# Add the version of Cura this was created with. Since there is no "version" or similar metadata name we need
# to prefix it with the cura namespace, as specified by the 3MF specification.
version_metadata = ET.SubElement(model, "metadata", name = "cura:version")
version_metadata.text = Application.getInstance().getVersion()
resources = ET.SubElement(model, "resources")
build = ET.SubElement(model, "build")
added_nodes = []
index = 0 # Ensure index always exists (even if there are no nodes to write)
# Write all nodes with meshData to the file as objects inside the resource tag
for index, n in enumerate(MeshWriter._meshNodes(nodes)):
added_nodes.append(n) # Save the nodes that have mesh data
object = ET.SubElement(resources, "object", id = str(index+1), type = "model")
mesh = ET.SubElement(object, "mesh")
mesh_data = n.getMeshData()
vertices = ET.SubElement(mesh, "vertices")
verts = mesh_data.getVertices()
if verts is None:
Logger.log("d", "3mf writer can't write nodes without mesh data. Skipping this node.")
continue # No mesh data, nothing to do.
if mesh_data.hasIndices():
for face in mesh_data.getIndices():
v1 = verts[face[0]]
v2 = verts[face[1]]
v3 = verts[face[2]]
xml_vertex1 = ET.SubElement(vertices, "vertex", x = str(v1[0]), y = str(v1[1]), z = str(v1[2]))
xml_vertex2 = ET.SubElement(vertices, "vertex", x = str(v2[0]), y = str(v2[1]), z = str(v2[2]))
xml_vertex3 = ET.SubElement(vertices, "vertex", x = str(v3[0]), y = str(v3[1]), z = str(v3[2]))
triangles = ET.SubElement(mesh, "triangles")
for face in mesh_data.getIndices():
triangle = ET.SubElement(triangles, "triangle", v1 = str(face[0]) , v2 = str(face[1]), v3 = str(face[2]))
else:
triangles = ET.SubElement(mesh, "triangles")
for idx, vert in enumerate(verts):
xml_vertex = ET.SubElement(vertices, "vertex", x = str(vert[0]), y = str(vert[1]), z = str(vert[2]))
# If we have no faces defined, assume that every three subsequent vertices form a face.
if idx % 3 == 0:
triangle = ET.SubElement(triangles, "triangle", v1 = str(idx), v2 = str(idx + 1), v3 = str(idx + 2))
# Handle per object settings
stack = n.callDecoration("getStack")
if stack is not None:
changed_setting_keys = set(stack.getTop().getAllKeys())
# Ensure that we save the extruder used for this object.
if stack.getProperty("machine_extruder_count", "value") > 1:
changed_setting_keys.add("extruder_nr")
settings_xml = ET.SubElement(object, "settings", xmlns=self._namespaces["cura"])
# Get values for all changed settings & save them.
for key in changed_setting_keys:
setting_xml = ET.SubElement(settings_xml, "setting", key = key)
setting_xml.text = str(stack.getProperty(key, "value"))
# Add one to the index as we haven't incremented the last iteration.
index += 1
nodes_to_add = set()
for node in added_nodes:
# Check the parents of the nodes with mesh_data and ensure that they are also added.
parent_node = node.getParent()
while parent_node is not None:
if parent_node.callDecoration("isGroup"):
nodes_to_add.add(parent_node)
parent_node = parent_node.getParent()
else:
parent_node = None
# Sort all the nodes by depth (so nodes with the highest depth are done first)
sorted_nodes_to_add = sorted(nodes_to_add, key=lambda node: node.getDepth(), reverse = True)
# We have already saved the nodes with mesh data, but now we also want to save nodes required for the scene
for node in sorted_nodes_to_add:
object = ET.SubElement(resources, "object", id=str(index + 1), type="model")
components = ET.SubElement(object, "components")
for child in node.getChildren():
if child in added_nodes:
component = ET.SubElement(components, "component", objectid = str(added_nodes.index(child) + 1), transform = self._convertMatrixToString(child.getLocalTransformation()))
index += 1
added_nodes.append(node)
# Create a transformation Matrix to convert from our worldspace into 3MF.
# First step: flip the y and z axis.
transformation_matrix = Matrix() transformation_matrix = Matrix()
transformation_matrix._data[1, 1] = 0 transformation_matrix._data[1, 1] = 0
transformation_matrix._data[1, 2] = -1 transformation_matrix._data[1, 2] = -1
@ -188,14 +144,23 @@ class ThreeMFWriter(MeshWriter):
translation_matrix.setByTranslation(translation_vector) translation_matrix.setByTranslation(translation_vector)
transformation_matrix.preMultiply(translation_matrix) transformation_matrix.preMultiply(translation_matrix)
# Find out what the final build items are and add them.
for node in added_nodes:
if node.getParent().callDecoration("isGroup") is None:
node_matrix = node.getLocalTransformation()
ET.SubElement(build, "item", objectid = str(added_nodes.index(node) + 1), transform = self._convertMatrixToString(node_matrix.preMultiply(transformation_matrix))) root_node = UM.Application.getInstance().getController().getScene().getRoot()
for node in nodes:
if node == root_node:
for root_child in node.getChildren():
savitar_node = self._convertUMNodeToSavitarNode(root_child, transformation_matrix)
if savitar_node:
savitar_scene.addSceneNode(savitar_node)
else:
savitar_node = self._convertUMNodeToSavitarNode(node, transformation_matrix)
if savitar_node:
savitar_scene.addSceneNode(savitar_node)
archive.writestr(model_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(model)) parser = Savitar.ThreeMFParser()
scene_string = parser.sceneToString(savitar_scene)
archive.writestr(model_file, scene_string)
archive.writestr(content_types_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(content_types)) archive.writestr(content_types_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(content_types))
archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element)) archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
except Exception as e: except Exception as e: