Merge branch 'master' into feature_tree_support

This commit is contained in:
Ghostkeeper 2017-12-18 13:25:59 +01:00
commit eb1efc4928
No known key found for this signature in database
GPG Key ID: 5252B696FB5E7C7A
31 changed files with 897 additions and 370 deletions

View File

@ -56,11 +56,6 @@ class ConvexHullDecorator(SceneNodeDecorator):
if self._node is None: if self._node is None:
return None return None
if getattr(self._node, "_non_printing_mesh", False):
# infill_mesh, cutting_mesh and anti_overhang_mesh do not need a convex hull
# node._non_printing_mesh is set in SettingOverrideDecorator
return None
hull = self._compute2DConvexHull() hull = self._compute2DConvexHull()
if self._global_stack and self._node: if self._global_stack and self._node:

View File

@ -59,7 +59,7 @@ class CrashHandler:
self.data = dict() self.data = dict()
self.data["time_stamp"] = time.time() self.data["time_stamp"] = time.time()
Logger.log("c", "An uncaught exception has occurred!") Logger.log("c", "An uncaught error has occurred!")
for line in traceback.format_exception(exception_type, value, tb): for line in traceback.format_exception(exception_type, value, tb):
for part in line.rstrip("\n").split("\n"): for part in line.rstrip("\n").split("\n"):
Logger.log("c", part) Logger.log("c", part)
@ -90,7 +90,7 @@ class CrashHandler:
def _messageWidget(self): def _messageWidget(self):
label = QLabel() label = QLabel()
label.setText(catalog.i18nc("@label crash message", """<p><b>A fatal exception has occurred. Please send us this Crash Report to fix the problem</p></b> label.setText(catalog.i18nc("@label crash message", """<p><b>A fatal error has occurred. Please send us this Crash Report to fix the problem</p></b>
<p>Please use the "Send report" button to post a bug report automatically to our servers</p> <p>Please use the "Send report" button to post a bug report automatically to our servers</p>
""")) """))
@ -143,7 +143,7 @@ class CrashHandler:
def _exceptionInfoWidget(self): def _exceptionInfoWidget(self):
group = QGroupBox() group = QGroupBox()
group.setTitle(catalog.i18nc("@title:groupbox", "Exception traceback")) group.setTitle(catalog.i18nc("@title:groupbox", "Error traceback"))
layout = QVBoxLayout() layout = QVBoxLayout()
text_area = QTextEdit() text_area = QTextEdit()

View File

@ -127,7 +127,7 @@ class CuraApplication(QtApplication):
# Cura will always show the Add Machine Dialog upon start. # Cura will always show the Add Machine Dialog upon start.
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
def __init__(self): def __init__(self, **kwargs):
# this list of dir names will be used by UM to detect an old cura directory # this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]: for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
@ -208,9 +208,12 @@ class CuraApplication(QtApplication):
self._additional_components = {} # Components to add to certain areas in the interface self._additional_components = {} # Components to add to certain areas in the interface
super().__init__(name = "cura", version = CuraVersion, buildtype = CuraBuildType, super().__init__(name = "cura",
version = CuraVersion,
buildtype = CuraBuildType,
is_debug_mode = CuraDebugMode, is_debug_mode = CuraDebugMode,
tray_icon_name = "cura-icon-32.png") tray_icon_name = "cura-icon-32.png",
**kwargs)
self.default_theme = "cura-light" self.default_theme = "cura-light"
@ -313,6 +316,7 @@ class CuraApplication(QtApplication):
preferences.addPreference("cura/material_settings", "{}") preferences.addPreference("cura/material_settings", "{}")
preferences.addPreference("view/invert_zoom", False) preferences.addPreference("view/invert_zoom", False)
preferences.addPreference("cura/sidebar_collapse", False)
self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement") self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")
@ -399,7 +403,11 @@ class CuraApplication(QtApplication):
@pyqtSlot() @pyqtSlot()
def closeApplication(self): def closeApplication(self):
Logger.log("i", "Close application") Logger.log("i", "Close application")
self._main_window.close() main_window = self.getMainWindow()
if main_window is not None:
main_window.close()
else:
self.exit(0)
## A reusable dialogbox ## A reusable dialogbox
# #
@ -507,11 +515,10 @@ class CuraApplication(QtApplication):
self._plugins_loaded = True self._plugins_loaded = True
@classmethod @classmethod
def addCommandLineOptions(self, parser): def addCommandLineOptions(self, parser, parsed_command_line = {}):
super().addCommandLineOptions(parser) super().addCommandLineOptions(parser, parsed_command_line = parsed_command_line)
parser.add_argument("file", nargs="*", help="Files to load after starting the application.") parser.add_argument("file", nargs="*", help="Files to load after starting the application.")
parser.add_argument("--single-instance", action="store_true", default=False) parser.add_argument("--single-instance", action="store_true", default=False)
parser.add_argument("--headless", action = "store_true", default=False)
# Set up a local socket server which listener which coordinates single instances Curas and accepts commands. # Set up a local socket server which listener which coordinates single instances Curas and accepts commands.
def _setUpSingleInstanceServer(self): def _setUpSingleInstanceServer(self):
@ -565,13 +572,16 @@ class CuraApplication(QtApplication):
# This should be called directly before creating an instance of CuraApplication. # This should be called directly before creating an instance of CuraApplication.
# \returns \type{bool} True if the whole Cura app should continue running. # \returns \type{bool} True if the whole Cura app should continue running.
@classmethod @classmethod
def preStartUp(cls): def preStartUp(cls, parser = None, parsed_command_line = {}):
# Peek the arguments and look for the 'single-instance' flag. # Peek the arguments and look for the 'single-instance' flag.
parser = argparse.ArgumentParser(prog="cura") # pylint: disable=bad-whitespace if not parser:
CuraApplication.addCommandLineOptions(parser) parser = argparse.ArgumentParser(prog = "cura", add_help = False) # pylint: disable=bad-whitespace
parsed_command_line = vars(parser.parse_args()) CuraApplication.addCommandLineOptions(parser, parsed_command_line = parsed_command_line)
# Important: It is important to keep this line here!
# In Uranium we allow to pass unknown arguments to the final executable or script.
parsed_command_line.update(vars(parser.parse_known_args()[0]))
if "single_instance" in parsed_command_line and parsed_command_line["single_instance"]: if parsed_command_line["single_instance"]:
Logger.log("i", "Checking for the presence of an ready running Cura instance.") Logger.log("i", "Checking for the presence of an ready running Cura instance.")
single_instance_socket = QLocalSocket() single_instance_socket = QLocalSocket()
Logger.log("d", "preStartUp(): full server name: " + single_instance_socket.fullServerName()) Logger.log("d", "preStartUp(): full server name: " + single_instance_socket.fullServerName())
@ -603,7 +613,22 @@ class CuraApplication(QtApplication):
return False return False
return True return True
def preRun(self):
# Last check for unknown commandline arguments
parser = self.getCommandlineParser()
parser.add_argument("--help", "-h",
action='store_true',
default = False,
help = "Show this help message and exit."
)
parsed_args = vars(parser.parse_args()) # This won't allow unknown arguments
if parsed_args["help"]:
parser.print_help()
sys.exit(0)
def run(self): def run(self):
self.preRun()
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene...")) self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
self._setUpSingleInstanceServer() self._setUpSingleInstanceServer()
@ -658,12 +683,12 @@ class CuraApplication(QtApplication):
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))
run_headless = self.getCommandLineOption("headless", False) run_without_gui = self.getCommandLineOption("headless", False) or self.getCommandLineOption("invisible", False)
if not run_headless: if not run_without_gui:
self.initializeEngine() self.initializeEngine()
controller.setActiveStage("PrepareStage") controller.setActiveStage("PrepareStage")
if run_headless or self._engine.rootObjects: if run_without_gui or self._engine.rootObjects:
self.closeSplash() self.closeSplash()
for file_name in self.getCommandLineOption("file", []): for file_name in self.getCommandLineOption("file", []):
@ -1361,7 +1386,8 @@ class CuraApplication(QtApplication):
# If a model is to small then it will not contain any points # If a model is to small then it will not contain any points
if offset_shape_arr is None and hull_shape_arr is None: if offset_shape_arr is None and hull_shape_arr is None:
Message(self._i18n_catalog.i18nc("@info:status", "The selected model was too small to load."), Message(self._i18n_catalog.i18nc("@info:status", "The selected model was too small to load."),
title=self._i18n_catalog.i18nc("@info:title", "Warning")).show() title=self._i18n_catalog.i18nc("@info:title", "Warning")
).show()
return return
# 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

View File

@ -16,6 +16,7 @@ import math
import os.path import os.path
import unicodedata import unicodedata
import json import json
import re #To create abbreviations for printer names.
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
@ -316,15 +317,14 @@ class PrintInformation(QObject):
return return
global_stack_name = global_container_stack.getName() global_stack_name = global_container_stack.getName()
split_name = global_stack_name.split(" ")
abbr_machine = "" abbr_machine = ""
for word in split_name: for word in re.findall(r"[\w']+", global_stack_name):
if word.lower() == "ultimaker": if word.lower() == "ultimaker":
abbr_machine += "UM" abbr_machine += "UM"
elif word.isdigit(): elif word.isdigit():
abbr_machine += word abbr_machine += word
else: else:
stripped_word = self._stripAccents(word.strip("()[]{}#").upper()) stripped_word = self._stripAccents(word.upper())
# - use only the first character if the word is too long (> 3 characters) # - use only the first character if the word is too long (> 3 characters)
# - use the whole word if it's not too long (<= 3 characters) # - use the whole word if it's not too long (<= 3 characters)
if len(stripped_word) > 3: if len(stripped_word) > 3:

View File

