mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-06 21:44:01 -06:00
Solved merge conflict. CURA-3321
This commit is contained in:
commit
42d939b34e
65 changed files with 1358 additions and 418 deletions
|
@ -18,6 +18,10 @@ from cura.QualityManager import QualityManager
|
|||
from UM.Scene.SceneNode import SceneNode
|
||||
|
||||
MYPY = False
|
||||
|
||||
import Savitar
|
||||
import numpy
|
||||
|
||||
try:
|
||||
if not MYPY:
|
||||
import xml.etree.cElementTree as ET
|
||||
|
@ -38,98 +42,10 @@ class ThreeMFReader(MeshReader):
|
|||
self._base_name = ""
|
||||
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):
|
||||
if transformation == "":
|
||||
return Matrix()
|
||||
|
||||
splitted_transformation = transformation.split()
|
||||
## Transformation is saved as:
|
||||
## M00 M01 M02 0.0
|
||||
|
@ -156,51 +72,96 @@ class ThreeMFReader(MeshReader):
|
|||
|
||||
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):
|
||||
result = []
|
||||
# The base object of 3mf is a zipped archive.
|
||||
archive = zipfile.ZipFile(file_name, "r")
|
||||
self._base_name = os.path.basename(file_name)
|
||||
try:
|
||||
self._root = ET.parse(archive.open("3D/3dmodel.model"))
|
||||
self._unit = self._root.getroot().get("unit")
|
||||
|
||||
build_items = self._root.findall("./3mf:build/3mf:item", self._namespaces)
|
||||
|
||||
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))
|
||||
|
||||
archive = zipfile.ZipFile(file_name, "r")
|
||||
self._base_name = os.path.basename(file_name)
|
||||
parser = Savitar.ThreeMFParser()
|
||||
scene_3mf = parser.parse(archive.open("3D/3dmodel.model").read())
|
||||
self._unit = scene_3mf.getUnit()
|
||||
for node in scene_3mf.getSceneNodes():
|
||||
um_node = self._convertSavitarNodeToUMNode(node)
|
||||
# compensate for original center position, if object(s) is/are not around its zero position
|
||||
|
||||
transform_matrix = Matrix()
|
||||
mesh_data = build_item_node.getMeshData()
|
||||
mesh_data = um_node.getMeshData()
|
||||
if mesh_data is not None:
|
||||
extents = mesh_data.getExtents()
|
||||
center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
|
||||
transform_matrix.setByTranslation(center_vector)
|
||||
|
||||
# offset with transform from 3mf
|
||||
transform = build_item.get("transform")
|
||||
if transform is not None:
|
||||
transform_matrix.multiply(self._createMatrixFromTransformationString(transform))
|
||||
|
||||
build_item_node.setTransformation(transform_matrix)
|
||||
transform_matrix.multiply(um_node.getLocalTransformation())
|
||||
um_node.setTransformation(transform_matrix)
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
|
||||
|
@ -215,9 +176,9 @@ class ThreeMFReader(MeshReader):
|
|||
# Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
|
||||
# build volume.
|
||||
if global_container_stack:
|
||||
translation_vector = Vector(x = -global_container_stack.getProperty("machine_width", "value") / 2,
|
||||
y = -global_container_stack.getProperty("machine_depth", "value") / 2,
|
||||
z = 0)
|
||||
translation_vector = Vector(x=-global_container_stack.getProperty("machine_width", "value") / 2,
|
||||
y=-global_container_stack.getProperty("machine_depth", "value") / 2,
|
||||
z=0)
|
||||
translation_matrix = Matrix()
|
||||
translation_matrix.setByTranslation(translation_vector)
|
||||
transformation_matrix.multiply(translation_matrix)
|
||||
|
@ -228,12 +189,13 @@ class ThreeMFReader(MeshReader):
|
|||
transformation_matrix.multiply(scale_matrix)
|
||||
|
||||
# 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:
|
||||
Logger.log("e", "An exception occurred in 3mf reader: %s", e)
|
||||
except Exception:
|
||||
Logger.logException("e", "An exception occurred in 3mf reader.")
|
||||
return []
|
||||
|
||||
return result
|
||||
|
||||
|
|
|
@ -476,7 +476,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
return nodes
|
||||
|
||||
def _stripFileToId(self, file):
|
||||
return file.replace("Cura/", "").split(".")[0]
|
||||
mime_type = MimeTypeDatabase.getMimeTypeForFile(file)
|
||||
file = mime_type.stripExtension(file)
|
||||
return file.replace("Cura/", "")
|
||||
|
||||
def _getXmlProfileClass(self):
|
||||
return self._container_registry.getContainerForMimeType(MimeTypeDatabase.getMimeType("application/x-ultimaker-material-profile"))
|
||||
|
|
|
@ -6,6 +6,11 @@ from UM.Math.Vector import Vector
|
|||
from UM.Logger import Logger
|
||||
from UM.Math.Matrix import Matrix
|
||||
from UM.Application import Application
|
||||
import UM.Scene.SceneNode
|
||||
|
||||
import Savitar
|
||||
|
||||
import numpy
|
||||
|
||||
MYPY = False
|
||||
try:
|
||||
|
@ -35,18 +40,18 @@ class ThreeMFWriter(MeshWriter):
|
|||
|
||||
def _convertMatrixToString(self, matrix):
|
||||
result = ""
|
||||
result += str(matrix._data[0,0]) + " "
|
||||
result += str(matrix._data[1,0]) + " "
|
||||
result += str(matrix._data[2,0]) + " "
|
||||
result += str(matrix._data[0,1]) + " "
|
||||
result += str(matrix._data[1,1]) + " "
|
||||
result += str(matrix._data[2,1]) + " "
|
||||
result += str(matrix._data[0,2]) + " "
|
||||
result += str(matrix._data[1,2]) + " "
|
||||
result += str(matrix._data[2,2]) + " "
|
||||
result += str(matrix._data[0,3]) + " "
|
||||
result += str(matrix._data[1,3]) + " "
|
||||
result += str(matrix._data[2,3])
|
||||
result += str(matrix._data[0, 0]) + " "
|
||||
result += str(matrix._data[1, 0]) + " "
|
||||
result += str(matrix._data[2, 0]) + " "
|
||||
result += str(matrix._data[0, 1]) + " "
|
||||
result += str(matrix._data[1, 1]) + " "
|
||||
result += str(matrix._data[2, 1]) + " "
|
||||
result += str(matrix._data[0, 2]) + " "
|
||||
result += str(matrix._data[1, 2]) + " "
|
||||
result += str(matrix._data[2, 2]) + " "
|
||||
result += str(matrix._data[0, 3]) + " "
|
||||
result += str(matrix._data[1, 3]) + " "
|
||||
result += str(matrix._data[2, 3])
|
||||
return result
|
||||
|
||||
## Should we store the archive
|
||||
|
@ -55,6 +60,48 @@ class ThreeMFWriter(MeshWriter):
|
|||
def setStoreArchive(self, 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):
|
||||
return self._archive
|
||||
|
||||
|
@ -79,98 +126,7 @@ class ThreeMFWriter(MeshWriter):
|
|||
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 = ET.Element("model", unit = "millimeter", xmlns = self._namespaces["3mf"])
|
||||
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.
|
||||
savitar_scene = Savitar.Scene()
|
||||
transformation_matrix = Matrix()
|
||||
transformation_matrix._data[1, 1] = 0
|
||||
transformation_matrix._data[1, 2] = -1
|
||||
|
@ -188,14 +144,23 @@ class ThreeMFWriter(MeshWriter):
|
|||
translation_matrix.setByTranslation(translation_vector)
|
||||
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(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
|
||||
except Exception as e:
|
||||
|
|
39
plugins/CuraEngineBackend/CuraEngineBackend.py
Normal file → Executable file
39
plugins/CuraEngineBackend/CuraEngineBackend.py
Normal file → Executable file
|
@ -105,6 +105,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||
|
||||
self._backend_log_max_lines = 20000 # Maximum number of lines to buffer
|
||||
self._error_message = None # Pop-up message that shows errors.
|
||||
self._last_num_objects = 0 # Count number of objects to see if there is something changed
|
||||
self._postponed_scene_change_sources = [] # scene change is postponed (by a tool)
|
||||
|
||||
self.backendQuit.connect(self._onBackendQuit)
|
||||
self.backendConnected.connect(self._onBackendConnected)
|
||||
|
@ -340,21 +342,32 @@ class CuraEngineBackend(QObject, Backend):
|
|||
#
|
||||
# \param source The scene node that was changed.
|
||||
def _onSceneChanged(self, source):
|
||||
if self._tool_active:
|
||||
return
|
||||
|
||||
if type(source) is not SceneNode:
|
||||
return
|
||||
|
||||
if source is self._scene.getRoot():
|
||||
return
|
||||
root_scene_nodes_changed = False
|
||||
if source == self._scene.getRoot():
|
||||
num_objects = 0
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
# Only count sliceable objects
|
||||
if node.callDecoration("isSliceable"):
|
||||
num_objects += 1
|
||||
if num_objects != self._last_num_objects:
|
||||
self._last_num_objects = num_objects
|
||||
root_scene_nodes_changed = True
|
||||
else:
|
||||
return
|
||||
|
||||
self.determineAutoSlicing()
|
||||
if not source.callDecoration("isGroup") and not root_scene_nodes_changed:
|
||||
if source.getMeshData() is None:
|
||||
return
|
||||
if source.getMeshData().getVertices() is None:
|
||||
return
|
||||
|
||||
if source.getMeshData() is None:
|
||||
return
|
||||
|
||||
if source.getMeshData().getVertices() is None:
|
||||
if self._tool_active:
|
||||
# do it later, each source only has to be done once
|
||||
if source not in self._postponed_scene_change_sources:
|
||||
self._postponed_scene_change_sources.append(source)
|
||||
return
|
||||
|
||||
self.needsSlicing()
|
||||
|
@ -501,7 +514,11 @@ class CuraEngineBackend(QObject, Backend):
|
|||
# \param tool The tool that the user was using.
|
||||
def _onToolOperationStopped(self, tool):
|
||||
self._tool_active = False # React on scene change again
|
||||
self.determineAutoSlicing()
|
||||
self.determineAutoSlicing() # Switch timer on if appropriate
|
||||
# Process all the postponed scene changes
|
||||
while self._postponed_scene_change_sources:
|
||||
source = self._postponed_scene_change_sources.pop(0)
|
||||
self._onSceneChanged(source)
|
||||
|
||||
## Called when the user changes the active view mode.
|
||||
def _onActiveViewChanged(self):
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
import configparser
|
||||
|
||||
from UM import PluginRegistry
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
|
||||
from cura.ProfileReader import ProfileReader
|
||||
|
|
0
plugins/LayerView/LayerPass.py
Normal file → Executable file
0
plugins/LayerView/LayerPass.py
Normal file → Executable file
0
plugins/LayerView/layers.shader
Normal file → Executable file
0
plugins/LayerView/layers.shader
Normal file → Executable file
4
plugins/LayerView/layers3d.shader
Normal file → Executable file
4
plugins/LayerView/layers3d.shader
Normal file → Executable file
|
@ -16,7 +16,7 @@ vertex41core =
|
|||
in lowp vec4 a_material_color;
|
||||
in highp vec4 a_normal;
|
||||
in highp vec2 a_line_dim; // line width and thickness
|
||||
in highp int a_extruder; // Note: cannot use this in compatibility, int is only available in newer OpenGL.
|
||||
in highp float a_extruder;
|
||||
in highp float a_line_type;
|
||||
|
||||
out lowp vec4 v_color;
|
||||
|
@ -53,7 +53,7 @@ vertex41core =
|
|||
v_vertex = world_space_vert.xyz;
|
||||
v_normal = (u_normalMatrix * normalize(a_normal)).xyz;
|
||||
v_line_dim = a_line_dim;
|
||||
v_extruder = a_extruder;
|
||||
v_extruder = int(a_extruder);
|
||||
v_line_type = a_line_type;
|
||||
v_extruder_opacity = u_extruder_opacity;
|
||||
|
||||
|
|
|
@ -209,6 +209,8 @@ Item {
|
|||
{
|
||||
case "int":
|
||||
return settingTextField
|
||||
case "[int]":
|
||||
return settingTextField
|
||||
case "float":
|
||||
return settingTextField
|
||||
case "enum":
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.Platform import Platform
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
|
5
plugins/SliceInfoPlugin/SliceInfo.py
Normal file → Executable file
5
plugins/SliceInfoPlugin/SliceInfo.py
Normal file → Executable file
|
@ -90,9 +90,8 @@ class SliceInfo(Extension):
|
|||
# Listing all files placed on the buildplate
|
||||
modelhashes = []
|
||||
for node in DepthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()):
|
||||
if type(node) is not SceneNode or not node.getMeshData():
|
||||
continue
|
||||
modelhashes.append(node.getMeshData().getHash())
|
||||
if node.callDecoration("isSliceable"):
|
||||
modelhashes.append(node.getMeshData().getHash())
|
||||
|
||||
# Creating md5sums and formatting them as discussed on JIRA
|
||||
modelhash_formatted = ",".join(modelhashes)
|
||||
|
|
|
@ -99,6 +99,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._material_ids = [""] * self._num_extruders
|
||||
self._hotend_ids = [""] * self._num_extruders
|
||||
self._target_bed_temperature = 0
|
||||
self._processing_preheat_requests = True
|
||||
|
||||
self.setPriority(2) # Make sure the output device gets selected above local file output
|
||||
self.setName(key)
|
||||
|
@ -262,6 +263,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
|||
Logger.log("i", "Pre-heating bed to %i degrees.", temperature)
|
||||
put_request = QNetworkRequest(url)
|
||||
put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
|
||||
self._processing_preheat_requests = False
|
||||
self._manager.put(put_request, data.encode())
|
||||
self._preheat_bed_timer.start(self._preheat_bed_timeout * 1000) #Times 1000 because it needs to be provided as milliseconds.
|
||||
self.preheatBedRemainingTimeChanged.emit()
|
||||
|
@ -532,6 +534,29 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._updateHeadPosition(head_x, head_y, head_z)
|
||||
self._updatePrinterState(self._json_printer_state["status"])
|
||||
|
||||
if self._processing_preheat_requests:
|
||||
try:
|
||||
is_preheating = self._json_printer_state["bed"]["pre_heat"]["active"]
|
||||
except KeyError: #Old firmware doesn't support that.
|
||||
pass #Don't update the pre-heat remaining time.
|
||||
else:
|
||||
if is_preheating:
|
||||
try:
|
||||
remaining_preheat_time = self._json_printer_state["bed"]["pre_heat"]["remaining"]
|
||||
except KeyError: #Error in firmware. If "active" is supported, "remaining" should also be supported.
|
||||
pass #Anyway, don't update.
|
||||
else:
|
||||
#Only update if time estimate is significantly off (>5000ms).
|
||||
#Otherwise we get issues with latency causing the timer to count inconsistently.
|
||||
if abs(self._preheat_bed_timer.remainingTime() - remaining_preheat_time * 1000) > 5000:
|
||||
self._preheat_bed_timer.setInterval(remaining_preheat_time * 1000)
|
||||
self._preheat_bed_timer.start()
|
||||
self.preheatBedRemainingTimeChanged.emit()
|
||||
else: #Not pre-heating. Must've cancelled.
|
||||
if self._preheat_bed_timer.isActive():
|
||||
self._preheat_bed_timer.setInterval(0)
|
||||
self._preheat_bed_timer.stop()
|
||||
self.preheatBedRemainingTimeChanged.emit()
|
||||
|
||||
def close(self):
|
||||
Logger.log("d", "Closing connection of printer %s with ip %s", self._key, self._address)
|
||||
|
@ -1033,6 +1058,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._progress_message.hide()
|
||||
|
||||
elif reply.operation() == QNetworkAccessManager.PutOperation:
|
||||
if "printer/bed/pre_heat" in reply_url: #Pre-heat command has completed. Re-enable syncing pre-heating.
|
||||
self._processing_preheat_requests = True
|
||||
if status_code in [200, 201, 202, 204]:
|
||||
pass # Request was successful!
|
||||
else:
|
||||
|
|
|
@ -45,7 +45,7 @@ class UMOUpgradeSelection(MachineAction):
|
|||
|
||||
def _createDefinitionChangesContainer(self, global_container_stack):
|
||||
# Create a definition_changes container to store the settings in and add it to the stack
|
||||
definition_changes_container = UM.Settings.InstanceContainer(global_container_stack.getName() + "_settings")
|
||||
definition_changes_container = UM.Settings.InstanceContainer.InstanceContainer(global_container_stack.getName() + "_settings")
|
||||
definition = global_container_stack.getBottom()
|
||||
definition_changes_container.setDefinition(definition)
|
||||
definition_changes_container.addMetaDataEntry("type", "definition_changes")
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import UM.VersionUpgrade #To indicate that a file is of incorrect format.
|
||||
import UM.VersionUpgradeManager #To schedule more files to be upgraded.
|
||||
import UM.Resources #To get the config storage path.
|
||||
from UM.Resources import Resources #To get the config storage path.
|
||||
|
||||
import configparser #To read config files.
|
||||
import io #To write config files to strings as if they were files.
|
||||
|
@ -107,7 +107,7 @@ class MachineInstance:
|
|||
user_profile["values"] = {}
|
||||
|
||||
version_upgrade_manager = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance()
|
||||
user_storage = os.path.join(UM.Resources.getDataStoragePath(), next(iter(version_upgrade_manager.getStoragePaths("user"))))
|
||||
user_storage = os.path.join(Resources.getDataStoragePath(), next(iter(version_upgrade_manager.getStoragePaths("user"))))
|
||||
user_profile_file = os.path.join(user_storage, urllib.parse.quote_plus(self._name) + "_current_settings.inst.cfg")
|
||||
if not os.path.exists(user_storage):
|
||||
os.makedirs(user_storage)
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
import configparser #To read config files.
|
||||
import io #To write config files to strings as if they were files.
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
||||
import UM.VersionUpgrade
|
||||
from UM.Logger import Logger
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue