mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-08 07:27:29 -06:00
Merge pull request #3031 from Ultimaker/feature_headless_docker
Feature headless
This commit is contained in:
commit
fb8aa08c6a
7 changed files with 138 additions and 79 deletions
|
@ -88,3 +88,5 @@ class ArrangeObjectsJob(Job):
|
||||||
no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"),
|
no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"),
|
||||||
title = i18n_catalog.i18nc("@info:title", "Can't Find Location"))
|
title = i18n_catalog.i18nc("@info:title", "Can't Find Location"))
|
||||||
no_full_solution_message.show()
|
no_full_solution_message.show()
|
||||||
|
|
||||||
|
self.finished.emit(self)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
@ -25,7 +26,7 @@ catalog = i18nCatalog("cura")
|
||||||
import numpy
|
import numpy
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
# Setting for clearance around the prime
|
# Setting for clearance around the prime
|
||||||
PRIME_CLEARANCE = 6.5
|
PRIME_CLEARANCE = 6.5
|
||||||
|
@ -194,8 +195,7 @@ class BuildVolume(SceneNode):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
## For every sliceable node, update node._outside_buildarea
|
## For every sliceable node, update outsideBuildArea
|
||||||
#
|
|
||||||
def updateNodeBoundaryCheck(self):
|
def updateNodeBoundaryCheck(self):
|
||||||
root = Application.getInstance().getController().getScene().getRoot()
|
root = Application.getInstance().getController().getScene().getRoot()
|
||||||
nodes = list(BreadthFirstIterator(root))
|
nodes = list(BreadthFirstIterator(root))
|
||||||
|
@ -212,35 +212,51 @@ class BuildVolume(SceneNode):
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
# Need to check group nodes later
|
# Need to check group nodes later
|
||||||
if node.callDecoration("isGroup"):
|
self.checkBoundsAndUpdate(node, bounds = build_volume_bounding_box)
|
||||||
group_nodes.append(node) # Keep list of affected group_nodes
|
|
||||||
|
|
||||||
if node.callDecoration("isSliceable") or node.callDecoration("isGroup"):
|
|
||||||
node._outside_buildarea = False
|
|
||||||
bbox = node.getBoundingBox()
|
|
||||||
|
|
||||||
# Mark the node as outside the build volume if the bounding box test fails.
|
|
||||||
if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
|
|
||||||
node._outside_buildarea = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
convex_hull = node.callDecoration("getConvexHull")
|
|
||||||
if convex_hull:
|
|
||||||
if not convex_hull.isValid():
|
|
||||||
return
|
|
||||||
# Check for collisions between disallowed areas and the object
|
|
||||||
for area in self.getDisallowedAreas():
|
|
||||||
overlap = convex_hull.intersectsPolygon(area)
|
|
||||||
if overlap is None:
|
|
||||||
continue
|
|
||||||
node._outside_buildarea = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Group nodes should override the _outside_buildarea property of their children.
|
# Group nodes should override the _outside_buildarea property of their children.
|
||||||
for group_node in group_nodes:
|
for group_node in group_nodes:
|
||||||
for child_node in group_node.getAllChildren():
|
for child_node in group_node.getAllChildren():
|
||||||
child_node._outside_buildarea = group_node._outside_buildarea
|
child_node._outside_buildarea = group_node._outside_buildarea
|
||||||
|
|
||||||
|
## Update the outsideBuildArea of a single node, given bounds or current build volume
|
||||||
|
def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None):
|
||||||
|
if not isinstance(node, CuraSceneNode):
|
||||||
|
return
|
||||||
|
|
||||||
|
if bounds is None:
|
||||||
|
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.
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
build_volume_bounding_box = bounds
|
||||||
|
|
||||||
|
if node.callDecoration("isSliceable") or node.callDecoration("isGroup"):
|
||||||
|
bbox = node.getBoundingBox()
|
||||||
|
|
||||||
|
# Mark the node as outside the build volume if the bounding box test fails.
|
||||||
|
if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
|
||||||
|
node.setOutsideBuildArea(True)
|
||||||
|
return
|
||||||
|
|
||||||
|
convex_hull = self.callDecoration("getConvexHull")
|
||||||
|
if convex_hull:
|
||||||
|
if not convex_hull.isValid():
|
||||||
|
return
|
||||||
|
# Check for collisions between disallowed areas and the object
|
||||||
|
for area in self.getDisallowedAreas():
|
||||||
|
overlap = convex_hull.intersectsPolygon(area)
|
||||||
|
if overlap is None:
|
||||||
|
continue
|
||||||
|
node.setOutsideBuildArea(True)
|
||||||
|
return
|
||||||
|
node.setOutsideBuildArea(False)
|
||||||
|
|
||||||
## Recalculates the build volume & disallowed areas.
|
## Recalculates the build volume & disallowed areas.
|
||||||
def rebuild(self):
|
def rebuild(self):
|
||||||
if not self._width or not self._height or not self._depth:
|
if not self._width or not self._height or not self._depth:
|
||||||
|
|
|
@ -142,6 +142,7 @@ class CuraApplication(QtApplication):
|
||||||
if not hasattr(sys, "frozen"):
|
if not hasattr(sys, "frozen"):
|
||||||
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources"))
|
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources"))
|
||||||
|
|
||||||
|
self._use_gui = True
|
||||||
self._open_file_queue = [] # Files to open when plug-ins are loaded.
|
self._open_file_queue = [] # Files to open when plug-ins are loaded.
|
||||||
|
|
||||||
# Need to do this before ContainerRegistry tries to load the machines
|
# Need to do this before ContainerRegistry tries to load the machines
|
||||||
|
@ -452,7 +453,7 @@ class CuraApplication(QtApplication):
|
||||||
elif choice == "always_keep":
|
elif choice == "always_keep":
|
||||||
# don't show dialog and KEEP the profile
|
# don't show dialog and KEEP the profile
|
||||||
self.discardOrKeepProfileChangesClosed("keep")
|
self.discardOrKeepProfileChangesClosed("keep")
|
||||||
else:
|
elif self._use_gui:
|
||||||
# ALWAYS ask whether to keep or discard the profile
|
# ALWAYS ask whether to keep or discard the profile
|
||||||
self.showDiscardOrKeepProfileChanges.emit()
|
self.showDiscardOrKeepProfileChanges.emit()
|
||||||
has_user_interaction = True
|
has_user_interaction = True
|
||||||
|
@ -652,12 +653,47 @@ class CuraApplication(QtApplication):
|
||||||
def run(self):
|
def run(self):
|
||||||
self.preRun()
|
self.preRun()
|
||||||
|
|
||||||
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
|
# Check if we should run as single instance or not
|
||||||
|
|
||||||
self._setUpSingleInstanceServer()
|
self._setUpSingleInstanceServer()
|
||||||
|
|
||||||
|
# Setup scene and build volume
|
||||||
|
root = self.getController().getScene().getRoot()
|
||||||
|
self._volume = BuildVolume.BuildVolume(self.getController().getScene().getRoot())
|
||||||
|
Arrange.build_volume = self._volume
|
||||||
|
|
||||||
|
# initialize info objects
|
||||||
|
self._print_information = PrintInformation.PrintInformation()
|
||||||
|
self._cura_actions = CuraActions.CuraActions(self)
|
||||||
|
|
||||||
|
# Detect in which mode to run and execute that mode
|
||||||
|
if self.getCommandLineOption("headless", False):
|
||||||
|
self.runWithoutGUI()
|
||||||
|
else:
|
||||||
|
self.runWithGUI()
|
||||||
|
|
||||||
|
# Pre-load files if requested
|
||||||
|
for file_name in self.getCommandLineOption("file", []):
|
||||||
|
self._openFile(file_name)
|
||||||
|
for file_name in self._open_file_queue: # Open all the files that were queued up while plug-ins were loading.
|
||||||
|
self._openFile(file_name)
|
||||||
|
|
||||||
|
self._started = True
|
||||||
|
self.exec_()
|
||||||
|
|
||||||
|
## Run Cura without GUI elements and interaction (server mode).
|
||||||
|
def runWithoutGUI(self):
|
||||||
|
self._use_gui = False
|
||||||
|
self.closeSplash()
|
||||||
|
|
||||||
|
## Run Cura with GUI (desktop mode).
|
||||||
|
def runWithGUI(self):
|
||||||
|
self._use_gui = True
|
||||||
|
|
||||||
|
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
|
||||||
|
|
||||||
controller = self.getController()
|
controller = self.getController()
|
||||||
|
|
||||||
|
# Initialize UI state
|
||||||
controller.setActiveStage("PrepareStage")
|
controller.setActiveStage("PrepareStage")
|
||||||
controller.setActiveView("SolidView")
|
controller.setActiveView("SolidView")
|
||||||
controller.setCameraTool("CameraTool")
|
controller.setCameraTool("CameraTool")
|
||||||
|
@ -669,67 +705,44 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
Selection.selectionChanged.connect(self.onSelectionChanged)
|
Selection.selectionChanged.connect(self.onSelectionChanged)
|
||||||
|
|
||||||
root = controller.getScene().getRoot()
|
# Set default background color for scene
|
||||||
|
|
||||||
# The platform is a child of BuildVolume
|
|
||||||
self._volume = BuildVolume.BuildVolume(root)
|
|
||||||
|
|
||||||
# Set the build volume of the arranger to the used build volume
|
|
||||||
Arrange.build_volume = self._volume
|
|
||||||
|
|
||||||
self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
|
self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
|
||||||
|
|
||||||
|
# Initialize platform physics
|
||||||
self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)
|
self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)
|
||||||
|
|
||||||
|
# Initialize camera
|
||||||
|
root = controller.getScene().getRoot()
|
||||||
camera = Camera("3d", root)
|
camera = Camera("3d", root)
|
||||||
camera.setPosition(Vector(-80, 250, 700))
|
camera.setPosition(Vector(-80, 250, 700))
|
||||||
camera.setPerspective(True)
|
camera.setPerspective(True)
|
||||||
camera.lookAt(Vector(0, 0, 0))
|
camera.lookAt(Vector(0, 0, 0))
|
||||||
controller.getScene().setActiveCamera("3d")
|
controller.getScene().setActiveCamera("3d")
|
||||||
|
|
||||||
camera_tool = self.getController().getTool("CameraTool")
|
# Initialize camera tool
|
||||||
|
camera_tool = controller.getTool("CameraTool")
|
||||||
camera_tool.setOrigin(Vector(0, 100, 0))
|
camera_tool.setOrigin(Vector(0, 100, 0))
|
||||||
camera_tool.setZoomRange(0.1, 200000)
|
camera_tool.setZoomRange(0.1, 200000)
|
||||||
|
|
||||||
|
# Initialize camera animations
|
||||||
self._camera_animation = CameraAnimation.CameraAnimation()
|
self._camera_animation = CameraAnimation.CameraAnimation()
|
||||||
self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))
|
self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))
|
||||||
|
|
||||||
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))
|
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))
|
||||||
|
|
||||||
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
|
# Initialize QML engine
|
||||||
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
|
||||||
qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
|
|
||||||
|
|
||||||
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager",
|
|
||||||
self.getSettingInheritanceManager)
|
|
||||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager",
|
|
||||||
self.getSimpleModeSettingsManager)
|
|
||||||
|
|
||||||
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 2, "ObjectsModel", self.getObjectsModel)
|
|
||||||
qmlRegisterSingletonType(BuildPlateModel, "Cura", 1, 2, "BuildPlateModel", self.getBuildPlateModel)
|
|
||||||
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 2, "SceneController", self.getCuraSceneController)
|
|
||||||
|
|
||||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
|
||||||
|
|
||||||
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
|
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
|
||||||
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
|
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
|
||||||
|
self.initializeEngine()
|
||||||
|
|
||||||
run_without_gui = self.getCommandLineOption("headless", False)
|
# Make sure the correct stage is activated after QML is loaded
|
||||||
if not run_without_gui:
|
controller.setActiveStage("PrepareStage")
|
||||||
self.initializeEngine()
|
|
||||||
controller.setActiveStage("PrepareStage")
|
|
||||||
|
|
||||||
if run_without_gui or self._engine.rootObjects:
|
# Hide the splash screen
|
||||||
self.closeSplash()
|
self.closeSplash()
|
||||||
|
|
||||||
for file_name in self.getCommandLineOption("file", []):
|
def hasGui(self):
|
||||||
self._openFile(file_name)
|
return self._use_gui
|
||||||
for file_name in self._open_file_queue: #Open all the files that were queued up while plug-ins were loading.
|
|
||||||
self._openFile(file_name)
|
|
||||||
|
|
||||||
self._started = True
|
|
||||||
|
|
||||||
self.exec_()
|
|
||||||
|
|
||||||
def getMachineManager(self, *args) -> MachineManager:
|
def getMachineManager(self, *args) -> MachineManager:
|
||||||
if self._machine_manager is None:
|
if self._machine_manager is None:
|
||||||
|
@ -797,15 +810,25 @@ class CuraApplication(QtApplication):
|
||||||
# \param engine The QML engine.
|
# \param engine The QML engine.
|
||||||
def registerObjects(self, engine):
|
def registerObjects(self, engine):
|
||||||
super().registerObjects(engine)
|
super().registerObjects(engine)
|
||||||
|
|
||||||
|
# global contexts
|
||||||
engine.rootContext().setContextProperty("Printer", self)
|
engine.rootContext().setContextProperty("Printer", self)
|
||||||
engine.rootContext().setContextProperty("CuraApplication", self)
|
engine.rootContext().setContextProperty("CuraApplication", self)
|
||||||
self._print_information = PrintInformation.PrintInformation()
|
|
||||||
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
|
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
|
||||||
self._cura_actions = CuraActions.CuraActions(self)
|
|
||||||
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
|
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
|
||||||
|
|
||||||
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
||||||
|
|
||||||
|
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 2, "SceneController", self.getCuraSceneController)
|
||||||
|
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
|
||||||
|
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
||||||
|
qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
|
||||||
|
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
|
||||||
|
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
|
||||||
|
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
||||||
|
|
||||||
|
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 2, "ObjectsModel", self.getObjectsModel)
|
||||||
|
qmlRegisterSingletonType(BuildPlateModel, "Cura", 1, 2, "BuildPlateModel", self.getBuildPlateModel)
|
||||||
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
||||||
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
||||||
qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
|
qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
|
||||||
|
@ -1342,6 +1365,7 @@ class CuraApplication(QtApplication):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
fileLoaded = pyqtSignal(str)
|
fileLoaded = pyqtSignal(str)
|
||||||
|
fileCompleted = pyqtSignal(str)
|
||||||
|
|
||||||
def _reloadMeshFinished(self, job):
|
def _reloadMeshFinished(self, job):
|
||||||
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
|
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
|
||||||
|
@ -1459,6 +1483,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
node.setSelectable(True)
|
node.setSelectable(True)
|
||||||
node.setName(os.path.basename(filename))
|
node.setName(os.path.basename(filename))
|
||||||
|
self.getBuildVolume().checkBoundsAndUpdate(node)
|
||||||
|
|
||||||
extension = os.path.splitext(filename)[1]
|
extension = os.path.splitext(filename)[1]
|
||||||
if extension.lower() in self._non_sliceable_extensions:
|
if extension.lower() in self._non_sliceable_extensions:
|
||||||
|
@ -1495,8 +1520,8 @@ class CuraApplication(QtApplication):
|
||||||
# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
|
# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
|
||||||
node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
|
node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
|
||||||
|
|
||||||
# This node is deepcopied from some other node which already has a BuildPlateDecorator, but the deepcopy
|
# This node is deep copied from some other node which already has a BuildPlateDecorator, but the deepcopy
|
||||||
# of BuildPlateDecorator produces one that's assoicated with build plate -1. So, here we need to check if
|
# of BuildPlateDecorator produces one that's associated with build plate -1. So, here we need to check if
|
||||||
# the BuildPlateDecorator exists or not and always set the correct build plate number.
|
# the BuildPlateDecorator exists or not and always set the correct build plate number.
|
||||||
build_plate_decorator = node.getDecorator(BuildPlateDecorator)
|
build_plate_decorator = node.getDecorator(BuildPlateDecorator)
|
||||||
if build_plate_decorator is None:
|
if build_plate_decorator is None:
|
||||||
|
@ -1508,6 +1533,8 @@ class CuraApplication(QtApplication):
|
||||||
op.push()
|
op.push()
|
||||||
scene.sceneChanged.emit(node)
|
scene.sceneChanged.emit(node)
|
||||||
|
|
||||||
|
self.fileCompleted.emit(filename)
|
||||||
|
|
||||||
def addNonSliceableExtension(self, extension):
|
def addNonSliceableExtension(self, extension):
|
||||||
self._non_sliceable_extensions.append(extension)
|
self._non_sliceable_extensions.append(extension)
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,10 @@ class ConvexHullNode(SceneNode):
|
||||||
self._original_parent = parent
|
self._original_parent = parent
|
||||||
|
|
||||||
# Color of the drawn convex hull
|
# Color of the drawn convex hull
|
||||||
self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb())
|
if Application.getInstance().hasGui():
|
||||||
|
self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb())
|
||||||
|
else:
|
||||||
|
self._color = Color(0, 0, 0)
|
||||||
|
|
||||||
# The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
|
# The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
|
||||||
self._mesh_height = 0.1
|
self._mesh_height = 0.1
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
|
@ -9,7 +10,7 @@ from copy import deepcopy
|
||||||
class CuraSceneNode(SceneNode):
|
class CuraSceneNode(SceneNode):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._outside_buildarea = True
|
self._outside_buildarea = False
|
||||||
|
|
||||||
def setOutsideBuildArea(self, new_value):
|
def setOutsideBuildArea(self, new_value):
|
||||||
self._outside_buildarea = new_value
|
self._outside_buildarea = new_value
|
||||||
|
|
|
@ -764,7 +764,7 @@ class MachineManager(QObject):
|
||||||
## Set the active material by switching out a container
|
## Set the active material by switching out a container
|
||||||
# Depending on from/to material+current variant, a quality profile is chosen and set.
|
# Depending on from/to material+current variant, a quality profile is chosen and set.
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def setActiveMaterial(self, material_id: str):
|
def setActiveMaterial(self, material_id: str, always_discard_changes = False):
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id = material_id)
|
containers = ContainerRegistry.getInstance().findInstanceContainers(id = material_id)
|
||||||
if not containers or not self._active_container_stack:
|
if not containers or not self._active_container_stack:
|
||||||
|
@ -846,10 +846,10 @@ class MachineManager(QObject):
|
||||||
if not old_quality_changes:
|
if not old_quality_changes:
|
||||||
new_quality_id = candidate_quality.getId()
|
new_quality_id = candidate_quality.getId()
|
||||||
|
|
||||||
self.setActiveQuality(new_quality_id)
|
self.setActiveQuality(new_quality_id, always_discard_changes = always_discard_changes)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def setActiveVariant(self, variant_id: str):
|
def setActiveVariant(self, variant_id: str, always_discard_changes = False):
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_id)
|
containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_id)
|
||||||
if not containers or not self._active_container_stack:
|
if not containers or not self._active_container_stack:
|
||||||
|
@ -865,7 +865,7 @@ class MachineManager(QObject):
|
||||||
if old_material:
|
if old_material:
|
||||||
preferred_material_name = old_material.getName()
|
preferred_material_name = old_material.getName()
|
||||||
preferred_material_id = self._updateMaterialContainer(self._global_container_stack.definition, self._global_container_stack, containers[0], preferred_material_name).id
|
preferred_material_id = self._updateMaterialContainer(self._global_container_stack.definition, self._global_container_stack, containers[0], preferred_material_name).id
|
||||||
self.setActiveMaterial(preferred_material_id)
|
self.setActiveMaterial(preferred_material_id, always_discard_changes = always_discard_changes)
|
||||||
else:
|
else:
|
||||||
Logger.log("w", "While trying to set the active variant, no variant was found to replace.")
|
Logger.log("w", "While trying to set the active variant, no variant was found to replace.")
|
||||||
|
|
||||||
|
@ -890,10 +890,12 @@ class MachineManager(QObject):
|
||||||
## set the active quality
|
## set the active quality
|
||||||
# \param quality_id The quality_id of either a quality or a quality_changes
|
# \param quality_id The quality_id of either a quality or a quality_changes
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def setActiveQuality(self, quality_id: str):
|
def setActiveQuality(self, quality_id: str, always_discard_changes = False):
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
|
|
||||||
|
Logger.log("d", "Attempting to change the active quality to %s", quality_id)
|
||||||
|
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = quality_id)
|
containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = quality_id)
|
||||||
if not containers or not self._global_container_stack:
|
if not containers or not self._global_container_stack:
|
||||||
return
|
return
|
||||||
|
@ -948,11 +950,13 @@ class MachineManager(QObject):
|
||||||
"quality_changes": stack_quality_changes
|
"quality_changes": stack_quality_changes
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Logger.log("d", "Active quality changed")
|
||||||
|
|
||||||
# show the keep/discard dialog after the containers have been switched. Otherwise, the default values on
|
# show the keep/discard dialog after the containers have been switched. Otherwise, the default values on
|
||||||
# the dialog will be the those before the switching.
|
# the dialog will be the those before the switching.
|
||||||
self._executeDelayedActiveContainerStackChanges()
|
self._executeDelayedActiveContainerStackChanges()
|
||||||
|
|
||||||
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
|
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1 and not always_discard_changes:
|
||||||
Application.getInstance().discardOrKeepProfileChanges()
|
Application.getInstance().discardOrKeepProfileChanges()
|
||||||
|
|
||||||
## Used to update material and variant in the active container stack with a delay.
|
## Used to update material and variant in the active container stack with a delay.
|
||||||
|
@ -960,6 +964,9 @@ class MachineManager(QObject):
|
||||||
# before the user decided to keep or discard any of their changes using the dialog.
|
# before the user decided to keep or discard any of their changes using the dialog.
|
||||||
# The Application.onDiscardOrKeepProfileChangesClosed signal triggers this method.
|
# The Application.onDiscardOrKeepProfileChangesClosed signal triggers this method.
|
||||||
def _executeDelayedActiveContainerStackChanges(self):
|
def _executeDelayedActiveContainerStackChanges(self):
|
||||||
|
|
||||||
|
Logger.log("d", "Applying configuration changes...")
|
||||||
|
|
||||||
if self._new_variant_container is not None:
|
if self._new_variant_container is not None:
|
||||||
self._active_container_stack.variant = self._new_variant_container
|
self._active_container_stack.variant = self._new_variant_container
|
||||||
self._new_variant_container = None
|
self._new_variant_container = None
|
||||||
|
@ -984,6 +991,8 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
self._new_quality_containers.clear()
|
self._new_quality_containers.clear()
|
||||||
|
|
||||||
|
Logger.log("d", "New configuration applied")
|
||||||
|
|
||||||
## Cancel set changes for material and variant in the active container stack.
|
## Cancel set changes for material and variant in the active container stack.
|
||||||
# Used for ignoring any changes when switching between printers (setActiveMachine)
|
# Used for ignoring any changes when switching between printers (setActiveMachine)
|
||||||
def _cancelDelayedActiveContainerStackChanges(self):
|
def _cancelDelayedActiveContainerStackChanges(self):
|
||||||
|
|
|
@ -291,6 +291,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._start_slice_job = None
|
self._start_slice_job = None
|
||||||
|
|
||||||
if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error:
|
if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error:
|
||||||
|
self.backendStateChange.emit(BackendState.Error)
|
||||||
return
|
return
|
||||||
|
|
||||||
if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible:
|
if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue