Merge pull request #3031 from Ultimaker/feature_headless_docker

Feature headless
This commit is contained in:
ChrisTerBeke 2018-01-19 10:31:20 +01:00 committed by GitHub
commit fb8aa08c6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 138 additions and 79 deletions

View file

@ -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"),
title = i18n_catalog.i18nc("@info:title", "Can't Find Location"))
no_full_solution_message.show()
self.finished.emit(self)

View file

@ -1,6 +1,7 @@
# Copyright (c) 2017 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
@ -25,7 +26,7 @@ catalog = i18nCatalog("cura")
import numpy
import math
from typing import List
from typing import List, Optional
# Setting for clearance around the prime
PRIME_CLEARANCE = 6.5
@ -194,8 +195,7 @@ class BuildVolume(SceneNode):
return True
## For every sliceable node, update node._outside_buildarea
#
## For every sliceable node, update outsideBuildArea
def updateNodeBoundaryCheck(self):
root = Application.getInstance().getController().getScene().getRoot()
nodes = list(BreadthFirstIterator(root))
@ -212,19 +212,39 @@ class BuildVolume(SceneNode):
for node in nodes:
# Need to check group nodes later
if node.callDecoration("isGroup"):
group_nodes.append(node) # Keep list of affected group_nodes
self.checkBoundsAndUpdate(node, bounds = build_volume_bounding_box)
# 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
## 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"):
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
node.setOutsideBuildArea(True)
return
convex_hull = node.callDecoration("getConvexHull")
convex_hull = self.callDecoration("getConvexHull")
if convex_hull:
if not convex_hull.isValid():
return
@ -233,13 +253,9 @@ class BuildVolume(SceneNode):
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.
for group_node in group_nodes:
for child_node in group_node.getAllChildren():
child_node._outside_buildarea = group_node._outside_buildarea
node.setOutsideBuildArea(True)
return
node.setOutsideBuildArea(False)
## Recalculates the build volume & disallowed areas.
def rebuild(self):

View file

@ -142,6 +142,7 @@ class CuraApplication(QtApplication):
if not hasattr(sys, "frozen"):
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.
# Need to do this before ContainerRegistry tries to load the machines
@ -452,7 +453,7 @@ class CuraApplication(QtApplication):
elif choice == "always_keep":
# don't show dialog and KEEP the profile
self.discardOrKeepProfileChangesClosed("keep")
else:
elif self._use_gui:
# ALWAYS ask whether to keep or discard the profile
self.showDiscardOrKeepProfileChanges.emit()
has_user_interaction = True
@ -652,12 +653,47 @@ class CuraApplication(QtApplication):
def run(self):
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()
# 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()
# Initialize UI state
controller.setActiveStage("PrepareStage")
controller.setActiveView("SolidView")
controller.setCameraTool("CameraTool")
@ -669,67 +705,44 @@ class CuraApplication(QtApplication):
Selection.selectionChanged.connect(self.onSelectionChanged)
root = controller.getScene().getRoot()
# 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
# Set default background color for scene
self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
# Initialize platform physics
self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)
# Initialize camera
root = controller.getScene().getRoot()
camera = Camera("3d", root)
camera.setPosition(Vector(-80, 250, 700))
camera.setPerspective(True)
camera.lookAt(Vector(0, 0, 0))
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.setZoomRange(0.1, 200000)
# Initialize camera animations
self._camera_animation = CameraAnimation.CameraAnimation()
self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))
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(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)
# Initialize QML engine
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
run_without_gui = self.getCommandLineOption("headless", False)
if not run_without_gui:
self.initializeEngine()
# Make sure the correct stage is activated after QML is loaded
controller.setActiveStage("PrepareStage")
if run_without_gui or self._engine.rootObjects:
# Hide the splash screen
self.closeSplash()
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_()
def hasGui(self):
return self._use_gui
def getMachineManager(self, *args) -> MachineManager:
if self._machine_manager is None:
@ -797,15 +810,25 @@ class CuraApplication(QtApplication):
# \param engine The QML engine.
def registerObjects(self, engine):
super().registerObjects(engine)
# global contexts
engine.rootContext().setContextProperty("Printer", self)
engine.rootContext().setContextProperty("CuraApplication", self)
self._print_information = PrintInformation.PrintInformation()
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
self._cura_actions = CuraActions.CuraActions(self)
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
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(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
@ -1342,6 +1365,7 @@ class CuraApplication(QtApplication):
pass
fileLoaded = pyqtSignal(str)
fileCompleted = pyqtSignal(str)
def _reloadMeshFinished(self, job):
# 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.setName(os.path.basename(filename))
self.getBuildVolume().checkBoundsAndUpdate(node)
extension = os.path.splitext(filename)[1]
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
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
# of BuildPlateDecorator produces one that's assoicated with build plate -1. So, here we need to check if
# This node is deep copied from some other node which already has a BuildPlateDecorator, but the deepcopy
# 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.
build_plate_decorator = node.getDecorator(BuildPlateDecorator)
if build_plate_decorator is None:
@ -1508,6 +1533,8 @@ class CuraApplication(QtApplication):
op.push()
scene.sceneChanged.emit(node)
self.fileCompleted.emit(filename)
def addNonSliceableExtension(self, extension):
self._non_sliceable_extensions.append(extension)

View file

@ -24,7 +24,10 @@ class ConvexHullNode(SceneNode):
self._original_parent = parent
# Color of the drawn convex hull
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.
self._mesh_height = 0.1

View file

@ -1,5 +1,6 @@
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
@ -9,7 +10,7 @@ from copy import deepcopy
class CuraSceneNode(SceneNode):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._outside_buildarea = True
self._outside_buildarea = False
def setOutsideBuildArea(self, new_value):
self._outside_buildarea = new_value

View file

@ -764,7 +764,7 @@ class MachineManager(QObject):
## Set the active material by switching out a container
# Depending on from/to material+current variant, a quality profile is chosen and set.
@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):
containers = ContainerRegistry.getInstance().findInstanceContainers(id = material_id)
if not containers or not self._active_container_stack:
@ -846,10 +846,10 @@ class MachineManager(QObject):
if not old_quality_changes:
new_quality_id = candidate_quality.getId()
self.setActiveQuality(new_quality_id)
self.setActiveQuality(new_quality_id, always_discard_changes = always_discard_changes)
@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):
containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_id)
if not containers or not self._active_container_stack:
@ -865,7 +865,7 @@ class MachineManager(QObject):
if old_material:
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
self.setActiveMaterial(preferred_material_id)
self.setActiveMaterial(preferred_material_id, always_discard_changes = always_discard_changes)
else:
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
# \param quality_id The quality_id of either a quality or a quality_changes
@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):
self.blurSettings.emit()
Logger.log("d", "Attempting to change the active quality to %s", quality_id)
containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = quality_id)
if not containers or not self._global_container_stack:
return
@ -948,11 +950,13 @@ class MachineManager(QObject):
"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
# the dialog will be the those before the switching.
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()
## 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.
# The Application.onDiscardOrKeepProfileChangesClosed signal triggers this method.
def _executeDelayedActiveContainerStackChanges(self):
Logger.log("d", "Applying configuration changes...")
if self._new_variant_container is not None:
self._active_container_stack.variant = self._new_variant_container
self._new_variant_container = None
@ -984,6 +991,8 @@ class MachineManager(QObject):
self._new_quality_containers.clear()
Logger.log("d", "New configuration applied")
## Cancel set changes for material and variant in the active container stack.
# Used for ignoring any changes when switching between printers (setActiveMachine)
def _cancelDelayedActiveContainerStackChanges(self):

View file

@ -291,6 +291,7 @@ class CuraEngineBackend(QObject, Backend):
self._start_slice_job = None
if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error:
self.backendStateChange.emit(BackendState.Error)
return
if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible: