mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-12 22:39:07 +08:00
Merge branch 'master' of github.com:Ultimaker/Cura
This commit is contained in:
commit
e561217415
4
Jenkinsfile
vendored
4
Jenkinsfile
vendored
@ -1,5 +1,5 @@
|
||||
timeout(time: 2, unit: "HOURS") {
|
||||
parallel_nodes(['linux && cura', 'windows && cura']) {
|
||||
parallel_nodes(['linux && cura', 'windows && cura']) {
|
||||
timeout(time: 2, unit: "HOURS") {
|
||||
// Prepare building
|
||||
stage('Prepare') {
|
||||
// Ensure we start with a clean build directory.
|
||||
|
@ -1,9 +1,8 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Scene.Platform import Platform
|
||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
@ -195,29 +194,51 @@ class BuildVolume(SceneNode):
|
||||
|
||||
return True
|
||||
|
||||
## For every sliceable node, update outsideBuildArea
|
||||
def updateNodeBoundaryCheck(self):
|
||||
root = Application.getInstance().getController().getScene().getRoot()
|
||||
nodes = list(BreadthFirstIterator(root))
|
||||
group_nodes = []
|
||||
## For every sliceable node, update node._outside_buildarea.
|
||||
def updateAllBoundaryChecks(self):
|
||||
self.updateNodeBoundaryCheck(Application.getInstance().getController().getScene().getRoot())
|
||||
|
||||
build_volume_bounding_box = self.getBoundingBox()
|
||||
if build_volume_bounding_box:
|
||||
# It's over 9000!
|
||||
build_volume_bounding_box = build_volume_bounding_box.set(bottom=-9001)
|
||||
else:
|
||||
# No bounding box. This is triggered when running Cura from command line with a model for the first time
|
||||
# In that situation there is a model, but no machine (and therefore no build volume.
|
||||
## For a single node, update _outside_buildarea.
|
||||
#
|
||||
# If the node is a group node, the child nodes will also get updated.
|
||||
# \param node The node to update the boundary checks of.
|
||||
def updateNodeBoundaryCheck(self, node: SceneNode):
|
||||
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
|
||||
for child in node.getChildren(): #Still update the children! For instance, the root is not sliceable.
|
||||
self.updateNodeBoundaryCheck(child)
|
||||
return #Don't compute for non-sliceable nodes.
|
||||
|
||||
#Mark the node as outside the build volume if the bounding box test fails.
|
||||
build_volume = self.getBoundingBox()
|
||||
if build_volume is None:
|
||||
#No bounding box. This is triggered when running Cura from command line with a model for the first time.
|
||||
#In that situation there is a model, but no machine (and therefore no build volume).
|
||||
return
|
||||
build_volume = build_volume.set(bottom = -999999) #Allow models to clip the build plate. This should allow printing but remove the bottom side of the model underneath the build plate.
|
||||
bounding_box = node.getBoundingBox()
|
||||
if build_volume.intersectsBox(bounding_box) != AxisAlignedBox.IntersectionResult.FullIntersection:
|
||||
node._outside_buildarea = True
|
||||
else:
|
||||
|
||||
for node in nodes:
|
||||
# Need to check group nodes later
|
||||
self.checkBoundsAndUpdate(node, bounds = build_volume_bounding_box)
|
||||
#Check for collisions between disallowed areas and the object.
|
||||
convex_hull = node.callDecoration("getConvexHull")
|
||||
if not convex_hull or not convex_hull.isValid():
|
||||
return
|
||||
for area in self.getDisallowedAreas():
|
||||
overlap = convex_hull.intersectsPolygon(area)
|
||||
if overlap is not None:
|
||||
node._outside_buildarea = True
|
||||
break
|
||||
else:
|
||||
node._outside_buildarea = False
|
||||
|
||||
# Group nodes should override the _outside_buildarea property of their children.
|
||||
for group_node in group_nodes:
|
||||
for child_node in group_node.getAllChildren():
|
||||
child_node._outside_buildarea = group_node._outside_buildarea
|
||||
#Group nodes should override the _outside_buildarea property of their children.
|
||||
if node.callDecoration("isGroup"):
|
||||
for child in node.getAllChildren():
|
||||
child._outside_buildarea = node._outside_buildarea
|
||||
else:
|
||||
for child in node.getChildren():
|
||||
self.updateNodeBoundaryCheck(child)
|
||||
|
||||
## Update the outsideBuildArea of a single node, given bounds or current build volume
|
||||
def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None):
|
||||
@ -440,7 +461,7 @@ class BuildVolume(SceneNode):
|
||||
|
||||
Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds
|
||||
|
||||
self.updateNodeBoundaryCheck()
|
||||
self.updateAllBoundaryChecks()
|
||||
|
||||
def getBoundingBox(self) -> AxisAlignedBox:
|
||||
return self._volume_aabb
|
||||
|
@ -94,6 +94,10 @@ class CuraActions(QObject):
|
||||
removed_group_nodes.append(group_node)
|
||||
op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent()))
|
||||
op.addOperation(RemoveSceneNodeOperation(group_node))
|
||||
|
||||
# Reset the print information
|
||||
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||
|
||||
op.push()
|
||||
|
||||
## Set the extruder that should be used to print the selection.
|
||||
|
@ -1089,12 +1089,12 @@ class CuraApplication(QtApplication):
|
||||
for node in nodes:
|
||||
op.addOperation(RemoveSceneNodeOperation(node))
|
||||
|
||||
# Reset the print information
|
||||
self.getController().getScene().sceneChanged.emit(node)
|
||||
|
||||
op.push()
|
||||
Selection.clear()
|
||||
|
||||
# Reset the print information:
|
||||
self.getController().getScene().sceneChanged.emit(node)
|
||||
|
||||
## Reset all translation on nodes with mesh data.
|
||||
@pyqtSlot()
|
||||
def resetAllTranslation(self):
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
@ -56,14 +56,17 @@ class PlatformPhysics:
|
||||
# By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve.
|
||||
nodes = list(BreadthFirstIterator(root))
|
||||
|
||||
# Only check nodes inside build area.
|
||||
nodes = [node for node in nodes if (hasattr(node, "_outside_buildarea") and not node._outside_buildarea)]
|
||||
|
||||
random.shuffle(nodes)
|
||||
for node in nodes:
|
||||
if node is root or not isinstance(node, SceneNode) or node.getBoundingBox() is None:
|
||||
continue
|
||||
|
||||
#Only check nodes inside the build area.
|
||||
if not hasattr(node, "_outside_buildarea"):
|
||||
self._build_volume.updateNodeBoundaryCheck(node)
|
||||
if getattr(node, "_outside_buildarea", True):
|
||||
continue
|
||||
|
||||
bbox = node.getBoundingBox()
|
||||
|
||||
# Move it downwards if bottom is above platform
|
||||
@ -155,7 +158,7 @@ class PlatformPhysics:
|
||||
|
||||
# After moving, we have to evaluate the boundary checks for nodes
|
||||
build_volume = Application.getInstance().getBuildVolume()
|
||||
build_volume.updateNodeBoundaryCheck()
|
||||
build_volume.updateAllBoundaryChecks()
|
||||
|
||||
def _onToolOperationStarted(self, tool):
|
||||
self._enabled = False
|
||||
|
@ -8,7 +8,9 @@ from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.Duration import Duration
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from typing import Dict
|
||||
@ -65,7 +67,7 @@ class PrintInformation(QObject):
|
||||
self._backend = Application.getInstance().getBackend()
|
||||
if self._backend:
|
||||
self._backend.printDurationMessage.connect(self._onPrintDurationMessage)
|
||||
Application.getInstance().getController().getScene().sceneChanged.connect(self.setToZeroPrintInformation)
|
||||
Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged)
|
||||
|
||||
self._base_name = ""
|
||||
self._abbr_machine = ""
|
||||
@ -395,12 +397,25 @@ class PrintInformation(QObject):
|
||||
return result
|
||||
|
||||
# Simulate message with zero time duration
|
||||
def setToZeroPrintInformation(self, build_plate_number):
|
||||
temp_message = {}
|
||||
if build_plate_number not in self._print_time_message_values:
|
||||
self._print_time_message_values[build_plate_number] = {}
|
||||
for key in self._print_time_message_values[build_plate_number].keys():
|
||||
temp_message[key] = 0
|
||||
def setToZeroPrintInformation(self, build_plate):
|
||||
|
||||
# Construct the 0-time message
|
||||
temp_message = {}
|
||||
if build_plate not in self._print_time_message_values:
|
||||
self._print_time_message_values[build_plate] = {}
|
||||
for key in self._print_time_message_values[build_plate].keys():
|
||||
temp_message[key] = 0
|
||||
temp_material_amounts = [0]
|
||||
self._onPrintDurationMessage(build_plate_number, temp_message, temp_material_amounts)
|
||||
|
||||
self._onPrintDurationMessage(build_plate, temp_message, temp_material_amounts)
|
||||
|
||||
## Listen to scene changes to check if we need to reset the print information
|
||||
def _onSceneChanged(self, scene_node):
|
||||
|
||||
# Ignore any changes that are not related to sliceable objects
|
||||
if not isinstance(scene_node, SceneNode)\
|
||||
or not scene_node.callDecoration("isSliceable")\
|
||||
or not scene_node.callDecoration("getBuildPlateNumber") == self._active_build_plate:
|
||||
return
|
||||
|
||||
self.setToZeroPrintInformation(self._active_build_plate)
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from copy import deepcopy
|
||||
|
||||
|
@ -449,7 +449,13 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
if not extruder_stacks:
|
||||
self.addExtruderStackForSingleExtrusionMachine(container, "fdmextruder")
|
||||
|
||||
def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id):
|
||||
#
|
||||
# new_global_quality_changes is optional. It is only used in project loading for a scenario like this:
|
||||
# - override the current machine
|
||||
# - create new for custom quality profile
|
||||
# new_global_quality_changes is the new global quality changes container in this scenario.
|
||||
#
|
||||
def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id, new_global_quality_changes = None):
|
||||
new_extruder_id = extruder_id
|
||||
|
||||
extruder_definitions = self.findDefinitionContainers(id = new_extruder_id)
|
||||
@ -545,8 +551,12 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
quality_id = "empty_quality"
|
||||
extruder_stack.setQualityById(quality_id)
|
||||
|
||||
if machine.qualityChanges.getId() not in ("empty", "empty_quality_changes"):
|
||||
extruder_quality_changes_container = self.findInstanceContainers(name = machine.qualityChanges.getName(), extruder = extruder_id)
|
||||
machine_quality_changes = machine.qualityChanges
|
||||
if new_global_quality_changes is not None:
|
||||
machine_quality_changes = new_global_quality_changes
|
||||
|
||||
if machine_quality_changes.getId() not in ("empty", "empty_quality_changes"):
|
||||
extruder_quality_changes_container = self.findInstanceContainers(name = machine_quality_changes.getName(), extruder = extruder_id)
|
||||
if extruder_quality_changes_container:
|
||||
extruder_quality_changes_container = extruder_quality_changes_container[0]
|
||||
|
||||
@ -556,34 +566,34 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
# Some extruder quality_changes containers can be created at runtime as files in the qualities
|
||||
# folder. Those files won't be loaded in the registry immediately. So we also need to search
|
||||
# the folder to see if the quality_changes exists.
|
||||
extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine.qualityChanges.getName())
|
||||
extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName())
|
||||
if extruder_quality_changes_container:
|
||||
quality_changes_id = extruder_quality_changes_container.getId()
|
||||
extruder_stack.setQualityChangesById(quality_changes_id)
|
||||
else:
|
||||
# if we still cannot find a quality changes container for the extruder, create a new one
|
||||
container_name = machine.qualityChanges.getName()
|
||||
container_name = machine_quality_changes.getName()
|
||||
container_id = self.uniqueName(extruder_stack.getId() + "_qc_" + container_name)
|
||||
extruder_quality_changes_container = InstanceContainer(container_id)
|
||||
extruder_quality_changes_container.setName(container_name)
|
||||
extruder_quality_changes_container.addMetaDataEntry("type", "quality_changes")
|
||||
extruder_quality_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
||||
extruder_quality_changes_container.addMetaDataEntry("extruder", extruder_stack.definition.getId())
|
||||
extruder_quality_changes_container.addMetaDataEntry("quality_type", machine.qualityChanges.getMetaDataEntry("quality_type"))
|
||||
extruder_quality_changes_container.setDefinition(machine.qualityChanges.getDefinition().getId())
|
||||
extruder_quality_changes_container.addMetaDataEntry("quality_type", machine_quality_changes.getMetaDataEntry("quality_type"))
|
||||
extruder_quality_changes_container.setDefinition(machine_quality_changes.getDefinition().getId())
|
||||
|
||||
self.addContainer(extruder_quality_changes_container)
|
||||
extruder_stack.qualityChanges = extruder_quality_changes_container
|
||||
|
||||
if not extruder_quality_changes_container:
|
||||
Logger.log("w", "Could not find quality_changes named [%s] for extruder [%s]",
|
||||
machine.qualityChanges.getName(), extruder_stack.getId())
|
||||
machine_quality_changes.getName(), extruder_stack.getId())
|
||||
else:
|
||||
# move all per-extruder settings to the extruder's quality changes
|
||||
for qc_setting_key in machine.qualityChanges.getAllKeys():
|
||||
for qc_setting_key in machine_quality_changes.getAllKeys():
|
||||
settable_per_extruder = machine.getProperty(qc_setting_key, "settable_per_extruder")
|
||||
if settable_per_extruder:
|
||||
setting_value = machine.qualityChanges.getProperty(qc_setting_key, "value")
|
||||
setting_value = machine_quality_changes.getProperty(qc_setting_key, "value")
|
||||
|
||||
setting_definition = machine.getSettingDefinition(qc_setting_key)
|
||||
new_instance = SettingInstance(setting_definition, definition_changes)
|
||||
@ -592,7 +602,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
extruder_quality_changes_container.addInstance(new_instance)
|
||||
extruder_quality_changes_container.setDirty(True)
|
||||
|
||||
machine.qualityChanges.removeInstance(qc_setting_key, postpone_emit=True)
|
||||
machine_quality_changes.removeInstance(qc_setting_key, postpone_emit=True)
|
||||
else:
|
||||
extruder_stack.setQualityChangesById("empty_quality_changes")
|
||||
|
||||
@ -600,8 +610,8 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
|
||||
# Also need to fix the other qualities that are suitable for this machine. Those quality changes may still have
|
||||
# per-extruder settings in the container for the machine instead of the extruder.
|
||||
if machine.qualityChanges.getId() not in ("empty", "empty_quality_changes"):
|
||||
quality_changes_machine_definition_id = machine.qualityChanges.getDefinition().getId()
|
||||
if machine_quality_changes.getId() not in ("empty", "empty_quality_changes"):
|
||||
quality_changes_machine_definition_id = machine_quality_changes.getDefinition().getId()
|
||||
else:
|
||||
whole_machine_definition = machine.definition
|
||||
machine_entry = machine.definition.getMetaDataEntry("machine")
|
||||
@ -621,7 +631,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
qc_groups[qc_name] = []
|
||||
qc_groups[qc_name].append(qc)
|
||||
# try to find from the quality changes cura directory too
|
||||
quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine.qualityChanges.getName())
|
||||
quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName())
|
||||
if quality_changes_container:
|
||||
qc_groups[qc_name].append(quality_changes_container)
|
||||
|
||||
|
@ -502,6 +502,89 @@ class ExtruderManager(QObject):
|
||||
def getInstanceExtruderValues(self, key):
|
||||
return ExtruderManager.getExtruderValues(key)
|
||||
|
||||
## Updates the material container to a material that matches the material diameter set for the printer
|
||||
def updateMaterialForDiameter(self, extruder_position: int):
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return
|
||||
|
||||
if not global_stack.getMetaDataEntry("has_materials", False):
|
||||
return
|
||||
|
||||
extruder_stack = global_stack.extruders[str(extruder_position)]
|
||||
|
||||
material_diameter = extruder_stack.material.getProperty("material_diameter", "value")
|
||||
if not material_diameter:
|
||||
# in case of "empty" material
|
||||
material_diameter = 0
|
||||
|
||||
material_approximate_diameter = str(round(material_diameter))
|
||||
machine_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value")
|
||||
if not machine_diameter:
|
||||
if extruder_stack.definition.hasProperty("material_diameter", "value"):
|
||||
machine_diameter = extruder_stack.definition.getProperty("material_diameter", "value")
|
||||
else:
|
||||
machine_diameter = global_stack.definition.getProperty("material_diameter", "value")
|
||||
machine_approximate_diameter = str(round(machine_diameter))
|
||||
|
||||
if material_approximate_diameter != machine_approximate_diameter:
|
||||
Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.")
|
||||
|
||||
if global_stack.getMetaDataEntry("has_machine_materials", False):
|
||||
materials_definition = global_stack.definition.getId()
|
||||
has_material_variants = global_stack.getMetaDataEntry("has_variants", False)
|
||||
else:
|
||||
materials_definition = "fdmprinter"
|
||||
has_material_variants = False
|
||||
|
||||
old_material = extruder_stack.material
|
||||
search_criteria = {
|
||||
"type": "material",
|
||||
"approximate_diameter": machine_approximate_diameter,
|
||||
"material": old_material.getMetaDataEntry("material", "value"),
|
||||
"brand": old_material.getMetaDataEntry("brand", "value"),
|
||||
"supplier": old_material.getMetaDataEntry("supplier", "value"),
|
||||
"color_name": old_material.getMetaDataEntry("color_name", "value"),
|
||||
"definition": materials_definition
|
||||
}
|
||||
if has_material_variants:
|
||||
search_criteria["variant"] = extruder_stack.variant.getId()
|
||||
|
||||
container_registry = Application.getInstance().getContainerRegistry()
|
||||
empty_material = container_registry.findInstanceContainers(id = "empty_material")[0]
|
||||
|
||||
if old_material == empty_material:
|
||||
search_criteria.pop("material", None)
|
||||
search_criteria.pop("supplier", None)
|
||||
search_criteria.pop("brand", None)
|
||||
search_criteria.pop("definition", None)
|
||||
search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
|
||||
|
||||
materials = container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Same material with new diameter is not found, search for generic version of the same material type
|
||||
search_criteria.pop("supplier", None)
|
||||
search_criteria.pop("brand", None)
|
||||
search_criteria["color_name"] = "Generic"
|
||||
materials = container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Generic material with new diameter is not found, search for preferred material
|
||||
search_criteria.pop("color_name", None)
|
||||
search_criteria.pop("material", None)
|
||||
search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
|
||||
materials = container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Preferred material with new diameter is not found, search for any material
|
||||
search_criteria.pop("id", None)
|
||||
materials = container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Just use empty material as a final fallback
|
||||
materials = [empty_material]
|
||||
|
||||
Logger.log("i", "Selecting new material: %s", materials[0].getId())
|
||||
|
||||
extruder_stack.material = materials[0]
|
||||
|
||||
## Get the value for a setting from a specific extruder.
|
||||
#
|
||||
# This is exposed to SettingFunction to use in value functions.
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
from typing import Any, TYPE_CHECKING, Optional
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Decorators import override
|
||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
@ -59,14 +60,14 @@ class ExtruderStack(CuraContainerStack):
|
||||
keys_to_copy = ["material_diameter", "machine_nozzle_size"] # these will be copied over to all extruders
|
||||
|
||||
for key in keys_to_copy:
|
||||
# Since material_diameter is not on the extruder definition, we need to add it here
|
||||
# WARNING: this might be very dangerous and should be refactored ASAP!
|
||||
definition = stack.getSettingDefinition(key)
|
||||
if definition:
|
||||
self.definition.addDefinition(definition)
|
||||
|
||||
# Only copy the value when this extruder doesn't have the value.
|
||||
if self.definitionChanges.hasProperty(key, "value"):
|
||||
# If the first extruder has a value for this setting, we must copy it to the other extruders via the global stack.
|
||||
# Note: this assumes the extruders are loaded in the same order as they are positioned on the machine.
|
||||
if self.getMetaDataEntry("position") == "0":
|
||||
setting_value = self.definitionChanges.getProperty(key, "value")
|
||||
stack.definitionChanges.setProperty(key, "value", setting_value)
|
||||
continue
|
||||
|
||||
setting_value = stack.definitionChanges.getProperty(key, "value")
|
||||
@ -80,6 +81,11 @@ class ExtruderStack(CuraContainerStack):
|
||||
self.definitionChanges.addInstance(new_instance)
|
||||
self.definitionChanges.setDirty(True)
|
||||
|
||||
# Make sure the material diameter is up to date for the extruder stack.
|
||||
if key == "material_diameter":
|
||||
position = self.getMetaDataEntry("position", "0")
|
||||
Application.getInstance().getExtruderManager().updateMaterialForDiameter(position)
|
||||
|
||||
# NOTE: We cannot remove the setting from the global stack's definition changes container because for
|
||||
# material diameter, it needs to be applied to all extruders, but here we don't know how many extruders
|
||||
# a machine actually has and how many extruders has already been loaded for that machine, so we have to
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import time
|
||||
#Type hinting.
|
||||
from typing import Union, List, Dict
|
||||
|
||||
@ -407,21 +408,28 @@ class MachineManager(QObject):
|
||||
Logger.log("w", "Failed creating a new machine!")
|
||||
|
||||
def _checkStacksHaveErrors(self) -> bool:
|
||||
time_start = time.time()
|
||||
if self._global_container_stack is None: #No active machine.
|
||||
return False
|
||||
|
||||
if self._global_container_stack.hasErrors():
|
||||
Logger.log("d", "Checking global stack for errors took %0.2f s and we found and error" % (time.time() - time_start))
|
||||
return True
|
||||
|
||||
# Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are
|
||||
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
||||
extruder_stacks = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
|
||||
if len(extruder_stacks) > machine_extruder_count:
|
||||
extruder_stacks = extruder_stacks[:machine_extruder_count] # we only have to check the used extruders
|
||||
count = 1 # we start with the global stack
|
||||
for stack in extruder_stacks:
|
||||
md = stack.getMetaData()
|
||||
if "position" in md and int(md["position"]) >= machine_extruder_count:
|
||||
continue
|
||||
count += 1
|
||||
if stack.hasErrors():
|
||||
Logger.log("d", "Checking %s stacks for errors took %.2f s and we found an error in stack [%s]" % (count, time.time() - time_start, str(stack)))
|
||||
return True
|
||||
|
||||
Logger.log("d", "Checking %s stacks for errors took %.2f s" % (count, time.time() - time_start))
|
||||
return False
|
||||
|
||||
## Remove all instances from the top instanceContainer (effectively removing all user-changed settings)
|
||||
|
@ -31,10 +31,42 @@ import zipfile
|
||||
import io
|
||||
import configparser
|
||||
import os
|
||||
import threading
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
#
|
||||
# HACK:
|
||||
#
|
||||
# In project loading, when override the existing machine is selected, the stacks and containers that are correctly
|
||||
# active in the system will be overridden at runtime. Because the project loading is done in a different thread than
|
||||
# the Qt thread, something else can kick in the middle of the process. One of them is the rendering. It will access
|
||||
# the current stacks and container, which have not completely been updated yet, so Cura will crash in this case.
|
||||
#
|
||||
# This "@call_on_qt_thread" decorator makes sure that a function will always be called on the Qt thread (blocking).
|
||||
# It is applied to the read() function of project loading so it can be guaranteed that only after the project loading
|
||||
# process is completely done, everything else that needs to occupy the QT thread will be executed.
|
||||
#
|
||||
class InterCallObject:
|
||||
def __init__(self):
|
||||
self.finish_event = threading.Event()
|
||||
self.result = None
|
||||
|
||||
|
||||
def call_on_qt_thread(func):
|
||||
def _call_on_qt_thread_wrapper(*args, **kwargs):
|
||||
def _handle_call(ico, *args, **kwargs):
|
||||
ico.result = func(*args, **kwargs)
|
||||
ico.finish_event.set()
|
||||
inter_call_object = InterCallObject()
|
||||
new_args = tuple([inter_call_object] + list(args)[:])
|
||||
CuraApplication.getInstance().callLater(_handle_call, *new_args, **kwargs)
|
||||
inter_call_object.finish_event.wait()
|
||||
return inter_call_object.result
|
||||
return _call_on_qt_thread_wrapper
|
||||
|
||||
|
||||
## Base implementation for reading 3MF workspace files.
|
||||
class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
def __init__(self):
|
||||
@ -401,6 +433,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
# containing global.cfg / extruder.cfg
|
||||
#
|
||||
# \param file_name
|
||||
@call_on_qt_thread
|
||||
def read(self, file_name):
|
||||
archive = zipfile.ZipFile(file_name, "r")
|
||||
|
||||
@ -525,6 +558,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
|
||||
user_instance_containers = []
|
||||
quality_and_definition_changes_instance_containers = []
|
||||
quality_changes_instance_containers = []
|
||||
for instance_container_file in instance_container_files:
|
||||
container_id = self._stripFileToId(instance_container_file)
|
||||
serialized = archive.open(instance_container_file).read().decode("utf-8")
|
||||
@ -630,6 +664,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
# The ID already exists, but nothing in the values changed, so do nothing.
|
||||
pass
|
||||
quality_and_definition_changes_instance_containers.append(instance_container)
|
||||
if container_type == "quality_changes":
|
||||
quality_changes_instance_containers.append(instance_container)
|
||||
|
||||
if container_type == "definition_changes":
|
||||
definition_changes_extruder_count = instance_container.getProperty("machine_extruder_count", "value")
|
||||
@ -754,7 +790,19 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
# If not extruder stacks were saved in the project file (pre 3.1) create one manually
|
||||
# We re-use the container registry's addExtruderStackForSingleExtrusionMachine method for this
|
||||
if not extruder_stacks:
|
||||
stack = self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder")
|
||||
# If we choose to override a machine but to create a new custom quality profile, the custom quality
|
||||
# profile is not immediately applied to the global_stack, so this fix for single extrusion machines
|
||||
# will use the current custom quality profile on the existing machine. The extra optional argument
|
||||
# in that function is used in thia case to specify a new global stack quality_changes container so
|
||||
# the fix can correctly create and copy over the custom quality settings to the newly created extruder.
|
||||
new_global_quality_changes = None
|
||||
if self._resolve_strategies["quality_changes"] == "new" and len(quality_changes_instance_containers) > 0:
|
||||
new_global_quality_changes = quality_changes_instance_containers[0]
|
||||
stack = self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder",
|
||||
new_global_quality_changes)
|
||||
if new_global_quality_changes is not None:
|
||||
quality_changes_instance_containers.append(stack.qualityChanges)
|
||||
quality_and_definition_changes_instance_containers.append(stack.qualityChanges)
|
||||
if global_stack.quality.getId() in ("empty", "empty_quality"):
|
||||
stack.quality = empty_quality_container
|
||||
if self._resolve_strategies["machine"] == "override":
|
||||
@ -995,8 +1043,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
stack.setNextStack(global_stack)
|
||||
stack.containersChanged.emit(stack.getTop())
|
||||
else:
|
||||
if quality_has_been_changed:
|
||||
CuraApplication.getInstance().getMachineManager().activeQualityChanged.emit()
|
||||
CuraApplication.getInstance().getMachineManager().activeQualityChanged.emit()
|
||||
|
||||
# Actually change the active machine.
|
||||
Application.getInstance().setGlobalContainerStack(global_stack)
|
||||
|
@ -158,79 +158,4 @@ class MachineSettingsAction(MachineAction):
|
||||
@pyqtSlot(int)
|
||||
def updateMaterialForDiameter(self, extruder_position: int):
|
||||
# Updates the material container to a material that matches the material diameter set for the printer
|
||||
if not self._global_container_stack:
|
||||
return
|
||||
|
||||
if not self._global_container_stack.getMetaDataEntry("has_materials", False):
|
||||
return
|
||||
|
||||
extruder_stack = self._global_container_stack.extruders[str(extruder_position)]
|
||||
|
||||
material_diameter = extruder_stack.material.getProperty("material_diameter", "value")
|
||||
if not material_diameter:
|
||||
# in case of "empty" material
|
||||
material_diameter = 0
|
||||
|
||||
material_approximate_diameter = str(round(material_diameter))
|
||||
machine_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value")
|
||||
if not machine_diameter:
|
||||
if extruder_stack.definition.hasProperty("material_diameter", "value"):
|
||||
machine_diameter = extruder_stack.definition.getProperty("material_diameter", "value")
|
||||
else:
|
||||
machine_diameter = self._global_container_stack.definition.getProperty("material_diameter", "value")
|
||||
machine_approximate_diameter = str(round(machine_diameter))
|
||||
|
||||
if material_approximate_diameter != machine_approximate_diameter:
|
||||
Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.")
|
||||
|
||||
if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
|
||||
materials_definition = self._global_container_stack.definition.getId()
|
||||
has_material_variants = self._global_container_stack.getMetaDataEntry("has_variants", False)
|
||||
else:
|
||||
materials_definition = "fdmprinter"
|
||||
has_material_variants = False
|
||||
|
||||
old_material = extruder_stack.material
|
||||
search_criteria = {
|
||||
"type": "material",
|
||||
"approximate_diameter": machine_approximate_diameter,
|
||||
"material": old_material.getMetaDataEntry("material", "value"),
|
||||
"brand": old_material.getMetaDataEntry("brand", "value"),
|
||||
"supplier": old_material.getMetaDataEntry("supplier", "value"),
|
||||
"color_name": old_material.getMetaDataEntry("color_name", "value"),
|
||||
"definition": materials_definition
|
||||
}
|
||||
if has_material_variants:
|
||||
search_criteria["variant"] = extruder_stack.variant.getId()
|
||||
|
||||
if old_material == self._empty_container:
|
||||
search_criteria.pop("material", None)
|
||||
search_criteria.pop("supplier", None)
|
||||
search_criteria.pop("brand", None)
|
||||
search_criteria.pop("definition", None)
|
||||
search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
|
||||
|
||||
materials = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Same material with new diameter is not found, search for generic version of the same material type
|
||||
search_criteria.pop("supplier", None)
|
||||
search_criteria.pop("brand", None)
|
||||
search_criteria["color_name"] = "Generic"
|
||||
materials = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Generic material with new diameter is not found, search for preferred material
|
||||
search_criteria.pop("color_name", None)
|
||||
search_criteria.pop("material", None)
|
||||
search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
|
||||
materials = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Preferred material with new diameter is not found, search for any material
|
||||
search_criteria.pop("id", None)
|
||||
materials = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Just use empty material as a final fallback
|
||||
materials = [self._empty_container]
|
||||
|
||||
Logger.log("i", "Selecting new material: %s", materials[0].getId())
|
||||
|
||||
extruder_stack.material = materials[0]
|
||||
Application.getInstance().getExtruderManager().updateMaterialForDiameter(extruder_position)
|
||||
|
@ -5339,7 +5339,7 @@
|
||||
},
|
||||
"infill_enable_travel_optimization":
|
||||
{
|
||||
"label": "Enable Travel Optimization",
|
||||
"label": "Infill Travel Optimization",
|
||||
"description": "When enabled, the order in which the infill lines are printed is optimized to reduce the distance travelled. The reduction in travel time achieved very much depends on the model being sliced, infill pattern, density, etc. Note that, for some models that have many small areas of infill, the time to slice the model may be greatly increased.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
|
Loading…
x
Reference in New Issue
Block a user