@ -36,6 +36,11 @@ class CuraContainerRegistry(ContainerRegistry):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# We don't have all the machines loaded in the beginning, so in order to add the missing extruder stack
# for single extrusion machines, we subscribe to the containerAdded signal, and whenever a global stack
# is added, we check to see if an extruder stack needs to be added.
self.containerAdded.connect(self._onContainerAdded)
## Overridden from ContainerRegistry ## Overridden from ContainerRegistry
# #
# Adds a container to the registry. # Adds a container to the registry.
@ -410,6 +415,17 @@ class CuraContainerRegistry(ContainerRegistry):
if not extruder_stacks: if not extruder_stacks:
self.addExtruderStackForSingleExtrusionMachine(machine, "fdmextruder") self.addExtruderStackForSingleExtrusionMachine(machine, "fdmextruder")
def _onContainerAdded(self, container):
# We don't have all the machines loaded in the beginning, so in order to add the missing extruder stack
# for single extrusion machines, we subscribe to the containerAdded signal, and whenever a global stack
# is added, we check to see if an extruder stack needs to be added.
if not isinstance(container, ContainerStack) or container.getMetaDataEntry("type") != "machine":
return
extruder_stacks = self.findContainerStacks(type = "extruder_train", machine = container.getId())
if not extruder_stacks:
self.addExtruderStackForSingleExtrusionMachine(container, "fdmextruder")
def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id): def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id):
new_extruder_id = extruder_id new_extruder_id = extruder_id
@ -425,7 +441,6 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_stack.setName(extruder_definition.getName()) extruder_stack.setName(extruder_definition.getName())
extruder_stack.setDefinition(extruder_definition) extruder_stack.setDefinition(extruder_definition)
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
extruder_stack.setNextStack(machine)
# create empty user changes container otherwise # create empty user changes container otherwise
user_container = InstanceContainer(extruder_stack.id + "_user") user_container = InstanceContainer(extruder_stack.id + "_user")
@ -433,7 +448,7 @@ class CuraContainerRegistry(ContainerRegistry):
user_container.addMetaDataEntry("machine", extruder_stack.getId()) user_container.addMetaDataEntry("machine", extruder_stack.getId())
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_container.setDefinition(machine.definition) user_container.setDefinition(machine.definition.getId())
if machine.userChanges: if machine.userChanges:
# for the newly created extruder stack, we need to move all "per-extruder" settings to the user changes # for the newly created extruder stack, we need to move all "per-extruder" settings to the user changes
@ -444,8 +459,8 @@ class CuraContainerRegistry(ContainerRegistry):
user_container.addInstance(machine.userChanges.getInstance(user_setting_key)) user_container.addInstance(machine.userChanges.getInstance(user_setting_key))
machine.userChanges.removeInstance(user_setting_key, postpone_emit = True) machine.userChanges.removeInstance(user_setting_key, postpone_emit = True)
extruder_stack.setUserChanges(user_container)
self.addContainer(user_container) self.addContainer(user_container)
extruder_stack.setUserChanges(user_container)
variant_id = "default" variant_id = "default"
if machine.variant.getId() not in ("empty", "empty_variant"): if machine.variant.getId() not in ("empty", "empty_variant"):
@ -491,6 +506,9 @@ class CuraContainerRegistry(ContainerRegistry):
self.addContainer(extruder_stack) self.addContainer(extruder_stack)
# Set next stack at the end
extruder_stack.setNextStack(machine)
return extruder_stack return extruder_stack
def _findQualityChangesContainerInCuraFolder(self, name): def _findQualityChangesContainerInCuraFolder(self, name):

View File

@ -377,7 +377,7 @@ class CuraContainerStack(ContainerStack):
if not container or not isinstance(container, DefinitionContainer): if not container or not isinstance(container, DefinitionContainer):
definition = self.findContainer(container_type = DefinitionContainer) definition = self.findContainer(container_type = DefinitionContainer)
if not definition: if not definition:
raise InvalidContainerStackError("Stack {id} does not have a definition!".format(id = self._id)) raise InvalidContainerStackError("Stack {id} does not have a definition!".format(id = self.getId()))
new_containers[index] = definition new_containers[index] = definition
continue continue
@ -508,7 +508,7 @@ class CuraContainerStack(ContainerStack):
def findDefaultQuality(self) -> Optional[ContainerInterface]: def findDefaultQuality(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition() definition = self._getMachineDefinition()
registry = ContainerRegistry.getInstance() registry = ContainerRegistry.getInstance()
material_container = self.material if self.material != self._empty_instance_container else None material_container = self.material if self.material.getId() not in (self._empty_material.getId(), self._empty_instance_container.getId()) else None
search_criteria = {"type": "quality"} search_criteria = {"type": "quality"}
@ -552,7 +552,7 @@ class CuraContainerStack(ContainerStack):
material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"} material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
if definition.getMetaDataEntry("has_machine_quality"): if definition.getMetaDataEntry("has_machine_quality"):
if self.material != self._empty_instance_container: if self.material != self._empty_instance_container:
material_search_criteria["definition"] = material_container.getDefinition().id material_search_criteria["definition"] = material_container.getMetaDataEntry("definition")
if definition.getMetaDataEntry("has_variants"): if definition.getMetaDataEntry("has_variants"):
material_search_criteria["variant"] = material_container.getMetaDataEntry("variant") material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")

View File

@ -32,6 +32,7 @@ from .CuraStackBuilder import CuraStackBuilder
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
from cura.Settings.ProfilesModel import ProfilesModel
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING: if TYPE_CHECKING:
@ -60,9 +61,11 @@ class MachineManager(QObject):
self._instance_container_timer = QTimer() self._instance_container_timer = QTimer()
self._instance_container_timer.setInterval(250) self._instance_container_timer.setInterval(250)
self._instance_container_timer.setSingleShot(True) self._instance_container_timer.setSingleShot(True)
self._instance_container_timer.timeout.connect(self.__onInstanceContainersChanged) self._instance_container_timer.timeout.connect(self.__emitChangedSignals)
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
Application.getInstance().getContainerRegistry().containerLoadComplete.connect(self._onInstanceContainersChanged)
self._connected_to_profiles_model = False
## When the global container is changed, active material probably needs to be updated. ## When the global container is changed, active material probably needs to be updated.
self.globalContainerChanged.connect(self.activeMaterialChanged) self.globalContainerChanged.connect(self.activeMaterialChanged)
@ -333,14 +336,24 @@ class MachineManager(QObject):
# on _active_container_stack. If it changes, then the properties change. # on _active_container_stack. If it changes, then the properties change.
self.activeQualityChanged.emit() self.activeQualityChanged.emit()
def __onInstanceContainersChanged(self): def __emitChangedSignals(self):
self.activeQualityChanged.emit() self.activeQualityChanged.emit()
self.activeVariantChanged.emit() self.activeVariantChanged.emit()
self.activeMaterialChanged.emit() self.activeMaterialChanged.emit()
self._updateStacksHaveErrors() # Prevents unwanted re-slices after changing machine self._updateStacksHaveErrors() # Prevents unwanted re-slices after changing machine
self._error_check_timer.start() self._error_check_timer.start()
def _onProfilesModelChanged(self, *args):
self.__emitChangedSignals()
def _onInstanceContainersChanged(self, container): def _onInstanceContainersChanged(self, container):
# This should not trigger the ProfilesModel to be created, or there will be an infinite recursion
if not self._connected_to_profiles_model and ProfilesModel.hasInstance():
# This triggers updating the qualityModel in SidebarSimple whenever ProfilesModel is updated
Logger.log("d", "Connecting profiles model...")
ProfilesModel.getInstance().itemsChanged.connect(self._onProfilesModelChanged)
self._connected_to_profiles_model = True
self._instance_container_timer.start() self._instance_container_timer.start()
def _onPropertyChanged(self, key: str, property_name: str): def _onPropertyChanged(self, key: str, property_name: str):
@ -356,11 +369,13 @@ class MachineManager(QObject):
self.blurSettings.emit() # Ensure no-one has focus. self.blurSettings.emit() # Ensure no-one has focus.
self._cancelDelayedActiveContainerStackChanges() self._cancelDelayedActiveContainerStackChanges()
containers = ContainerRegistry.getInstance().findContainerStacks(id = stack_id) container_registry = ContainerRegistry.getInstance()
containers = container_registry.findContainerStacks(id = stack_id)
if containers: if containers:
Application.getInstance().setGlobalContainerStack(containers[0]) Application.getInstance().setGlobalContainerStack(containers[0])
self.__onInstanceContainersChanged() self.__emitChangedSignals()
@pyqtSlot(str, str) @pyqtSlot(str, str)
def addMachine(self, name: str, definition_id: str) -> None: def addMachine(self, name: str, definition_id: str) -> None:

View File

@ -49,6 +49,10 @@ class ProfilesModel(InstanceContainersModel):
ProfilesModel.__instance = cls() ProfilesModel.__instance = cls()
return ProfilesModel.__instance return ProfilesModel.__instance
@classmethod
def hasInstance(cls) -> bool:
return ProfilesModel.__instance is not None
__instance = None # type: "ProfilesModel" __instance = None # type: "ProfilesModel"
## Fetch the list of containers to display. ## Fetch the list of containers to display.
@ -91,7 +95,6 @@ class ProfilesModel(InstanceContainersModel):
## Re-computes the items in this model, and adds the layer height role. ## Re-computes the items in this model, and adds the layer height role.
def _recomputeItems(self): def _recomputeItems(self):
# Some globals that we can re-use. # Some globals that we can re-use.
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None: if global_container_stack is None:

View File

@ -2,13 +2,40 @@
# Copyright (c) 2015 Ultimaker B.V. # Copyright (c) 2015 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.
import argparse
import os import os
import sys import sys
import platform
import faulthandler
from UM.Platform import Platform from UM.Platform import Platform
parser = argparse.ArgumentParser(prog = "cura",
add_help = False)
parser.add_argument('--debug',
action='store_true',
default = False,
help = "Turn on the debug mode by setting this option."
)
known_args = vars(parser.parse_known_args()[0])
if not known_args["debug"]:
def get_cura_dir_path():
if Platform.isWindows():
return os.path.expanduser("~/AppData/Roaming/cura/")
elif Platform.isLinux():
return os.path.expanduser("~/.local/share/cura")
elif Platform.isOSX():
return os.path.expanduser("~/Library/Logs/cura")
if hasattr(sys, "frozen"):
dirpath = get_cura_dir_path()
os.makedirs(dirpath, exist_ok = True)
sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w")
sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w")
import platform
import faulthandler
#WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612 #WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612
if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX
# For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826 # For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
@ -47,8 +74,8 @@ def exceptHook(hook_type, value, traceback):
_crash_handler = CrashHandler(hook_type, value, traceback) _crash_handler = CrashHandler(hook_type, value, traceback)
_crash_handler.show() _crash_handler.show()
if not known_args["debug"]:
sys.excepthook = exceptHook sys.excepthook = exceptHook
# Workaround for a race condition on certain systems where there # Workaround for a race condition on certain systems where there
# is a race condition between Arcus and PyQt. Importing Arcus # is a race condition between Arcus and PyQt. Importing Arcus
@ -58,29 +85,14 @@ import Arcus #@UnusedImport
import cura.CuraApplication import cura.CuraApplication
import cura.Settings.CuraContainerRegistry import cura.Settings.CuraContainerRegistry
def get_cura_dir_path():
if Platform.isWindows():
return os.path.expanduser("~/AppData/Local/cura/")
elif Platform.isLinux():
return os.path.expanduser("~/.local/share/cura")
elif Platform.isOSX():
return os.path.expanduser("~/Library/Logs/cura")
if hasattr(sys, "frozen"):
dirpath = get_cura_dir_path()
os.makedirs(dirpath, exist_ok = True)
sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w")
sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w")
faulthandler.enable() faulthandler.enable()
# Force an instance of CuraContainerRegistry to be created and reused later. # Force an instance of CuraContainerRegistry to be created and reused later.
cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance() cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance()
# This pre-start up check is needed to determine if we should start the application at all. # This pre-start up check is needed to determine if we should start the application at all.
if not cura.CuraApplication.CuraApplication.preStartUp(): if not cura.CuraApplication.CuraApplication.preStartUp(parser = parser, parsed_command_line = known_args):
sys.exit(0) sys.exit(0)
app = cura.CuraApplication.CuraApplication.getInstance() app = cura.CuraApplication.CuraApplication.getInstance(parser = parser, parsed_command_line = known_args)
app.run() app.run()

View File

@ -86,6 +86,7 @@ class CuraEngineBackend(QObject, Backend):
# #
self._global_container_stack = None self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
Application.getInstance().getExtruderManager().activeExtruderChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged() self._onGlobalStackChanged()
Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished) Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished)

