diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..b78b9b91a2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,36 @@ + + +**Application Version** + + +**Platform** + + +**Qt** + + +**PyQt** + + +**Display Driver** + + +**Steps to Reproduce** + + +**Actual Results** + + +**Expected results** + + +**Additional Information** + diff --git a/.gitignore b/.gitignore index 88c4d5b6eb..71e83433cf 100644 --- a/.gitignore +++ b/.gitignore @@ -33,21 +33,23 @@ cura.desktop .settings #Externally located plug-ins. -plugins/CuraSolidWorksPlugin -plugins/Doodle3D-cura-plugin -plugins/GodMode -plugins/PostProcessingPlugin -plugins/X3GWriter -plugins/FlatProfileExporter -plugins/ProfileFlattener -plugins/cura-god-mode-plugin plugins/cura-big-flame-graph +plugins/cura-god-mode-plugin plugins/cura-siemensnx-plugin -plugins/CuraVariSlicePlugin -plugins/CuraLiveScriptingPlugin -plugins/CuraPrintProfileCreator -plugins/OctoPrintPlugin +plugins/CuraBlenderPlugin plugins/CuraCloudPlugin +plugins/CuraLiveScriptingPlugin +plugins/CuraOpenSCADPlugin +plugins/CuraPrintProfileCreator +plugins/CuraSolidWorksPlugin +plugins/CuraVariSlicePlugin +plugins/Doodle3D-cura-plugin +plugins/FlatProfileExporter +plugins/GodMode +plugins/OctoPrintPlugin +plugins/PostProcessingPlugin +plugins/ProfileFlattener +plugins/X3GWriter #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/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 43e2e072b0..089d513071 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -218,25 +218,6 @@ class CuraContainerRegistry(ContainerRegistry): if type(profile_or_list) is not list: profile_or_list = [profile_or_list] - if len(profile_or_list) == 1: - # If there is only 1 stack file it means we're loading a legacy (pre-3.1) .curaprofile. - # In that case we find the per-extruder settings and put those in a new quality_changes container - # so that it is compatible with the new stack setup. - profile = profile_or_list[0] - extruder_stack_quality_changes_container = ContainerManager.getInstance().duplicateContainerInstance(profile) - extruder_stack_quality_changes_container.addMetaDataEntry("extruder", "fdmextruder") - - for quality_changes_setting_key in extruder_stack_quality_changes_container.getAllKeys(): - settable_per_extruder = extruder_stack_quality_changes_container.getProperty(quality_changes_setting_key, "settable_per_extruder") - if settable_per_extruder: - profile.removeInstance(quality_changes_setting_key, postpone_emit = True) - else: - extruder_stack_quality_changes_container.removeInstance(quality_changes_setting_key, postpone_emit = True) - - # We add the new container to the profile list so things like extruder positions are taken care of - # in the next code segment. - profile_or_list.append(extruder_stack_quality_changes_container) - # Import all profiles for profile_index, profile in enumerate(profile_or_list): if profile_index == 0: @@ -252,6 +233,9 @@ class CuraContainerRegistry(ContainerRegistry): profile.setMetaDataEntry("extruder", extruder_id) profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_") + else: #More extruders in the imported file than in the machine. + continue #Delete the additional profiles. + result = self._configureProfile(profile, profile_id, new_name) if result is not None: return {"status": "error", "message": catalog.i18nc( @@ -305,7 +289,7 @@ class CuraContainerRegistry(ContainerRegistry): quality_type_criteria["definition"] = profile.getDefinition().getId() else: - profile.setDefinition(fdmprinter) + profile.setDefinition("fdmprinter") quality_type_criteria["definition"] = "fdmprinter" machine_definition = Application.getInstance().getGlobalContainerStack().getBottom() @@ -422,6 +406,10 @@ class CuraContainerRegistry(ContainerRegistry): if not isinstance(container, ContainerStack) or container.getMetaDataEntry("type") != "machine": return + machine_extruder_trains = container.getMetaDataEntry("machine_extruder_trains") + if machine_extruder_trains is not None and machine_extruder_trains != {"0": "fdmextruder"}: + return + extruder_stacks = self.findContainerStacks(type = "extruder_train", machine = container.getId()) if not extruder_stacks: self.addExtruderStackForSingleExtrusionMachine(container, "fdmextruder") diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 05aed1f5e2..b3a2e6612b 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -739,7 +739,7 @@ class MachineManager(QObject): ## Set the active material by switching out a container # Depending on from/to material+current variant, a quality profile is chosen and set. @pyqtSlot(str) - def setActiveMaterial(self, material_id: str): + def setActiveMaterial(self, material_id: str, always_discard_changes = False): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): containers = ContainerRegistry.getInstance().findInstanceContainers(id = material_id) if not containers or not self._active_container_stack: @@ -821,10 +821,10 @@ class MachineManager(QObject): if not old_quality_changes: new_quality_id = candidate_quality.getId() - self.setActiveQuality(new_quality_id) + self.setActiveQuality(new_quality_id, always_discard_changes = always_discard_changes) @pyqtSlot(str) - def setActiveVariant(self, variant_id: str): + def setActiveVariant(self, variant_id: str, always_discard_changes = False): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_id) if not containers or not self._active_container_stack: @@ -840,14 +840,14 @@ class MachineManager(QObject): if old_material: preferred_material_name = old_material.getName() preferred_material_id = self._updateMaterialContainer(self._global_container_stack.definition, self._global_container_stack, containers[0], preferred_material_name).id - self.setActiveMaterial(preferred_material_id) + self.setActiveMaterial(preferred_material_id, always_discard_changes = always_discard_changes) else: Logger.log("w", "While trying to set the active variant, no variant was found to replace.") ## set the active quality # \param quality_id The quality_id of either a quality or a quality_changes @pyqtSlot(str) - def setActiveQuality(self, quality_id: str): + def setActiveQuality(self, quality_id: str, always_discard_changes = False): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self.blurSettings.emit() @@ -909,7 +909,7 @@ class MachineManager(QObject): # the dialog will be the those before the switching. self._executeDelayedActiveContainerStackChanges() - if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: + if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1 and not always_discard_changes: Application.getInstance().discardOrKeepProfileChanges() ## Used to update material and variant in the active container stack with a delay. diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index c70d9343cf..b853c06c8e 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -32,14 +32,16 @@ class SettingOverrideDecorator(SceneNodeDecorator): def __init__(self): super().__init__() - self._stack = PerObjectContainerStack(stack_id = id(self)) + self._stack = PerObjectContainerStack(stack_id = "per_object_stack_" + str(id(self))) self._stack.setDirty(False) # This stack does not need to be saved. self._stack.addContainer(InstanceContainer(container_id = "SettingOverrideInstanceContainer")) self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId() + self._is_non_printing_mesh = False + self._stack.propertyChanged.connect(self._onSettingChanged) - ContainerRegistry.getInstance().addContainer(self._stack) + Application.getInstance().getContainerRegistry().addContainer(self._stack) Application.getInstance().globalContainerStackChanged.connect(self._updateNextStack) self.activeExtruderChanged.connect(self._updateNextStack) @@ -57,6 +59,10 @@ class SettingOverrideDecorator(SceneNodeDecorator): # Properly set the right extruder on the copy deep_copy.setActiveExtruder(self._extruder_stack) + # use value from the stack because there can be a delay in signal triggering and "_is_non_printing_mesh" + # has not been updated yet. + deep_copy._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings) + return deep_copy ## Gets the currently active extruder to print this object with. @@ -80,14 +86,17 @@ class SettingOverrideDecorator(SceneNodeDecorator): container_stack = containers[0] return container_stack.getMetaDataEntry("position", default=None) + def isNonPrintingMesh(self): + return self._is_non_printing_mesh + def _onSettingChanged(self, instance, property_name): # Reminder: 'property' is a built-in function # Trigger slice/need slicing if the value has changed. if property_name == "value": + self._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings) + Application.getInstance().getBackend().needsSlicing() Application.getInstance().getBackend().tickle() - self._node._non_printing_mesh = any(self._stack.getProperty(setting, "value") for setting in self._non_printing_mesh_settings) - ## Makes sure that the stack upon which the container stack is placed is # kept up to date. def _updateNextStack(self): diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 16fd2ee93c..40d64590f5 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -461,7 +461,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader): global_stack_id_new = self.getNewId(global_stack_id_original) global_stack_need_rename = True - global_stack_name_new = self._container_registry.uniqueName(global_stack_name_original) + if self._container_registry.findContainerStacksMetadata(name = global_stack_id_original): + global_stack_name_new = self._container_registry.uniqueName(global_stack_name_original) for each_extruder_stack_file in extruder_stack_files: old_container_id = self._stripFileToId(each_extruder_stack_file) @@ -583,7 +584,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if machine_id: new_machine_id = self.getNewId(machine_id) new_id = new_machine_id + "_current_settings" - instance_container.setMetadataEntry("id", new_id) + instance_container.setMetaDataEntry("id", new_id) instance_container.setName(new_id) instance_container.setMetaDataEntry("machine", new_machine_id) containers_to_add.append(instance_container) @@ -681,12 +682,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): file_name = global_stack_file) # Ensure a unique ID and name - stack._id = global_stack_id_new - - # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the - # bound machine also needs to change. - if stack.getMetaDataEntry("machine", None): - stack.setMetaDataEntry("machine", global_stack_id_new) + stack.setMetaDataEntry("id", global_stack_id_new) # Only machines need a new name, stacks may be non-unique stack.setName(global_stack_name_new) @@ -740,7 +736,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): stack.deserialize(extruder_file_content, file_name = extruder_stack_file) # Ensure a unique ID and name - stack._id = new_id + stack.setMetaDataEntry("id", new_id) self._container_registry.addContainer(stack) extruder_stacks_added.append(stack) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index efa0e87b9e..4da26aa78f 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -19,6 +19,10 @@ from UM.Settings.SettingRelation import RelationType from cura.OneAtATimeIterator import OneAtATimeIterator from cura.Settings.ExtruderManager import ExtruderManager + +NON_PRINTING_MESH_SETTINGS = ["anti_overhang_mesh", "infill_mesh", "cutting_mesh"] + + class StartJobResult(IntEnum): Finished = 1 Error = 2 @@ -133,11 +137,15 @@ class StartSliceJob(Job): 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: - _non_printing_mesh = getattr(node, "_non_printing_mesh", False) - if not getattr(node, "_outside_buildarea", False) or _non_printing_mesh: + if node.callDecoration("isSliceable") and type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None: + per_object_stack = node.callDecoration("getStack") + is_non_printing_mesh = False + if per_object_stack: + is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS) + + if not getattr(node, "_outside_buildarea", False) or not is_non_printing_mesh: temp_list.append(node) - if not _non_printing_mesh: + if not is_non_printing_mesh: has_printing_mesh = True Job.yieldThread() diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py index 32cfb5d027..07cd8b0aad 100644 --- a/plugins/LegacyProfileReader/LegacyProfileReader.py +++ b/plugins/LegacyProfileReader/LegacyProfileReader.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import configparser # For reading the legacy profile INI files. @@ -10,8 +10,10 @@ import os.path # For concatenating the path to the plugin and the relative path from UM.Application import Application # To get the machine manager to create the new profile in. from UM.Logger import Logger # Logging errors. from UM.PluginRegistry import PluginRegistry # For getting the path to this plugin's directory. +from UM.Settings.ContainerRegistry import ContainerRegistry #To create unique profile IDs. from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make. from cura.ProfileReader import ProfileReader # The plug-in type to implement. +from cura.Settings.ExtruderManager import ExtruderManager #To get the current extruder definition. ## A plugin that reads profile data from legacy Cura versions. @@ -77,7 +79,9 @@ class LegacyProfileReader(ProfileReader): raise Exception("Unable to import legacy profile. Multi extrusion is not supported") Logger.log("i", "Importing legacy profile from file " + file_name + ".") - profile = InstanceContainer("Imported Legacy Profile") # Create an empty profile. + container_registry = ContainerRegistry.getInstance() + profile_id = container_registry.uniqueName("Imported Legacy Profile") + profile = InstanceContainer(profile_id) # Create an empty profile. parser = configparser.ConfigParser(interpolation = None) try: @@ -120,7 +124,7 @@ class LegacyProfileReader(ProfileReader): if "translation" not in dict_of_doom: Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?") return None - current_printer_definition = global_container_stack.getBottom() + current_printer_definition = global_container_stack.definition profile.setDefinition(current_printer_definition.getId()) for new_setting in dict_of_doom["translation"]: # Evaluate all new settings that would get a value from the translations. old_setting_expression = dict_of_doom["translation"][new_setting] @@ -139,14 +143,13 @@ class LegacyProfileReader(ProfileReader): if len(profile.getAllKeys()) == 0: Logger.log("i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile.") - - # We need to downgrade the container to version 1 (in Cura 2.1) so the upgrade system can correctly upgrade - # it to the latest version. profile.addMetaDataEntry("type", "profile") # don't know what quality_type it is based on, so use "normal" by default profile.addMetaDataEntry("quality_type", "normal") + profile.setName(profile_id) profile.setDirty(True) + #Serialise and deserialise in order to perform the version upgrade. parser = configparser.ConfigParser(interpolation=None) data = profile.serialize() parser.read_string(data) @@ -159,4 +162,20 @@ class LegacyProfileReader(ProfileReader): data = stream.getvalue() profile.deserialize(data) - return profile + #We need to return one extruder stack and one global stack. + global_container_id = container_registry.uniqueName("Global Imported Legacy Profile") + global_profile = profile.duplicate(new_id = global_container_id, new_name = profile_id) #Needs to have the same name as the extruder profile. + global_profile.setDirty(True) + + #Only the extruder stack has an extruder metadata entry. + profile.addMetaDataEntry("extruder", ExtruderManager.getInstance().getActiveExtruderStack().definition.getId()) + + #Split all settings into per-extruder and global settings. + for setting_key in profile.getAllKeys(): + settable_per_extruder = global_container_stack.getProperty(setting_key, "settable_per_extruder") + if settable_per_extruder: + global_profile.removeInstance(setting_key) + else: + profile.removeInstance(setting_key) + + return [global_profile, profile] diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.qml b/plugins/MachineSettingsAction/MachineSettingsAction.qml index b36fb989f0..6ff70a1503 100644 --- a/plugins/MachineSettingsAction/MachineSettingsAction.qml +++ b/plugins/MachineSettingsAction/MachineSettingsAction.qml @@ -270,6 +270,20 @@ Cura.MachineAction } } } + + Connections + { + target: manager + onDefinedExtruderCountChanged: + { + extruderCountModel.clear(); + for(var i = 0; i < manager.definedExtruderCount; ++i) + { + extruderCountModel.append({text: String(i + 1), value: i}); + } + } + } + currentIndex: machineExtruderCountProvider.properties.value - 1 onActivated: { @@ -432,7 +446,7 @@ Cura.MachineAction property int areaHeight: parent.height - y property string settingKey: "machine_extruder_start_code" property bool isExtruderSetting: true - } + } } Column { height: parent.height @@ -714,7 +728,7 @@ Cura.MachineAction width: gcodeArea.width text: _tooltip - property bool _isExtruderSetting: (typeof(isExtruderSetting) === 'undefined') ? false: isExtruderSetting + property bool _isExtruderSetting: (typeof(isExtruderSetting) === 'undefined') ? false : isExtruderSetting property string _tooltip: (typeof(tooltip) === 'undefined') ? propertyProvider.properties.description : tooltip UM.SettingPropertyProvider @@ -726,7 +740,7 @@ Cura.MachineAction { if(settingsTabs.currentIndex > 0) { - return Cura.MachineManager.activeStackId; + return Cura.ExtruderManager.extruderIds[String(settingsTabs.currentIndex - 1)]; } return ""; } diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index e96c7e9bda..e156e655ce 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -110,7 +110,7 @@ class SolidView(View): except ValueError: pass - if getattr(node, "_non_printing_mesh", False): + if node.callDecoration("isNonPrintingMesh"): if per_mesh_stack and (per_mesh_stack.getProperty("infill_mesh", "value") or per_mesh_stack.getProperty("cutting_mesh", "value")): renderer.queueNode(node, shader = self._non_printing_shader, uniforms = uniforms, transparent = True) else: diff --git a/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py index 853ef72f72..7143b462e6 100644 --- a/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py @@ -234,7 +234,7 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte def spawnPrintView(self): if self._print_view is None: path = os.path.join(self._plugin_path, "PrintWindow.qml") - self._print_view = Application.getInstance().createQmlComponent(path, {"OutputDevice", self}) + self._print_view = Application.getInstance().createQmlComponent(path, {"OutputDevice": self}) if self._print_view is not None: self._print_view.show() diff --git a/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py b/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py index c01ff158b1..c350dadefe 100644 --- a/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py +++ b/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py @@ -129,12 +129,17 @@ class VersionUpgrade30to31(VersionUpgrade): 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) + machine_extruder_count = '1' # by default it is 1 and the value cannot be stored in the global stack + if parser.has_option("values", "machine_extruder_count"): + machine_extruder_count = parser["values"]["machine_extruder_count"] - #For single extuder machine we nee only first extruder - if len(machine_extruders) !=0: - if self._updateSingleExtuderDefinitionFile(machine_extruders, machine_nozzle_size): + if machine_extruder_count == '1': + definition_name = parser["general"]["name"] + machine_extruders = self._getSingleExtrusionMachineExtruders(definition_name) + + # For single extruder machine we need only first extruder + if len(machine_extruders) !=0: + self._updateSingleExtruderDefinitionFile(machine_extruders, machine_nozzle_size) parser.remove_option("values", "machine_nozzle_size") # Update version numbers @@ -219,9 +224,9 @@ class VersionUpgrade30to31(VersionUpgrade): machine_instances_dir = Resources.getPath(CuraApplication.ResourceTypes.MachineStack) - machine_instances = [] + machine_instance_id = None - #Find all machine instances + # Find 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): @@ -242,57 +247,51 @@ class VersionUpgrade30to31(VersionUpgrade): 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): + id = parser["general"]["id"] + if id + "_settings" != definition_name: 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 + else: + machine_instance_id = id break - return extruder_instances_per_machine + if machine_instance_id is not None: - #Find extruder defition at index 0 and update its values - def _updateSingleExtuderDefinitionFile(self, extruder_instances_per_machine, machine_nozzle_size): + extruders_instances_dir = Resources.getPath(CuraApplication.ResourceTypes.ExtruderStack) + #"machine",[extruders] + extruder_instances = [] + + # Find all custom extruders for found 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 + + if machine_instance_id != parser["metadata"]["machine"]: + continue + + extruder_instances.append(parser) + + return extruder_instances + + # Find extruder definition at index 0 and update its values + def _updateSingleExtruderDefinitionFile(self, extruder_instances_per_machine, machine_nozzle_size): defintion_instances_dir = Resources.getPath(CuraApplication.ResourceTypes.DefinitionChangesContainer) @@ -312,19 +311,15 @@ class VersionUpgrade30to31(VersionUpgrade): 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]: + for extruder_instance in extruder_instances_per_machine: - if extruder_instance["general"]["id"] + "_settings" == name: - defition_position = extruder_instance["metadata"]["position"] + definition_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: + if definition_position == "0": + custom_extruder_at_0_position = extruder_instance break - #If not null, then parsed file is for first extuder and then can be updated. I need to update only + # 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: @@ -374,4 +369,4 @@ class VersionUpgrade30to31(VersionUpgrade): quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer) with open(os.path.join(quality_changes_dir, extruder_quality_changes_filename), "w") as f: - f.write(extruder_quality_changes_output.getvalue()) + f.write(extruder_quality_changes_output.getvalue()) \ No newline at end of file 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.