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:
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()
if self._global_stack and self._node:

View File

@ -59,7 +59,7 @@ class CrashHandler:
self.data = dict()
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 part in line.rstrip("\n").split("\n"):
Logger.log("c", part)
@ -90,7 +90,7 @@ class CrashHandler:
def _messageWidget(self):
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>
"""))
@ -143,7 +143,7 @@ class CrashHandler:
def _exceptionInfoWidget(self):
group = QGroupBox()
group.setTitle(catalog.i18nc("@title:groupbox", "Exception traceback"))
group.setTitle(catalog.i18nc("@title:groupbox", "Error traceback"))
layout = QVBoxLayout()
text_area = QTextEdit()

View File

@ -127,7 +127,7 @@ class CuraApplication(QtApplication):
# Cura will always show the Add Machine Dialog upon start.
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
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
super().__init__(name = "cura", version = CuraVersion, buildtype = CuraBuildType,
super().__init__(name = "cura",
version = CuraVersion,
buildtype = CuraBuildType,
is_debug_mode = CuraDebugMode,
tray_icon_name = "cura-icon-32.png")
tray_icon_name = "cura-icon-32.png",
**kwargs)
self.default_theme = "cura-light"
@ -313,6 +316,7 @@ class CuraApplication(QtApplication):
preferences.addPreference("cura/material_settings", "{}")
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")
@ -399,7 +403,11 @@ class CuraApplication(QtApplication):
@pyqtSlot()
def closeApplication(self):
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
#
@ -507,11 +515,10 @@ class CuraApplication(QtApplication):
self._plugins_loaded = True
@classmethod
def addCommandLineOptions(self, parser):
super().addCommandLineOptions(parser)
def addCommandLineOptions(self, parser, parsed_command_line = {}):
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("--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.
def _setUpSingleInstanceServer(self):
@ -565,13 +572,16 @@ class CuraApplication(QtApplication):
# This should be called directly before creating an instance of CuraApplication.
# \returns \type{bool} True if the whole Cura app should continue running.
@classmethod
def preStartUp(cls):
def preStartUp(cls, parser = None, parsed_command_line = {}):
# Peek the arguments and look for the 'single-instance' flag.
parser = argparse.ArgumentParser(prog="cura") # pylint: disable=bad-whitespace
CuraApplication.addCommandLineOptions(parser)
parsed_command_line = vars(parser.parse_args())
if not parser:
parser = argparse.ArgumentParser(prog = "cura", add_help = False) # pylint: disable=bad-whitespace
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.")
single_instance_socket = QLocalSocket()
Logger.log("d", "preStartUp(): full server name: " + single_instance_socket.fullServerName())
@ -603,7 +613,22 @@ class CuraApplication(QtApplication):
return False
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):
self.preRun()
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
self._setUpSingleInstanceServer()
@ -658,12 +683,12 @@ class CuraApplication(QtApplication):
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
run_headless = self.getCommandLineOption("headless", False)
if not run_headless:
run_without_gui = self.getCommandLineOption("headless", False) or self.getCommandLineOption("invisible", False)
if not run_without_gui:
self.initializeEngine()
controller.setActiveStage("PrepareStage")
if run_headless or self._engine.rootObjects:
if run_without_gui or self._engine.rootObjects:
self.closeSplash()
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 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."),
title=self._i18n_catalog.i18nc("@info:title", "Warning")).show()
title=self._i18n_catalog.i18nc("@info:title", "Warning")
).show()
return
# 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 unicodedata
import json
import re #To create abbreviations for printer names.
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -316,15 +317,14 @@ class PrintInformation(QObject):
return
global_stack_name = global_container_stack.getName()
split_name = global_stack_name.split(" ")
abbr_machine = ""
for word in split_name:
for word in re.findall(r"[\w']+", global_stack_name):
if word.lower() == "ultimaker":
abbr_machine += "UM"
elif word.isdigit():
abbr_machine += word
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 the whole word if it's not too long (<= 3 characters)
if len(stripped_word) > 3:

View File

@ -36,6 +36,11 @@ class CuraContainerRegistry(ContainerRegistry):
def __init__(self, *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
#
# Adds a container to the registry.
@ -410,6 +415,17 @@ class CuraContainerRegistry(ContainerRegistry):
if not extruder_stacks:
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):
new_extruder_id = extruder_id
@ -425,7 +441,6 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_stack.setName(extruder_definition.getName())
extruder_stack.setDefinition(extruder_definition)
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
extruder_stack.setNextStack(machine)
# create empty user changes container otherwise
user_container = InstanceContainer(extruder_stack.id + "_user")
@ -433,7 +448,7 @@ class CuraContainerRegistry(ContainerRegistry):
user_container.addMetaDataEntry("machine", extruder_stack.getId())
from cura.CuraApplication import CuraApplication
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_container.setDefinition(machine.definition)
user_container.setDefinition(machine.definition.getId())
if machine.userChanges:
# 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))
machine.userChanges.removeInstance(user_setting_key, postpone_emit = True)
extruder_stack.setUserChanges(user_container)
self.addContainer(user_container)
extruder_stack.setUserChanges(user_container)
variant_id = "default"
if machine.variant.getId() not in ("empty", "empty_variant"):
@ -491,6 +506,9 @@ class CuraContainerRegistry(ContainerRegistry):
self.addContainer(extruder_stack)
# Set next stack at the end
extruder_stack.setNextStack(machine)
return extruder_stack
def _findQualityChangesContainerInCuraFolder(self, name):

View File

@ -377,7 +377,7 @@ class CuraContainerStack(ContainerStack):
if not container or not isinstance(container, DefinitionContainer):
definition = self.findContainer(container_type = DefinitionContainer)
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
continue
@ -508,7 +508,7 @@ class CuraContainerStack(ContainerStack):
def findDefaultQuality(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition()
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"}
@ -552,7 +552,7 @@ class CuraContainerStack(ContainerStack):
material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
if definition.getMetaDataEntry("has_machine_quality"):
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"):
material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")

View File

@ -32,6 +32,7 @@ from .CuraStackBuilder import CuraStackBuilder
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
from cura.Settings.ProfilesModel import ProfilesModel
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
@ -60,9 +61,11 @@ class MachineManager(QObject):
self._instance_container_timer = QTimer()
self._instance_container_timer.setInterval(250)
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().getContainerRegistry().containerLoadComplete.connect(self._onInstanceContainersChanged)
self._connected_to_profiles_model = False
## When the global container is changed, active material probably needs to be updated.
self.globalContainerChanged.connect(self.activeMaterialChanged)
@ -333,14 +336,24 @@ class MachineManager(QObject):
# on _active_container_stack. If it changes, then the properties change.
self.activeQualityChanged.emit()
def __onInstanceContainersChanged(self):
def __emitChangedSignals(self):
self.activeQualityChanged.emit()
self.activeVariantChanged.emit()
self.activeMaterialChanged.emit()
self._updateStacksHaveErrors() # Prevents unwanted re-slices after changing machine
self._error_check_timer.start()
def _onProfilesModelChanged(self, *args):
self.__emitChangedSignals()
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()
def _onPropertyChanged(self, key: str, property_name: str):
@ -356,11 +369,13 @@ class MachineManager(QObject):
self.blurSettings.emit() # Ensure no-one has focus.
self._cancelDelayedActiveContainerStackChanges()
containers = ContainerRegistry.getInstance().findContainerStacks(id = stack_id)
container_registry = ContainerRegistry.getInstance()
containers = container_registry.findContainerStacks(id = stack_id)
if containers:
Application.getInstance().setGlobalContainerStack(containers[0])
self.__onInstanceContainersChanged()
self.__emitChangedSignals()
@pyqtSlot(str, str)
def addMachine(self, name: str, definition_id: str) -> None:

View File

@ -49,6 +49,10 @@ class ProfilesModel(InstanceContainersModel):
ProfilesModel.__instance = cls()
return ProfilesModel.__instance
@classmethod
def hasInstance(cls) -> bool:
return ProfilesModel.__instance is not None
__instance = None # type: "ProfilesModel"
## 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.
def _recomputeItems(self):
# Some globals that we can re-use.
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None:

View File

@ -2,13 +2,40 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import argparse
import os
import sys
import platform
import faulthandler
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
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
@ -47,8 +74,8 @@ def exceptHook(hook_type, value, traceback):
_crash_handler = CrashHandler(hook_type, value, traceback)
_crash_handler.show()
sys.excepthook = exceptHook
if not known_args["debug"]:
sys.excepthook = exceptHook
# Workaround for a race condition on certain systems where there
# is a race condition between Arcus and PyQt. Importing Arcus
@ -58,29 +85,14 @@ import Arcus #@UnusedImport
import cura.CuraApplication
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()
# Force an instance of CuraContainerRegistry to be created and reused later.
cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance()
# 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)
app = cura.CuraApplication.CuraApplication.getInstance()
app = cura.CuraApplication.CuraApplication.getInstance(parser = parser, parsed_command_line = known_args)
app.run()

View File

@ -86,6 +86,7 @@ class CuraEngineBackend(QObject, Backend):
#
self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
Application.getInstance().getExtruderManager().activeExtruderChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged()
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")
else:
temp_list = []
has_printing_mesh = False
for node in DepthFirstIterator(self._scene.getRoot()):
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)
if not _non_printing_mesh:
has_printing_mesh = True
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:
object_groups.append(temp_list)
@ -209,7 +218,7 @@ class StartSliceJob(Job):
result[key] = stack.getProperty(key, "value")
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["time"] = time.strftime("%H:%M:%S") #Some extra settings.
result["date"] = time.strftime("%d-%m-%Y")
@ -237,10 +246,10 @@ class StartSliceJob(Job):
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", "")
#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_end_code"] = self._expandGcodeTokens(settings["machine_extruder_end_code"], settings)
@ -260,18 +269,23 @@ class StartSliceJob(Job):
def _buildGlobalSettingsMessage(self, stack):
settings = self._buildReplacementTokens(stack)
# Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
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"}
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"}
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.
settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], settings)
settings["machine_end_gcode"] = self._expandGcodeTokens(settings["machine_end_gcode"], settings)
# Find the correct temperatures from the first used extruder
extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
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.name = key
setting_message.value = str(value).encode("utf-8")

View File

@ -92,7 +92,7 @@ class SimulationPass(RenderPass):
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
nozzle_node = None
@ -149,7 +149,7 @@ class SimulationPass(RenderPass):
self._current_shader = self._layer_shader
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.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_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();
} else {
// 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[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_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));
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();

View File

@ -99,7 +99,9 @@ class SliceInfo(Extension):
"type": extruder.material.getMetaData().get("material", ""),
"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["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
# authentication requests.
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.
self.addPrinterSignal.connect(self.addPrinter)
@ -300,6 +301,9 @@ class NetworkPrinterOutputDevicePlugin(QObject, OutputDevicePlugin):
if type_of_device:
if type_of_device == b"printer":
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)
else:
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"):
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
parser["general"]["version"] = "2"
parser["metadata"]["setting_version"] = "4"
@ -200,6 +215,133 @@ class VersionUpgrade30to31(VersionUpgrade):
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):
suffix = "_" + quote_plus(global_quality_changes["general"]["name"].lower())
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():
definition = container.getDefinition()
try:
product = UM.Dictionary.findKey(product_id_map, definition_id)
except ValueError:
# An unknown product id; export it anyway
product = definition_id
product = definition_id
for product_name, product_id_list in product_id_map.items():
if definition_id in product_id_list:
product = product_name
break
builder.start("machine")
builder.start("machine_identifier", {
@ -530,106 +531,112 @@ class XmlMaterialProfile(InstanceContainer):
identifiers = machine.iterfind("./um:machine_identifier", self.__namespaces)
for identifier in identifiers:
machine_id = product_id_map.get(identifier.get("product"), None)
if machine_id is None:
# Lets try again with some naive heuristics.
machine_id = identifier.get("product").replace(" ", "").lower()
machine_id_list = product_id_map.get(identifier.get("product"), [])
if not machine_id_list:
machine_id_list = self.getPossibleDefinitionIDsFromName(identifier.get("product"))
definitions = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
if not definitions:
Logger.log("w", "No definition found for machine ID %s", machine_id)
continue
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:
for machine_id in machine_id_list:
definitions = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
if not definitions:
Logger.log("w", "No definition found for machine ID %s", machine_id)
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 = definition["id"], name = hotend_id)
Logger.log("d", "Found definition for machine ID %s", machine_id)
definition = definitions[0]
if not variant_containers:
continue
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.
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)
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:
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
# 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_material.setCachedValues(cached_machine_setting_properties)
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"]
# 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
new_material._dirty = False
cached_hotend_setting_properties = cached_machine_setting_properties.copy()
cached_hotend_setting_properties.update(hotend_setting_values)
if is_new_material:
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:
containers_to_add.append(new_hotend_material)
if not variant_containers:
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:
ContainerRegistry.getInstance().addContainer(container_to_add)
@ -720,79 +727,86 @@ class XmlMaterialProfile(InstanceContainer):
machine_compatibility = cls._parseCompatibleValue(entry.text)
for identifier in machine.iterfind("./um:machine_identifier", cls.__namespaces):
machine_id = product_id_map.get(identifier.get("product"), None)
if machine_id is None:
# Lets try again with some naive heuristics.
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_id_list = product_id_map.get(identifier.get("product"), [])
if not machine_id_list:
machine_id_list = cls.getPossibleDefinitionIDsFromName(identifier.get("product"))
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.
if machine_compatibility:
new_material_id = container_id + "_" + 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:
for machine_id in machine_id_list:
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
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)
Logger.log("d", "Found def for machine [%s]", machine_id)
definition_metadata = definition_metadata[0]
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)
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.
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
# 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 = {}
# 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_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
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:
result_metadata.append(new_hotend_material_metadata)
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
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
@ -813,15 +827,41 @@ class XmlMaterialProfile(InstanceContainer):
else:
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
# IDs.
#
# This loads the mapping from a file.
@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")
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.
@classmethod

View File

@ -46,7 +46,7 @@
"default_value": "RepRap (Marlin/Sprinter)"
},
"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": {
"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 homeCamera: homeCameraAction;
property alias expandSidebar: expandSidebarAction;
property alias deleteSelection: deleteSelectionAction;
property alias centerSelection: centerSelectionAction;
@ -389,4 +390,11 @@ Item
text: catalog.i18nc("@action:menu", "Installed plugins...");
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:
{
CuraApplication.setMinimumWindowSize(UM.Theme.getSize("window_minimum_size"))
@ -369,16 +387,59 @@ UM.MainWindow
{
id: sidebar
anchors
{
top: topbar.bottom
bottom: parent.bottom
right: parent.right
property bool collapsed: false;
property var initialWidth: UM.Theme.getSize("sidebar").width;
function callExpandOrCollapse() {
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
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
@ -417,6 +478,13 @@ UM.MainWindow
}
}
// Expand or collapse sidebar
Connections
{
target: Cura.Actions.expandSidebar
onTriggered: sidebar.callExpandOrCollapse()
}
UM.PreferencesDialog
{
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 {}
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;
UM.I18nCatalog { id: catalog; name:"cura"}
property real progress: UM.Backend.progress;
property int backendState: UM.Backend.state;
property var backend: CuraApplication.getBackend();
property bool activity: CuraApplication.platformActivity;
property real progress: UM.Backend.progress
property int backendState: UM.Backend.state
property bool activity: CuraApplication.platformActivity
property alias buttonRowWidth: saveRow.width
@ -50,10 +48,14 @@ Item {
}
function sliceOrStopSlicing() {
if (backend != "undefined" && [1, 5].indexOf(UM.Backend.state) != -1) {
backend.forceSlice();
} else {
backend.stopSlicing();
try {
if ([1, 5].indexOf(base.backendState) != -1) {
CuraApplication.backend.forceSlice();
} else {
CuraApplication.backend.stopSlicing();
}
} catch (e) {
console.log('Could not start or stop slicing', e)
}
}
@ -166,7 +168,7 @@ Item {
Button {
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
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
@ -178,7 +180,7 @@ Item {
anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
// 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:
{
sliceOrStopSlicing();

View File

@ -14,11 +14,13 @@ import "."
Item {
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 var resetHandler: false
property var showRevertButton: true
property var showInheritButton: true
property var showLinkedSettingIcon: true
@ -179,8 +181,13 @@ Item {
iconSource: UM.Theme.getIcon("reset")
onClicked: {
revertButton.focus = true;
Cura.MachineManager.clearUserSettingAllCurrentStacks(propertyProvider.key);
revertButton.focus = true
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.")) }

View File

@ -22,7 +22,6 @@ Rectangle
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
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"
@ -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 {
id: header
width: parent.width
visible: machineExtruderCount.properties.value > 1 || Cura.MachineManager.hasMaterials || Cura.MachineManager.hasVariants
anchors.top: machineSelection.bottom
onShowTooltip: base.showTooltip(item, location, text)
onHideTooltip: base.hideTooltip()
@ -263,7 +271,7 @@ Rectangle
{
id: controlItem
anchors.bottom: footerSeparator.top
anchors.top: monitoringPrint ? base.top : headerSeparator.bottom
anchors.top: monitoringPrint ? machineSelection.bottom : headerSeparator.bottom
anchors.left: base.left
anchors.right: base.right
sourceComponent:
@ -282,7 +290,7 @@ Rectangle
Loader
{
anchors.bottom: footerSeparator.top
anchors.top: monitoringPrint ? base.top : headerSeparator.bottom
anchors.top: monitoringPrint ? machineSelection.bottom : headerSeparator.bottom
anchors.left: base.left
anchors.right: base.right
source:

View File

@ -21,6 +21,22 @@ Rectangle
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
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
{
id: catalog
@ -44,10 +60,9 @@ Rectangle
Row
{
id: stagesMenuContainer
anchors.left: logo.right
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
// The topbar is dynamically filled with all available stages
@ -76,71 +91,6 @@ Rectangle
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
Row
{
@ -152,8 +102,8 @@ Rectangle
anchors
{
verticalCenter: base.verticalCenter
right: viewModeButton.right
rightMargin: UM.Theme.getSize("default_margin").width + viewModeButton.width
right: viewModeButton.left
rightMargin: UM.Theme.getSize("default_margin").width
}
// #1 3d view
@ -165,7 +115,7 @@ Rectangle
onClicked:{
UM.Controller.rotateView("3d", 0);
}
visible: base.width > 1100
visible: base.width - allItemsWidth - 4 * this.width > 0;
}
// #2 Front view
@ -177,7 +127,7 @@ Rectangle
onClicked:{
UM.Controller.rotateView("home", 0);
}
visible: base.width > 1130
visible: base.width - allItemsWidth - 3 * this.width > 0;
}
// #3 Top view
@ -189,7 +139,7 @@ Rectangle
onClicked:{
UM.Controller.rotateView("y", 90);
}
visible: base.width > 1160
visible: base.width - allItemsWidth - 2 * this.width > 0;
}
// #4 Left view
@ -201,7 +151,7 @@ Rectangle
onClicked:{
UM.Controller.rotateView("x", 90);
}
visible: base.width > 1190
visible: base.width - allItemsWidth - 1 * this.width > 0;
}
// #5 Left view
@ -213,7 +163,7 @@ Rectangle
onClicked:{
UM.Controller.rotateView("x", -90);
}
visible: base.width > 1210
visible: base.width - allItemsWidth > 0;
}
}
@ -224,7 +174,7 @@ Rectangle
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: UM.Theme.getSize("sidebar").width + UM.Theme.getSize("default_margin").width
rightMargin: rightMargin
}
style: UM.Theme.styles.combobox
@ -284,4 +234,16 @@ Rectangle
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 unittest.mock #To mock and monkeypatch stuff.
import urllib.parse
import copy
import cura.CuraApplication
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.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.ContainerStack #Setting the container registry here properly.
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.
@pytest.fixture()
@ -37,6 +65,7 @@ def teardown():
## Tests whether addContainer properly converts to ExtruderStack.
def test_addContainerExtruderStack(container_registry, definition_container):
creteEmptyContainers()
container_registry.addContainer(definition_container)
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!
## Tests whether loading gives objects of the correct type.
@pytest.mark.parametrize("filename, output_class", [
("ExtruderLegacy.stack.cfg", ExtruderStack),
("MachineLegacy.stack.cfg", GlobalStack),
("Left.extruder.cfg", ExtruderStack),
("Global.global.cfg", GlobalStack),
("Global.stack.cfg", GlobalStack)
])
def test_loadTypes(filename, output_class, container_registry):
#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.
def findContainers(container_type = 0, id = None):
if id == "some_instance":
return [UM.Settings.ContainerRegistry._EmptyInstanceContainer(id)]
elif id == "some_definition":
return [DefinitionContainer(container_id = id)]
else:
return []
container_registry.findContainers = findContainers
with unittest.mock.patch("cura.Settings.GlobalStack.GlobalStack.findContainer"):
with unittest.mock.patch("os.remove"):
container_registry.load()
#Check whether the resulting type was correct.
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.
if container_id == stack_id: #This is the one we're testing.
assert type(container) == output_class
break
else:
assert False #Container stack with specified ID was not loaded.
# @pytest.mark.parametrize("filename, output_class", [
# ("ExtruderLegacy.stack.cfg", ExtruderStack),
# ("MachineLegacy.stack.cfg", GlobalStack),
# ("Left.extruder.cfg", ExtruderStack),
# ("Global.global.cfg", GlobalStack),
# ("Global.stack.cfg", GlobalStack)
# ])
# def test_loadTypes(filename, output_class, container_registry):
# #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.
#
# def findContainers(container_type = 0, id = None):
# if id == "some_instance":
# return [UM.Settings.ContainerRegistry._EmptyInstanceContainer(id)]
# elif id == "some_definition":
# return [DefinitionContainer(container_id = id)]
# else:
# return []
#
# container_registry.findContainers = findContainers
#
# with unittest.mock.patch("cura.Settings.GlobalStack.GlobalStack.findContainer"):
# with unittest.mock.patch("os.remove"):
# container_registry.load()
#
# #Check whether the resulting type was correct.
# 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.
# if container_id == stack_id: #This is the one we're testing.
# assert type(container) == output_class
# break
# else:
# assert False #Container stack with specified ID was not loaded.

View File

@ -3,7 +3,9 @@
import pytest #This module contains automated tests.
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.ContainerStack #To set the container registry the container stacks use.
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.ExtruderManager import ExtruderManager
from UM.Settings.ContainerRegistry import ContainerRegistry
## Fake container registry that always provides all containers you ask of.
@pytest.yield_fixture()
@ -32,6 +35,7 @@ def container_registry():
## An empty extruder stack to test with.
@pytest.fixture()
def extruder_stack() -> cura.Settings.ExtruderStack.ExtruderStack:
creteEmptyContainers()
return cura.Settings.ExtruderStack.ExtruderStack("TestStack")
## Gets an instance container with a specified container type.
@ -43,6 +47,31 @@ def getInstanceContainer(container_type) -> InstanceContainer:
container.addMetaDataEntry("type", container_type)
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):
def __init__(self):
super().__init__(container_id = "SubDefinitionContainer")

View File

@ -3,7 +3,9 @@
import pytest #This module contains unit tests.
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.CuraContainerStack #To get the list of container types.
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.ContainerStack
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.
@pytest.yield_fixture()
@ -33,6 +36,7 @@ def container_registry():
#An empty global stack to test with.
@pytest.fixture()
def global_stack() -> cura.Settings.GlobalStack.GlobalStack:
creteEmptyContainers()
return cura.Settings.GlobalStack.GlobalStack("TestStack")
## Gets an instance container with a specified container type.
@ -44,6 +48,31 @@ def getInstanceContainer(container_type) -> InstanceContainer:
container.addMetaDataEntry("type", container_type)
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):
def __init__(self):
super().__init__(container_id = "SubDefinitionContainer")