View File

@ -131,12 +131,21 @@ class StartSliceJob(Job):
Logger.log("w", "No objects suitable for one at a time found, or no correct order found") Logger.log("w", "No objects suitable for one at a time found, or no correct order found")
else: else:
temp_list = [] temp_list = []
has_printing_mesh = False
for node in DepthFirstIterator(self._scene.getRoot()): for node in DepthFirstIterator(self._scene.getRoot()):
if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None: if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
if not getattr(node, "_outside_buildarea", False) or getattr(node, "_non_printing_mesh", False): _non_printing_mesh = getattr(node, "_non_printing_mesh", False)
if not getattr(node, "_outside_buildarea", False) or _non_printing_mesh:
temp_list.append(node) temp_list.append(node)
if not _non_printing_mesh:
has_printing_mesh = True
Job.yieldThread() Job.yieldThread()
#If the list doesn't have any model with suitable settings then clean the list
# otherwise CuraEngine will crash
if not has_printing_mesh:
temp_list.clear()
if temp_list: if temp_list:
object_groups.append(temp_list) object_groups.append(temp_list)
@ -209,7 +218,7 @@ class StartSliceJob(Job):
result[key] = stack.getProperty(key, "value") result[key] = stack.getProperty(key, "value")
Job.yieldThread() Job.yieldThread()
result["print_bed_temperature"] = result["material_bed_temperature"] #Renamed settings. result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings.
result["print_temperature"] = result["material_print_temperature"] result["print_temperature"] = result["material_print_temperature"]
result["time"] = time.strftime("%H:%M:%S") #Some extra settings. result["time"] = time.strftime("%H:%M:%S") #Some extra settings.
result["date"] = time.strftime("%d-%m-%Y") result["date"] = time.strftime("%d-%m-%Y")
@ -237,10 +246,10 @@ class StartSliceJob(Job):
settings = self._buildReplacementTokens(stack) settings = self._buildReplacementTokens(stack)
#Also send the material GUID. This is a setting in fdmprinter, but we have no interface for it. # Also send the material GUID. This is a setting in fdmprinter, but we have no interface for it.
settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "") settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "")
#Replace the setting tokens in start and end g-code. # Replace the setting tokens in start and end g-code.
settings["machine_extruder_start_code"] = self._expandGcodeTokens(settings["machine_extruder_start_code"], settings) settings["machine_extruder_start_code"] = self._expandGcodeTokens(settings["machine_extruder_start_code"], settings)
settings["machine_extruder_end_code"] = self._expandGcodeTokens(settings["machine_extruder_end_code"], settings) settings["machine_extruder_end_code"] = self._expandGcodeTokens(settings["machine_extruder_end_code"], settings)
@ -260,18 +269,23 @@ class StartSliceJob(Job):
def _buildGlobalSettingsMessage(self, stack): def _buildGlobalSettingsMessage(self, stack):
settings = self._buildReplacementTokens(stack) settings = self._buildReplacementTokens(stack)
# Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
start_gcode = settings["machine_start_gcode"] start_gcode = settings["machine_start_gcode"]
#Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
bed_temperature_settings = {"material_bed_temperature", "material_bed_temperature_layer_0"} bed_temperature_settings = {"material_bed_temperature", "material_bed_temperature_layer_0"}
settings["material_bed_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in bed_temperature_settings)) settings["material_bed_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in bed_temperature_settings))
print_temperature_settings = {"material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"} print_temperature_settings = {"material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"}
settings["material_print_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in print_temperature_settings)) settings["material_print_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in print_temperature_settings))
#Replace the setting tokens in start and end g-code. # Find the correct temperatures from the first used extruder
settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], settings) extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
settings["machine_end_gcode"] = self._expandGcodeTokens(settings["machine_end_gcode"], settings) extruder_0_settings = self._buildReplacementTokens(extruder_stack)
for key, value in settings.items(): #Add all submessages for each individual setting. # Replace the setting tokens in start and end g-code.
settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], extruder_0_settings)
settings["machine_end_gcode"] = self._expandGcodeTokens(settings["machine_end_gcode"], extruder_0_settings)
# Add all sub-messages for each individual setting.
for key, value in settings.items():
setting_message = self._slice_message.getMessage("global_settings").addRepeatedMessage("settings") setting_message = self._slice_message.getMessage("global_settings").addRepeatedMessage("settings")
setting_message.name = key setting_message.name = key
setting_message.value = str(value).encode("utf-8") setting_message.value = str(value).encode("utf-8")

View File

@ -92,7 +92,7 @@ class SimulationPass(RenderPass):
self.bind() self.bind()
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay) tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay, backface_cull = True)
head_position = None # Indicates the current position of the print head head_position = None # Indicates the current position of the print head
nozzle_node = None nozzle_node = None
@ -149,7 +149,7 @@ class SimulationPass(RenderPass):
self._current_shader = self._layer_shader self._current_shader = self._layer_shader
self._switching_layers = True self._switching_layers = True
layers_batch = RenderBatch(self._current_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end)) layers_batch = RenderBatch(self._current_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end), backface_cull = True)
layers_batch.addItem(node.getWorldTransformation(), layer_data) layers_batch.addItem(node.getWorldTransformation(), layer_data)
layers_batch.render(self._scene.getActiveCamera()) layers_batch.render(self._scene.getActiveCamera())

View File

@ -187,20 +187,27 @@ geometry41core =
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert)); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert)); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert)); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert));
//And reverse so that the line is also visible from the back side.
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
EndPrimitive(); EndPrimitive();
} else { } else {
// All normal lines are rendered as 3d tubes. // All normal lines are rendered as 3d tubes.
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz)); myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz)); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert)); myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert)); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz)); myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz)); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
EndPrimitive(); EndPrimitive();

View File

@ -99,7 +99,9 @@ class SliceInfo(Extension):
"type": extruder.material.getMetaData().get("material", ""), "type": extruder.material.getMetaData().get("material", ""),
"brand": extruder.material.getMetaData().get("brand", "") "brand": extruder.material.getMetaData().get("brand", "")
} }
extruder_dict["material_used"] = print_information.materialLengths[int(extruder.getMetaDataEntry("position", "0"))] extruder_position = int(extruder.getMetaDataEntry("position", "0"))
if extruder_position in print_information.materialLengths:
extruder_dict["material_used"] = print_information.materialLengths[extruder_position]
extruder_dict["variant"] = extruder.variant.getName() extruder_dict["variant"] = extruder.variant.getName()
extruder_dict["nozzle_size"] = extruder.getProperty("machine_nozzle_size", "value") extruder_dict["nozzle_size"] = extruder.getProperty("machine_nozzle_size", "value")

View File

@ -43,6 +43,7 @@ class NetworkPrinterOutputDevicePlugin(QObject, OutputDevicePlugin):
# List of old printer names. This is used to ensure that a refresh of zeroconf does not needlessly forces # List of old printer names. This is used to ensure that a refresh of zeroconf does not needlessly forces
# authentication requests. # authentication requests.
self._old_printers = [] self._old_printers = []
self._excluded_addresses = ["127.0.0.1"] # Adding a list of not allowed IP addresses. At this moment, just localhost
# Because the model needs to be created in the same thread as the QMLEngine, we use a signal. # Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
self.addPrinterSignal.connect(self.addPrinter) self.addPrinterSignal.connect(self.addPrinter)
@ -300,6 +301,9 @@ class NetworkPrinterOutputDevicePlugin(QObject, OutputDevicePlugin):
if type_of_device: if type_of_device:
if type_of_device == b"printer": if type_of_device == b"printer":
address = '.'.join(map(lambda n: str(n), info.address)) address = '.'.join(map(lambda n: str(n), info.address))
if address in self._excluded_addresses:
Logger.log("d", "The IP address %s of the printer \'%s\' is not correct. Trying to reconnect.", address, name)
return False # When getting the localhost IP, then try to reconnect
self.addPrinterSignal.emit(str(name), address, info.properties) self.addPrinterSignal.emit(str(name), address, info.properties)
else: else:
Logger.log("w", "The type of the found device is '%s', not 'printer'! Ignoring.." % type_of_device ) Logger.log("w", "The type of the found device is '%s', not 'printer'! Ignoring.." % type_of_device )

View File

