diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..7bdf35455c --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,32 @@ + + +Application Version: + + +Platform: + + +Qt: + + +PyQt: + + +Display Driver: + + +Steps to Reproduce: + + +Actual Results: + + +Expected results: + + +Additional Information: + diff --git a/.gitignore b/.gitignore index 8d9ba4b2d2..88c4d5b6eb 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ plugins/CuraVariSlicePlugin plugins/CuraLiveScriptingPlugin plugins/CuraPrintProfileCreator plugins/OctoPrintPlugin +plugins/CuraCloudPlugin #Build stuff CMakeCache.txt diff --git a/README.md b/README.md index 90f17a5463..6ea839a300 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,16 @@ Cura ==== - -This is the new, shiny frontend for Cura. [daid/Cura](https://github.com/daid/Cura.git) is the old legacy Cura that everyone knows and loves/hates. - -We re-worked the whole GUI code at Ultimaker, because the old code started to become unmaintainable. - +This is the new, shiny frontend for Cura. Check [daid/LegacyCura](https://github.com/daid/LegacyCura) for the legacy Cura that everyone knows and loves/hates. We re-worked the whole GUI code at Ultimaker, because the old code started to become unmaintainable. Logging Issues ------------ -Use [this](https://github.com/Ultimaker/Uranium/wiki/Bug-Reporting-Template) template to report issues. New issues that do not adhere to this template will take us a lot longer to handle and will therefore have a lower pirority. - For crashes and similar issues, please attach the following information: * (On Windows) The log as produced by dxdiag (start -> run -> dxdiag -> save output) * The Cura GUI log file, located at - * %APPDATA%\cura\\``\cura.log (Windows), or usually C:\Users\\``\AppData\Roaming\cura\\``\cura.log - * $User/Library/Application Support/cura/``/cura.log (OSX) - * $USER/.local/share/cura/``/cura.log (Ubuntu/Linux) + * `%APPDATA%\cura\\cura.log` (Windows), or usually `C:\Users\\\AppData\Roaming\cura\\cura.log` + * `$USER/Library/Application Support/cura//cura.log` (OSX) + * `$USER/.local/share/cura//cura.log` (Ubuntu/Linux) If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder @@ -24,53 +18,26 @@ For additional support, you could also ask in the #cura channel on FreeNode IRC. Dependencies ------------ - -* [Uranium](https://github.com/Ultimaker/Uranium) - Cura is built on top of the Uranium framework. -* [CuraEngine](https://github.com/Ultimaker/CuraEngine) - This will be needed at runtime to perform the actual slicing. -* [PySerial](https://github.com/pyserial/pyserial) - Only required for USB printing support. -* [python-zeroconf](https://github.com/jstasiak/python-zeroconf) - Only required to detect mDNS-enabled printers - -Configuring Cura ----------------- -Link your CuraEngine backend by inserting the following lines in `$HOME/.config/cura/config.cfg` : -``` -[backend] -location = /[path_to_the..]/CuraEngine/build/CuraEngine -``` +* [Uranium](https://github.com/Ultimaker/Uranium) Cura is built on top of the Uranium framework. +* [CuraEngine](https://github.com/Ultimaker/CuraEngine) This will be needed at runtime to perform the actual slicing. +* [PySerial](https://github.com/pyserial/pyserial) Only required for USB printing support. +* [python-zeroconf](https://github.com/jstasiak/python-zeroconf) Only required to detect mDNS-enabled printers Build scripts ------------- +Please checkout [cura-build](https://github.com/Ultimaker/cura-build) for detailed building instructions. -Please checkout [cura-build](https://github.com/Ultimaker/cura-build) - -Third party plugins +Plugins ------------- -* [Post Processing Plugin](https://github.com/nallath/PostProcessingPlugin): Allows for post-processing scripts to run on g-code. -* [Barbarian Plugin](https://github.com/nallath/BarbarianPlugin): Simple scale tool for imperial to metric. -* [X3G Writer](https://github.com/Ghostkeeper/X3GWriter): Adds support for exporting X3G files. -* [Auto orientation](https://github.com/nallath/CuraOrientationPlugin): Calculate the optimal orientation for a model. -* [OctoPrint Plugin](https://github.com/fieldofview/OctoPrintPlugin): Send printjobs directly to OctoPrint and monitor their progress in Cura. -* [Electric Print Cost Calculator Plugin](https://github.com/zoff99/ElectricPrintCostCalculator): Calculate the electric costs of a print. +Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Plugin-Directory) for details about creating and using plugins. -Making profiles for other printers ----------------------------------- -If your make of printer is not in the list of supported printers, and using the "Custom FDM Printer" does not offer enough flexibility, you can use [this](https://github.com/Ultimaker/Cura/blob/master/resources/definitions/ultimaker_original.def.json) as a template. +Supported printers +------------- +Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Adding-new-machine-profiles-to-Cura) for guidelines about adding support for new machines. -* Change the machine ID to something unique -* Change the machine_name to your printer's name -* If you have a 3D model of your platform you can put it in resources/meshes and put its name under platform -* Set your machine's dimensions with machine_width, machine_depth, and machine_height -* If your printer's origin is in the center of the bed, set machine_center_is_zero to true. -* Set your print head dimensions with the machine_head_shape parameters -* Set the start and end gcode in machine_start_gcode and machine_end_gcode - -Once you are done, put the profile you have made into resources/definitions, or in definitions in your cura profile folder. - -If you want to make a definition for a multi-extrusion printer, have a look at [this](https://github.com/Ultimaker/Cura/blob/master/resources/definitions/ultimaker_original_dual.def.json) as a template, along with the two extruder definitions it references [here](https://github.com/Ultimaker/Cura/blob/master/resources/extruders/ultimaker_original_dual_1st.def.json) and [here](https://github.com/Ultimaker/Cura/blob/master/resources/extruders/ultimaker_original_dual_2nd.def.json) +Configuring Cura +---------------- +Please check out [Wiki page](https://github.com/Ultimaker/Cura/wiki/Cura-Settings) about configuration options for developers. Translating Cura ---------------- @@ -93,3 +60,7 @@ To submit your translation, ideally you would make two pull requests where all ` After the translation is submitted, the Cura maintainers will check for its completeness and check whether it is consistent. We will take special care to look for common mistakes, such as translating mark-up `` code and such. We are often not fluent in every language, so we expect the translator and the international users to make corrections where necessary. Of course, there will always be some mistakes in every translation. When the next Cura release comes around, some of the texts will have changed and some new texts will have been added. Around the time when the beta is released we will invoke a string freeze, meaning that no developer is allowed to make changes to the texts. Then we will update the translation template `.pot` files and ask all our translators to update their translations. If you are unable to update the translation in time for the actual release, we will remove the language from the drop-down menu in the Preferences window. The translation stays in Cura however, so that someone might pick it up again later and update it with the newest texts. Also, users who had previously selected the language can still continue Cura in their language but English text will appear among the original text. + +License +---------------- +Cura is released under the terms of the LGPLv3 or higher. A copy of this license should be included with the software. diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 81eca67a46..b5d4001fe2 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -37,7 +37,7 @@ else: # List of exceptions that should be considered "fatal" and abort the program. # These are primarily some exception types that we simply cannot really recover from # (MemoryError and SystemError) and exceptions that indicate grave errors in the -# code that cause the Python interpreter to fail (SyntaxError, ImportError). +# code that cause the Python interpreter to fail (SyntaxError, ImportError). fatal_exception_types = [ MemoryError, SyntaxError, @@ -53,7 +53,7 @@ class CrashHandler: self.exception_type = exception_type self.value = value self.traceback = tb - self.dialog = QDialog() + self.dialog = None # Don't create a QDialog before there is a QApplication # While we create the GUI, the information will be stored for sending afterwards self.data = dict() @@ -71,6 +71,7 @@ class CrashHandler: if not application: sys.exit(1) + self.dialog = QDialog() self._createDialog() ## Creates a modal dialog. @@ -288,4 +289,4 @@ class CrashHandler: # When the exception is not in the fatal_exception_types list, the dialog is not created, so we don't need to show it if self.dialog: self.dialog.exec_() - os._exit(1) + os._exit(1) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index c4e1c416dd..18d1bde57e 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -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" @@ -400,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 # @@ -508,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): @@ -566,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()) @@ -604,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() @@ -659,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", []): @@ -1362,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 diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index ca5433f305..5e64413d27 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -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: diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index bbabdadbde..209e1ec8fd 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -514,7 +514,7 @@ class ContainerManager(QObject): for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): # Find the quality_changes container for this stack and merge the contents of the top container into it. quality_changes = stack.qualityChanges - if not quality_changes or quality_changes.isReadOnly(): + if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()): Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId()) continue @@ -687,7 +687,7 @@ class ContainerManager(QObject): global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack or not quality_name: return "" - machine_definition = global_stack.getBottom() + machine_definition = global_stack.definition active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() if active_stacks is None: diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 07eb357f39..43e2e072b0 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -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): diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index 61c28df570..8e13b24358 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -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 diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index a54eb987ae..e78c0b9d97 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -368,7 +368,9 @@ 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]) diff --git a/cura_app.py b/cura_app.py index 7583dd054e..b5844055ab 100755 --- a/cura_app.py +++ b/cura_app.py @@ -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() diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 3c1998094c..efa0e87b9e 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -218,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") @@ -246,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) @@ -269,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") diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml index 65b67791c7..ea126bfd44 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml @@ -145,6 +145,7 @@ Item { property var settingDefinitionsModel: addedSettingsModel property var propertyProvider: provider property var globalPropertyProvider: inheritStackProvider + property var externalResetHandler: false //Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989 //In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes, diff --git a/plugins/SimulationView/SimulationPass.py b/plugins/SimulationView/SimulationPass.py index 46fa7f1240..24bdedd368 100644 --- a/plugins/SimulationView/SimulationPass.py +++ b/plugins/SimulationView/SimulationPass.py @@ -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()) diff --git a/plugins/SimulationView/layers3d.shader b/plugins/SimulationView/layers3d.shader index 2633b54787..86a88fab83 100644 --- a/plugins/SimulationView/layers3d.shader +++ b/plugins/SimulationView/layers3d.shader @@ -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(); diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py index 67f977adce..508d174cf2 100755 --- a/plugins/SliceInfoPlugin/SliceInfo.py +++ b/plugins/SliceInfoPlugin/SliceInfo.py @@ -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") diff --git a/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py b/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py index 8c5a160ff4..c01ff158b1 100644 --- a/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py +++ b/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py @@ -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, "") diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index c31a60c778..125fe1e344 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -737,7 +737,6 @@ class XmlMaterialProfile(InstanceContainer): Logger.log("w", "No definition found for machine ID %s", machine_id) continue - Logger.log("d", "Found def for machine [%s]", machine_id) 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. diff --git a/resources/definitions/innovo_inventor.def.json b/resources/definitions/innovo_inventor.def.json index 84b6697d34..5d587f9b30 100644 --- a/resources/definitions/innovo_inventor.def.json +++ b/resources/definitions/innovo_inventor.def.json @@ -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" diff --git a/resources/qml/MachineSelection.qml b/resources/qml/MachineSelection.qml new file mode 100644 index 0000000000..e40731f3ca --- /dev/null +++ b/resources/qml/MachineSelection.qml @@ -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 { } +} diff --git a/resources/qml/PrintMonitor.qml b/resources/qml/PrintMonitor.qml index b3c36f7fd4..6fc4d8847d 100644 --- a/resources/qml/PrintMonitor.qml +++ b/resources/qml/PrintMonitor.qml @@ -141,4 +141,4 @@ Column } width: base.width } -} \ No newline at end of file +} diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml index d53b43e459..c5025dea78 100644 --- a/resources/qml/SaveButton.qml +++ b/resources/qml/SaveButton.qml @@ -14,7 +14,6 @@ Item { property real progress: UM.Backend.progress property int backendState: UM.Backend.state - property var backend: CuraApplication.getBackend() property bool activity: CuraApplication.platformActivity property alias buttonRowWidth: saveRow.width @@ -49,12 +48,14 @@ Item { } function sliceOrStopSlicing() { - if (base.backendState != "undefined" && backend !== "undefined") { + try { if ([1, 5].indexOf(base.backendState) != -1) { - backend.forceSlice(); + CuraApplication.backend.forceSlice(); } else { - backend.stopSlicing(); + CuraApplication.backend.stopSlicing(); } + } catch (e) { + console.log('Could not start or stop slicing', e) } } diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml index 6234e5f1f7..1fa83cb6d4 100644 --- a/resources/qml/Settings/SettingItem.qml +++ b/resources/qml/Settings/SettingItem.qml @@ -14,9 +14,9 @@ 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 showRevertButton: true @@ -179,8 +179,13 @@ Item { iconSource: UM.Theme.getIcon("reset") onClicked: { - revertButton.focus = true; - Cura.MachineManager.clearUserSettingAllCurrentStacks(propertyProvider.key); + revertButton.focus = true + + if (externalResetHandler) { + externalResetHandler(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.")) } diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 1ebb5cc40e..5d39572647 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -287,6 +287,7 @@ Item property var settingDefinitionsModel: definitionsModel property var propertyProvider: provider property var globalPropertyProvider: inheritStackProvider + property var externalResetHandler: false //Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989 //In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes, diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index a45303aab8..db7851a101 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -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,70 +86,12 @@ Rectangle } } - ToolButton - { + MachineSelection { id: machineSelection - text: Cura.MachineManager.activeMachineName - width: base.width height: UM.Theme.getSize("sidebar_header").height - tooltip: Cura.MachineManager.activeMachineName - anchors.top: base.top - //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 { } } SidebarHeader { @@ -330,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: @@ -349,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: diff --git a/resources/quality/ultimaker3/um3_aa0.4_BAM_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_BAM_Normal_Quality.inst.cfg new file mode 100644 index 0000000000..3ced3a0417 --- /dev/null +++ b/resources/quality/ultimaker3/um3_aa0.4_BAM_Normal_Quality.inst.cfg @@ -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 diff --git a/tests/Settings/TestCuraContainerRegistry.py b/tests/Settings/TestCuraContainerRegistry.py index e6dc6b99d8..9e5692b565 100644 --- a/tests/Settings/TestCuraContainerRegistry.py +++ b/tests/Settings/TestCuraContainerRegistry.py @@ -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. \ No newline at end of file +# @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. \ No newline at end of file diff --git a/tests/Settings/TestExtruderStack.py b/tests/Settings/TestExtruderStack.py index 6ed2c6649b..2a8d19b80f 100644 --- a/tests/Settings/TestExtruderStack.py +++ b/tests/Settings/TestExtruderStack.py @@ -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") diff --git a/tests/Settings/TestGlobalStack.py b/tests/Settings/TestGlobalStack.py index afd3d2b425..9b0735c8f2 100755 --- a/tests/Settings/TestGlobalStack.py +++ b/tests/Settings/TestGlobalStack.py @@ -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")