@ -122,6 +122,21 @@ class VersionUpgrade30to31(VersionUpgrade):
if len(all_quality_changes) <= 1 and not parser.has_option("metadata", "extruder"): if len(all_quality_changes) <= 1 and not parser.has_option("metadata", "extruder"):
self._createExtruderQualityChangesForSingleExtrusionMachine(filename, parser) self._createExtruderQualityChangesForSingleExtrusionMachine(filename, parser)
if parser["metadata"]["type"] == "definition_changes":
if parser["general"]["definition"] == "custom":
# We are only interested in machine_nozzle_size
if parser.has_option("values", "machine_nozzle_size"):
machine_nozzle_size = parser["values"]["machine_nozzle_size"]
definition_name = parser["general"]["name"]
machine_extruders = self._getSingleExtrusionMachineExtruders(definition_name)
#For single extuder machine we nee only first extruder
if len(machine_extruders) !=0:
if self._updateSingleExtuderDefinitionFile(machine_extruders, machine_nozzle_size):
parser.remove_option("values", "machine_nozzle_size")
# Update version numbers # Update version numbers
parser["general"]["version"] = "2" parser["general"]["version"] = "2"
parser["metadata"]["setting_version"] = "4" parser["metadata"]["setting_version"] = "4"
@ -200,6 +215,133 @@ class VersionUpgrade30to31(VersionUpgrade):
return quality_changes_containers return quality_changes_containers
def _getSingleExtrusionMachineExtruders(self, definition_name):
machine_instances_dir = Resources.getPath(CuraApplication.ResourceTypes.MachineStack)
machine_instances = []
#Find all machine instances
for item in os.listdir(machine_instances_dir):
file_path = os.path.join(machine_instances_dir, item)
if not os.path.isfile(file_path):
continue
parser = configparser.ConfigParser(interpolation=None)
try:
parser.read([file_path])
except:
# skip, it is not a valid stack file
continue
if not parser.has_option("metadata", "type"):
continue
if "machine" != parser["metadata"]["type"]:
continue
if not parser.has_option("general", "id"):
continue
machine_instances.append(parser)
#Find for extruders
extruders_instances_dir = Resources.getPath(CuraApplication.ResourceTypes.ExtruderStack)
#"machine",[extruders]
extruder_instances_per_machine = {}
#Find all custom extruders for founded machines
for item in os.listdir(extruders_instances_dir):
file_path = os.path.join(extruders_instances_dir, item)
if not os.path.isfile(file_path):
continue
parser = configparser.ConfigParser(interpolation=None)
try:
parser.read([file_path])
except:
# skip, it is not a valid stack file
continue
if not parser.has_option("metadata", "type"):
continue
if "extruder_train" != parser["metadata"]["type"]:
continue
if not parser.has_option("metadata", "machine"):
continue
if not parser.has_option("metadata", "position"):
continue
for machine_instace in machine_instances:
machine_id = machine_instace["general"]["id"]
if machine_id != parser["metadata"]["machine"]:
continue
if machine_id + "_settings" != definition_name:
continue
if extruder_instances_per_machine.get(machine_id) is None:
extruder_instances_per_machine.update({machine_id:[]})
extruder_instances_per_machine.get(machine_id).append(parser)
#the extruder can be related only to one machine
break
return extruder_instances_per_machine
#Find extruder defition at index 0 and update its values
def _updateSingleExtuderDefinitionFile(self, extruder_instances_per_machine, machine_nozzle_size):
defintion_instances_dir = Resources.getPath(CuraApplication.ResourceTypes.DefinitionChangesContainer)
for item in os.listdir(defintion_instances_dir):
file_path = os.path.join(defintion_instances_dir, item)
if not os.path.isfile(file_path):
continue
parser = configparser.ConfigParser(interpolation=None)
try:
parser.read([file_path])
except:
# skip, it is not a valid stack file
continue
if not parser.has_option("general", "name"):
continue
name = parser["general"]["name"]
custom_extruder_at_0_position = None
for machine_extruders in extruder_instances_per_machine:
for extruder_instance in extruder_instances_per_machine[machine_extruders]:
if extruder_instance["general"]["id"] + "_settings" == name:
defition_position = extruder_instance["metadata"]["position"]
if defition_position == "0":
custom_extruder_at_0_position = extruder_instance
break
if custom_extruder_at_0_position is not None:
break
#If not null, then parsed file is for first extuder and then can be updated. I need to update only
# first, because this update for single extuder machine
if custom_extruder_at_0_position is not None:
#Add new value
parser["values"]["machine_nozzle_size"] = machine_nozzle_size
definition_output = io.StringIO()
parser.write(definition_output)
with open(file_path, "w") as f:
f.write(definition_output.getvalue())
return True
return False
def _createExtruderQualityChangesForSingleExtrusionMachine(self, filename, global_quality_changes): def _createExtruderQualityChangesForSingleExtrusionMachine(self, filename, global_quality_changes):
suffix = "_" + quote_plus(global_quality_changes["general"]["name"].lower()) suffix = "_" + quote_plus(global_quality_changes["general"]["name"].lower())
machine_name = os.path.os.path.basename(filename).replace(".inst.cfg", "").replace(suffix, "") machine_name = os.path.os.path.basename(filename).replace(".inst.cfg", "").replace(suffix, "")

View File

@ -212,11 +212,12 @@ class XmlMaterialProfile(InstanceContainer):
for definition_id, container in machine_container_map.items(): for definition_id, container in machine_container_map.items():
definition = container.getDefinition() definition = container.getDefinition()
try:
product = UM.Dictionary.findKey(product_id_map, definition_id) product = definition_id
except ValueError: for product_name, product_id_list in product_id_map.items():
# An unknown product id; export it anyway if definition_id in product_id_list:
product = definition_id product = product_name
break
builder.start("machine") builder.start("machine")
builder.start("machine_identifier", { builder.start("machine_identifier", {
@ -530,106 +531,112 @@ class XmlMaterialProfile(InstanceContainer):
identifiers = machine.iterfind("./um:machine_identifier", self.__namespaces) identifiers = machine.iterfind("./um:machine_identifier", self.__namespaces)
for identifier in identifiers: for identifier in identifiers:
machine_id = product_id_map.get(identifier.get("product"), None) machine_id_list = product_id_map.get(identifier.get("product"), [])
if machine_id is None: if not machine_id_list:
# Lets try again with some naive heuristics. machine_id_list = self.getPossibleDefinitionIDsFromName(identifier.get("product"))
machine_id = identifier.get("product").replace(" ", "").lower()
definitions = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id) for machine_id in machine_id_list:
if not definitions: definitions = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
Logger.log("w", "No definition found for machine ID %s", machine_id) if not definitions:
continue Logger.log("w", "No definition found for machine ID %s", machine_id)
definition = definitions[0]
machine_manufacturer = identifier.get("manufacturer", definition.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.
if machine_compatibility:
new_material_id = self.getId() + "_" + machine_id
# The child or derived material container may already exist. This can happen when a material in a
# project file and the a material in Cura have the same ID.
# In the case if a derived material already exists, override that material container because if
# the data in the parent material has been changed, the derived ones should be updated too.
if ContainerRegistry.getInstance().isLoaded(new_material_id):
new_material = ContainerRegistry.getInstance().findContainers(id = new_material_id)[0]
is_new_material = False
else:
new_material = XmlMaterialProfile(new_material_id)
is_new_material = True
new_material.setMetaData(copy.deepcopy(self.getMetaData()))
new_material.getMetaData()["id"] = new_material_id
new_material.getMetaData()["name"] = self.getName()
new_material.setDefinition(machine_id)
# Don't use setMetadata, as that overrides it for all materials with same base file
new_material.getMetaData()["compatible"] = machine_compatibility
new_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
new_material.getMetaData()["definition"] = machine_id
new_material.setCachedValues(cached_machine_setting_properties)
new_material._dirty = False
if is_new_material:
containers_to_add.append(new_material)
hotends = machine.iterfind("./um:hotend", self.__namespaces)
for hotend in hotends:
hotend_id = hotend.get("id")
if hotend_id is None:
continue continue
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id) Logger.log("d", "Found definition for machine ID %s", machine_id)
if not variant_containers: definition = definitions[0]
# It is not really properly defined what "ID" is so also search for variants by name.
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = definition["id"], name = hotend_id)
if not variant_containers: machine_manufacturer = identifier.get("manufacturer", definition.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.
continue
hotend_compatibility = machine_compatibility if machine_compatibility:
hotend_setting_values = {} new_material_id = self.getId() + "_" + machine_id
settings = hotend.iterfind("./um:setting", self.__namespaces)
for entry in settings: # The child or derived material container may already exist. This can happen when a material in a
key = entry.get("key") # project file and the a material in Cura have the same ID.
if key in self.__material_settings_setting_map: # In the case if a derived material already exists, override that material container because if
hotend_setting_values[self.__material_settings_setting_map[key]] = entry.text # the data in the parent material has been changed, the derived ones should be updated too.
elif key in self.__unmapped_settings: if ContainerRegistry.getInstance().isLoaded(new_material_id):
if key == "hardware compatible": new_material = ContainerRegistry.getInstance().findContainers(id = new_material_id)[0]
hotend_compatibility = self._parseCompatibleValue(entry.text) is_new_material = False
else: else:
Logger.log("d", "Unsupported material setting %s", key) new_material = XmlMaterialProfile(new_material_id)
is_new_material = True
new_hotend_id = self.getId() + "_" + machine_id + "_" + hotend_id.replace(" ", "_") new_material.setMetaData(copy.deepcopy(self.getMetaData()))
new_material.getMetaData()["id"] = new_material_id
new_material.getMetaData()["name"] = self.getName()
new_material.setDefinition(machine_id)
# Don't use setMetadata, as that overrides it for all materials with same base file
new_material.getMetaData()["compatible"] = machine_compatibility
new_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
new_material.getMetaData()["definition"] = machine_id
# Same as machine compatibility, keep the derived material containers consistent with the parent new_material.setCachedValues(cached_machine_setting_properties)
# material
if ContainerRegistry.getInstance().isLoaded(new_hotend_id):
new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_id)[0]
is_new_material = False
else:
new_hotend_material = XmlMaterialProfile(new_hotend_id)
is_new_material = True
new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData())) new_material._dirty = False
new_hotend_material.getMetaData()["id"] = new_hotend_id
new_hotend_material.getMetaData()["name"] = self.getName()
new_hotend_material.getMetaData()["variant"] = variant_containers[0]["id"]
# Don't use setMetadata, as that overrides it for all materials with same base file
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
new_hotend_material.getMetaData()["definition"] = machine_id
cached_hotend_setting_properties = cached_machine_setting_properties.copy() if is_new_material:
cached_hotend_setting_properties.update(hotend_setting_values) containers_to_add.append(new_material)
new_hotend_material.setCachedValues(cached_hotend_setting_properties) hotends = machine.iterfind("./um:hotend", self.__namespaces)
for hotend in hotends:
hotend_id = hotend.get("id")
if hotend_id is None:
continue
new_hotend_material._dirty = False variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id)
if not variant_containers:
# It is not really properly defined what "ID" is so also search for variants by name.
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id)
if is_new_material: if not variant_containers:
containers_to_add.append(new_hotend_material) continue
hotend_compatibility = machine_compatibility
hotend_setting_values = {}
settings = hotend.iterfind("./um:setting", self.__namespaces)
for entry in settings:
key = entry.get("key")
if key in self.__material_settings_setting_map:
hotend_setting_values[self.__material_settings_setting_map[key]] = entry.text
elif key in self.__unmapped_settings:
if key == "hardware compatible":
hotend_compatibility = self._parseCompatibleValue(entry.text)
else:
Logger.log("d", "Unsupported material setting %s", key)
new_hotend_id = self.getId() + "_" + machine_id + "_" + hotend_id.replace(" ", "_")
# Same as machine compatibility, keep the derived material containers consistent with the parent
# material
if ContainerRegistry.getInstance().isLoaded(new_hotend_id):
new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_id)[0]
is_new_material = False
else:
new_hotend_material = XmlMaterialProfile(new_hotend_id)
is_new_material = True
new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
new_hotend_material.getMetaData()["id"] = new_hotend_id
new_hotend_material.getMetaData()["name"] = self.getName()
new_hotend_material.getMetaData()["variant"] = variant_containers[0]["id"]
new_hotend_material.setDefinition(machine_id)
# Don't use setMetadata, as that overrides it for all materials with same base file
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
new_hotend_material.getMetaData()["definition"] = machine_id
cached_hotend_setting_properties = cached_machine_setting_properties.copy()
cached_hotend_setting_properties.update(hotend_setting_values)
new_hotend_material.setCachedValues(cached_hotend_setting_properties)
new_hotend_material._dirty = False
if is_new_material:
containers_to_add.append(new_hotend_material)
# there is only one ID for a machine. Once we have reached here, it means we have already found
# a workable ID for that machine, so there is no need to continue
break
for container_to_add in containers_to_add: for container_to_add in containers_to_add:
ContainerRegistry.getInstance().addContainer(container_to_add) ContainerRegistry.getInstance().addContainer(container_to_add)
@ -720,79 +727,86 @@ class XmlMaterialProfile(InstanceContainer):
machine_compatibility = cls._parseCompatibleValue(entry.text) machine_compatibility = cls._parseCompatibleValue(entry.text)
for identifier in machine.iterfind("./um:machine_identifier", cls.__namespaces): for identifier in machine.iterfind("./um:machine_identifier", cls.__namespaces):
machine_id = product_id_map.get(identifier.get("product"), None) machine_id_list = product_id_map.get(identifier.get("product"), [])
if machine_id is None: if not machine_id_list:
# Lets try again with some naive heuristics. machine_id_list = cls.getPossibleDefinitionIDsFromName(identifier.get("product"))
machine_id = identifier.get("product").replace(" ", "").lower()
definition_metadata = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
if not definition_metadata:
Logger.log("w", "No definition found for machine ID %s", machine_id)
continue
definition_metadata = definition_metadata[0]
machine_manufacturer = identifier.get("manufacturer", definition_metadata.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition. for machine_id in machine_id_list:
definition_metadata = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
if machine_compatibility: if not definition_metadata:
new_material_id = container_id + "_" + machine_id Logger.log("w", "No definition found for machine ID %s", machine_id)
# The child or derived material container may already exist. This can happen when a material in a
# project file and the a material in Cura have the same ID.
# In the case if a derived material already exists, override that material container because if
# the data in the parent material has been changed, the derived ones should be updated too.
found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_material_id)
if found_materials:
new_material_metadata = found_materials[0]
else:
new_material_metadata = {}
new_material_metadata.update(base_metadata)
new_material_metadata["id"] = new_material_id
new_material_metadata["compatible"] = machine_compatibility
new_material_metadata["machine_manufacturer"] = machine_manufacturer
new_material_metadata["definition"] = machine_id
if len(found_materials) == 0: #This is a new material.
result_metadata.append(new_material_metadata)
for hotend in machine.iterfind("./um:hotend", cls.__namespaces):
hotend_id = hotend.get("id")
if hotend_id is None:
continue continue
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id) Logger.log("d", "Found def for machine [%s]", machine_id)
if not variant_containers: definition_metadata = definition_metadata[0]
# It is not really properly defined what "ID" is so also search for variants by name.
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id)
hotend_compatibility = machine_compatibility machine_manufacturer = identifier.get("manufacturer", definition_metadata.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.
for entry in hotend.iterfind("./um:setting", cls.__namespaces):
key = entry.get("key")
if key == "hardware compatible":
hotend_compatibility = cls._parseCompatibleValue(entry.text)
new_hotend_id = container_id + "_" + machine_id + "_" + hotend_id.replace(" ", "_") if machine_compatibility:
new_material_id = container_id + "_" + machine_id
# Same as machine compatibility, keep the derived material containers consistent with the parent # The child or derived material container may already exist. This can happen when a material in a
# material # project file and the a material in Cura have the same ID.
found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_id) # In the case if a derived material already exists, override that material container because if
if found_materials: # the data in the parent material has been changed, the derived ones should be updated too.
new_hotend_material_metadata = found_materials[0] found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_material_id)
else: if found_materials:
new_hotend_material_metadata = {} new_material_metadata = found_materials[0]
else:
new_material_metadata = {}
new_hotend_material_metadata.update(base_metadata) new_material_metadata.update(base_metadata)
if variant_containers: new_material_metadata["id"] = new_material_id
new_hotend_material_metadata["variant"] = variant_containers[0]["id"] new_material_metadata["compatible"] = machine_compatibility
else: new_material_metadata["machine_manufacturer"] = machine_manufacturer
new_hotend_material_metadata["variant"] = hotend_id new_material_metadata["definition"] = machine_id
_with_missing_variants.append(new_hotend_material_metadata)
new_hotend_material_metadata["compatible"] = hotend_compatibility
new_hotend_material_metadata["machine_manufacturer"] = machine_manufacturer
new_hotend_material_metadata["id"] = new_hotend_id
new_hotend_material_metadata["definition"] = machine_id
if len(found_materials) == 0: if len(found_materials) == 0: #This is a new material.
result_metadata.append(new_hotend_material_metadata) result_metadata.append(new_material_metadata)
for hotend in machine.iterfind("./um:hotend", cls.__namespaces):
hotend_id = hotend.get("id")
if hotend_id is None:
continue
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id)
if not variant_containers:
# It is not really properly defined what "ID" is so also search for variants by name.
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id)
hotend_compatibility = machine_compatibility
for entry in hotend.iterfind("./um:setting", cls.__namespaces):
key = entry.get("key")
if key == "hardware compatible":
hotend_compatibility = cls._parseCompatibleValue(entry.text)
new_hotend_id = container_id + "_" + machine_id + "_" + hotend_id.replace(" ", "_")
# Same as machine compatibility, keep the derived material containers consistent with the parent
# material
found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_id)
if found_materials:
new_hotend_material_metadata = found_materials[0]
else:
new_hotend_material_metadata = {}
new_hotend_material_metadata.update(base_metadata)
if variant_containers:
new_hotend_material_metadata["variant"] = variant_containers[0]["id"]
else:
new_hotend_material_metadata["variant"] = hotend_id
_with_missing_variants.append(new_hotend_material_metadata)
new_hotend_material_metadata["compatible"] = hotend_compatibility
new_hotend_material_metadata["machine_manufacturer"] = machine_manufacturer
new_hotend_material_metadata["id"] = new_hotend_id
new_hotend_material_metadata["definition"] = machine_id
if len(found_materials) == 0:
result_metadata.append(new_hotend_material_metadata)
# there is only one ID for a machine. Once we have reached here, it means we have already found
# a workable ID for that machine, so there is no need to continue
break
return result_metadata return result_metadata
@ -813,15 +827,41 @@ class XmlMaterialProfile(InstanceContainer):
else: else:
return material_name return material_name
@classmethod
def getPossibleDefinitionIDsFromName(cls, name):
name_parts = name.lower().split(" ")
merged_name_parts = []
for part in name_parts:
if len(part) == 0:
continue
if len(merged_name_parts) == 0:
merged_name_parts.append(part)
continue
if part.isdigit():
# for names with digit(s) such as Ultimaker 3 Extended, we generate an ID like
# "ultimaker3_extended", ignoring the space between "Ultimaker" and "3".
merged_name_parts[-1] = merged_name_parts[-1] + part
else:
merged_name_parts.append(part)
id_list = [name.lower().replace(" ", ""), # simply removing all spaces
name.lower().replace(" ", "_"), # simply replacing all spaces with underscores
"_".join(merged_name_parts),
]
return id_list
## Gets a mapping from product names in the XML files to their definition ## Gets a mapping from product names in the XML files to their definition
# IDs. # IDs.
# #
# This loads the mapping from a file. # This loads the mapping from a file.
@classmethod @classmethod
def getProductIdMap(cls) -> Dict[str, str]: def getProductIdMap(cls) -> Dict[str, List[str]]:
product_to_id_file = os.path.join(os.path.dirname(sys.modules[cls.__module__].__file__), "product_to_id.json") product_to_id_file = os.path.join(os.path.dirname(sys.modules[cls.__module__].__file__), "product_to_id.json")
with open(product_to_id_file) as f: with open(product_to_id_file) as f:
return json.load(f) product_to_id_map = json.load(f)
product_to_id_map = {key: [value] for key, value in product_to_id_map.items()}
return product_to_id_map
## Parse the value of the "material compatible" property. ## Parse the value of the "material compatible" property.
@classmethod @classmethod

View File

@ -46,7 +46,7 @@
"default_value": "RepRap (Marlin/Sprinter)" "default_value": "RepRap (Marlin/Sprinter)"
}, },
"machine_start_gcode": { "machine_start_gcode": {
"default_value": "G28 ; Home extruder\nM107 ; Turn off fan\nG90 ; Absolute positioning\nM82 ; Extruder in absolute mode\nM190 S{material_bed_temperature}\nM104 T0 S{material_print_temperature}\nM109 T0 S{material_print_temperature}\nM104 T1 S{material_print_temperature}\nM109 T1 S{material_print_temperature}\nG32 S3 ; auto level\nG92 E0 ; Reset extruder position" "default_value": "G28 ; Home extruder\nM107 ; Turn off fan\nG90 ; Absolute positioning\nM82 ; Extruder in absolute mode\nM190 S{material_bed_temperature}\nM104 T0 S{material_print_temperature}\nM109 T0 S{material_print_temperature}\nM104 T1 S{material_print_temperature}\nM109 T1 S{material_print_temperature}\n;G32 S3 ; auto level\nG92 E0 ; Reset extruder position"
}, },
"machine_end_gcode": { "machine_end_gcode": {
"default_value": "M104 S0 ; turn off extruders\nM140 S0 ; heated bed heater off\nG91 ; relative positioning\nG1 E-2 F5000; retract 2mm\nG28 Z; move bed down\nG90 ; absolute positioning\nM84 ; disable motors" "default_value": "M104 S0 ; turn off extruders\nM140 S0 ; heated bed heater off\nG91 ; relative positioning\nG1 E-2 F5000; retract 2mm\nG28 Z; move bed down\nG90 ; absolute positioning\nM84 ; disable motors"

View File

@ -18,6 +18,7 @@ Item
property alias redo: redoAction; property alias redo: redoAction;
property alias homeCamera: homeCameraAction; property alias homeCamera: homeCameraAction;
property alias expandSidebar: expandSidebarAction;
property alias deleteSelection: deleteSelectionAction; property alias deleteSelection: deleteSelectionAction;
property alias centerSelection: centerSelectionAction; property alias centerSelection: centerSelectionAction;
@ -389,4 +390,11 @@ Item
text: catalog.i18nc("@action:menu", "Installed plugins..."); text: catalog.i18nc("@action:menu", "Installed plugins...");
iconName: "plugins_configure" iconName: "plugins_configure"
} }
Action
{
id: expandSidebarAction;
text: catalog.i18nc("@action:inmenu menubar:view","Expand/Collapse Sidebar");
shortcut: "Ctrl+E";
}
} }

View File

@ -34,6 +34,24 @@ UM.MainWindow
} }
} }
onWidthChanged:
{
// If slidebar is collapsed then it should be invisible
// otherwise after the main_window resize the sidebar will be fully re-drawn
if (sidebar.collapsed){
if (sidebar.visible == true){
sidebar.visible = false
sidebar.initialWidth = 0
}
}
else{
if (sidebar.visible == false){
sidebar.visible = true
sidebar.initialWidth = UM.Theme.getSize("sidebar").width
}
}
}
Component.onCompleted: Component.onCompleted:
{ {
CuraApplication.setMinimumWindowSize(UM.Theme.getSize("window_minimum_size")) CuraApplication.setMinimumWindowSize(UM.Theme.getSize("window_minimum_size"))
@ -369,16 +387,59 @@ UM.MainWindow
{ {
id: sidebar id: sidebar
anchors property bool collapsed: false;
{ property var initialWidth: UM.Theme.getSize("sidebar").width;
top: topbar.bottom
bottom: parent.bottom function callExpandOrCollapse() {
right: parent.right if (collapsed) {
sidebar.visible = true;
sidebar.initialWidth = UM.Theme.getSize("sidebar").width;
viewportRect = Qt.rect(0, 0, (base.width - sidebar.width) / base.width, 1.0);
expandSidebarAnimation.start();
} else {
viewportRect = Qt.rect(0, 0, 1, 1.0);
collapseSidebarAnimation.start();
}
collapsed = !collapsed;
UM.Preferences.setValue("cura/sidebar_collapse", collapsed);
} }
width: UM.Theme.getSize("sidebar").width anchors
{
top: topbar.top
bottom: parent.bottom
}
width: initialWidth
x: base.width - sidebar.width
source: UM.Controller.activeStage.sidebarComponent source: UM.Controller.activeStage.sidebarComponent
NumberAnimation {
id: collapseSidebarAnimation
target: sidebar
properties: "x"
to: base.width
duration: 100
}
NumberAnimation {
id: expandSidebarAnimation
target: sidebar
properties: "x"
to: base.width - sidebar.width
duration: 100
}
Component.onCompleted:
{
var sidebarCollapsed = UM.Preferences.getValue("cura/sidebar_collapse");
if (sidebarCollapsed) {
sidebar.collapsed = true;
viewportRect = Qt.rect(0, 0, 1, 1.0)
collapseSidebarAnimation.start();
}
}
} }
Loader Loader
@ -417,6 +478,13 @@ UM.MainWindow
} }
} }
// Expand or collapse sidebar
Connections
{
target: Cura.Actions.expandSidebar
onTriggered: sidebar.callExpandOrCollapse()
}
UM.PreferencesDialog UM.PreferencesDialog
{ {
id: preferences id: preferences

View File

@ -0,0 +1,71 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
import UM 1.2 as UM
import Cura 1.0 as Cura
import "Menus"
ToolButton
{
text: Cura.MachineManager.activeMachineName
tooltip: Cura.MachineManager.activeMachineName
style: ButtonStyle
{
background: Rectangle
{
color:
{
if(control.pressed)
{
return UM.Theme.getColor("sidebar_header_active");
}
else if(control.hovered)
{
return UM.Theme.getColor("sidebar_header_hover");
}
else
{
return UM.Theme.getColor("sidebar_header_bar");
}
}
Behavior on color { ColorAnimation { duration: 50; } }
UM.RecolorImage
{
id: downArrow
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width
sourceSize.height: width
color: UM.Theme.getColor("text_emphasis")
source: UM.Theme.getIcon("arrow_bottom")
}
Label
{
id: sidebarComboBoxLabel
color: UM.Theme.getColor("sidebar_header_text_active")
text: control.text;
elide: Text.ElideRight;
anchors.left: parent.left;
anchors.leftMargin: UM.Theme.getSize("default_margin").width * 2
anchors.right: downArrow.left;
anchors.rightMargin: control.rightMargin;
anchors.verticalCenter: parent.verticalCenter;
font: UM.Theme.getFont("large")
}
}
label: Label {}
}
menu: PrinterMenu { }
}

View File

@ -32,4 +32,5 @@ Menu
MenuSeparator {} MenuSeparator {}
MenuItem { action: Cura.Actions.homeCamera; } MenuItem { action: Cura.Actions.homeCamera; }
MenuItem { action: Cura.Actions.expandSidebar; }
} }

View File

@ -1171,4 +1171,4 @@ Column
} }
} }
} }
} }

View File

@ -12,11 +12,9 @@ Item {
id: base; id: base;
UM.I18nCatalog { id: catalog; name:"cura"} UM.I18nCatalog { id: catalog; name:"cura"}
property real progress: UM.Backend.progress; property real progress: UM.Backend.progress
property int backendState: UM.Backend.state; property int backendState: UM.Backend.state
property bool activity: CuraApplication.platformActivity
property var backend: CuraApplication.getBackend();
property bool activity: CuraApplication.platformActivity;
property alias buttonRowWidth: saveRow.width property alias buttonRowWidth: saveRow.width
@ -50,10 +48,14 @@ Item {
} }
function sliceOrStopSlicing() { function sliceOrStopSlicing() {
if (backend != "undefined" && [1, 5].indexOf(UM.Backend.state) != -1) { try {
backend.forceSlice(); if ([1, 5].indexOf(base.backendState) != -1) {
} else { CuraApplication.backend.forceSlice();
backend.stopSlicing(); } else {
CuraApplication.backend.stopSlicing();
}
} catch (e) {
console.log('Could not start or stop slicing', e)
} }
} }
@ -166,7 +168,7 @@ Item {
Button { Button {
id: prepareButton id: prepareButton
tooltip: [1, 5].indexOf(UM.Backend.state) != -1 ? catalog.i18nc("@info:tooltip","Slice current printjob") : catalog.i18nc("@info:tooltip","Cancel slicing process") tooltip: [1, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@info:tooltip","Slice current printjob") : catalog.i18nc("@info:tooltip","Cancel slicing process")
// 1 = not started, 2 = Processing // 1 = not started, 2 = Processing
enabled: base.backendState != "undefined" && (base.backendState == 1 || base.backendState == 2) && base.activity == true enabled: base.backendState != "undefined" && (base.backendState == 1 || base.backendState == 2) && base.activity == true
visible: base.backendState != "undefined" && !autoSlice && (base.backendState == 1 || base.backendState == 2) && base.activity == true visible: base.backendState != "undefined" && !autoSlice && (base.backendState == 1 || base.backendState == 2) && base.activity == true
@ -178,7 +180,7 @@ Item {
anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
// 1 = not started, 5 = disabled // 1 = not started, 5 = disabled
text: [1, 5].indexOf(UM.Backend.state) != -1 ? catalog.i18nc("@label:Printjob", "Prepare") : catalog.i18nc("@label:Printjob", "Cancel") text: [1, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@label:Printjob", "Prepare") : catalog.i18nc("@label:Printjob", "Cancel")
onClicked: onClicked:
{ {
sliceOrStopSlicing(); sliceOrStopSlicing();

View File

@ -14,11 +14,13 @@ import "."
Item { Item {
id: base; id: base;
height: UM.Theme.getSize("section").height; height: UM.Theme.getSize("section").height
property alias contents: controlContainer.children; property alias contents: controlContainer.children
property alias hovered: mouse.containsMouse property alias hovered: mouse.containsMouse
property var resetHandler: false
property var showRevertButton: true property var showRevertButton: true
property var showInheritButton: true property var showInheritButton: true
property var showLinkedSettingIcon: true property var showLinkedSettingIcon: true
@ -179,8 +181,13 @@ Item {
iconSource: UM.Theme.getIcon("reset") iconSource: UM.Theme.getIcon("reset")
onClicked: { onClicked: {
revertButton.focus = true; revertButton.focus = true
Cura.MachineManager.clearUserSettingAllCurrentStacks(propertyProvider.key);
if (resetHandler) {
resetHandler(propertyProvider.key)
} else {
Cura.MachineManager.clearUserSettingAllCurrentStacks(propertyProvider.key)
}
} }
onEntered: { hoverTimer.stop(); base.showTooltip(catalog.i18nc("@label", "This setting has a value that is different from the profile.\n\nClick to restore the value of the profile.")) } onEntered: { hoverTimer.stop(); base.showTooltip(catalog.i18nc("@label", "This setting has a value that is different from the profile.\n\nClick to restore the value of the profile.")) }

View File

@ -22,7 +22,6 @@ Rectangle
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
property int backendState: UM.Backend.state
property bool monitoringPrint: UM.Controller.activeStage.stageId == "MonitorStage" property bool monitoringPrint: UM.Controller.activeStage.stageId == "MonitorStage"
@ -87,10 +86,19 @@ Rectangle
} }
} }
MachineSelection {
id: machineSelection
width: base.width
height: UM.Theme.getSize("sidebar_header").height
anchors.top: base.top
anchors.right: parent.right
}
SidebarHeader { SidebarHeader {
id: header id: header
width: parent.width width: parent.width
visible: machineExtruderCount.properties.value > 1 || Cura.MachineManager.hasMaterials || Cura.MachineManager.hasVariants visible: machineExtruderCount.properties.value > 1 || Cura.MachineManager.hasMaterials || Cura.MachineManager.hasVariants
anchors.top: machineSelection.bottom
onShowTooltip: base.showTooltip(item, location, text) onShowTooltip: base.showTooltip(item, location, text)
onHideTooltip: base.hideTooltip() onHideTooltip: base.hideTooltip()
@ -263,7 +271,7 @@ Rectangle
{ {
id: controlItem id: controlItem
anchors.bottom: footerSeparator.top anchors.bottom: footerSeparator.top
anchors.top: monitoringPrint ? base.top : headerSeparator.bottom anchors.top: monitoringPrint ? machineSelection.bottom : headerSeparator.bottom
anchors.left: base.left anchors.left: base.left
anchors.right: base.right anchors.right: base.right
sourceComponent: sourceComponent:
@ -282,7 +290,7 @@ Rectangle
Loader Loader
{ {
anchors.bottom: footerSeparator.top anchors.bottom: footerSeparator.top
anchors.top: monitoringPrint ? base.top : headerSeparator.bottom anchors.top: monitoringPrint ? machineSelection.bottom : headerSeparator.bottom
anchors.left: base.left anchors.left: base.left
anchors.right: base.right anchors.right: base.right
source: source:

View File

@ -21,6 +21,22 @@ Rectangle
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
property int rightMargin: UM.Theme.getSize("sidebar").width + UM.Theme.getSize("default_margin").width;
property int allItemsWidth: 0;
function updateMarginsAndSizes() {
if (UM.Preferences.getValue("cura/sidebar_collapse")) {
rightMargin = UM.Theme.getSize("default_margin").width;
} else {
rightMargin = UM.Theme.getSize("sidebar").width + UM.Theme.getSize("default_margin").width;
}
allItemsWidth = (
logo.width + UM.Theme.getSize("topbar_logo_right_margin").width +
UM.Theme.getSize("topbar_logo_right_margin").width + stagesMenuContainer.width +
UM.Theme.getSize("default_margin").width + viewModeButton.width +
rightMargin);
}
UM.I18nCatalog UM.I18nCatalog
{ {
id: catalog id: catalog
@ -44,10 +60,9 @@ Rectangle
Row Row
{ {
id: stagesMenuContainer
anchors.left: logo.right anchors.left: logo.right
anchors.leftMargin: UM.Theme.getSize("topbar_logo_right_margin").width anchors.leftMargin: UM.Theme.getSize("topbar_logo_right_margin").width
anchors.right: machineSelection.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
spacing: UM.Theme.getSize("default_margin").width spacing: UM.Theme.getSize("default_margin").width
// The topbar is dynamically filled with all available stages // The topbar is dynamically filled with all available stages
@ -76,71 +91,6 @@ Rectangle
ExclusiveGroup { id: topbarMenuGroup } ExclusiveGroup { id: topbarMenuGroup }
} }
ToolButton
{
id: machineSelection
text: Cura.MachineManager.activeMachineName
width: UM.Theme.getSize("sidebar").width
height: UM.Theme.getSize("sidebar_header").height
tooltip: Cura.MachineManager.activeMachineName
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
style: ButtonStyle
{
background: Rectangle
{
color:
{
if(control.pressed)
{
return UM.Theme.getColor("sidebar_header_active");
}
else if(control.hovered)
{
return UM.Theme.getColor("sidebar_header_hover");
}
else
{
return UM.Theme.getColor("sidebar_header_bar");
}
}
Behavior on color { ColorAnimation { duration: 50; } }
UM.RecolorImage
{
id: downArrow
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width
sourceSize.height: width
color: UM.Theme.getColor("text_emphasis")
source: UM.Theme.getIcon("arrow_bottom")
}
Label
{
id: sidebarComboBoxLabel
color: UM.Theme.getColor("sidebar_header_text_active")
text: control.text;
elide: Text.ElideRight;
anchors.left: parent.left;
anchors.leftMargin: UM.Theme.getSize("default_margin").width * 2
anchors.right: downArrow.left;
anchors.rightMargin: control.rightMargin;
anchors.verticalCenter: parent.verticalCenter;
font: UM.Theme.getFont("large")
}
}
label: Label {}
}
menu: PrinterMenu { }
}
// View orientation Item // View orientation Item
Row Row
{ {
@ -152,8 +102,8 @@ Rectangle
anchors anchors
{ {
verticalCenter: base.verticalCenter verticalCenter: base.verticalCenter
right: viewModeButton.right right: viewModeButton.left
rightMargin: UM.Theme.getSize("default_margin").width + viewModeButton.width rightMargin: UM.Theme.getSize("default_margin").width
} }
// #1 3d view // #1 3d view
@ -165,7 +115,7 @@ Rectangle
onClicked:{ onClicked:{
UM.Controller.rotateView("3d", 0); UM.Controller.rotateView("3d", 0);
} }
visible: base.width > 1100 visible: base.width - allItemsWidth - 4 * this.width > 0;
} }
// #2 Front view // #2 Front view
@ -177,7 +127,7 @@ Rectangle
onClicked:{ onClicked:{
UM.Controller.rotateView("home", 0); UM.Controller.rotateView("home", 0);
} }
visible: base.width > 1130 visible: base.width - allItemsWidth - 3 * this.width > 0;
} }
// #3 Top view // #3 Top view
@ -189,7 +139,7 @@ Rectangle
onClicked:{ onClicked:{
UM.Controller.rotateView("y", 90); UM.Controller.rotateView("y", 90);
} }
visible: base.width > 1160 visible: base.width - allItemsWidth - 2 * this.width > 0;
} }
// #4 Left view // #4 Left view
@ -201,7 +151,7 @@ Rectangle
onClicked:{ onClicked:{
UM.Controller.rotateView("x", 90); UM.Controller.rotateView("x", 90);
} }
visible: base.width > 1190 visible: base.width - allItemsWidth - 1 * this.width > 0;
} }
// #5 Left view // #5 Left view
@ -213,7 +163,7 @@ Rectangle
onClicked:{ onClicked:{
UM.Controller.rotateView("x", -90); UM.Controller.rotateView("x", -90);
} }
visible: base.width > 1210 visible: base.width - allItemsWidth > 0;
} }
} }
@ -224,7 +174,7 @@ Rectangle
anchors { anchors {
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
right: parent.right right: parent.right
rightMargin: UM.Theme.getSize("sidebar").width + UM.Theme.getSize("default_margin").width rightMargin: rightMargin
} }
style: UM.Theme.styles.combobox style: UM.Theme.styles.combobox
@ -284,4 +234,16 @@ Rectangle
source: UM.ActiveView.valid ? UM.ActiveView.activeViewPanel : ""; source: UM.ActiveView.valid ? UM.ActiveView.activeViewPanel : "";
} }
// Expand or collapse sidebar
Connections
{
target: Cura.Actions.expandSidebar
onTriggered: updateMarginsAndSizes()
}
Component.onCompleted:
{
updateMarginsAndSizes();
}
} }

View File

@ -0,0 +1,34 @@
[general]
version = 2
name = Fine
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_bam_ultimaker3_AA_0.4
weight = 0
setting_version = 4
[values]
cool_fan_full_at_height = =layer_height_0 + 2 * layer_height
cool_fan_speed_max = =cool_fan_speed
cool_min_speed = 7
machine_nozzle_cool_down_speed = 0.75
machine_nozzle_heat_up_speed = 1.6
material_print_temperature = =default_material_print_temperature - 10
prime_tower_enable = =min(extruderValues('material_surface_energy')) < 100
skin_overlap = 10
speed_layer_0 = 20
support_interface_enable = True
support_interface_density = =min(extruderValues('material_surface_energy'))
support_interface_pattern = ='lines' if support_interface_density < 100 else 'concentric'
support_top_distance = =math.ceil(min(extruderValues('material_adhesion_tendency')) / 1) * layer_height
support_bottom_distance = =math.ceil(min(extruderValues('material_adhesion_tendency')) / 2) * layer_height
support_angle = 45
support_join_distance = 5
support_offset = 2
support_pattern = triangles
support_infill_rate = 10
top_bottom_thickness = 1
wall_thickness = 1

View File

@ -6,7 +6,9 @@ import pytest #This module contains unit tests.
import shutil #To copy files to make a temporary file. import shutil #To copy files to make a temporary file.
import unittest.mock #To mock and monkeypatch stuff. import unittest.mock #To mock and monkeypatch stuff.
import urllib.parse import urllib.parse
import copy
import cura.CuraApplication
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry #The class we're testing. from cura.Settings.CuraContainerRegistry import CuraContainerRegistry #The class we're testing.
from cura.Settings.ExtruderStack import ExtruderStack #Testing for returning the correct types of stacks. from cura.Settings.ExtruderStack import ExtruderStack #Testing for returning the correct types of stacks.
from cura.Settings.GlobalStack import GlobalStack #Testing for returning the correct types of stacks. from cura.Settings.GlobalStack import GlobalStack #Testing for returning the correct types of stacks.
@ -15,6 +17,32 @@ import UM.Settings.InstanceContainer #Creating instance containers to register.
import UM.Settings.ContainerRegistry #Making empty container stacks. import UM.Settings.ContainerRegistry #Making empty container stacks.
import UM.Settings.ContainerStack #Setting the container registry here properly. import UM.Settings.ContainerStack #Setting the container registry here properly.
from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
def creteEmptyContainers():
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
empty_variant_container = copy.deepcopy(empty_container)
empty_variant_container.setMetaDataEntry("id", "empty_variant")
empty_variant_container.addMetaDataEntry("type", "variant")
ContainerRegistry.getInstance().addContainer(empty_variant_container)
empty_material_container = copy.deepcopy(empty_container)
empty_material_container.setMetaDataEntry("id", "empty_material")
empty_material_container.addMetaDataEntry("type", "material")
ContainerRegistry.getInstance().addContainer(empty_material_container)
empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container.setMetaDataEntry("id", "empty_quality")
empty_quality_container.setName("Not Supported")
empty_quality_container.addMetaDataEntry("quality_type", "not_supported")
empty_quality_container.addMetaDataEntry("type", "quality")
empty_quality_container.addMetaDataEntry("supported", False)
ContainerRegistry.getInstance().addContainer(empty_quality_container)
empty_quality_changes_container = copy.deepcopy(empty_container)
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
## Gives a fresh CuraContainerRegistry instance. ## Gives a fresh CuraContainerRegistry instance.
@pytest.fixture() @pytest.fixture()
@ -37,6 +65,7 @@ def teardown():
## Tests whether addContainer properly converts to ExtruderStack. ## Tests whether addContainer properly converts to ExtruderStack.
def test_addContainerExtruderStack(container_registry, definition_container): def test_addContainerExtruderStack(container_registry, definition_container):
creteEmptyContainers()
container_registry.addContainer(definition_container) container_registry.addContainer(definition_container)
container_stack = UM.Settings.ContainerStack.ContainerStack(stack_id = "Test Container Stack") #A container we're going to convert. container_stack = UM.Settings.ContainerStack.ContainerStack(stack_id = "Test Container Stack") #A container we're going to convert.
@ -113,36 +142,36 @@ def test_addContainerBadSettingVersion(container_registry, definition_container)
mock_super_add_container.assert_not_called() #Should not get passed on to UM.Settings.ContainerRegistry.addContainer, because the setting_version doesn't match its definition! mock_super_add_container.assert_not_called() #Should not get passed on to UM.Settings.ContainerRegistry.addContainer, because the setting_version doesn't match its definition!
## Tests whether loading gives objects of the correct type. ## Tests whether loading gives objects of the correct type.
@pytest.mark.parametrize("filename, output_class", [ # @pytest.mark.parametrize("filename, output_class", [
("ExtruderLegacy.stack.cfg", ExtruderStack), # ("ExtruderLegacy.stack.cfg", ExtruderStack),
("MachineLegacy.stack.cfg", GlobalStack), # ("MachineLegacy.stack.cfg", GlobalStack),
("Left.extruder.cfg", ExtruderStack), # ("Left.extruder.cfg", ExtruderStack),
("Global.global.cfg", GlobalStack), # ("Global.global.cfg", GlobalStack),
("Global.stack.cfg", GlobalStack) # ("Global.stack.cfg", GlobalStack)
]) # ])
def test_loadTypes(filename, output_class, container_registry): # def test_loadTypes(filename, output_class, container_registry):
#Mock some dependencies. # #Mock some dependencies.
Resources.getAllResourcesOfType = unittest.mock.MagicMock(return_value = [os.path.join(os.path.dirname(os.path.abspath(__file__)), "stacks", filename)]) #Return just this tested file. # Resources.getAllResourcesOfType = unittest.mock.MagicMock(return_value = [os.path.join(os.path.dirname(os.path.abspath(__file__)), "stacks", filename)]) #Return just this tested file.
#
def findContainers(container_type = 0, id = None): # def findContainers(container_type = 0, id = None):
if id == "some_instance": # if id == "some_instance":
return [UM.Settings.ContainerRegistry._EmptyInstanceContainer(id)] # return [UM.Settings.ContainerRegistry._EmptyInstanceContainer(id)]
elif id == "some_definition": # elif id == "some_definition":
return [DefinitionContainer(container_id = id)] # return [DefinitionContainer(container_id = id)]
else: # else:
return [] # return []
#
container_registry.findContainers = findContainers # container_registry.findContainers = findContainers
#
with unittest.mock.patch("cura.Settings.GlobalStack.GlobalStack.findContainer"): # with unittest.mock.patch("cura.Settings.GlobalStack.GlobalStack.findContainer"):
with unittest.mock.patch("os.remove"): # with unittest.mock.patch("os.remove"):
container_registry.load() # container_registry.load()
#
#Check whether the resulting type was correct. # #Check whether the resulting type was correct.
stack_id = filename.split(".")[0] # stack_id = filename.split(".")[0]
for container_id, container in container_registry._containers.items(): #Stupid ContainerRegistry class doesn't expose any way of getting at this except by prodding the privates. # for container_id, container in container_registry._containers.items(): #Stupid ContainerRegistry class doesn't expose any way of getting at this except by prodding the privates.
if container_id == stack_id: #This is the one we're testing. # if container_id == stack_id: #This is the one we're testing.
assert type(container) == output_class # assert type(container) == output_class
break # break
else: # else:
assert False #Container stack with specified ID was not loaded. # assert False #Container stack with specified ID was not loaded.

View File

@ -3,7 +3,9 @@
import pytest #This module contains automated tests. import pytest #This module contains automated tests.
import unittest.mock #For the mocking and monkeypatching functionality. import unittest.mock #For the mocking and monkeypatching functionality.
import copy
import cura.CuraApplication
import UM.Settings.ContainerRegistry #To create empty instance containers. import UM.Settings.ContainerRegistry #To create empty instance containers.
import UM.Settings.ContainerStack #To set the container registry the container stacks use. import UM.Settings.ContainerStack #To set the container registry the container stacks use.
from UM.Settings.DefinitionContainer import DefinitionContainer #To check against the class of DefinitionContainer. from UM.Settings.DefinitionContainer import DefinitionContainer #To check against the class of DefinitionContainer.
@ -12,6 +14,7 @@ import cura.Settings.ExtruderStack #The module we're testing.
from cura.Settings.Exceptions import InvalidContainerError, InvalidOperationError #To check whether the correct exceptions are raised. from cura.Settings.Exceptions import InvalidContainerError, InvalidOperationError #To check whether the correct exceptions are raised.
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from UM.Settings.ContainerRegistry import ContainerRegistry
## Fake container registry that always provides all containers you ask of. ## Fake container registry that always provides all containers you ask of.
@pytest.yield_fixture() @pytest.yield_fixture()
@ -32,6 +35,7 @@ def container_registry():
## An empty extruder stack to test with. ## An empty extruder stack to test with.
@pytest.fixture() @pytest.fixture()
def extruder_stack() -> cura.Settings.ExtruderStack.ExtruderStack: def extruder_stack() -> cura.Settings.ExtruderStack.ExtruderStack:
creteEmptyContainers()
return cura.Settings.ExtruderStack.ExtruderStack("TestStack") return cura.Settings.ExtruderStack.ExtruderStack("TestStack")
## Gets an instance container with a specified container type. ## Gets an instance container with a specified container type.
@ -43,6 +47,31 @@ def getInstanceContainer(container_type) -> InstanceContainer:
container.addMetaDataEntry("type", container_type) container.addMetaDataEntry("type", container_type)
return container return container
def creteEmptyContainers():
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
empty_variant_container = copy.deepcopy(empty_container)
empty_variant_container.setMetaDataEntry("id", "empty_variant")
empty_variant_container.addMetaDataEntry("type", "variant")
ContainerRegistry.getInstance().addContainer(empty_variant_container)
empty_material_container = copy.deepcopy(empty_container)
empty_material_container.setMetaDataEntry("id", "empty_material")
empty_material_container.addMetaDataEntry("type", "material")
ContainerRegistry.getInstance().addContainer(empty_material_container)
empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container.setMetaDataEntry("id", "empty_quality")
empty_quality_container.setName("Not Supported")
empty_quality_container.addMetaDataEntry("quality_type", "not_supported")
empty_quality_container.addMetaDataEntry("type", "quality")
empty_quality_container.addMetaDataEntry("supported", False)
ContainerRegistry.getInstance().addContainer(empty_quality_container)
empty_quality_changes_container = copy.deepcopy(empty_container)
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
class DefinitionContainerSubClass(DefinitionContainer): class DefinitionContainerSubClass(DefinitionContainer):
def __init__(self): def __init__(self):
super().__init__(container_id = "SubDefinitionContainer") super().__init__(container_id = "SubDefinitionContainer")

View File

@ -3,7 +3,9 @@
import pytest #This module contains unit tests. import pytest #This module contains unit tests.
import unittest.mock #To monkeypatch some mocks in place of dependencies. import unittest.mock #To monkeypatch some mocks in place of dependencies.
import copy
import cura.CuraApplication
import cura.Settings.GlobalStack #The module we're testing. import cura.Settings.GlobalStack #The module we're testing.
import cura.Settings.CuraContainerStack #To get the list of container types. import cura.Settings.CuraContainerStack #To get the list of container types.
from cura.Settings.Exceptions import TooManyExtrudersError, InvalidContainerError, InvalidOperationError #To test raising these errors. from cura.Settings.Exceptions import TooManyExtrudersError, InvalidContainerError, InvalidOperationError #To test raising these errors.
@ -13,6 +15,7 @@ from UM.Settings.SettingInstance import InstanceState
import UM.Settings.ContainerRegistry import UM.Settings.ContainerRegistry
import UM.Settings.ContainerStack import UM.Settings.ContainerStack
import UM.Settings.SettingDefinition #To add settings to the definition. import UM.Settings.SettingDefinition #To add settings to the definition.
from UM.Settings.ContainerRegistry import ContainerRegistry
## Fake container registry that always provides all containers you ask of. ## Fake container registry that always provides all containers you ask of.
@pytest.yield_fixture() @pytest.yield_fixture()
@ -33,6 +36,7 @@ def container_registry():
#An empty global stack to test with. #An empty global stack to test with.
@pytest.fixture() @pytest.fixture()
def global_stack() -> cura.Settings.GlobalStack.GlobalStack: def global_stack() -> cura.Settings.GlobalStack.GlobalStack:
creteEmptyContainers()
return cura.Settings.GlobalStack.GlobalStack("TestStack") return cura.Settings.GlobalStack.GlobalStack("TestStack")
## Gets an instance container with a specified container type. ## Gets an instance container with a specified container type.
@ -44,6 +48,31 @@ def getInstanceContainer(container_type) -> InstanceContainer:
container.addMetaDataEntry("type", container_type) container.addMetaDataEntry("type", container_type)
return container return container
def creteEmptyContainers():
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
empty_variant_container = copy.deepcopy(empty_container)
empty_variant_container.setMetaDataEntry("id", "empty_variant")
empty_variant_container.addMetaDataEntry("type", "variant")
ContainerRegistry.getInstance().addContainer(empty_variant_container)
empty_material_container = copy.deepcopy(empty_container)
empty_material_container.setMetaDataEntry("id", "empty_material")
empty_material_container.addMetaDataEntry("type", "material")
ContainerRegistry.getInstance().addContainer(empty_material_container)
empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container.setMetaDataEntry("id", "empty_quality")
empty_quality_container.setName("Not Supported")
empty_quality_container.addMetaDataEntry("quality_type", "not_supported")
empty_quality_container.addMetaDataEntry("type", "quality")
empty_quality_container.addMetaDataEntry("supported", False)
ContainerRegistry.getInstance().addContainer(empty_quality_container)
empty_quality_changes_container = copy.deepcopy(empty_container)
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
class DefinitionContainerSubClass(DefinitionContainer): class DefinitionContainerSubClass(DefinitionContainer):
def __init__(self): def __init__(self):
super().__init__(container_id = "SubDefinitionContainer") super().__init__(container_id = "SubDefinitionContainer")