From c132300fd553d28e10f1a6b9a361dd78688cac70 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Mon, 23 Oct 2017 13:56:26 +0200 Subject: [PATCH 01/67] start --- cura/Settings/ExtruderManager.py | 44 +++++++++---------- .../MachineSettingsAction.py | 22 ++++++---- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index c8daca7f92..50525b6aeb 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -28,6 +28,20 @@ if TYPE_CHECKING: # # This keeps a list of extruder stacks for each machine. class ExtruderManager(QObject): + + ## Registers listeners and such to listen to changes to the extruders. + def __init__(self, parent = None): + super().__init__(parent) + + self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders. + self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack + self._selected_object_extruders = [] + self._global_container_stack_definition_id = None + self._addCurrentMachineExtruders() + + Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged) + Selection.selectionChanged.connect(self.resetSelectedObjectExtruders) + ## Signal to notify other components when the list of extruders for a machine definition changes. extrudersChanged = pyqtSignal(QVariant) @@ -38,18 +52,6 @@ class ExtruderManager(QObject): ## Notify when the user switches the currently active extruder. activeExtruderChanged = pyqtSignal() - ## Registers listeners and such to listen to changes to the extruders. - def __init__(self, parent = None): - super().__init__(parent) - self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders. - self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack - self._selected_object_extruders = [] - Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged) - self._global_container_stack_definition_id = None - self._addCurrentMachineExtruders() - - Selection.selectionChanged.connect(self.resetSelectedObjectExtruders) - ## Gets the unique identifier of the currently active extruder stack. # # The currently active extruder stack is the stack that is currently being @@ -59,10 +61,10 @@ class ExtruderManager(QObject): @pyqtProperty(str, notify = activeExtruderChanged) def activeExtruderStackId(self) -> Optional[str]: if not Application.getInstance().getGlobalContainerStack(): - return None # No active machine, so no active extruder. + return None # No active machine, so no active extruder. try: return self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId() - except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong. + except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong. return None ## Return extruder count according to extruder trains. @@ -521,8 +523,8 @@ class ExtruderManager(QObject): machine_extruder_count = global_stack.getProperty("machine_extruder_count", "value") # In case the printer is using one extruder, shouldn't exist active extruder stacks - if machine_extruder_count == 1: - return result + # if machine_extruder_count == 1: + # return result if global_stack and global_stack.getId() in self._extruder_trains: for extruder in sorted(self._extruder_trains[global_stack.getId()]): @@ -537,16 +539,10 @@ class ExtruderManager(QObject): self.globalContainerStackDefinitionChanged.emit() # If the global container changed, the number of extruders could be changed and so the active_extruder_index is updated - extruder_count = global_container_stack.getProperty("machine_extruder_count", "value") - if extruder_count > 1: - if self._active_extruder_index == -1: - self.setActiveExtruderIndex(0) - else: - if self._active_extruder_index != -1: - self.setActiveExtruderIndex(-1) + if self._active_extruder_index == -1: + self.setActiveExtruderIndex(0) self.activeExtruderChanged.emit() - self.resetSelectedObjectExtruders() ## Adds the extruders of the currently active machine. diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index 4343c2c780..49f82a5c9b 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -171,17 +171,21 @@ class MachineSettingsAction(MachineAction): definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count) - if extruder_count > 1: - # Multiextrusion + # Make sure one of the extruder stacks is active + if extruder_manager.activeExtruderIndex == -1: + extruder_manager.setActiveExtruderIndex(0) + + # Move settable_per_extruder values out of the global container + extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStack() + global_user_container = self._global_container_stack.getTop() + + for setting_instance in global_user_container.findInstances(): + setting_key = setting_instance.definition.key + + + - # Make sure one of the extruder stacks is active - if extruder_manager.activeExtruderIndex == -1: - extruder_manager.setActiveExtruderIndex(0) - # Move settable_per_extruder values out of the global container - if previous_extruder_count == 1: - extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - global_user_container = self._global_container_stack.getTop() for setting_instance in global_user_container.findInstances(): setting_key = setting_instance.definition.key From f0ed3bc588632837921795de5cc74f79a60c9568 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 11:05:35 +0200 Subject: [PATCH 02/67] Always populate extruder stacks when creating global stack - CURA-4482 --- cura/Settings/GlobalStack.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 88218c2f1e..81f9db76d0 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -23,9 +23,9 @@ class GlobalStack(CuraContainerStack): def __init__(self, container_id: str, *args, **kwargs): super().__init__(container_id, *args, **kwargs) - self.addMetaDataEntry("type", "machine") # For backward compatibility + self.addMetaDataEntry("type", "machine") # For backward compatibility - self._extruders = {} + self._extruders = {} # type: Dict[str, "ExtruderStack"] # This property is used to track which settings we are calculating the "resolve" for # and if so, to bypass the resolve to prevent an infinite recursion that would occur @@ -61,12 +61,15 @@ class GlobalStack(CuraContainerStack): # \throws Exceptions.TooManyExtrudersError Raised when trying to add an extruder while we # already have the maximum number of extruders. def addExtruder(self, extruder: ContainerStack) -> None: - extruder_count = self.getProperty("machine_extruder_count", "value") - if extruder_count <= 1: - Logger.log("i", "Not adding extruder[%s] to [%s] because it is a single-extrusion machine.", - extruder.id, self.id) - return + # CURA-4482 + # extruder_count = self.getProperty("machine_extruder_count", "value") + + # CURA-4482 + # if extruder_count <= 1: + # Logger.log("i", "Not adding extruder[%s] to [%s] because it is a single-extrusion machine.", + # extruder.id, self.id) + # return position = extruder.getMetaDataEntry("position") if position is None: From de34464e47a822887c6e8b4d9ddedbd2c3f5cb54 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 11:38:21 +0200 Subject: [PATCH 03/67] Update ProfilesModel to always use extruder stacks + some refactoring - CURA-4482 --- cura/Settings/ProfilesModel.py | 70 ++++++++++++++++------------------ 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/cura/Settings/ProfilesModel.py b/cura/Settings/ProfilesModel.py index bf1993b184..a04ff00392 100644 --- a/cura/Settings/ProfilesModel.py +++ b/cura/Settings/ProfilesModel.py @@ -12,6 +12,11 @@ from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel from cura.QualityManager import QualityManager from cura.Settings.ExtruderManager import ExtruderManager +from typing import List, TYPE_CHECKING + +if TYPE_CHECKING: + from cura.Settings.ExtruderStack import ExtruderStack + ## QML Model for listing the current list of valid quality profiles. # @@ -28,9 +33,10 @@ class ProfilesModel(InstanceContainersModel): Application.getInstance().globalContainerStackChanged.connect(self._update) - Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update) - Application.getInstance().getMachineManager().activeStackChanged.connect(self._update) - Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update) + self._machine_manager = Application.getInstance().getMachineManager() + self._machine_manager.activeVariantChanged.connect(self._update) + self._machine_manager.activeStackChanged.connect(self._update) + self._machine_manager.activeMaterialChanged.connect(self._update) # Factory function, used by QML @staticmethod @@ -54,17 +60,12 @@ class ProfilesModel(InstanceContainersModel): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack is None: return [] - global_stack_definition = global_container_stack.getBottom() + + global_stack_definition = global_container_stack.definition # Get the list of extruders and place the selected extruder at the front of the list. - extruder_manager = ExtruderManager.getInstance() - active_extruder = extruder_manager.getActiveExtruderStack() - extruder_stacks = extruder_manager.getActiveExtruderStacks() - materials = [global_container_stack.material] - if active_extruder in extruder_stacks: - extruder_stacks.remove(active_extruder) - extruder_stacks = [active_extruder] + extruder_stacks - materials = [extruder.material for extruder in extruder_stacks] + extruder_stacks = self._getOrderedExtruderStacksList() + materials = [extruder.material for extruder in extruder_stacks] # Fetch the list of usable qualities across all extruders. # The actual list of quality profiles come from the first extruder in the extruder list. @@ -87,35 +88,16 @@ class ProfilesModel(InstanceContainersModel): ## Re-computes the items in this model, and adds the layer height role. def _recomputeItems(self): - #Some globals that we can re-use. global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack is None: return - # Detecting if the machine has multiple extrusion - multiple_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 - # Get the list of extruders and place the selected extruder at the front of the list. - extruder_manager = ExtruderManager.getInstance() - active_extruder = extruder_manager.getActiveExtruderStack() - extruder_stacks = extruder_manager.getActiveExtruderStacks() - if multiple_extrusion: - # Place the active extruder at the front of the list. - # This is a workaround checking if there is an active_extruder or not before moving it to the front of the list. - # Actually, when a printer has multiple extruders, should exist always an active_extruder. However, in some - # cases the active_extruder is still None. - if active_extruder in extruder_stacks: - extruder_stacks.remove(active_extruder) - new_extruder_stacks = [] - if active_extruder is not None: - new_extruder_stacks = [active_extruder] - extruder_stacks = new_extruder_stacks + extruder_stacks - - # Get a list of usable/available qualities for this machine and material - qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, - extruder_stacks) + extruder_stacks = self._getOrderedExtruderStacksList() container_registry = ContainerRegistry.getInstance() - machine_manager = Application.getInstance().getMachineManager() + + # Get a list of usable/available qualities for this machine and material + qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks) unit = global_container_stack.getBottom().getProperty("layer_height", "unit") if not unit: @@ -174,7 +156,7 @@ class ProfilesModel(InstanceContainersModel): # Quality-changes profile that has no value for layer height. Get the corresponding quality profile and ask that profile. quality_type = profile.getMetaDataEntry("quality_type", None) if quality_type: - quality_results = machine_manager.determineQualityAndQualityChangesForQualityType(quality_type) + quality_results = self._machine_manager.determineQualityAndQualityChangesForQualityType(quality_type) for quality_result in quality_results: if quality_result["stack"] is global_container_stack: quality = quality_result["quality"] @@ -198,6 +180,20 @@ class ProfilesModel(InstanceContainersModel): self._setItemLayerHeight(item, global_container_stack.getRawProperty("layer_height", "value", skip_until_container = skip_until_container.getId()), unit) # Fall through to the currently loaded material. yield item - def _setItemLayerHeight(self, item, value, unit): + ## Get a list of extruder stacks with the active extruder at the front of the list. + @staticmethod + def _getOrderedExtruderStacksList() -> List["ExtruderStack"]: + extruder_manager = ExtruderManager.getInstance() + extruder_stacks = extruder_manager.getActiveExtruderStacks() + active_extruder = extruder_manager.getActiveExtruderStack() + + if active_extruder in extruder_stacks: + extruder_stacks.remove(active_extruder) + extruder_stacks = [active_extruder] + extruder_stacks + + return extruder_stacks + + @staticmethod + def _setItemLayerHeight(item, value, unit): item["layer_height"] = str(value) + unit item["layer_height_without_unit"] = str(value) From 152f6f840579b8cbf077fc24e3667b1cbe8bbe40 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 11:51:24 +0200 Subject: [PATCH 04/67] More profiles model refactoring - CURA-4482 --- cura/CrashHandler.py | 1 + cura/Settings/ProfilesModel.py | 24 +++++++------ .../MachineSettingsAction.py | 34 ++++++++++--------- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index a78ecb8a72..ad80d286c3 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -53,6 +53,7 @@ class CrashHandler: self.exception_type = exception_type self.value = value self.traceback = tb + self.dialog = QDialog() # While we create the GUI, the information will be stored for sending afterwards self.data = dict() diff --git a/cura/Settings/ProfilesModel.py b/cura/Settings/ProfilesModel.py index a04ff00392..a46e3d146b 100644 --- a/cura/Settings/ProfilesModel.py +++ b/cura/Settings/ProfilesModel.py @@ -32,11 +32,9 @@ class ProfilesModel(InstanceContainersModel): self.addRoleName(self.AvailableRole, "available") Application.getInstance().globalContainerStackChanged.connect(self._update) - - self._machine_manager = Application.getInstance().getMachineManager() - self._machine_manager.activeVariantChanged.connect(self._update) - self._machine_manager.activeStackChanged.connect(self._update) - self._machine_manager.activeMaterialChanged.connect(self._update) + Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update) + Application.getInstance().getMachineManager().activeStackChanged.connect(self._update) + Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update) # Factory function, used by QML @staticmethod @@ -153,17 +151,21 @@ class ProfilesModel(InstanceContainersModel): yield item continue + machine_manager = Application.getInstance().getMachineManager() + # Quality-changes profile that has no value for layer height. Get the corresponding quality profile and ask that profile. quality_type = profile.getMetaDataEntry("quality_type", None) if quality_type: - quality_results = self._machine_manager.determineQualityAndQualityChangesForQualityType(quality_type) + quality_results = machine_manager.determineQualityAndQualityChangesForQualityType(quality_type) for quality_result in quality_results: if quality_result["stack"] is global_container_stack: quality = quality_result["quality"] break - else: #No global container stack in the results: + else: + # No global container stack in the results: if quality_results: - quality = quality_results[0]["quality"] #Take any of the extruders. + # Take any of the extruders. + quality = quality_results[0]["quality"] else: quality = None if quality and quality.hasProperty("layer_height", "value"): @@ -171,11 +173,11 @@ class ProfilesModel(InstanceContainersModel): yield item continue - #Quality has no value for layer height either. Get the layer height from somewhere lower in the stack. + # Quality has no value for layer height either. Get the layer height from somewhere lower in the stack. skip_until_container = global_container_stack.material - if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): #No material in stack. + if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No material in stack. skip_until_container = global_container_stack.variant - if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): #No variant in stack. + if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No variant in stack. skip_until_container = global_container_stack.getBottom() self._setItemLayerHeight(item, global_container_stack.getRawProperty("layer_height", "value", skip_until_container = skip_until_container.getId()), unit) # Fall through to the currently loaded material. yield item diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index 49f82a5c9b..be76c0724a 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -176,25 +176,27 @@ class MachineSettingsAction(MachineAction): extruder_manager.setActiveExtruderIndex(0) # Move settable_per_extruder values out of the global container - extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStack() + # extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStack() global_user_container = self._global_container_stack.getTop() - for setting_instance in global_user_container.findInstances(): - setting_key = setting_instance.definition.key - + if extruder_count > 1: + # Multi extrusion + # Make sure one of the extruder stacks is active + if extruder_manager.activeExtruderIndex == -1: + extruder_manager.setActiveExtruderIndex(0) - - - - - for setting_instance in global_user_container.findInstances(): - setting_key = setting_instance.definition.key - settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") - if settable_per_extruder: - limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder")) - extruder_stack = extruder_stacks[max(0, limit_to_extruder)] - extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) - global_user_container.removeInstance(setting_key) + # Move settable_per_extruder values out of the global container + if previous_extruder_count == 1: + extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() + global_user_container = self._global_container_stack.getTop() + for setting_instance in global_user_container.findInstances(): + setting_key = setting_instance.definition.key + settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") + if settable_per_extruder: + limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder")) + extruder_stack = extruder_stacks[max(0, limit_to_extruder)] + extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) + global_user_container.removeInstance(setting_key) else: # Single extrusion From 59580c0ade189a6a131ba1f5e848ab0f0cef8072 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 11:52:58 +0200 Subject: [PATCH 05/67] Remove commented out code - CURA-4482 --- cura/Settings/GlobalStack.py | 10 ---------- cura/Settings/ProfilesModel.py | 1 - 2 files changed, 11 deletions(-) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 81f9db76d0..2f27c4ab72 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -61,16 +61,6 @@ class GlobalStack(CuraContainerStack): # \throws Exceptions.TooManyExtrudersError Raised when trying to add an extruder while we # already have the maximum number of extruders. def addExtruder(self, extruder: ContainerStack) -> None: - - # CURA-4482 - # extruder_count = self.getProperty("machine_extruder_count", "value") - - # CURA-4482 - # if extruder_count <= 1: - # Logger.log("i", "Not adding extruder[%s] to [%s] because it is a single-extrusion machine.", - # extruder.id, self.id) - # return - position = extruder.getMetaDataEntry("position") if position is None: Logger.log("w", "No position defined for extruder {extruder}, cannot add it to stack {stack}", extruder = extruder.id, stack = self.id) diff --git a/cura/Settings/ProfilesModel.py b/cura/Settings/ProfilesModel.py index a46e3d146b..7432605481 100644 --- a/cura/Settings/ProfilesModel.py +++ b/cura/Settings/ProfilesModel.py @@ -91,7 +91,6 @@ class ProfilesModel(InstanceContainersModel): return extruder_stacks = self._getOrderedExtruderStacksList() - container_registry = ContainerRegistry.getInstance() # Get a list of usable/available qualities for this machine and material From 62cb5a48cb1634c2e47cd6bb5c153bcc92d537e4 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 12:42:33 +0200 Subject: [PATCH 06/67] Refactor user profiles model to always use extruder stack - CURA-4482 --- cura/Settings/UserProfilesModel.py | 42 +++++++----------------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/cura/Settings/UserProfilesModel.py b/cura/Settings/UserProfilesModel.py index aa815ef4aa..5ae9055759 100644 --- a/cura/Settings/UserProfilesModel.py +++ b/cura/Settings/UserProfilesModel.py @@ -22,47 +22,23 @@ class UserProfilesModel(ProfilesModel): # Fetch the list of quality changes. quality_manager = QualityManager.getInstance() - machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) + machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition) quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition) - # Detecting if the machine has multiple extrusion - multiple_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 - # Get the list of extruders and place the selected extruder at the front of the list. extruder_manager = ExtruderManager.getInstance() active_extruder = extruder_manager.getActiveExtruderStack() - extruder_stacks = extruder_manager.getActiveExtruderStacks() - if multiple_extrusion: - # Place the active extruder at the front of the list. - # This is a workaround checking if there is an active_extruder or not before moving it to the front of the list. - # Actually, when a printer has multiple extruders, should exist always an active_extruder. However, in some - # cases the active_extruder is still None. - if active_extruder in extruder_stacks: - extruder_stacks.remove(active_extruder) - new_extruder_stacks = [] - if active_extruder is not None: - new_extruder_stacks = [active_extruder] - else: - # if there is no active extruder, use the first one in the active extruder stacks - active_extruder = extruder_stacks[0] - extruder_stacks = new_extruder_stacks + extruder_stacks + extruder_stacks = self._getOrderedExtruderStacksList() - # Fetch the list of useable qualities across all extruders. + # Fetch the list of usable qualities across all extruders. # The actual list of quality profiles come from the first extruder in the extruder list. - quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, - extruder_stacks) + quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks) # Filter the quality_change by the list of available quality_types quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list]) - - if multiple_extrusion: - # If the printer has multiple extruders then quality changes related to the current extruder are kept - filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and - qc.getMetaDataEntry("extruder") is not None and - (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or - qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())] - else: - # If not, the quality changes of the global stack are selected - filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and - qc.getMetaDataEntry("extruder") is None] + filtered_quality_changes = [qc for qc in quality_changes_list if + qc.getMetaDataEntry("quality_type") in quality_type_set and + qc.getMetaDataEntry("extruder") is not None and + (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or + qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())] return filtered_quality_changes From 79754209015e7fce11a66cbc7870a6946245c86f Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 13:34:59 +0200 Subject: [PATCH 07/67] Always set active extruder index when loading active machine on start - CURA-4482 --- cura/Settings/MachineManager.py | 5 ++--- cura/Settings/QualitySettingsModel.py | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index fc5c415f87..d0f5cfb119 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -106,9 +106,8 @@ class MachineManager(QObject): if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacks(id = active_machine_id): # An active machine was saved, so restore it. self.setActiveMachine(active_machine_id) - if self._global_container_stack and self._global_container_stack.getProperty("machine_extruder_count", "value") > 1: - # Make sure _active_container_stack is properly initiated - ExtruderManager.getInstance().setActiveExtruderIndex(0) + # Make sure _active_container_stack is properly initiated + ExtruderManager.getInstance().setActiveExtruderIndex(0) self._auto_materials_changed = {} self._auto_hotends_changed = {} diff --git a/cura/Settings/QualitySettingsModel.py b/cura/Settings/QualitySettingsModel.py index 2ab4e2a9b5..d0379dc510 100644 --- a/cura/Settings/QualitySettingsModel.py +++ b/cura/Settings/QualitySettingsModel.py @@ -224,7 +224,6 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel): if self._extruder_id == "" and settable_per_extruder: continue - label = definition.label if self._i18n_catalog: label = self._i18n_catalog.i18nc(definition.key + " label", label) From 0b5709605654c91e17bf7b4bc7de57f751306659 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 13:44:47 +0200 Subject: [PATCH 08/67] Always use extruder stacks when switching global container in machine manager - CURA-4482 --- cura/Settings/MachineManager.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index d0f5cfb119..c52dc65bf3 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -248,13 +248,13 @@ class MachineManager(QObject): if old_index is not None: extruder_manager.setActiveExtruderIndex(old_index) - self._auto_hotends_changed = {} #Processed all of them now. + self._auto_hotends_changed = {} # Processed all of them now. def _onGlobalContainerChanged(self): if self._global_container_stack: try: self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged) - except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that was already disconnected. + except TypeError: # pyQtSignal gives a TypeError when disconnecting from something that was already disconnected. pass try: self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged) @@ -270,10 +270,9 @@ class MachineManager(QObject): quality = self._global_container_stack.quality quality.nameChanged.disconnect(self._onQualityNameChanged) - if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1: - for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): - extruder_stack.propertyChanged.disconnect(self._onPropertyChanged) - extruder_stack.containersChanged.disconnect(self._onInstanceContainersChanged) + for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): + extruder_stack.propertyChanged.disconnect(self._onPropertyChanged) + extruder_stack.containersChanged.disconnect(self._onInstanceContainersChanged) self._global_container_stack = Application.getInstance().getGlobalContainerStack() From 0021dee84bd89705e13739459c6d7f0a52689894 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 14:04:05 +0200 Subject: [PATCH 09/67] Always use extruder stacks when copying value to all extruder - CURA-4482 --- cura/Settings/MachineManager.py | 56 +++++++++++++++------------------ 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index c52dc65bf3..f547ccf4a7 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -264,9 +264,12 @@ class MachineManager(QObject): self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) except TypeError: pass + + # TODO: remove this - CURA-4482 material = self._global_container_stack.material material.nameChanged.disconnect(self._onMaterialNameChanged) + # TODO: remove this - CURA-4482 quality = self._global_container_stack.quality quality.nameChanged.disconnect(self._onQualityNameChanged) @@ -274,41 +277,33 @@ class MachineManager(QObject): extruder_stack.propertyChanged.disconnect(self._onPropertyChanged) extruder_stack.containersChanged.disconnect(self._onInstanceContainersChanged) + # update the local global container stack reference self._global_container_stack = Application.getInstance().getGlobalContainerStack() self.globalContainerChanged.emit() + # after switching the global stack we reconnect all the signals and set the variant and material references if self._global_container_stack: Preferences.getInstance().setValue("cura/active_machine", self._global_container_stack.getId()) + self._global_container_stack.nameChanged.connect(self._onMachineNameChanged) self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged) self._global_container_stack.propertyChanged.connect(self._onPropertyChanged) - if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1: - # For multi-extrusion machines, we do not want variant or material profiles in the stack, - # because these are extruder specific and may cause wrong values to be used for extruders - # that did not specify a value in the extruder. - global_variant = self._global_container_stack.variant - if global_variant != self._empty_variant_container: - self._global_container_stack.setVariant(self._empty_variant_container) + # set the global variant to empty as we now use the extruder stack at all times - CURA-4482 + global_variant = self._global_container_stack.variant + if global_variant != self._empty_variant_container: + self._global_container_stack.setVariant(self._empty_variant_container) - global_material = self._global_container_stack.material - if global_material != self._empty_material_container: - self._global_container_stack.setMaterial(self._empty_material_container) + # set the global material to empty as we now use the extruder stack at all times - CURA-4482 + global_material = self._global_container_stack.material + if global_material != self._empty_material_container: + self._global_container_stack.setMaterial(self._empty_material_container) - for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): #Listen for changes on all extruder stacks. - extruder_stack.propertyChanged.connect(self._onPropertyChanged) - extruder_stack.containersChanged.connect(self._onInstanceContainersChanged) - - else: - material = self._global_container_stack.material - material.nameChanged.connect(self._onMaterialNameChanged) - - quality = self._global_container_stack.quality - quality.nameChanged.connect(self._onQualityNameChanged) - - self._active_container_stack = self._global_container_stack - self.activeStackChanged.emit() + # Listen for changes on all extruder stacks + for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): + extruder_stack.propertyChanged.connect(self._onPropertyChanged) + extruder_stack.containersChanged.connect(self._onInstanceContainersChanged) self._error_check_timer.start() @@ -721,15 +716,16 @@ class MachineManager(QObject): ## Copy the value of the setting of the current extruder to all other extruders as well as the global container. @pyqtSlot(str) def copyValueToExtruders(self, key: str): - if not self._active_container_stack or self._global_container_stack.getProperty("machine_extruder_count", "value") <= 1: - return - new_value = self._active_container_stack.getProperty(key, "value") - stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] - stacks.append(self._global_container_stack) - for extruder_stack in stacks: + extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] + + # TODO: remove this - CURA-4482 + extruder_stacks.append(self._global_container_stack) + + # check in which stack the value has to be replaced + for extruder_stack in extruder_stacks: if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: - extruder_stack.getTop().setProperty(key, "value", new_value) + extruder_stack.userChanges.setProperty(key, "value", new_value) ## Set the active material by switching out a container # Depending on from/to material+current variant, a quality profile is chosen and set. From d6ef96a8250528a50ee6f7a0eb33b3d8ad6d62a3 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 14:07:47 +0200 Subject: [PATCH 10/67] Add todo comment --- cura/Settings/MachineManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index f547ccf4a7..68302186b7 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -725,7 +725,7 @@ class MachineManager(QObject): # check in which stack the value has to be replaced for extruder_stack in extruder_stacks: if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: - extruder_stack.userChanges.setProperty(key, "value", new_value) + extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved ## Set the active material by switching out a container # Depending on from/to material+current variant, a quality profile is chosen and set. From 8378c6f3c94a878ea7156057cc1d2d7addeacf18 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 14:30:18 +0200 Subject: [PATCH 11/67] Always add extruder quality changes when getting quality changes by name - CURA-4482 --- cura/Settings/MachineManager.py | 57 +++++++++++++++++---------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 68302186b7..26de5dba87 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -945,10 +945,8 @@ class MachineManager(QObject): quality_manager = QualityManager.getInstance() global_container_stack = self._global_container_stack - global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) - - quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name, - global_machine_definition) + global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition) + quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name, global_machine_definition) global_quality_changes = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") is None] if global_quality_changes: @@ -956,47 +954,52 @@ class MachineManager(QObject): else: Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name) return None + + # TODO: remove this - CURA-4482 material = global_container_stack.material - # For the global stack, find a quality which matches the quality_type in - # the quality changes profile and also satisfies any material constraints. + # find a quality type that matches both machine and materials quality_type = global_quality_changes.getMetaDataEntry("quality_type") - if global_container_stack.getProperty("machine_extruder_count", "value") > 1: - global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = True) - else: - global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) - if not global_quality: - global_quality = self._empty_quality_container - # Find the values for each extruder. extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - for stack in extruder_stacks: - extruder_definition = quality_manager.getParentMachineDefinition(stack.getBottom()) + # append the extruder quality changes + for extruder_stack in extruder_stacks: + extruder_definition = quality_manager.getParentMachineDefinition(extruder_stack.definition) + + quality_changes_list = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") == extruder_definition.getId()] - quality_changes_list = [qcp for qcp in quality_changes_profiles - if qcp.getMetaDataEntry("extruder") == extruder_definition.getId()] if quality_changes_list: quality_changes = quality_changes_list[0] + # TODO: remove this - CURA-4482 else: quality_changes = global_quality_changes if not quality_changes: quality_changes = self._empty_quality_changes_container - material = stack.material + material = extruder_stack.material quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) - if not quality: #No quality profile found for this quality type. + + if not quality: + # No quality profile found for this quality type. quality = self._empty_quality_container - result.append({"stack": stack, "quality": quality, "quality_changes": quality_changes}) + result.append({ + "stack": extruder_stack, + "quality": quality, + "quality_changes": quality_changes + }) - if extruder_stacks: - global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material], global_quality = "True") - if not global_quality: - global_quality = self._empty_quality_container - result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": global_quality_changes}) - else: - result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": global_quality_changes}) + # append the global quality changes + global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material], global_quality = True) + if not global_quality: + global_quality = self._empty_quality_container + + result.append({ + "stack": global_container_stack, + "quality": global_quality, + "quality_changes": global_quality_changes + }) return result From 38fdb5e56f72aa96ffc04f87e9877196fce05dd6 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 14:45:24 +0200 Subject: [PATCH 12/67] Refactor extruders model to always use extruder stacks + cleanup - CURA-4482 --- cura/Settings/ExtrudersModel.py | 72 +++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index b13e51723b..f2bb9f761c 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -66,18 +66,18 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): self._update_extruder_timer = QTimer() self._update_extruder_timer.setInterval(100) self._update_extruder_timer.setSingleShot(True) - self._update_extruder_timer.timeout.connect(self.__updateExtruders) + self._update_extruder_timer.connect(self.__updateExtruders) self._add_global = False self._simple_names = False - self._active_machine_extruders = [] # type: Iterable[ExtruderStack] + self._active_machine_extruders = [] # type: Iterable[ExtruderStack] self._add_optional_extruder = False - #Listen to changes. - Application.getInstance().globalContainerStackChanged.connect(self._extrudersChanged) #When the machine is swapped we must update the active machine extruders. - ExtruderManager.getInstance().extrudersChanged.connect(self._extrudersChanged) #When the extruders change we must link to the stack-changed signal of the new extruder. - self._extrudersChanged() #Also calls _updateExtruders. + # Listen to changes + Application.getInstance().globalContainerStackChanged.connect(self._extrudersChanged) # When the machine is swapped we must update the active machine extruders + ExtruderManager.getInstance().extrudersChanged.connect(self._extrudersChanged) # When the extruders change we must link to the stack-changed signal of the new extruder + self._extrudersChanged() # Also calls _updateExtruders def setAddGlobal(self, add): if add != self._add_global: @@ -128,21 +128,24 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): def _extrudersChanged(self, machine_id = None): if machine_id is not None: if Application.getInstance().getGlobalContainerStack() is None: - return #No machine, don't need to update the current machine's extruders. + # No machine, don't need to update the current machine's extruders + return if machine_id != Application.getInstance().getGlobalContainerStack().getId(): - return #Not the current machine. - #Unlink from old extruders. + # Not the current machine + return + + # Unlink from old extruders for extruder in self._active_machine_extruders: extruder.containersChanged.disconnect(self._onExtruderStackContainersChanged) - #Link to new extruders. + # Link to new extruders self._active_machine_extruders = [] extruder_manager = ExtruderManager.getInstance() for extruder in extruder_manager.getExtruderStacks(): extruder.containersChanged.connect(self._onExtruderStackContainersChanged) self._active_machine_extruders.append(extruder) - self._updateExtruders() #Since the new extruders may have different properties, update our own model. + self._updateExtruders() # Since the new extruders may have different properties, update our own model. def _onExtruderStackContainersChanged(self, container): # Update when there is an empty container or material change @@ -150,7 +153,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): # The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name self._updateExtruders() - modelChanged = pyqtSignal() def _updateExtruders(self): @@ -161,14 +163,17 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): # This should be called whenever the list of extruders changes. @UM.FlameProfiler.profile def __updateExtruders(self): - changed = False + extruders_changed = False if self.rowCount() != 0: - changed = True + extruders_changed = True items = [] + global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: + + # TODO: remove this - CURA-4482 if self._add_global: material = global_container_stack.material color = material.getMetaDataEntry("color_code", default = self.defaultColors[0]) if material else self.defaultColors[0] @@ -180,40 +185,44 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): "definition": "" } items.append(item) - changed = True + extruders_changed = True + # get machine extruder count for verification machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value") - manager = ExtruderManager.getInstance() - for extruder in manager.getMachineExtruders(global_container_stack.getId()): + + for extruder in ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()): position = extruder.getMetaDataEntry("position", default = "0") # Get the position try: position = int(position) - except ValueError: #Not a proper int. + except ValueError: + # Not a proper int. position = -1 if position >= machine_extruder_count: continue - extruder_name = extruder.getName() - material = extruder.material - variant = extruder.variant - default_color = self.defaultColors[position] if position >= 0 and position < len(self.defaultColors) else self.defaultColors[0] - color = material.getMetaDataEntry("color_code", default = default_color) if material else default_color - item = { #Construct an item with only the relevant information. + default_color = self.defaultColors[position] if 0 <= position < len(self.defaultColors) else self.defaultColors[0] + color = extruder.material.getMetaDataEntry("color_code", default = default_color) if material else default_color + + # construct an item with only the relevant information + item = { "id": extruder.getId(), - "name": extruder_name, + "name": extruder.getName(), "color": color, "index": position, "definition": extruder.getBottom().getId(), - "material": material.getName() if material else "", - "variant": variant.getName() if variant else "", + "material": extruder.material.getName() if material else "", + "variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core } - items.append(item) - changed = True - if changed: + items.append(item) + extruders_changed = True + + if extruders_changed: + # sort by extruder index items.sort(key = lambda i: i["index"]) + # We need optional extruder to be last, so add it after we do sorting. - # This way we can simply intrepret the -1 of the index as the last item (which it now always is) + # This way we can simply interpret the -1 of the index as the last item (which it now always is) if self._add_optional_extruder: item = { "id": "", @@ -223,5 +232,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): "definition": "" } items.append(item) + self.setItems(items) self.modelChanged.emit() From 723f6ce2265c889cb3f5b4385420fc43cf7f3238 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 15:10:52 +0200 Subject: [PATCH 13/67] Only get extruder settings from extruder stack - CURA-4482 --- cura/Settings/ExtruderManager.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 50525b6aeb..0b48568364 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -398,16 +398,12 @@ class ExtruderManager(QObject): # \param setting_key \type{str} The setting to get the property of. # \param property \type{str} The property to get. # \return \type{List} the list of results - def getAllExtruderSettings(self, setting_key, property): - global_container_stack = Application.getInstance().getGlobalContainerStack() - if global_container_stack.getProperty("machine_extruder_count", "value") <= 1: - return [global_container_stack.getProperty(setting_key, property)] - + def getAllExtruderSettings(self, setting_key: str, prop: str): result = [] for index in self.extruderIds: extruder_stack_id = self.extruderIds[str(index)] - stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] - result.append(stack.getProperty(setting_key, property)) + extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] + result.append(extruder_stack.getProperty(setting_key, prop)) return result ## Gets the extruder stacks that are actually being used at the moment. From b91824aab1bad7391ab5b16623297e823b77e445 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 26 Oct 2017 17:54:36 +0200 Subject: [PATCH 14/67] Move towards making extruder manager a first class citizen - CURA-4482 --- cura/BuildVolume.py | 51 +++++++++-------- cura/ConvexHullDecorator.py | 21 ++++--- cura/CuraApplication.py | 27 +++++---- cura/Settings/CuraStackBuilder.py | 5 +- cura/Settings/ExtruderManager.py | 58 ++++++++++++++------ cura/Settings/ExtrudersModel.py | 2 +- cura/Settings/MachineManager.py | 2 +- cura/Settings/QualityAndUserProfilesModel.py | 42 +++----------- cura/Settings/SettingInheritanceManager.py | 21 ++++--- 9 files changed, 121 insertions(+), 108 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index e87bfebd94..ba9314477d 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -917,24 +917,23 @@ class BuildVolume(SceneNode): # which extruder to get the setting, if there are multiple extruders. # \param property The property to get from the setting. # \return The property of the specified setting in the specified extruder. - def _getSettingFromExtruder(self, setting_key, extruder_setting_key, property = "value"): - multi_extrusion = self._global_container_stack.getProperty("machine_extruder_count", "value") > 1 + def _getSettingFromExtruder(self, setting_key, extruder_setting_key, prop = "value"): + extruder_index = self._global_container_stack.getProperty(extruder_setting_key, "value") - if not multi_extrusion: - stack = self._global_container_stack + # TODO: remove this - CURA-4482 + if str(extruder_index) == "-1": # If extruder index is -1 use global instead + extruder_stack = self._global_container_stack else: - extruder_index = self._global_container_stack.getProperty(extruder_setting_key, "value") + extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)] + extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] - if str(extruder_index) == "-1": # If extruder index is -1 use global instead - stack = self._global_container_stack - else: - extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)] - stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] + value = extruder_stack.getProperty(setting_key, prop) + setting_type = extruder_stack.getProperty(setting_key, "type") - value = stack.getProperty(setting_key, property) - setting_type = stack.getProperty(setting_key, "type") + # default 0 for numerical values if not value and (setting_type == "int" or setting_type == "float"): return 0 + return value ## Convenience function to calculate the disallowed radius around the edge. @@ -945,6 +944,7 @@ class BuildVolume(SceneNode): def _getEdgeDisallowedSize(self): if not self._global_container_stack: return 0 + container_stack = self._global_container_stack used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks() @@ -953,26 +953,33 @@ class BuildVolume(SceneNode): return 0.1 # Return a very small value, so we do draw disallowed area's near the edges. adhesion_type = container_stack.getProperty("adhesion_type", "value") + if adhesion_type == "skirt": skirt_distance = self._getSettingFromAdhesionExtruder("skirt_gap") skirt_line_count = self._getSettingFromAdhesionExtruder("skirt_line_count") bed_adhesion_size = skirt_distance + (self._getSettingFromAdhesionExtruder("skirt_brim_line_width") * skirt_line_count) * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor") / 100.0 - if len(used_extruders) > 1: - for extruder_stack in used_extruders: - bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0 - #We don't create an additional line for the extruder we're printing the skirt with. - bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0 + + for extruder_stack in used_extruders: + bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0 + + # We don't create an additional line for the extruder we're printing the skirt with. + bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0 + elif adhesion_type == "brim": bed_adhesion_size = self._getSettingFromAdhesionExtruder("skirt_brim_line_width") * self._getSettingFromAdhesionExtruder("brim_line_count") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor") / 100.0 - if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1: - for extruder_stack in used_extruders: - bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0 - #We don't create an additional line for the extruder we're printing the brim with. - bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0 + + for extruder_stack in used_extruders: + bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0 + + # We don't create an additional line for the extruder we're printing the brim with. + bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0 + elif adhesion_type == "raft": bed_adhesion_size = self._getSettingFromAdhesionExtruder("raft_margin") + elif adhesion_type == "none": bed_adhesion_size = 0 + else: raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?") diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py index bfeb690192..50fa8ce7f6 100644 --- a/cura/ConvexHullDecorator.py +++ b/cura/ConvexHullDecorator.py @@ -302,24 +302,23 @@ class ConvexHullDecorator(SceneNodeDecorator): self._onChanged() ## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property). - def _getSettingProperty(self, setting_key, property = "value"): + def _getSettingProperty(self, setting_key, prop = "value"): per_mesh_stack = self._node.callDecoration("getStack") if per_mesh_stack: - return per_mesh_stack.getProperty(setting_key, property) - - multi_extrusion = self._global_stack.getProperty("machine_extruder_count", "value") > 1 - if not multi_extrusion: - return self._global_stack.getProperty(setting_key, property) + return per_mesh_stack.getProperty(setting_key, prop) extruder_index = self._global_stack.getProperty(setting_key, "limit_to_extruder") - if extruder_index == "-1": #No limit_to_extruder. + if extruder_index == "-1": + # No limit_to_extruder extruder_stack_id = self._node.callDecoration("getActiveExtruder") - if not extruder_stack_id: #Decoration doesn't exist. + if not extruder_stack_id: + # Decoration doesn't exist extruder_stack_id = ExtruderManager.getInstance().extruderIds["0"] extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] - return extruder_stack.getProperty(setting_key, property) - else: #Limit_to_extruder is set. The global stack handles this then. - return self._global_stack.getProperty(setting_key, property) + return extruder_stack.getProperty(setting_key, prop) + else: + # Limit_to_extruder is set. The global stack handles this then + return self._global_stack.getProperty(setting_key, prop) ## Returns true if node is a descendant or the same as the root node. def __isDescendant(self, root, node): diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 1680e7c6a6..7904066966 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -200,6 +200,7 @@ class CuraApplication(QtApplication): self._machine_action_manager = MachineActionManager.MachineActionManager() self._machine_manager = None # This is initialized on demand. + self._extruder_manager = None self._material_manager = None self._setting_inheritance_manager = None self._simple_mode_settings_manager = None @@ -259,20 +260,24 @@ class CuraApplication(QtApplication): # Since they are empty, they should never be serialized and instead just programmatically created. # We need them to simplify the switching between materials. empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + empty_variant_container = copy.deepcopy(empty_container) empty_variant_container._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._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._id = "empty_quality" empty_quality_container.setName("Not Supported") empty_quality_container.addMetaDataEntry("quality_type", "normal") empty_quality_container.addMetaDataEntry("type", "quality") ContainerRegistry.getInstance().addContainer(empty_quality_container) + empty_quality_changes_container = copy.deepcopy(empty_container) empty_quality_changes_container._id = "empty_quality_changes" empty_quality_changes_container.addMetaDataEntry("type", "quality_changes") @@ -413,7 +418,7 @@ class CuraApplication(QtApplication): def discardOrKeepProfileChangesClosed(self, option): if option == "discard": global_stack = self.getGlobalContainerStack() - for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): + for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()): extruder.getTop().clear() global_stack.getTop().clear() @@ -421,7 +426,7 @@ class CuraApplication(QtApplication): # before slicing. To ensure that slicer uses right settings values elif option == "keep": global_stack = self.getGlobalContainerStack() - for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): + for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()): user_extruder_container = extruder.getTop() if user_extruder_container: user_extruder_container.update() @@ -686,16 +691,13 @@ class CuraApplication(QtApplication): self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface...")) - # Initialise extruder so as to listen to global container stack changes before the first global container stack is set. - ExtruderManager.getInstance() + qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager) qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager) qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager) - qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", - self.getSettingInheritanceManager) - qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager", - self.getSimpleModeSettingsManager) - + qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager) + qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager) qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) + self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml")) self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles)) self.initializeEngine() @@ -717,6 +719,11 @@ class CuraApplication(QtApplication): self._machine_manager = MachineManager.createMachineManager() return self._machine_manager + def getExtruderManager(self, *args): + if self._extruder_manager is None: + self._extruder_manager = ExtruderManager.createExtruderManager() + return self._extruder_manager + def getMaterialManager(self, *args): if self._material_manager is None: self._material_manager = MaterialManager.createMaterialManager() @@ -784,7 +791,7 @@ class CuraApplication(QtApplication): actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml"))) qmlRegisterSingletonType(actions_url, "Cura", 1, 0, "Actions") - engine.rootContext().setContextProperty("ExtruderManager", ExtruderManager.getInstance()) + # engine.rootContext().setContextProperty("ExtruderManager", ExtruderManager.getInstance()) for path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.QmlFiles): type_name = os.path.splitext(os.path.basename(path))[0] diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index 09815da319..e3774b0153 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -62,6 +62,7 @@ class CuraStackBuilder: variant = "default", next_stack = new_global_stack ) + new_global_stack.addExtruder(new_extruder) return new_global_stack @@ -79,7 +80,9 @@ class CuraStackBuilder: stack.setName(definition.getName()) stack.setDefinition(definition) stack.addMetaDataEntry("position", definition.getMetaDataEntry("position")) - if "next_stack" in kwargs: #Add stacks before containers are added, since they may trigger a setting update. + + if "next_stack" in kwargs: + # Add stacks before containers are added, since they may trigger a setting update. stack.setNextStack(kwargs["next_stack"]) user_container = InstanceContainer(new_stack_id + "_user") diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 0b48568364..a32b333326 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -83,18 +83,23 @@ class ExtruderManager(QObject): @pyqtProperty("QVariantMap", notify = extrudersChanged) def extruderIds(self): extruder_stack_ids = {} + global_stack_id = Application.getInstance().getGlobalContainerStack().getId() - extruder_stack_ids["-1"] = global_stack_id + + # TODO: remove this? - CURA-4482 + # extruder_stack_ids["-1"] = global_stack_id + if global_stack_id in self._extruder_trains: for position in self._extruder_trains[global_stack_id]: extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId() + return extruder_stack_ids @pyqtSlot(str, result = str) - def getQualityChangesIdByExtruderStackId(self, id: str) -> str: + def getQualityChangesIdByExtruderStackId(self, extruder_stack_id: str) -> str: for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]: extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position] - if extruder.getId() == id: + if extruder.getId() == extruder_stack_id: return extruder.qualityChanges.getId() ## The instance of the singleton pattern. @@ -102,6 +107,10 @@ class ExtruderManager(QObject): # It's None if the extruder manager hasn't been created yet. __instance = None + @staticmethod + def createExtruderManager(): + return ExtruderManager() + ## Gets an instance of the extruder manager, or creates one if no instance # exists yet. # @@ -420,20 +429,21 @@ class ExtruderManager(QObject): global_stack = Application.getInstance().getGlobalContainerStack() container_registry = ContainerRegistry.getInstance() - if global_stack.getProperty("machine_extruder_count", "value") <= 1: #For single extrusion. - return [global_stack] - used_extruder_stack_ids = set() - #Get the extruders of all meshes in the scene. + # Get the extruders of all meshes in the scene support_enabled = False support_bottom_enabled = False support_roof_enabled = False + scene_root = Application.getInstance().getController().getScene().getRoot() - meshes = [node for node in DepthFirstIterator(scene_root) if type(node) is SceneNode and node.isSelectable()] #Only use the nodes that will be printed. + + # Get the extruders of all printable meshes in the scene + meshes = [node for node in DepthFirstIterator(scene_root) if type(node) is SceneNode and node.isSelectable()] for mesh in meshes: extruder_stack_id = mesh.callDecoration("getActiveExtruder") - if not extruder_stack_id: #No per-object settings for this node. + if not extruder_stack_id: + # No per-object settings for this node extruder_stack_id = self.extruderIds["0"] used_extruder_stack_ids.add(extruder_stack_id) @@ -469,9 +479,10 @@ class ExtruderManager(QObject): if support_roof_enabled: used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_roof_extruder_nr", "value"))]) - #The platform adhesion extruder. Not used if using none. + # The platform adhesion extruder. Not used if using none. if global_stack.getProperty("adhesion_type", "value") != "none": used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("adhesion_extruder_nr", "value"))]) + try: return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids] except IndexError: # One or more of the extruders was not found. @@ -518,10 +529,6 @@ class ExtruderManager(QObject): result = [] machine_extruder_count = global_stack.getProperty("machine_extruder_count", "value") - # In case the printer is using one extruder, shouldn't exist active extruder stacks - # if machine_extruder_count == 1: - # return result - if global_stack and global_stack.getId() in self._extruder_trains: for extruder in sorted(self._extruder_trains[global_stack.getId()]): result.append(self._extruder_trains[global_stack.getId()][extruder]) @@ -544,8 +551,23 @@ class ExtruderManager(QObject): ## Adds the extruders of the currently active machine. def _addCurrentMachineExtruders(self) -> None: global_stack = Application.getInstance().getGlobalContainerStack() + extruders_changed = False + if global_stack and global_stack.getBottom(): - self.addMachineExtruders(global_stack.getBottom(), global_stack.getId()) + container_registry = ContainerRegistry.getInstance() + machine_id = global_stack.getBottom().getId() + + # Gets the extruder trains that we just created as well as any that still existed. + extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = machine_id) + for extruder_train in extruder_trains: + self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train + + # regardless of what the next stack is, we have to set it again, because of signal routing. + extruder_train.setNextStack(global_stack) + extruders_changed = True + + if extruders_changed: + self.extrudersChanged.emit(machine_id) ## Get all extruder values for a certain setting. # @@ -560,7 +582,7 @@ class ExtruderManager(QObject): global_stack = Application.getInstance().getGlobalContainerStack() result = [] - for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): + for extruder in ExtruderManager.getMachineExtruders(global_stack.getId()): # only include values from extruders that are "active" for the current machine instance if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value"): continue @@ -600,7 +622,7 @@ class ExtruderManager(QObject): } result = [] - for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): + for extruder in ExtruderManager.getMachineExtruders(global_stack.getId()): # only include values from extruders that are "active" for the current machine instance if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context): continue @@ -624,7 +646,7 @@ class ExtruderManager(QObject): # # This is exposed to qml for display purposes # - # \param key The key of the setting to retieve values for. + # \param key The key of the setting to retrieve values for. # # \return String representing the extruder values @pyqtSlot(str, result="QVariant") diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index f2bb9f761c..996bf51138 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -66,7 +66,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): self._update_extruder_timer = QTimer() self._update_extruder_timer.setInterval(100) self._update_extruder_timer.setSingleShot(True) - self._update_extruder_timer.connect(self.__updateExtruders) + self._update_extruder_timer.timeout.connect(self.__updateExtruders) self._add_global = False self._simple_names = False diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 26de5dba87..c4be25abd9 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1173,7 +1173,7 @@ class MachineManager(QObject): return containers[0].getBottom().getId() @staticmethod - def createMachineManager(engine=None, script_engine=None): + def createMachineManager(): return MachineManager() @deprecated("Use ExtruderStack.material = ... and it won't be necessary", "2.7") diff --git a/cura/Settings/QualityAndUserProfilesModel.py b/cura/Settings/QualityAndUserProfilesModel.py index 9d7d913d5e..2e181c6031 100644 --- a/cura/Settings/QualityAndUserProfilesModel.py +++ b/cura/Settings/QualityAndUserProfilesModel.py @@ -22,47 +22,23 @@ class QualityAndUserProfilesModel(ProfilesModel): # Fetch the list of quality changes. quality_manager = QualityManager.getInstance() - machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) + machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition) quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition) - # Detecting if the machine has multiple extrusion - multiple_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 - # Get the list of extruders extruder_manager = ExtruderManager.getInstance() active_extruder = extruder_manager.getActiveExtruderStack() - extruder_stacks = extruder_manager.getActiveExtruderStacks() - if multiple_extrusion: - # Place the active extruder at the front of the list. - # This is a workaround checking if there is an active_extruder or not before moving it to the front of the list. - # Actually, when a printer has multiple extruders, should exist always an active_extruder. However, in some - # cases the active_extruder is still None. - if active_extruder in extruder_stacks: - extruder_stacks.remove(active_extruder) - new_extruder_stacks = [] - if active_extruder is not None: - new_extruder_stacks = [active_extruder] - else: - # if there is no active extruder, use the first one in the active extruder stacks - active_extruder = extruder_stacks[0] - extruder_stacks = new_extruder_stacks + extruder_stacks + extruder_stacks = self._getOrderedExtruderStacksList() - # Fetch the list of useable qualities across all extruders. + # Fetch the list of usable qualities across all extruders. # The actual list of quality profiles come from the first extruder in the extruder list. - quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, - extruder_stacks) + quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks) # Filter the quality_change by the list of available quality_types quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list]) - - if multiple_extrusion: - # If the printer has multiple extruders then quality changes related to the current extruder are kept - filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and - qc.getMetaDataEntry("extruder") is not None and - (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or - qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())] - else: - # If not, the quality changes of the global stack are selected - filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and - qc.getMetaDataEntry("extruder") is None] + filtered_quality_changes = [qc for qc in quality_changes_list if + qc.getMetaDataEntry("quality_type") in quality_type_set and + qc.getMetaDataEntry("extruder") is not None and + (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or + qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())] return quality_list + filtered_quality_changes diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py index 8c9f5b20d6..00f637d34c 100644 --- a/cura/Settings/SettingInheritanceManager.py +++ b/cura/Settings/SettingInheritanceManager.py @@ -47,21 +47,20 @@ class SettingInheritanceManager(QObject): @pyqtSlot(str, str, result = "QStringList") def getOverridesForExtruder(self, key, extruder_index): - multi_extrusion = self._global_container_stack.getProperty("machine_extruder_count", "value") > 1 - if not multi_extrusion: - return self._settings_with_inheritance_warning - extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index) - if not extruder: - Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index) - return [] + result = [] - definitions = self._global_container_stack.definition.findDefinitions(key=key) + extruder_stack = ExtruderManager.getInstance().getExtruderStack(extruder_index) + if not extruder_stack: + Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index) + return result + + definitions = self._global_container_stack.definition.findDefinitions(key = key) if not definitions: Logger.log("w", "Could not find definition for key [%s] (2)", key) - return [] - result = [] + return result + for key in definitions[0].getAllKeys(): - if self._settingIsOverwritingInheritance(key, extruder): + if self._settingIsOverwritingInheritance(key, extruder_stack): result.append(key) return result From d718e6e36c3b44c9a4e81618a031bc7f727bfc18 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 09:08:20 +0100 Subject: [PATCH 15/67] Create extruder stack for single extruder machines on start - CURA-4482 --- cura/Settings/CuraContainerRegistry.py | 60 +++- cura/Settings/CuraStackBuilder.py | 2 +- cura/Settings/ExtruderManager.py | 334 ++++++++++----------- cura/Settings/ExtrudersModel.py | 4 +- cura/Settings/MachineManager.py | 15 +- cura/Settings/SettingInheritanceManager.py | 4 +- cura/Settings/SettingOverrideDecorator.py | 6 +- resources/definitions/101Hero.def.json | 4 - resources/definitions/3dator.def.json | 7 +- resources/qml/Cura.qml | 2 +- resources/qml/ExtruderButton.qml | 2 +- resources/qml/Menus/ContextMenu.qml | 2 +- resources/qml/Menus/MaterialMenu.qml | 16 +- resources/qml/Menus/NozzleMenu.qml | 14 +- resources/qml/Preferences/ProfilesPage.qml | 2 +- resources/qml/PrintMonitor.qml | 2 +- resources/qml/Settings/SettingItem.qml | 2 +- resources/qml/Settings/SettingView.qml | 8 +- resources/qml/SidebarHeader.qml | 4 +- resources/qml/SidebarSimple.qml | 2 +- 20 files changed, 268 insertions(+), 224 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 12f27e8156..26dc8b775e 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -12,6 +12,7 @@ from PyQt5.QtWidgets import QMessageBox from UM.Decorators import override from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerStack import ContainerStack +from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.InstanceContainer import InstanceContainer from UM.Application import Application from UM.Logger import Logger @@ -42,12 +43,13 @@ class CuraContainerRegistry(ContainerRegistry): # Global stack based on metadata information. @override(ContainerRegistry) def addContainer(self, container): + # Note: Intentional check with type() because we want to ignore subclasses if type(container) == ContainerStack: container = self._convertContainerStack(container) if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()): - #Check against setting version of the definition. + # Check against setting version of the definition. required_setting_version = CuraApplication.SettingVersion actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0)) if required_setting_version != actual_setting_version: @@ -256,7 +258,8 @@ class CuraContainerRegistry(ContainerRegistry): @override(ContainerRegistry) def load(self): super().load() - self._fixupExtruders() + self._registerSingleExtrusionMachinesExtruderStacks() + self._connectUpgradedExtruderStacksToMachines() ## Update an imported profile to match the current machine configuration. # @@ -357,8 +360,8 @@ class CuraContainerRegistry(ContainerRegistry): return global_container_stack.material.getId() return "" - ## Returns true if the current machien requires its own quality profiles - # \return true if the current machien requires its own quality profiles + ## Returns true if the current machine requires its own quality profiles + # \return true if the current machine requires its own quality profiles def _machineHasOwnQualities(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: @@ -391,12 +394,59 @@ class CuraContainerRegistry(ContainerRegistry): return new_stack + def _registerSingleExtrusionMachinesExtruderStacks(self): + machines = ContainerRegistry.getInstance().findContainerStacks(machine_extruder_trains = {"0": "fdmextruder"}) + for machine in machines: + self._addExtruderStackForSingleExtrusionMachine(machine, "fdmextruder") + + def _addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id): + new_extruder_id = extruder_id + + if machine.extruders and len(machine.extruders) > 0: + new_extruder_id = machine.extruders["0"].getId() + + extruder_definitions = self.findDefinitionContainers(id = new_extruder_id) + + if not extruder_definitions: + Logger.log("w", "Could not find definition containers for extruder %s", new_extruder_id) + return + + extruder_definition = extruder_definitions[0] + unique_name = self.uniqueName(machine.getId() + " " + new_extruder_id) + + extruder_stack = ExtruderStack.ExtruderStack(unique_name) + extruder_stack.setName(extruder_definition.getName()) + extruder_stack.setDefinition(extruder_definition) + extruder_stack.addMetaDataEntry("machine", machine.getId()) + extruder_stack.addMetaDataEntry("position", "0") + extruder_stack.setNextStack(machine) + + # if machine.userChanges: + # # set existing user changes if found + # extruder_stack.setUserChanges(machine.userChanges) + # else: + # # create empty user changes container otherwise + # user_container = InstanceContainer(extruder_stack.getId() + "_user") + # user_container.addMetaDataEntry("type", "user") + # user_container.addMetaDataEntry("machine", extruder_stack.getId()) + # from cura.CuraApplication import CuraApplication + # user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) + # user_container.setDefinition(extruder_definition) + # extruder_stack.setUserChanges(user_container) + # self.addContainer(user_container) + + # extruder_stack.setVariantById("default") + # extruder_stack.setMaterialById("default") + # extruder_stack.setQualityById("default") + + self.addContainer(extruder_stack) + # Fix the extruders that were upgraded to ExtruderStack instances during addContainer. # The stacks are now responsible for setting the next stack on deserialize. However, # due to problems with loading order, some stacks may not have the proper next stack # set after upgrading, because the proper global stack was not yet loaded. This method # makes sure those extruders also get the right stack set. - def _fixupExtruders(self): + def _connectUpgradedExtruderStacksToMachines(self): extruder_stacks = self.findContainers(ExtruderStack.ExtruderStack) for extruder_stack in extruder_stacks: if extruder_stack.getNextStack(): diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index e3774b0153..368fd9847d 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -62,7 +62,7 @@ class CuraStackBuilder: variant = "default", next_stack = new_global_stack ) - new_global_stack.addExtruder(new_extruder) + # new_global_stack.addExtruder(new_extruder) return new_global_stack diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index a32b333326..97b622132a 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -1,21 +1,18 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant #For communicating data and events to Qt. +from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt. from UM.FlameProfiler import pyqtSlot -from UM.Application import Application #To get the global container stack to find the current machine. +from UM.Application import Application # To get the global container stack to find the current machine. from UM.Logger import Logger -from UM.Decorators import deprecated from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.SceneNode import SceneNode from UM.Scene.Selection import Selection from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator -from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers by ID. -from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID. from UM.Settings.SettingFunction import SettingFunction from UM.Settings.ContainerStack import ContainerStack -from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext from typing import Optional, List, TYPE_CHECKING, Union @@ -214,39 +211,39 @@ class ExtruderManager(QObject): result.append(self.getExtruderStack(i)) return result - ## Adds all extruders of a specific machine definition to the extruder - # manager. + # ## Adds all extruders of a specific machine definition to the extruder + # # manager. + # # + # # \param machine_definition The machine definition to add the extruders for. + # # \param machine_id The machine_id to add the extruders for. + # @deprecated("Use CuraStackBuilder", "2.6") + # def addMachineExtruders(self, machine_definition: DefinitionContainerInterface, machine_id: str) -> None: + # changed = False + # machine_definition_id = machine_definition.getId() + # if machine_id not in self._extruder_trains: + # self._extruder_trains[machine_id] = { } + # changed = True + # container_registry = ContainerRegistry.getInstance() + # if container_registry: + # # Add the extruder trains that don't exist yet. + # for extruder_definition in container_registry.findDefinitionContainers(machine = machine_definition_id): + # position = extruder_definition.getMetaDataEntry("position", None) + # if not position: + # Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.getId()) + # if not container_registry.findContainerStacks(machine = machine_id, position = position): # Doesn't exist yet. + # self.createExtruderTrain(extruder_definition, machine_definition, position, machine_id) + # changed = True # - # \param machine_definition The machine definition to add the extruders for. - # \param machine_id The machine_id to add the extruders for. - @deprecated("Use CuraStackBuilder", "2.6") - def addMachineExtruders(self, machine_definition: DefinitionContainerInterface, machine_id: str) -> None: - changed = False - machine_definition_id = machine_definition.getId() - if machine_id not in self._extruder_trains: - self._extruder_trains[machine_id] = { } - changed = True - container_registry = ContainerRegistry.getInstance() - if container_registry: - # Add the extruder trains that don't exist yet. - for extruder_definition in container_registry.findDefinitionContainers(machine = machine_definition_id): - position = extruder_definition.getMetaDataEntry("position", None) - if not position: - Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.getId()) - if not container_registry.findContainerStacks(machine = machine_id, position = position): # Doesn't exist yet. - self.createExtruderTrain(extruder_definition, machine_definition, position, machine_id) - changed = True - - # Gets the extruder trains that we just created as well as any that still existed. - extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = machine_id) - for extruder_train in extruder_trains: - self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train - - # regardless of what the next stack is, we have to set it again, because of signal routing. - extruder_train.setNextStack(Application.getInstance().getGlobalContainerStack()) - changed = True - if changed: - self.extrudersChanged.emit(machine_id) + # # Gets the extruder trains that we just created as well as any that still existed. + # extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = machine_id) + # for extruder_train in extruder_trains: + # self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train + # + # # regardless of what the next stack is, we have to set it again, because of signal routing. + # extruder_train.setNextStack(Application.getInstance().getGlobalContainerStack()) + # changed = True + # if changed: + # self.extrudersChanged.emit(machine_id) def registerExtruder(self, extruder_train, machine_id): changed = False @@ -267,137 +264,137 @@ class ExtruderManager(QObject): if changed: self.extrudersChanged.emit(machine_id) - ## Creates a container stack for an extruder train. + # ## Creates a container stack for an extruder train. + # # + # # The container stack has an extruder definition at the bottom, which is + # # linked to a machine definition. Then it has a variant profile, a material + # # profile, a quality profile and a user profile, in that order. + # # + # # The resulting container stack is added to the registry. + # # + # # \param extruder_definition The extruder to create the extruder train for. + # # \param machine_definition The machine that the extruder train belongs to. + # # \param position The position of this extruder train in the extruder slots of the machine. + # # \param machine_id The id of the "global" stack this extruder is linked to. + # @deprecated("Use CuraStackBuilder::createExtruderStack", "2.6") + # def createExtruderTrain(self, extruder_definition: DefinitionContainerInterface, machine_definition: DefinitionContainerInterface, + # position, machine_id: str) -> None: + # # Cache some things. + # container_registry = ContainerRegistry.getInstance() + # machine_definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_definition) # - # The container stack has an extruder definition at the bottom, which is - # linked to a machine definition. Then it has a variant profile, a material - # profile, a quality profile and a user profile, in that order. + # # Create a container stack for this extruder. + # extruder_stack_id = container_registry.uniqueName(extruder_definition.getId()) + # container_stack = ContainerStack(extruder_stack_id) + # container_stack.setName(extruder_definition.getName()) # Take over the display name to display the stack with. + # container_stack.addMetaDataEntry("type", "extruder_train") + # container_stack.addMetaDataEntry("machine", machine_id) + # container_stack.addMetaDataEntry("position", position) + # container_stack.addContainer(extruder_definition) # - # The resulting container stack is added to the registry. + # # Find the variant to use for this extruder. + # variant = container_registry.findInstanceContainers(id = "empty_variant")[0] + # if machine_definition.getMetaDataEntry("has_variants"): + # # First add any variant. Later, overwrite with preference if the preference is valid. + # variants = container_registry.findInstanceContainers(definition = machine_definition_id, type = "variant") + # if len(variants) >= 1: + # variant = variants[0] + # preferred_variant_id = machine_definition.getMetaDataEntry("preferred_variant") + # if preferred_variant_id: + # preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, definition = machine_definition_id, type = "variant") + # if len(preferred_variants) >= 1: + # variant = preferred_variants[0] + # else: + # Logger.log("w", "The preferred variant \"%s\" of machine %s doesn't exist or is not a variant profile.", preferred_variant_id, machine_id) + # # And leave it at the default variant. + # container_stack.addContainer(variant) # - # \param extruder_definition The extruder to create the extruder train for. - # \param machine_definition The machine that the extruder train belongs to. - # \param position The position of this extruder train in the extruder slots of the machine. - # \param machine_id The id of the "global" stack this extruder is linked to. - @deprecated("Use CuraStackBuilder::createExtruderStack", "2.6") - def createExtruderTrain(self, extruder_definition: DefinitionContainerInterface, machine_definition: DefinitionContainerInterface, - position, machine_id: str) -> None: - # Cache some things. - container_registry = ContainerRegistry.getInstance() - machine_definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_definition) - - # Create a container stack for this extruder. - extruder_stack_id = container_registry.uniqueName(extruder_definition.getId()) - container_stack = ContainerStack(extruder_stack_id) - container_stack.setName(extruder_definition.getName()) # Take over the display name to display the stack with. - container_stack.addMetaDataEntry("type", "extruder_train") - container_stack.addMetaDataEntry("machine", machine_id) - container_stack.addMetaDataEntry("position", position) - container_stack.addContainer(extruder_definition) - - # Find the variant to use for this extruder. - variant = container_registry.findInstanceContainers(id = "empty_variant")[0] - if machine_definition.getMetaDataEntry("has_variants"): - # First add any variant. Later, overwrite with preference if the preference is valid. - variants = container_registry.findInstanceContainers(definition = machine_definition_id, type = "variant") - if len(variants) >= 1: - variant = variants[0] - preferred_variant_id = machine_definition.getMetaDataEntry("preferred_variant") - if preferred_variant_id: - preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, definition = machine_definition_id, type = "variant") - if len(preferred_variants) >= 1: - variant = preferred_variants[0] - else: - Logger.log("w", "The preferred variant \"%s\" of machine %s doesn't exist or is not a variant profile.", preferred_variant_id, machine_id) - # And leave it at the default variant. - container_stack.addContainer(variant) - - # Find a material to use for this variant. - material = container_registry.findInstanceContainers(id = "empty_material")[0] - if machine_definition.getMetaDataEntry("has_materials"): - # First add any material. Later, overwrite with preference if the preference is valid. - machine_has_variant_materials = machine_definition.getMetaDataEntry("has_variant_materials", default = False) - if machine_has_variant_materials or machine_has_variant_materials == "True": - materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id, variant = variant.getId()) - else: - materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id) - if len(materials) >= 1: - material = materials[0] - preferred_material_id = machine_definition.getMetaDataEntry("preferred_material") - if preferred_material_id: - global_stack = ContainerRegistry.getInstance().findContainerStacks(id = machine_id) - if global_stack: - approximate_material_diameter = str(round(global_stack[0].getProperty("material_diameter", "value"))) - else: - approximate_material_diameter = str(round(machine_definition.getProperty("material_diameter", "value"))) - - search_criteria = { "type": "material", "id": preferred_material_id, "approximate_diameter": approximate_material_diameter} - if machine_definition.getMetaDataEntry("has_machine_materials"): - search_criteria["definition"] = machine_definition_id - - if machine_definition.getMetaDataEntry("has_variants") and variant: - search_criteria["variant"] = variant.id - else: - search_criteria["definition"] = "fdmprinter" - - preferred_materials = container_registry.findInstanceContainers(**search_criteria) - if len(preferred_materials) >= 1: - # In some cases we get multiple materials. In that case, prefer materials that are marked as read only. - read_only_preferred_materials = [preferred_material for preferred_material in preferred_materials if preferred_material.isReadOnly()] - if len(read_only_preferred_materials) >= 1: - material = read_only_preferred_materials[0] - else: - material = preferred_materials[0] - else: - Logger.log("w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id) - # And leave it at the default material. - container_stack.addContainer(material) - - # Find a quality to use for this extruder. - quality = container_registry.getEmptyInstanceContainer() - - search_criteria = { "type": "quality" } - if machine_definition.getMetaDataEntry("has_machine_quality"): - search_criteria["definition"] = machine_definition_id - if machine_definition.getMetaDataEntry("has_materials") and material: - search_criteria["material"] = material.id - else: - search_criteria["definition"] = "fdmprinter" - - preferred_quality = machine_definition.getMetaDataEntry("preferred_quality") - if preferred_quality: - search_criteria["id"] = preferred_quality - - containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) - if not containers and preferred_quality: - Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id) - search_criteria.pop("id", None) - containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) - if containers: - quality = containers[0] - - container_stack.addContainer(quality) - - empty_quality_changes = container_registry.findInstanceContainers(id = "empty_quality_changes")[0] - container_stack.addContainer(empty_quality_changes) - - user_profile = container_registry.findInstanceContainers(type = "user", extruder = extruder_stack_id) - if user_profile: # There was already a user profile, loaded from settings. - user_profile = user_profile[0] - else: - user_profile = InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile. - user_profile.addMetaDataEntry("type", "user") - user_profile.addMetaDataEntry("extruder", extruder_stack_id) - from cura.CuraApplication import CuraApplication - user_profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) - user_profile.setDefinition(machine_definition) - container_registry.addContainer(user_profile) - container_stack.addContainer(user_profile) - - # regardless of what the next stack is, we have to set it again, because of signal routing. - container_stack.setNextStack(Application.getInstance().getGlobalContainerStack()) - - container_registry.addContainer(container_stack) + # # Find a material to use for this variant. + # material = container_registry.findInstanceContainers(id = "empty_material")[0] + # if machine_definition.getMetaDataEntry("has_materials"): + # # First add any material. Later, overwrite with preference if the preference is valid. + # machine_has_variant_materials = machine_definition.getMetaDataEntry("has_variant_materials", default = False) + # if machine_has_variant_materials or machine_has_variant_materials == "True": + # materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id, variant = variant.getId()) + # else: + # materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id) + # if len(materials) >= 1: + # material = materials[0] + # preferred_material_id = machine_definition.getMetaDataEntry("preferred_material") + # if preferred_material_id: + # global_stack = ContainerRegistry.getInstance().findContainerStacks(id = machine_id) + # if global_stack: + # approximate_material_diameter = str(round(global_stack[0].getProperty("material_diameter", "value"))) + # else: + # approximate_material_diameter = str(round(machine_definition.getProperty("material_diameter", "value"))) + # + # search_criteria = { "type": "material", "id": preferred_material_id, "approximate_diameter": approximate_material_diameter} + # if machine_definition.getMetaDataEntry("has_machine_materials"): + # search_criteria["definition"] = machine_definition_id + # + # if machine_definition.getMetaDataEntry("has_variants") and variant: + # search_criteria["variant"] = variant.id + # else: + # search_criteria["definition"] = "fdmprinter" + # + # preferred_materials = container_registry.findInstanceContainers(**search_criteria) + # if len(preferred_materials) >= 1: + # # In some cases we get multiple materials. In that case, prefer materials that are marked as read only. + # read_only_preferred_materials = [preferred_material for preferred_material in preferred_materials if preferred_material.isReadOnly()] + # if len(read_only_preferred_materials) >= 1: + # material = read_only_preferred_materials[0] + # else: + # material = preferred_materials[0] + # else: + # Logger.log("w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id) + # # And leave it at the default material. + # container_stack.addContainer(material) + # + # # Find a quality to use for this extruder. + # quality = container_registry.getEmptyInstanceContainer() + # + # search_criteria = { "type": "quality" } + # if machine_definition.getMetaDataEntry("has_machine_quality"): + # search_criteria["definition"] = machine_definition_id + # if machine_definition.getMetaDataEntry("has_materials") and material: + # search_criteria["material"] = material.id + # else: + # search_criteria["definition"] = "fdmprinter" + # + # preferred_quality = machine_definition.getMetaDataEntry("preferred_quality") + # if preferred_quality: + # search_criteria["id"] = preferred_quality + # + # containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) + # if not containers and preferred_quality: + # Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id) + # search_criteria.pop("id", None) + # containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) + # if containers: + # quality = containers[0] + # + # container_stack.addContainer(quality) + # + # empty_quality_changes = container_registry.findInstanceContainers(id = "empty_quality_changes")[0] + # container_stack.addContainer(empty_quality_changes) + # + # user_profile = container_registry.findInstanceContainers(type = "user", extruder = extruder_stack_id) + # if user_profile: # There was already a user profile, loaded from settings. + # user_profile = user_profile[0] + # else: + # user_profile = InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile. + # user_profile.addMetaDataEntry("type", "user") + # user_profile.addMetaDataEntry("extruder", extruder_stack_id) + # from cura.CuraApplication import CuraApplication + # user_profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) + # user_profile.setDefinition(machine_definition) + # container_registry.addContainer(user_profile) + # container_stack.addContainer(user_profile) + # + # # regardless of what the next stack is, we have to set it again, because of signal routing. + # container_stack.setNextStack(Application.getInstance().getGlobalContainerStack()) + # + # container_registry.addContainer(container_stack) def getAllExtruderValues(self, setting_key): return self.getAllExtruderSettings(setting_key, "value") @@ -545,7 +542,6 @@ class ExtruderManager(QObject): if self._active_extruder_index == -1: self.setActiveExtruderIndex(0) - self.activeExtruderChanged.emit() self.resetSelectedObjectExtruders() ## Adds the extruders of the currently active machine. @@ -562,7 +558,7 @@ class ExtruderManager(QObject): for extruder_train in extruder_trains: self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train - # regardless of what the next stack is, we have to set it again, because of signal routing. + # regardless of what the next stack is, we have to set it again, because of signal routing. ??? extruder_train.setNextStack(global_stack) extruders_changed = True @@ -582,7 +578,7 @@ class ExtruderManager(QObject): global_stack = Application.getInstance().getGlobalContainerStack() result = [] - for extruder in ExtruderManager.getMachineExtruders(global_stack.getId()): + for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): # only include values from extruders that are "active" for the current machine instance if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value"): continue diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index 996bf51138..40d13461cc 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -201,7 +201,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): continue default_color = self.defaultColors[position] if 0 <= position < len(self.defaultColors) else self.defaultColors[0] - color = extruder.material.getMetaDataEntry("color_code", default = default_color) if material else default_color + color = extruder.material.getMetaDataEntry("color_code", default = default_color) if extruder.material else default_color # construct an item with only the relevant information item = { @@ -210,7 +210,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): "color": color, "index": position, "definition": extruder.getBottom().getId(), - "material": extruder.material.getName() if material else "", + "material": extruder.material.getName() if extruder.material else "", "variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core } diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index c4be25abd9..bcc64fdefb 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -300,6 +300,17 @@ class MachineManager(QObject): if global_material != self._empty_material_container: self._global_container_stack.setMaterial(self._empty_material_container) + # TODO: update stack builder since this is not always a user created stack + # if len(self._global_container_stack.extruders) == 0: + # extruder_stack = CuraStackBuilder.createExtruderStack( + # self._global_container_stack.getId(), + # definition = self._global_container_stack.definition, + # machine_definition = self._global_container_stack.definition, + # ) + # extruder_stack.setNextStack(self._global_container_stack) + # extruder_stack.propertyChanged.connect(self._onPropertyChanged) + # extruder_stack.containersChanged.connect(self._onInstanceContainersChanged) + # Listen for changes on all extruder stacks for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): extruder_stack.propertyChanged.connect(self._onPropertyChanged) @@ -320,8 +331,8 @@ class MachineManager(QObject): old_active_container_stack = self._active_container_stack self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack() - if not self._active_container_stack: - self._active_container_stack = self._global_container_stack + # if not self._active_container_stack: + # self._active_container_stack = self._global_container_stack self._error_check_timer.start() diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py index 00f637d34c..0d4cd02cdb 100644 --- a/cura/Settings/SettingInheritanceManager.py +++ b/cura/Settings/SettingInheritanceManager.py @@ -77,8 +77,8 @@ class SettingInheritanceManager(QObject): def _onActiveExtruderChanged(self): new_active_stack = ExtruderManager.getInstance().getActiveExtruderStack() - if not new_active_stack: - new_active_stack = self._global_container_stack + # if not new_active_stack: + # new_active_stack = self._global_container_stack if new_active_stack != self._active_container_stack: # Check if changed if self._active_container_stack: # Disconnect signal from old container (if any) diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index 4e0893a35f..5026f9760d 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -27,11 +27,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): self._stack = PerObjectContainerStack(stack_id = id(self)) self._stack.setDirty(False) # This stack does not need to be saved. self._stack.addContainer(InstanceContainer(container_id = "SettingOverrideInstanceContainer")) - - if ExtruderManager.getInstance().extruderCount > 1: - self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId() - else: - self._extruder_stack = None + self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId() self._stack.propertyChanged.connect(self._onSettingChanged) diff --git a/resources/definitions/101Hero.def.json b/resources/definitions/101Hero.def.json index 3d19aef626..aaea743b47 100644 --- a/resources/definitions/101Hero.def.json +++ b/resources/definitions/101Hero.def.json @@ -7,10 +7,6 @@ "visible": true, "author": "rikky", "manufacturer": "101Hero", - "machine_extruder_trains": - { - "0": "fdmextruder" - }, "file_formats": "text/x-gcode", "platform": "101hero-platform.stl", "supports_usb_connection": true diff --git a/resources/definitions/3dator.def.json b/resources/definitions/3dator.def.json index b72a49a35b..513ee8f0e1 100644 --- a/resources/definitions/3dator.def.json +++ b/resources/definitions/3dator.def.json @@ -10,11 +10,7 @@ "file_formats": "text/x-gcode", "icon": "icon_ultimaker2", "supports_usb_connection": true, - "platform": "3dator_platform.stl", - "machine_extruder_trains": - { - "0": "fdmextruder" - } + "platform": "3dator_platform.stl" }, "overrides": { @@ -29,7 +25,6 @@ "layer_height": { "default_value": 0.2 }, "speed_print": { "default_value": 50 }, "speed_infill": { "default_value": 60 }, - "machine_extruder_count": { "default_value": 1 }, "machine_heated_bed": { "default_value": true }, "machine_center_is_zero": { "default_value": false }, "machine_height": { "default_value": 260 }, diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index cb0211f29f..325902e1be 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -177,7 +177,7 @@ UM.MainWindow MenuSeparator { } - MenuItem { text: catalog.i18nc("@action:inmenu", "Set as Active Extruder"); onTriggered: ExtruderManager.setActiveExtruderIndex(model.index) } + MenuItem { text: catalog.i18nc("@action:inmenu", "Set as Active Extruder"); onTriggered: Cura.ExtruderManager.setActiveExtruderIndex(model.index) } } onObjectAdded: settingsMenu.insertItem(index, object) onObjectRemoved: settingsMenu.removeItem(object) diff --git a/resources/qml/ExtruderButton.qml b/resources/qml/ExtruderButton.qml index 99196b0c9f..9212c705f7 100644 --- a/resources/qml/ExtruderButton.qml +++ b/resources/qml/ExtruderButton.qml @@ -18,7 +18,7 @@ Button style: UM.Theme.styles.tool_button; iconSource: UM.Theme.getIcon("extruder_button") - checked: ExtruderManager.selectedObjectExtruders.indexOf(extruder.id) != -1 + checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(extruder.id) != -1 enabled: UM.Selection.hasSelection property color customColor: base.hovered ? UM.Theme.getColor("button_hover") : UM.Theme.getColor("button"); diff --git a/resources/qml/Menus/ContextMenu.qml b/resources/qml/Menus/ContextMenu.qml index 39d497722f..b2c95ebcd8 100644 --- a/resources/qml/Menus/ContextMenu.qml +++ b/resources/qml/Menus/ContextMenu.qml @@ -31,7 +31,7 @@ Menu visible: base.shouldShowExtruders enabled: UM.Selection.hasSelection checkable: true - checked: ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1 + checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1 onTriggered: CuraActions.setExtruderForSelection(model.id) shortcut: "Ctrl+" + (model.index + 1) } diff --git a/resources/qml/Menus/MaterialMenu.qml b/resources/qml/Menus/MaterialMenu.qml index 359f4f41d0..1feb351bb1 100644 --- a/resources/qml/Menus/MaterialMenu.qml +++ b/resources/qml/Menus/MaterialMenu.qml @@ -72,16 +72,16 @@ Menu { text: model.name checkable: true - checked: model.id == Cura.MachineManager.allActiveMaterialIds[ExtruderManager.extruderIds[extruderIndex]] + checked: model.id == Cura.MachineManager.allActiveMaterialIds[Cura.ExtruderManager.extruderIds[extruderIndex]] exclusiveGroup: group onTriggered: { // This workaround is done because of the application menus for materials and variants for multiextrusion printers. // The extruder menu would always act on the correspoding extruder only, instead of acting on the extruder selected in the UI. - var activeExtruderIndex = ExtruderManager.activeExtruderIndex; - ExtruderManager.setActiveExtruderIndex(extruderIndex); + var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex; + Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex); Cura.MachineManager.setActiveMaterial(model.id); - ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); + Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); } } onObjectAdded: menu.insertItem(index, object) @@ -115,16 +115,16 @@ Menu { text: model.name checkable: true - checked: model.id == Cura.MachineManager.allActiveMaterialIds[ExtruderManager.extruderIds[extruderIndex]] + checked: model.id == Cura.MachineManager.allActiveMaterialIds[Cura.ExtruderManager.extruderIds[extruderIndex]] exclusiveGroup: group onTriggered: { // This workaround is done because of the application menus for materials and variants for multiextrusion printers. // The extruder menu would always act on the correspoding extruder only, instead of acting on the extruder selected in the UI. - var activeExtruderIndex = ExtruderManager.activeExtruderIndex; - ExtruderManager.setActiveExtruderIndex(extruderIndex); + var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex; + Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex); Cura.MachineManager.setActiveMaterial(model.id); - ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); + Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); } } onObjectAdded: brandMaterialsMenu.insertItem(index, object) diff --git a/resources/qml/Menus/NozzleMenu.qml b/resources/qml/Menus/NozzleMenu.qml index 1c75a346f3..9dead66499 100644 --- a/resources/qml/Menus/NozzleMenu.qml +++ b/resources/qml/Menus/NozzleMenu.qml @@ -38,15 +38,15 @@ Menu visible: printerConnected && Cura.MachineManager.printerOutputDevices[0].hotendIds.length > extruderIndex && !isClusterPrinter onTriggered: { - var activeExtruderIndex = ExtruderManager.activeExtruderIndex; - ExtruderManager.setActiveExtruderIndex(extruderIndex); + var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex; + Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex); var hotendId = Cura.MachineManager.printerOutputDevices[0].hotendIds[extruderIndex]; var itemIndex = nozzleInstantiator.model.find("name", hotendId); if(itemIndex > -1) { Cura.MachineManager.setActiveVariant(nozzleInstantiator.model.getItem(itemIndex).id); } - ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); + Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); } } @@ -69,14 +69,14 @@ Menu MenuItem { text: model.name checkable: true - checked: model.id == Cura.MachineManager.allActiveVariantIds[ExtruderManager.extruderIds[extruderIndex]] + checked: model.id == Cura.MachineManager.allActiveVariantIds[Cura.ExtruderManager.extruderIds[extruderIndex]] exclusiveGroup: group onTriggered: { - var activeExtruderIndex = ExtruderManager.activeExtruderIndex; - ExtruderManager.setActiveExtruderIndex(extruderIndex); + var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex; + Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex); Cura.MachineManager.setActiveVariant(model.id); - ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); + Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); } } onObjectAdded: menu.insertItem(index, object) diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index c7b6b3e933..e3ba9b23a4 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -208,7 +208,7 @@ UM.ManagementPage anchors.right: parent.right anchors.bottom: parent.bottom - currentIndex: ExtruderManager.extruderCount > 0 ? ExtruderManager.activeExtruderIndex + 1 : 0 + currentIndex: Cura.ExtruderManager.extruderCount > 0 ? Cura.ExtruderManager.activeExtruderIndex + 1 : 0 ProfileTab { diff --git a/resources/qml/PrintMonitor.qml b/resources/qml/PrintMonitor.qml index a2626e53de..34e68148b8 100644 --- a/resources/qml/PrintMonitor.qml +++ b/resources/qml/PrintMonitor.qml @@ -87,7 +87,7 @@ Column Label //Extruder name. { - text: ExtruderManager.getExtruderName(index) != "" ? ExtruderManager.getExtruderName(index) : catalog.i18nc("@label", "Extruder") + text: Cura.ExtruderManager.getExtruderName(index) != "" ? Cura.ExtruderManager.getExtruderName(index) : catalog.i18nc("@label", "Extruder") color: UM.Theme.getColor("text") font: UM.Theme.getFont("default") anchors.left: parent.left diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml index 2bf2c17273..6234e5f1f7 100644 --- a/resources/qml/Settings/SettingItem.qml +++ b/resources/qml/Settings/SettingItem.qml @@ -157,7 +157,7 @@ Item { var tooltipText = catalog.i18nc("@label", "This setting is always shared between all extruders. Changing it here will change the value for all extruders") + "."; if ((resolve != "None") && (stackLevel != 0)) { // We come here if a setting has a resolve and the setting is not manually edited. - tooltipText += " " + catalog.i18nc("@label", "The value is resolved from per-extruder values ") + "[" + ExtruderManager.getInstanceExtruderValues(definition.key) + "]."; + tooltipText += " " + catalog.i18nc("@label", "The value is resolved from per-extruder values ") + "[" + Cura.ExtruderManager.getInstanceExtruderValues(definition.key) + "]."; } base.showTooltip(tooltipText); } diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 56fd789564..93bb52de79 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -268,7 +268,7 @@ Item Behavior on opacity { NumberAnimation { duration: 100 } } enabled: { - if(!ExtruderManager.activeExtruderStackId && machineExtruderCount.properties.value > 1) + if (!Cura.ExtruderManager.activeExtruderStackId && machineExtruderCount.properties.value > 1) { // disable all controls on the global tab, except categories return model.type == "category" @@ -338,12 +338,12 @@ Item if(inheritStackProvider.properties.limit_to_extruder != null && inheritStackProvider.properties.limit_to_extruder >= 0) { //We have limit_to_extruder, so pick that stack. - return ExtruderManager.extruderIds[String(inheritStackProvider.properties.limit_to_extruder)]; + return Cura.ExtruderManager.extruderIds[String(inheritStackProvider.properties.limit_to_extruder)]; } - if(ExtruderManager.activeExtruderStackId) + if(Cura.ExtruderManager.activeExtruderStackId) { //We're on an extruder tab. Pick the current extruder. - return ExtruderManager.activeExtruderStackId; + return Cura.ExtruderManager.activeExtruderStackId; } //No extruder tab is selected. Pick the global stack. Shouldn't happen any more since we removed the global tab. return activeMachineId; diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml index f3887e2885..2ea5b0b3d2 100644 --- a/resources/qml/SidebarHeader.qml +++ b/resources/qml/SidebarHeader.qml @@ -14,7 +14,7 @@ Column { id: base; - property int currentExtruderIndex: ExtruderManager.activeExtruderIndex; + property int currentExtruderIndex: Cura.ExtruderManager.activeExtruderIndex; property bool currentExtruderVisible: extrudersList.visible; spacing: Math.floor(UM.Theme.getSize("sidebar_margin").width * 0.9) @@ -93,7 +93,7 @@ Column onClicked: { forceActiveFocus() // Changing focus applies the currently-being-typed values so it can change the displayed setting values. - ExtruderManager.setActiveExtruderIndex(index); + Cura.ExtruderManager.setActiveExtruderIndex(index); } style: ButtonStyle diff --git a/resources/qml/SidebarSimple.qml b/resources/qml/SidebarSimple.qml index 65a3d612dc..34780d2219 100644 --- a/resources/qml/SidebarSimple.qml +++ b/resources/qml/SidebarSimple.qml @@ -19,7 +19,7 @@ Item property Action configureSettings; property variant minimumPrintTime: PrintInformation.minimumPrintTime; property variant maximumPrintTime: PrintInformation.maximumPrintTime; - property bool settingsEnabled: ExtruderManager.activeExtruderStackId || machineExtruderCount.properties.value == 1 + property bool settingsEnabled: Cura.ExtruderManager.activeExtruderStackId || machineExtruderCount.properties.value == 1 Component.onCompleted: PrintInformation.enabled = true Component.onDestruction: PrintInformation.enabled = false From f36c9ffbc0d7100ba11cbd56b9aeb0a5ee4854bc Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 09:49:24 +0100 Subject: [PATCH 16/67] Create extruder stack for single extrusion machines when adding new machine - CURA-4482 --- cura/BuildVolume.py | 9 ++----- cura/Settings/CuraStackBuilder.py | 42 ++++++++++++++++++++++--------- cura/Settings/ExtruderManager.py | 5 ++++ cura/Settings/MachineManager.py | 2 -- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index ba9314477d..7362a5aff2 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -919,13 +919,8 @@ class BuildVolume(SceneNode): # \return The property of the specified setting in the specified extruder. def _getSettingFromExtruder(self, setting_key, extruder_setting_key, prop = "value"): extruder_index = self._global_container_stack.getProperty(extruder_setting_key, "value") - - # TODO: remove this - CURA-4482 - if str(extruder_index) == "-1": # If extruder index is -1 use global instead - extruder_stack = self._global_container_stack - else: - extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)] - extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] + extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)] + extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] value = extruder_stack.getProperty(setting_key, prop) setting_type = extruder_stack.getProperty(setting_key, "type") diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index 368fd9847d..b9fbbc43f0 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -47,22 +47,40 @@ class CuraStackBuilder: new_global_stack.setName(generated_name) - for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id): - position = extruder_definition.getMetaDataEntry("position", None) - if not position: - Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.id) + extruder_definition = registry.findDefinitionContainers(machine = machine_definition.getId()) - new_extruder_id = registry.uniqueName(extruder_definition.id) + if not extruder_definition: + # create extruder stack for single extrusion machines that have no separate extruder definition files + extruder_definition = registry.findDefinitionContainers(id = "fdmextruder") + new_extruder_id = registry.uniqueName(machine_definition.getId() + " " + "fdmextruder") new_extruder = cls.createExtruderStack( new_extruder_id, - definition = extruder_definition, - machine_definition = machine_definition, - quality = "default", - material = "default", - variant = "default", - next_stack = new_global_stack + definition=extruder_definition, + machine_definition=machine_definition, + quality="default", + material="default", + variant="default", + next_stack=new_global_stack ) - # new_global_stack.addExtruder(new_extruder) + new_global_stack.addExtruder(new_extruder) + else: + # create extruder stack for each found extruder definition + for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id): + position = extruder_definition.getMetaDataEntry("position", None) + if not position: + Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.id) + + new_extruder_id = registry.uniqueName(extruder_definition.id) + new_extruder = cls.createExtruderStack( + new_extruder_id, + definition = extruder_definition, + machine_definition = machine_definition, + quality = "default", + material = "default", + variant = "default", + next_stack = new_global_stack + ) + new_global_stack.addExtruder(new_extruder) return new_global_stack diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 97b622132a..38bc41813b 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -193,6 +193,7 @@ class ExtruderManager(QObject): if global_container_stack.getId() in self._extruder_trains: if str(self._active_extruder_index) in self._extruder_trains[global_container_stack.getId()]: return self._extruder_trains[global_container_stack.getId()][str(self._active_extruder_index)] + return None ## Get an extruder stack by index @@ -435,6 +436,10 @@ class ExtruderManager(QObject): scene_root = Application.getInstance().getController().getScene().getRoot() + # If no extruders are registered in the extruder manager yet, return an empty array + if len(self.extruderIds) == 0: + return [] + # Get the extruders of all printable meshes in the scene meshes = [node for node in DepthFirstIterator(scene_root) if type(node) is SceneNode and node.isSelectable()] for mesh in meshes: diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 13751794f0..0f3a690967 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -340,8 +340,6 @@ class MachineManager(QObject): old_active_container_stack = self._active_container_stack self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack() - # if not self._active_container_stack: - # self._active_container_stack = self._global_container_stack self._error_check_timer.start() From 4701518404a5243a28b9f342d6613c15ab8e705d Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 10:23:30 +0100 Subject: [PATCH 17/67] Add default position to fdm extruder definition - CURA-4482 --- cura/Settings/CuraContainerRegistry.py | 30 +++++++++++----------- cura/Settings/CuraStackBuilder.py | 2 +- resources/definitions/fdmextruder.def.json | 3 ++- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 11c4cf2d4a..6ce87aafea 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -424,23 +424,23 @@ class CuraContainerRegistry(ContainerRegistry): extruder_stack.addMetaDataEntry("position", "0") extruder_stack.setNextStack(machine) - # if machine.userChanges: - # # set existing user changes if found - # extruder_stack.setUserChanges(machine.userChanges) - # else: - # # create empty user changes container otherwise - # user_container = InstanceContainer(extruder_stack.getId() + "_user") - # user_container.addMetaDataEntry("type", "user") - # user_container.addMetaDataEntry("machine", extruder_stack.getId()) - # from cura.CuraApplication import CuraApplication - # user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) - # user_container.setDefinition(extruder_definition) - # extruder_stack.setUserChanges(user_container) - # self.addContainer(user_container) + if machine.userChanges: + # set existing user changes if found + extruder_stack.setUserChanges(machine.userChanges) + else: + # create empty user changes container otherwise + user_container = InstanceContainer(extruder_stack.getId() + "_user") + user_container.addMetaDataEntry("type", "user") + user_container.addMetaDataEntry("machine", extruder_stack.getId()) + from cura.CuraApplication import CuraApplication + user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) + user_container.setDefinition(extruder_definition) + extruder_stack.setUserChanges(user_container) + self.addContainer(user_container) # extruder_stack.setVariantById("default") - # extruder_stack.setMaterialById("default") - # extruder_stack.setQualityById("default") + extruder_stack.setMaterialById("default") + extruder_stack.setQualityById("default") self.addContainer(extruder_stack) diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index b9fbbc43f0..57eefa0397 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -51,7 +51,7 @@ class CuraStackBuilder: if not extruder_definition: # create extruder stack for single extrusion machines that have no separate extruder definition files - extruder_definition = registry.findDefinitionContainers(id = "fdmextruder") + extruder_definition = registry.findDefinitionContainers(id = "fdmextruder")[0] new_extruder_id = registry.uniqueName(machine_definition.getId() + " " + "fdmextruder") new_extruder = cls.createExtruderStack( new_extruder_id, diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json index 8ed194fc2d..ada35fafe0 100644 --- a/resources/definitions/fdmextruder.def.json +++ b/resources/definitions/fdmextruder.def.json @@ -8,7 +8,8 @@ "author": "Ultimaker", "manufacturer": "Unknown", "setting_version": 1, - "visible": false + "visible": false, + "position": "0" }, "settings": { From bc6984a0edacf5948c73c23ce2f694ac1c6c031a Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 13:19:28 +0100 Subject: [PATCH 18/67] Fixes for selecting active variant, material and quality - CURA-4482 --- cura/Settings/CuraContainerRegistry.py | 10 ++-- cura/Settings/CuraStackBuilder.py | 2 +- cura/Settings/ExtruderManager.py | 23 +++++---- cura/Settings/MachineManager.py | 65 +++++++++++++++----------- 4 files changed, 58 insertions(+), 42 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 6ce87aafea..4923a343a0 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -405,6 +405,7 @@ class CuraContainerRegistry(ContainerRegistry): def _addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id): new_extruder_id = extruder_id + # if extruders are defined in the machine definition use those instead if machine.extruders and len(machine.extruders) > 0: new_extruder_id = machine.extruders["0"].getId() @@ -415,13 +416,12 @@ class CuraContainerRegistry(ContainerRegistry): return extruder_definition = extruder_definitions[0] - unique_name = self.uniqueName(machine.getId() + " " + new_extruder_id) + unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) extruder_stack = ExtruderStack.ExtruderStack(unique_name) extruder_stack.setName(extruder_definition.getName()) extruder_stack.setDefinition(extruder_definition) - extruder_stack.addMetaDataEntry("machine", machine.getId()) - extruder_stack.addMetaDataEntry("position", "0") + extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) extruder_stack.setNextStack(machine) if machine.userChanges: @@ -429,7 +429,7 @@ class CuraContainerRegistry(ContainerRegistry): extruder_stack.setUserChanges(machine.userChanges) else: # create empty user changes container otherwise - user_container = InstanceContainer(extruder_stack.getId() + "_user") + user_container = InstanceContainer(extruder_stack.id + "_user") user_container.addMetaDataEntry("type", "user") user_container.addMetaDataEntry("machine", extruder_stack.getId()) from cura.CuraApplication import CuraApplication @@ -438,7 +438,7 @@ class CuraContainerRegistry(ContainerRegistry): extruder_stack.setUserChanges(user_container) self.addContainer(user_container) - # extruder_stack.setVariantById("default") + extruder_stack.setVariantById("default") extruder_stack.setMaterialById("default") extruder_stack.setQualityById("default") diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index 57eefa0397..a661237722 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -52,7 +52,7 @@ class CuraStackBuilder: if not extruder_definition: # create extruder stack for single extrusion machines that have no separate extruder definition files extruder_definition = registry.findDefinitionContainers(id = "fdmextruder")[0] - new_extruder_id = registry.uniqueName(machine_definition.getId() + " " + "fdmextruder") + new_extruder_id = registry.uniqueName(machine_definition.getName() + " " + extruder_definition.id) new_extruder = cls.createExtruderStack( new_extruder_id, definition=extruder_definition, diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 38bc41813b..40ac2f1c63 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -543,9 +543,8 @@ class ExtruderManager(QObject): self._global_container_stack_definition_id = global_container_stack.getBottom().getId() self.globalContainerStackDefinitionChanged.emit() - # If the global container changed, the number of extruders could be changed and so the active_extruder_index is updated - if self._active_extruder_index == -1: - self.setActiveExtruderIndex(0) + # If the global container changed, the machine changed and might have extruders that were not registered yet + self._addCurrentMachineExtruders() self.resetSelectedObjectExtruders() @@ -554,21 +553,29 @@ class ExtruderManager(QObject): global_stack = Application.getInstance().getGlobalContainerStack() extruders_changed = False - if global_stack and global_stack.getBottom(): + if global_stack: container_registry = ContainerRegistry.getInstance() - machine_id = global_stack.getBottom().getId() + global_stack_id = global_stack.getId() # Gets the extruder trains that we just created as well as any that still existed. - extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = machine_id) + extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = global_stack_id) + + # Make sure the extruder trains for the new machine can be placed in the set of sets + if global_stack_id not in self._extruder_trains: + self._extruder_trains[global_stack_id] = {} + extruders_changed = True + + # Register the extruder trains by position for extruder_train in extruder_trains: - self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train + self._extruder_trains[global_stack_id][extruder_train.getMetaDataEntry("position")] = extruder_train # regardless of what the next stack is, we have to set it again, because of signal routing. ??? extruder_train.setNextStack(global_stack) extruders_changed = True if extruders_changed: - self.extrudersChanged.emit(machine_id) + self.extrudersChanged.emit(global_stack_id) + self.setActiveExtruderIndex(0) ## Get all extruder values for a certain setting. # diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 0f3a690967..69435b7077 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -161,7 +161,7 @@ class MachineManager(QObject): @pyqtProperty(int, constant=True) def totalNumberOfSettings(self) -> int: - return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys()) + return len(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0].getAllKeys()) def _onHotendIdChanged(self, index: Union[str, int], hotend_id: str) -> None: if not self._global_container_stack: @@ -386,15 +386,6 @@ class MachineManager(QObject): else: Logger.log("w", "Failed creating a new machine!") - ## Create a name that is not empty and unique - # \param container_type \type{string} Type of the container (machine, quality, ...) - # \param current_name \type{} Current name of the container, which may be an acceptable option - # \param new_name \type{string} Base name, which may not be unique - # \param fallback_name \type{string} Name to use when (stripped) new_name is empty - # \return \type{string} Name that is unique for the specified type and name/id - def _createUniqueName(self, container_type: str, current_name: str, new_name: str, fallback_name: str) -> str: - return ContainerRegistry.getInstance().createUniqueName(container_type, current_name, new_name, fallback_name) - def _checkStacksHaveErrors(self) -> bool: if self._global_container_stack is None: #No active machine. return False @@ -950,15 +941,12 @@ class MachineManager(QObject): global_container_stack = self._global_container_stack if not global_container_stack: return [] + global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) - extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - if extruder_stacks: - stacks = extruder_stacks - else: - stacks = [global_container_stack] - for stack in stacks: + # find qualities for extruders + for stack in extruder_stacks: material = stack.material # TODO: fix this @@ -966,19 +954,33 @@ class MachineManager(QObject): material = self._new_material_container quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) + if not quality: # No quality profile is found for this quality type. quality = self._empty_quality_container - result.append({"stack": stack, "quality": quality, "quality_changes": empty_quality_changes}) - if extruder_stacks: - # Add an extra entry for the global stack. - global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = "True") + result.append({ + "stack": stack, + "quality": quality, + "quality_changes": empty_quality_changes + }) - if not global_quality: - global_quality = self._empty_quality_container + # also find a global quality for the machine + global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = True) - result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": empty_quality_changes}) + # if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482 + if not global_quality and len(extruder_stacks) == 1: + global_quality = result[0]["quality"] + + # if there is still no global quality, set it to empty (not supported) + if not global_quality: + global_quality = self._empty_quality_container + + result.append({ + "stack": global_container_stack, + "quality": global_quality, + "quality_changes": empty_quality_changes + }) return result @@ -1030,7 +1032,7 @@ class MachineManager(QObject): material = extruder_stack.material - if self._new_material_container and self._active_container_stack.getId() == stack.getId(): + if self._new_material_container and self._active_container_stack.getId() == extruder_stack.getId(): material = self._new_material_container quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) @@ -1047,6 +1049,12 @@ class MachineManager(QObject): # append the global quality changes global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material], global_quality = True) + + # if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482 + if not global_quality and len(extruder_stacks) == 1: + global_quality = result[0]["quality_changes"] + + # if still no global quality changes are found we set it to empty (not supported) if not global_quality: global_quality = self._empty_quality_container @@ -1163,10 +1171,11 @@ class MachineManager(QObject): @pyqtSlot(str, str) def renameMachine(self, machine_id: str, new_name: str): - containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id) - if containers: - new_name = self._createUniqueName("machine", containers[0].getName(), new_name, containers[0].getBottom().getName()) - containers[0].setName(new_name) + container_registry = ContainerRegistry.getInstance() + machine_stack = container_registry.findContainerStacks(id = machine_id) + if machine_stack: + new_name = container_registry.createUniqueName("machine", machine_stack[0].getName(), new_name, machine_stack[0].getBottom().getName()) + machine_stack[0].setName(new_name) self.globalContainerChanged.emit() @pyqtSlot(str) From 6ac81635897d338f79270997c38d772a42114a55 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 13:43:01 +0100 Subject: [PATCH 19/67] Cleanup CuraApplication.py - CURA-4482 --- cura/CuraApplication.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 292ad93121..a3bf4bf462 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -786,7 +786,6 @@ class CuraApplication(QtApplication): qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type") qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") - qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel") qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel) qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel") @@ -796,15 +795,12 @@ class CuraApplication(QtApplication): qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(UserChangesModel, "Cura", 1, 1, "UserChangesModel") - qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager) # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work. actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml"))) qmlRegisterSingletonType(actions_url, "Cura", 1, 0, "Actions") - # engine.rootContext().setContextProperty("ExtruderManager", ExtruderManager.getInstance()) - for path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.QmlFiles): type_name = os.path.splitext(os.path.basename(path))[0] if type_name in ("Cura", "Actions"): From dfe0212a413df932ceb5d9eb615155a49c45a208 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 13:48:08 +0100 Subject: [PATCH 20/67] Comment out unneeded materials fetching --- cura/Settings/CuraContainerRegistry.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 4923a343a0..986792c6bb 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -302,10 +302,13 @@ class CuraContainerRegistry(ContainerRegistry): machine_definition = Application.getInstance().getGlobalContainerStack().getBottom() del quality_type_criteria["definition"] - materials = None + + # materials = None + if "material" in quality_type_criteria: - materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"]) + # materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"]) del quality_type_criteria["material"] + # Do not filter quality containers here with materials because we are trying to import a profile, so it should # NOT be restricted by the active materials on the current machine. materials = None From 359514e08b4a243be8d8228b00f28da1bb7f0427 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 13:49:04 +0100 Subject: [PATCH 21/67] Cleanup CuraContainerRegistry.py - CURA-4482 --- cura/Settings/CuraContainerRegistry.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 986792c6bb..d69a97c92f 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -12,13 +12,12 @@ from PyQt5.QtWidgets import QMessageBox from UM.Decorators import override from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerStack import ContainerStack -from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.InstanceContainer import InstanceContainer from UM.Application import Application from UM.Logger import Logger from UM.Message import Message from UM.Platform import Platform -from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with. +from UM.PluginRegistry import PluginRegistry # For getting the possible profile writers to write with. from UM.Util import parseBool from . import ExtruderStack From 569047693e2f5a7ac38d051a59a44c12c2bddb1d Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 13:55:47 +0100 Subject: [PATCH 22/67] Rename loop variable to be more explicit - CURA-4482 --- cura/Settings/MachineManager.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 69435b7077..a858f15cfc 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -946,11 +946,11 @@ class MachineManager(QObject): extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() # find qualities for extruders - for stack in extruder_stacks: - material = stack.material + for extruder_stack in extruder_stacks: + material = extruder_stack.material # TODO: fix this - if self._new_material_container and stack.getId() == self._active_container_stack.getId(): + if self._new_material_container and extruder_stack.getId() == self._active_container_stack.getId(): material = self._new_material_container quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) @@ -960,7 +960,7 @@ class MachineManager(QObject): quality = self._empty_quality_container result.append({ - "stack": stack, + "stack": extruder_stack, "quality": quality, "quality_changes": empty_quality_changes }) @@ -1003,7 +1003,6 @@ class MachineManager(QObject): Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name) return None - # TODO: remove this - CURA-4482 material = global_container_stack.material # find a quality type that matches both machine and materials @@ -1024,7 +1023,6 @@ class MachineManager(QObject): if quality_changes_list: quality_changes = quality_changes_list[0] - # TODO: remove this - CURA-4482 else: quality_changes = global_quality_changes if not quality_changes: From e7f1900f71cc2e292a734e1629d9498b75ca07bf Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 14:01:15 +0100 Subject: [PATCH 23/67] Remove layer height from custom profiles in dropdown as it might have changed - CURA-4482 --- cura/Settings/MachineManager.py | 3 --- resources/qml/Menus/ProfileMenu.qml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index a858f15cfc..b2baf433dd 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -728,9 +728,6 @@ class MachineManager(QObject): new_value = self._active_container_stack.getProperty(key, "value") extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] - # TODO: remove this - CURA-4482 - extruder_stacks.append(self._global_container_stack) - # check in which stack the value has to be replaced for extruder_stack in extruder_stacks: if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: diff --git a/resources/qml/Menus/ProfileMenu.qml b/resources/qml/Menus/ProfileMenu.qml index fecea5ef99..edce2641af 100644 --- a/resources/qml/Menus/ProfileMenu.qml +++ b/resources/qml/Menus/ProfileMenu.qml @@ -41,7 +41,7 @@ Menu MenuItem { - text: model.name + " - " + model.layer_height + text: model.name checkable: true checked: Cura.MachineManager.activeQualityChangesId == model.id exclusiveGroup: group From f20ba4a118b9102e9486f79a1040865e0d29934c Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 14:37:58 +0100 Subject: [PATCH 24/67] Only create new extruder stack for single extrusion machine if it does not exist yet - CURA-4482 --- cura/Settings/CuraContainerRegistry.py | 61 ++++++++++++++------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index d69a97c92f..0c0386ade1 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -406,45 +406,48 @@ class CuraContainerRegistry(ContainerRegistry): def _addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id): new_extruder_id = extruder_id + extruder_stack = None # if extruders are defined in the machine definition use those instead if machine.extruders and len(machine.extruders) > 0: new_extruder_id = machine.extruders["0"].getId() + extruder_stack = machine.extruders["0"] - extruder_definitions = self.findDefinitionContainers(id = new_extruder_id) + # if the extruder stack doesn't exist yet we create and add it + if not extruder_stack: + extruder_definitions = self.findDefinitionContainers(id = new_extruder_id) + if not extruder_definitions: + Logger.log("w", "Could not find definition containers for extruder %s", new_extruder_id) + return - if not extruder_definitions: - Logger.log("w", "Could not find definition containers for extruder %s", new_extruder_id) - return + extruder_definition = extruder_definitions[0] + unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) - extruder_definition = extruder_definitions[0] - unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) + extruder_stack = ExtruderStack.ExtruderStack(unique_name) + extruder_stack.setName(extruder_definition.getName()) + extruder_stack.setDefinition(extruder_definition) + extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) + extruder_stack.setNextStack(machine) - extruder_stack = ExtruderStack.ExtruderStack(unique_name) - extruder_stack.setName(extruder_definition.getName()) - extruder_stack.setDefinition(extruder_definition) - extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) - extruder_stack.setNextStack(machine) + if machine.userChanges: + # set existing user changes if found + extruder_stack.setUserChanges(machine.userChanges) + else: + # create empty user changes container otherwise + user_container = InstanceContainer(extruder_stack.id + "_user") + user_container.addMetaDataEntry("type", "user") + user_container.addMetaDataEntry("machine", extruder_stack.getId()) + from cura.CuraApplication import CuraApplication + user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) + user_container.setDefinition(extruder_definition) + extruder_stack.setUserChanges(user_container) + self.addContainer(user_container) - if machine.userChanges: - # set existing user changes if found - extruder_stack.setUserChanges(machine.userChanges) - else: - # create empty user changes container otherwise - user_container = InstanceContainer(extruder_stack.id + "_user") - user_container.addMetaDataEntry("type", "user") - user_container.addMetaDataEntry("machine", extruder_stack.getId()) - from cura.CuraApplication import CuraApplication - user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) - user_container.setDefinition(extruder_definition) - extruder_stack.setUserChanges(user_container) - self.addContainer(user_container) + extruder_stack.setVariantById("default") + extruder_stack.setMaterialById("default") + extruder_stack.setQualityById("default") - extruder_stack.setVariantById("default") - extruder_stack.setMaterialById("default") - extruder_stack.setQualityById("default") - - self.addContainer(extruder_stack) + self.addContainer(extruder_stack) # Fix the extruders that were upgraded to ExtruderStack instances during addContainer. # The stacks are now responsible for setting the next stack on deserialize. However, From 911f619000d505a8916cf25a5e0c62fc95dc021f Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 14:43:48 +0100 Subject: [PATCH 25/67] Fix for getting machine extruders from manager instance - CURA-4482 --- cura/Settings/ExtruderManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 40ac2f1c63..295b10e6db 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -630,7 +630,7 @@ class ExtruderManager(QObject): } result = [] - for extruder in ExtruderManager.getMachineExtruders(global_stack.getId()): + for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): # only include values from extruders that are "active" for the current machine instance if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context): continue From cc267646e7475800679423aca5ac909efb432598 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 14:48:52 +0100 Subject: [PATCH 26/67] Always use extruder stacks when building slicer message and deprecate otherwise - CURA-4482 --- plugins/CuraEngineBackend/StartSliceJob.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index a53daa4e63..49dfc71769 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -9,6 +9,7 @@ import time from UM.Job import Job from UM.Application import Application from UM.Logger import Logger +from UM.Decorators import deprecated from UM.Scene.SceneNode import SceneNode from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator @@ -158,13 +159,9 @@ class StartSliceJob(Job): self._buildGlobalSettingsMessage(stack) self._buildGlobalInheritsStackMessage(stack) - # Only add extruder stacks if there are multiple extruders - # Single extruder machines only use the global stack to store setting values - if stack.getProperty("machine_extruder_count", "value") > 1: - for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): - self._buildExtruderMessage(extruder_stack) - else: - self._buildExtruderMessageFromGlobalStack(stack) + # Build messages for extruder stacks + for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): + self._buildExtruderMessage(extruder_stack) for group in object_groups: group_message = self._slice_message.addRepeatedMessage("object_lists") @@ -251,6 +248,7 @@ class StartSliceJob(Job): Job.yieldThread() ## Create extruder message from global stack + @deprecated("Extruder stack is always used since version 3.1, even with single extrusion machines", "3.1") def _buildExtruderMessageFromGlobalStack(self, stack): message = self._slice_message.addRepeatedMessage("extruders") From c23c031152fd67f5d5e87c9918cc858143327b85 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 14:51:31 +0100 Subject: [PATCH 27/67] Always use extruder stacks in slice info - CURA-4482 --- plugins/SliceInfoPlugin/SliceInfo.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py index 0514c4dacf..6ba34e04c8 100755 --- a/plugins/SliceInfoPlugin/SliceInfo.py +++ b/plugins/SliceInfoPlugin/SliceInfo.py @@ -87,15 +87,10 @@ class SliceInfo(Extension): data["active_machine"] = {"definition_id": global_container_stack.definition.getId(), "manufacturer": global_container_stack.definition.getMetaData().get("manufacturer","")} + # add extruder specific data to slice info data["extruders"] = [] - extruder_count = len(global_container_stack.extruders) - extruders = [] - if extruder_count > 1: - extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId())) - extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position")) - - if not extruders: - extruders = [global_container_stack] + extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId())) + extruders = sorted(extruders, key=lambda extruder: extruder.getMetaDataEntry("position")) for extruder in extruders: extruder_dict = dict() From 77cd10374c3aa6211c40d791a19c7564d12200b4 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 16:00:22 +0100 Subject: [PATCH 28/67] Always set the active extruder stack when loading project file settings - CURA-4482 --- plugins/3MFReader/ThreeMFReader.py | 17 +++++------------ plugins/SliceInfoPlugin/SliceInfo.py | 2 +- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index a34bf771d7..786226ae61 100755 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -107,20 +107,13 @@ class ThreeMFReader(MeshReader): um_node.addDecorator(SettingOverrideDecorator()) global_container_stack = Application.getInstance().getGlobalContainerStack() + # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: - multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 + default_stack = ExtruderManager.getInstance().getExtruderStack(0) - # Ensure that all extruder data is reset - if not multi_extrusion: - default_stack_id = global_container_stack.getId() - else: - default_stack = ExtruderManager.getInstance().getExtruderStack(0) - if default_stack: - default_stack_id = default_stack.getId() - else: - default_stack_id = global_container_stack.getId() - um_node.callDecoration("setActiveExtruder", default_stack_id) + if default_stack: + um_node.callDecoration("setActiveExtruder", default_stack.getId()) # Get the definition & set it definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom()) @@ -139,7 +132,7 @@ class ThreeMFReader(MeshReader): else: Logger.log("w", "Unable to find extruder in position %s", setting_value) continue - setting_container.setProperty(key,"value", setting_value) + setting_container.setProperty(key, "value", setting_value) if len(um_node.getChildren()) > 0: group_decorator = GroupDecorator() diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py index 6ba34e04c8..79963a4740 100755 --- a/plugins/SliceInfoPlugin/SliceInfo.py +++ b/plugins/SliceInfoPlugin/SliceInfo.py @@ -90,7 +90,7 @@ class SliceInfo(Extension): # add extruder specific data to slice info data["extruders"] = [] extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId())) - extruders = sorted(extruders, key=lambda extruder: extruder.getMetaDataEntry("position")) + extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position")) for extruder in extruders: extruder_dict = dict() From a7383b42d5645ab64a0eac14482454a09b3d2356 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 16:04:06 +0100 Subject: [PATCH 29/67] Always use extruder stack when adding settings per object - CURA-4482 --- .../PerObjectSettingsTool.py | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py index d0cb53c4f8..dc6efafa13 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py @@ -78,31 +78,26 @@ class PerObjectSettingsTool(Tool): def _onGlobalContainerChanged(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: + + # used for enabling or disabling per extruder settings per object self._multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 - # Ensure that all extruder data is reset - if not self._multi_extrusion: - default_stack_id = global_container_stack.getId() - else: - default_stack = ExtruderManager.getInstance().getExtruderStack(0) - if default_stack: - default_stack_id = default_stack.getId() - else: - default_stack_id = global_container_stack.getId() + extruder_stack = ExtruderManager.getInstance().getExtruderStack(0) - root_node = Application.getInstance().getController().getScene().getRoot() - for node in DepthFirstIterator(root_node): - new_stack_id = default_stack_id - # Get position of old extruder stack for this node - old_extruder_pos = node.callDecoration("getActiveExtruderPosition") - if old_extruder_pos is not None: - # Fetch current (new) extruder stack at position - new_stack = ExtruderManager.getInstance().getExtruderStack(old_extruder_pos) - if new_stack: - new_stack_id = new_stack.getId() - node.callDecoration("setActiveExtruder", new_stack_id) + if extruder_stack: + root_node = Application.getInstance().getController().getScene().getRoot() + for node in DepthFirstIterator(root_node): + new_stack_id = extruder_stack.getId() + # Get position of old extruder stack for this node + old_extruder_pos = node.callDecoration("getActiveExtruderPosition") + if old_extruder_pos is not None: + # Fetch current (new) extruder stack at position + new_stack = ExtruderManager.getInstance().getExtruderStack(old_extruder_pos) + if new_stack: + new_stack_id = new_stack.getId() + node.callDecoration("setActiveExtruder", new_stack_id) - self._updateEnabled() + self._updateEnabled() def _updateEnabled(self): selected_objects = Selection.getAllSelectedObjects() From 52782b8d13b5886a8df9aeed43338ca2d18e6d34 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 16:08:59 +0100 Subject: [PATCH 30/67] Always use the extruder stacks to determine model color in solid view - CURA-4482 --- plugins/SolidView/SolidView.py | 46 +++++++++++----------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 8f0c9a4dc1..bc1b08cc5e 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -46,19 +46,10 @@ class SolidView(View): self._disabled_shader.setUniformValue("u_diffuseColor2", Color(*theme.getColor("model_unslicable_alt").getRgb())) self._disabled_shader.setUniformValue("u_width", 50.0) - multi_extrusion = False - global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: - multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 - - if multi_extrusion: - support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value") - support_angle_stack = ExtruderManager.getInstance().getExtruderStack(support_extruder_nr) - if not support_angle_stack: - support_angle_stack = global_container_stack - else: - support_angle_stack = global_container_stack + support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value") + support_angle_stack = ExtruderManager.getInstance().getExtruderStack(support_extruder_nr) if Preferences.getInstance().getValue("view/show_overhang"): angle = support_angle_stack.getProperty("support_angle", "value") @@ -71,33 +62,26 @@ class SolidView(View): else: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) - for node in DepthFirstIterator(scene.getRoot()): if not node.render(renderer): if node.getMeshData() and node.isVisible(): uniforms = {} shade_factor = 1.0 - if not multi_extrusion: - if global_container_stack: - material = global_container_stack.findContainer({ "type": "material" }) - material_color = material.getMetaDataEntry("color_code", default = self._extruders_model.defaultColors[0]) if material else self._extruders_model.defaultColors[0] - else: - material_color = self._extruders_model.defaultColors[0] - else: - # Get color to render this mesh in from ExtrudersModel - extruder_index = 0 - extruder_id = node.callDecoration("getActiveExtruder") - if extruder_id: - extruder_index = max(0, self._extruders_model.find("id", extruder_id)) - try: - material_color = self._extruders_model.getItem(extruder_index)["color"] - except KeyError: - material_color = self._extruders_model.defaultColors[0] + # Get color to render this mesh in from ExtrudersModel + extruder_index = 0 + extruder_id = node.callDecoration("getActiveExtruder") + if extruder_id: + extruder_index = max(0, self._extruders_model.find("id", extruder_id)) + try: + material_color = self._extruders_model.getItem(extruder_index)["color"] + except KeyError: + material_color = self._extruders_model.defaultColors[0] + + if extruder_index != ExtruderManager.getInstance().activeExtruderIndex: + # Shade objects that are printed with the non-active extruder 25% darker + shade_factor = 0.6 - if extruder_index != ExtruderManager.getInstance().activeExtruderIndex: - # Shade objects that are printed with the non-active extruder 25% darker - shade_factor = 0.6 try: # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0]) From f421166b182a407bbbb3f89d3fdd9ba8b4e74c8b Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 16:11:27 +0100 Subject: [PATCH 31/67] Clarify 3mf saving multi extrusion setting - CURA-4482 --- plugins/3MFWriter/ThreeMFWriter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index a764d30fac..6254bef03a 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -87,7 +87,7 @@ class ThreeMFWriter(MeshWriter): if stack is not None: changed_setting_keys = set(stack.getTop().getAllKeys()) - # Ensure that we save the extruder used for this object. + # Ensure that we save the extruder used for this object in a multi-extrusion setup if stack.getProperty("machine_extruder_count", "value") > 1: changed_setting_keys.add("extruder_nr") From b8757b46b350d03b4085b9712b909ec62a774447 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 16:16:51 +0100 Subject: [PATCH 32/67] Always use extruder stack in machine settings action plugin, keep support for older project files - CURA-4482 --- .../MachineSettingsAction.py | 129 +++++++++--------- 1 file changed, 66 insertions(+), 63 deletions(-) diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index d4d6c430a8..d7fdc216f4 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -116,7 +116,7 @@ class MachineSettingsAction(MachineAction): @pyqtSlot(int) def setMachineExtruderCount(self, extruder_count): - machine_manager = Application.getInstance().getMachineManager() + # machine_manager = Application.getInstance().getMachineManager() extruder_manager = ExtruderManager.getInstance() definition_changes_container = self._global_container_stack.definitionChanges @@ -127,33 +127,34 @@ class MachineSettingsAction(MachineAction): if extruder_count == previous_extruder_count: return - extruder_material_id = None - extruder_variant_id = None - if extruder_count == 1: - # Get the material and variant of the first extruder before setting the number extruders to 1 - if machine_manager.hasMaterials: - extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]] - if machine_manager.hasVariants: - extruder_variant_id = machine_manager.allActiveVariantIds[extruder_manager.extruderIds["0"]] + # extruder_material_id = None + # extruder_variant_id = None - # Copy any settable_per_extruder setting value from the extruders to the global stack - extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - extruder_stacks.reverse() # make sure the first extruder is done last, so its settings override any higher extruder settings - - global_user_container = self._global_container_stack.getTop() - for extruder_stack in extruder_stacks: - extruder_index = extruder_stack.getMetaDataEntry("position") - extruder_user_container = extruder_stack.getTop() - for setting_instance in extruder_user_container.findInstances(): - setting_key = setting_instance.definition.key - settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") - - if settable_per_extruder: - limit_to_extruder = self._global_container_stack.getProperty(setting_key, "limit_to_extruder") - - if limit_to_extruder == "-1" or limit_to_extruder == extruder_index: - global_user_container.setProperty(setting_key, "value", extruder_user_container.getProperty(setting_key, "value")) - extruder_user_container.removeInstance(setting_key) + # if extruder_count == 1: + # # Get the material and variant of the first extruder before setting the number extruders to 1 + # if machine_manager.hasMaterials: + # extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]] + # if machine_manager.hasVariants: + # extruder_variant_id = machine_manager.allActiveVariantIds[extruder_manager.extruderIds["0"]] + # + # # Copy any settable_per_extruder setting value from the extruders to the global stack + # extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() + # extruder_stacks.reverse() # make sure the first extruder is done last, so its settings override any higher extruder settings + # + # global_user_container = self._global_container_stack.getTop() + # for extruder_stack in extruder_stacks: + # extruder_index = extruder_stack.getMetaDataEntry("position") + # extruder_user_container = extruder_stack.getTop() + # for setting_instance in extruder_user_container.findInstances(): + # setting_key = setting_instance.definition.key + # settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") + # + # if settable_per_extruder: + # limit_to_extruder = self._global_container_stack.getProperty(setting_key, "limit_to_extruder") + # + # if limit_to_extruder == "-1" or limit_to_extruder == extruder_index: + # global_user_container.setProperty(setting_key, "value", extruder_user_container.getProperty(setting_key, "value")) + # extruder_user_container.removeInstance(setting_key) # reset all extruder number settings whose value is no longer valid for setting_instance in self._global_container_stack.userChanges.findInstances(): @@ -185,50 +186,52 @@ class MachineSettingsAction(MachineAction): # extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStack() global_user_container = self._global_container_stack.getTop() - if extruder_count > 1: - # Multi extrusion - # Make sure one of the extruder stacks is active - if extruder_manager.activeExtruderIndex == -1: - extruder_manager.setActiveExtruderIndex(0) + # if extruder_count > 1: - # Move settable_per_extruder values out of the global container - if previous_extruder_count == 1: - extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - global_user_container = self._global_container_stack.getTop() - for setting_instance in global_user_container.findInstances(): - setting_key = setting_instance.definition.key - settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") - if settable_per_extruder: - limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder")) - extruder_stack = extruder_stacks[max(0, limit_to_extruder)] - extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) - global_user_container.removeInstance(setting_key) - else: - # Single extrusion + # Make sure one of the extruder stacks is active + extruder_manager.setActiveExtruderIndex(0) - # Make sure the machine stack is active - if extruder_manager.activeExtruderIndex > -1: - extruder_manager.setActiveExtruderIndex(-1) + # Move settable_per_extruder values out of the global container + # After CURA-4482 this should not be the case anymore, but we still want to support older project files. + if previous_extruder_count == 1: + extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() + global_user_container = self._global_container_stack.getTop() - # Restore material and variant on global stack - # MachineManager._onGlobalContainerChanged removes the global material and variant of multiextruder machines - if extruder_material_id or extruder_variant_id: - # Prevent the DiscardOrKeepProfileChangesDialog from popping up (twice) if there are user changes - # The dialog is not relevant here, since we're restoring the previous situation as good as possible - preferences = Preferences.getInstance() - choice_on_profile_override = preferences.getValue("cura/choice_on_profile_override") - preferences.setValue("cura/choice_on_profile_override", "always_keep") + for setting_instance in global_user_container.findInstances(): + setting_key = setting_instance.definition.key + settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") - if extruder_material_id: - machine_manager.setActiveMaterial(extruder_material_id) - if extruder_variant_id: - machine_manager.setActiveVariant(extruder_variant_id) + if settable_per_extruder: + limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder")) + extruder_stack = extruder_stacks[max(0, limit_to_extruder)] + extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) + global_user_container.removeInstance(setting_key) - preferences.setValue("cura/choice_on_profile_override", choice_on_profile_override) + # else: + # # Single extrusion + # + # # Make sure the machine stack is active + # if extruder_manager.activeExtruderIndex > -1: + # extruder_manager.setActiveExtruderIndex(-1) + # + # # Restore material and variant on global stack + # # MachineManager._onGlobalContainerChanged removes the global material and variant of multiextruder machines + # if extruder_material_id or extruder_variant_id: + # # Prevent the DiscardOrKeepProfileChangesDialog from popping up (twice) if there are user changes + # # The dialog is not relevant here, since we're restoring the previous situation as good as possible + # preferences = Preferences.getInstance() + # choice_on_profile_override = preferences.getValue("cura/choice_on_profile_override") + # preferences.setValue("cura/choice_on_profile_override", "always_keep") + # + # if extruder_material_id: + # machine_manager.setActiveMaterial(extruder_material_id) + # if extruder_variant_id: + # machine_manager.setActiveVariant(extruder_variant_id) + # + # preferences.setValue("cura/choice_on_profile_override", choice_on_profile_override) self.forceUpdate() - @pyqtSlot() def forceUpdate(self): # Force rebuilding the build volume by reloading the global container stack. From cc6be48a7973b6a2ae35ab05d2feb75f1edc07cb Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 16:19:16 +0100 Subject: [PATCH 33/67] Always use extruder stack when updating material diameter - CURA-4482 --- .../MachineSettingsAction.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index d7fdc216f4..868e87ca7c 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -284,16 +284,13 @@ class MachineSettingsAction(MachineAction): if not self._global_container_stack.getMetaDataEntry("has_materials", False): return - machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value") - if machine_extruder_count > 1: - material = ExtruderManager.getInstance().getActiveExtruderStack().material - else: - material = self._global_container_stack.material + material = ExtruderManager.getInstance().getActiveExtruderStack().material material_diameter = material.getProperty("material_diameter", "value") - if not material_diameter: # in case of "empty" material + if not material_diameter: + # in case of "empty" material material_diameter = 0 - material_approximate_diameter = str(round(material_diameter)) + material_approximate_diameter = str(round(material_diameter)) definition_changes = self._global_container_stack.definitionChanges machine_diameter = definition_changes.getProperty("material_diameter", "value") if not machine_diameter: @@ -303,10 +300,7 @@ class MachineSettingsAction(MachineAction): if material_approximate_diameter != machine_approximate_diameter: Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.") - if machine_extruder_count > 1: - stacks = ExtruderManager.getInstance().getExtruderStacks() - else: - stacks = [self._global_container_stack] + stacks = ExtruderManager.getInstance().getExtruderStacks() if self._global_container_stack.getMetaDataEntry("has_machine_materials", False): materials_definition = self._global_container_stack.definition.getId() @@ -347,7 +341,7 @@ class MachineSettingsAction(MachineAction): search_criteria["id"] = stack.getMetaDataEntry("preferred_material") materials = self._container_registry.findInstanceContainers(**search_criteria) if not materials: - # Preferrd material with new diameter is not found, search for any material + # Preferred material with new diameter is not found, search for any material search_criteria.pop("id", None) materials = self._container_registry.findInstanceContainers(**search_criteria) if not materials: From c59e4bb3e161179ab991beb3ac6c5941ecacfd96 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 16:20:25 +0100 Subject: [PATCH 34/67] Cleanup machine settings action - CURA-4482 --- .../MachineSettingsAction.py | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index 868e87ca7c..44d0e05485 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -127,35 +127,6 @@ class MachineSettingsAction(MachineAction): if extruder_count == previous_extruder_count: return - # extruder_material_id = None - # extruder_variant_id = None - - # if extruder_count == 1: - # # Get the material and variant of the first extruder before setting the number extruders to 1 - # if machine_manager.hasMaterials: - # extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]] - # if machine_manager.hasVariants: - # extruder_variant_id = machine_manager.allActiveVariantIds[extruder_manager.extruderIds["0"]] - # - # # Copy any settable_per_extruder setting value from the extruders to the global stack - # extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - # extruder_stacks.reverse() # make sure the first extruder is done last, so its settings override any higher extruder settings - # - # global_user_container = self._global_container_stack.getTop() - # for extruder_stack in extruder_stacks: - # extruder_index = extruder_stack.getMetaDataEntry("position") - # extruder_user_container = extruder_stack.getTop() - # for setting_instance in extruder_user_container.findInstances(): - # setting_key = setting_instance.definition.key - # settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") - # - # if settable_per_extruder: - # limit_to_extruder = self._global_container_stack.getProperty(setting_key, "limit_to_extruder") - # - # if limit_to_extruder == "-1" or limit_to_extruder == extruder_index: - # global_user_container.setProperty(setting_key, "value", extruder_user_container.getProperty(setting_key, "value")) - # extruder_user_container.removeInstance(setting_key) - # reset all extruder number settings whose value is no longer valid for setting_instance in self._global_container_stack.userChanges.findInstances(): setting_key = setting_instance.definition.key @@ -183,11 +154,8 @@ class MachineSettingsAction(MachineAction): extruder_manager.setActiveExtruderIndex(0) # Move settable_per_extruder values out of the global container - # extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStack() global_user_container = self._global_container_stack.getTop() - # if extruder_count > 1: - # Make sure one of the extruder stacks is active extruder_manager.setActiveExtruderIndex(0) @@ -207,29 +175,6 @@ class MachineSettingsAction(MachineAction): extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) global_user_container.removeInstance(setting_key) - # else: - # # Single extrusion - # - # # Make sure the machine stack is active - # if extruder_manager.activeExtruderIndex > -1: - # extruder_manager.setActiveExtruderIndex(-1) - # - # # Restore material and variant on global stack - # # MachineManager._onGlobalContainerChanged removes the global material and variant of multiextruder machines - # if extruder_material_id or extruder_variant_id: - # # Prevent the DiscardOrKeepProfileChangesDialog from popping up (twice) if there are user changes - # # The dialog is not relevant here, since we're restoring the previous situation as good as possible - # preferences = Preferences.getInstance() - # choice_on_profile_override = preferences.getValue("cura/choice_on_profile_override") - # preferences.setValue("cura/choice_on_profile_override", "always_keep") - # - # if extruder_material_id: - # machine_manager.setActiveMaterial(extruder_material_id) - # if extruder_variant_id: - # machine_manager.setActiveVariant(extruder_variant_id) - # - # preferences.setValue("cura/choice_on_profile_override", choice_on_profile_override) - self.forceUpdate() @pyqtSlot() From 8676792ef8a7eee79dbc630f09d7ee948988dff1 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 16:23:45 +0100 Subject: [PATCH 35/67] Disable annoying debug message about not found printer definition --- plugins/MachineSettingsAction/MachineSettingsAction.py | 9 ++------- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index 44d0e05485..dacbe2efc5 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -149,18 +149,13 @@ class MachineSettingsAction(MachineAction): definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count) - # Make sure one of the extruder stacks is active - if extruder_manager.activeExtruderIndex == -1: - extruder_manager.setActiveExtruderIndex(0) - - # Move settable_per_extruder values out of the global container - global_user_container = self._global_container_stack.getTop() - # Make sure one of the extruder stacks is active extruder_manager.setActiveExtruderIndex(0) # Move settable_per_extruder values out of the global container # After CURA-4482 this should not be the case anymore, but we still want to support older project files. + global_user_container = self._global_container_stack.getTop() + if previous_extruder_count == 1: extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() global_user_container = self._global_container_stack.getTop() diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 51a3c51a8b..7af011ee2e 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -547,7 +547,7 @@ class XmlMaterialProfile(InstanceContainer): definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = machine_id) if not definitions: - Logger.log("w", "No definition found for machine ID %s", machine_id) + # Logger.log("w", "No definition found for machine ID %s", machine_id) continue definition = definitions[0] From ce896177cb066f95ecf140e9c81c8603f74b7819 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 16:32:04 +0100 Subject: [PATCH 36/67] Cleanup 3mf workspace reader - CURA-4482 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 28d12bf2d5..170b616677 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -644,9 +644,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Get the stack(s) saved in the workspace. Logger.log("d", "Workspace loading is checking stacks containers...") - # -- # load global stack file try: + stack = None + if self._resolve_strategies["machine"] == "override": container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original) stack = container_stacks[0] @@ -682,12 +683,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._container_registry.addContainer(stack) containers_added.append(stack) else: - Logger.log("e", "Resolve strategy of %s for machine is not supported", - self._resolve_strategies["machine"]) + Logger.log("e", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"]) # Create a new definition_changes container if it was empty if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer(): - stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack._id + "_settings")) + stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings")) global_stack = stack Job.yieldThread() except: @@ -697,16 +697,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._container_registry.removeContainer(container.getId()) return - # - # Use the number of extruders from the global stack instead of the number of extruder stacks this project file - # contains. The Custom FDM Printer can have multiple extruders, but the actual number of extruders in used is - # defined in the global stack. - # Because for single-extrusion machines, there won't be an extruder stack, so relying on the the extruder count - # in the global stack can avoid problems in those cases. - # - extruder_count_from_global_stack = global_stack.getProperty("machine_extruder_count", "value") - - # -- # load extruder stack files try: for extruder_stack_file in extruder_stack_files: From 2e1da5857083e8b0b9576c93a15e726e0e57b2f6 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 17:06:34 +0100 Subject: [PATCH 37/67] Cleanup - CURA-4482 --- cura/Settings/MachineManager.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index b2baf433dd..62095945af 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -274,14 +274,6 @@ class MachineManager(QObject): except TypeError: pass - # TODO: remove this - CURA-4482 - material = self._global_container_stack.material - material.nameChanged.disconnect(self._onMaterialNameChanged) - - # TODO: remove this - CURA-4482 - quality = self._global_container_stack.quality - quality.nameChanged.disconnect(self._onQualityNameChanged) - for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): extruder_stack.propertyChanged.disconnect(self._onPropertyChanged) extruder_stack.containersChanged.disconnect(self._onInstanceContainersChanged) @@ -309,17 +301,6 @@ class MachineManager(QObject): if global_material != self._empty_material_container: self._global_container_stack.setMaterial(self._empty_material_container) - # TODO: update stack builder since this is not always a user created stack - # if len(self._global_container_stack.extruders) == 0: - # extruder_stack = CuraStackBuilder.createExtruderStack( - # self._global_container_stack.getId(), - # definition = self._global_container_stack.definition, - # machine_definition = self._global_container_stack.definition, - # ) - # extruder_stack.setNextStack(self._global_container_stack) - # extruder_stack.propertyChanged.connect(self._onPropertyChanged) - # extruder_stack.containersChanged.connect(self._onInstanceContainersChanged) - # Listen for changes on all extruder stacks for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): extruder_stack.propertyChanged.connect(self._onPropertyChanged) From 8f41185f228eaf153bcc37bb6bf1eb1018a7d0e5 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 17:18:19 +0100 Subject: [PATCH 38/67] Fix extruder manager single instance from qml - CURA-4482 --- cura/Settings/ExtruderManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 295b10e6db..e5eb00c4b4 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -106,7 +106,7 @@ class ExtruderManager(QObject): @staticmethod def createExtruderManager(): - return ExtruderManager() + return ExtruderManager().getInstance() ## Gets an instance of the extruder manager, or creates one if no instance # exists yet. From 957009a768d8fa8808dbf14ec06e8abfca04e120 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 31 Oct 2017 17:32:21 +0100 Subject: [PATCH 39/67] Fix finding global quality for multi extrusion machines - CURA-4482 --- cura/Settings/MachineManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 62095945af..b345649052 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -944,7 +944,7 @@ class MachineManager(QObject): }) # also find a global quality for the machine - global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = True) + global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = "True") # if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482 if not global_quality and len(extruder_stacks) == 1: From 97421ecf5812cc055f7fbfff45f6bd271de7a462 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Wed, 1 Nov 2017 13:31:25 +0100 Subject: [PATCH 40/67] Create container stacks when loading older project file - CURA-4482 --- cura/Settings/CuraContainerRegistry.py | 4 ++-- plugins/3MFReader/ThreeMFWorkspaceReader.py | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 0c0386ade1..526685465e 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -402,9 +402,9 @@ class CuraContainerRegistry(ContainerRegistry): def _registerSingleExtrusionMachinesExtruderStacks(self): machines = ContainerRegistry.getInstance().findContainerStacks(machine_extruder_trains = {"0": "fdmextruder"}) for machine in machines: - self._addExtruderStackForSingleExtrusionMachine(machine, "fdmextruder") + self.addExtruderStackForSingleExtrusionMachine(machine, "fdmextruder") - def _addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id): + def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id): new_extruder_id = extruder_id extruder_stack = None diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 170b616677..a3aadc79b8 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -739,9 +739,15 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Create a new definition_changes container if it was empty if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer(): - stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack._id + "_settings")) - if global_stack.getProperty("machine_extruder_count", "value") > 1: - extruder_stacks.append(stack) + stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings")) + + extruder_stacks.append(stack) + + # If not extruder stacks were saved in the project file (pre 3.1) create one manually + # We re-use the container registry's addExtruderStackForSingleExtrusionMachine method for this + if not extruder_stacks: + self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder") + except: Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") # Something went really wrong. Try to remove any data that we added. @@ -774,7 +780,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): for stack in [global_stack] + extruder_stacks: stack.replaceContainer(_ContainerIndexes.Quality, empty_quality_container) - # # Replacing the old containers if resolve is "new". # When resolve is "new", some containers will get renamed, so all the other containers that reference to those # MUST get updated too. From e23e6cfa31e55e2de1e9bd8af31af7beb316ac3f Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Wed, 1 Nov 2017 13:59:21 +0100 Subject: [PATCH 41/67] Fix for global quality in case of user created quality profile - CURA-4482 --- cura/Settings/MachineManager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index b345649052..7bbbd868f4 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1024,11 +1024,11 @@ class MachineManager(QObject): }) # append the global quality changes - global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material], global_quality = True) + global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material], global_quality = "True") # if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482 if not global_quality and len(extruder_stacks) == 1: - global_quality = result[0]["quality_changes"] + global_quality = result[0]["quality"] # if still no global quality changes are found we set it to empty (not supported) if not global_quality: From e29fdbe76d77a47a57537b79736d7ee24714d87d Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Wed, 1 Nov 2017 14:32:58 +0100 Subject: [PATCH 42/67] Cleanup - CURA-4482 --- cura/Settings/ExtruderManager.py | 174 +----------------- .../MachineSettingsAction.py | 3 +- 2 files changed, 3 insertions(+), 174 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index e5eb00c4b4..34b283107d 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -75,17 +75,12 @@ class ExtruderManager(QObject): return 0 ## Gets a dict with the extruder stack ids with the extruder number as the key. - # The key "-1" indicates the global stack id. - # @pyqtProperty("QVariantMap", notify = extrudersChanged) def extruderIds(self): extruder_stack_ids = {} global_stack_id = Application.getInstance().getGlobalContainerStack().getId() - # TODO: remove this? - CURA-4482 - # extruder_stack_ids["-1"] = global_stack_id - if global_stack_id in self._extruder_trains: for position in self._extruder_trains[global_stack_id]: extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId() @@ -212,40 +207,6 @@ class ExtruderManager(QObject): result.append(self.getExtruderStack(i)) return result - # ## Adds all extruders of a specific machine definition to the extruder - # # manager. - # # - # # \param machine_definition The machine definition to add the extruders for. - # # \param machine_id The machine_id to add the extruders for. - # @deprecated("Use CuraStackBuilder", "2.6") - # def addMachineExtruders(self, machine_definition: DefinitionContainerInterface, machine_id: str) -> None: - # changed = False - # machine_definition_id = machine_definition.getId() - # if machine_id not in self._extruder_trains: - # self._extruder_trains[machine_id] = { } - # changed = True - # container_registry = ContainerRegistry.getInstance() - # if container_registry: - # # Add the extruder trains that don't exist yet. - # for extruder_definition in container_registry.findDefinitionContainers(machine = machine_definition_id): - # position = extruder_definition.getMetaDataEntry("position", None) - # if not position: - # Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.getId()) - # if not container_registry.findContainerStacks(machine = machine_id, position = position): # Doesn't exist yet. - # self.createExtruderTrain(extruder_definition, machine_definition, position, machine_id) - # changed = True - # - # # Gets the extruder trains that we just created as well as any that still existed. - # extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = machine_id) - # for extruder_train in extruder_trains: - # self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train - # - # # regardless of what the next stack is, we have to set it again, because of signal routing. - # extruder_train.setNextStack(Application.getInstance().getGlobalContainerStack()) - # changed = True - # if changed: - # self.extrudersChanged.emit(machine_id) - def registerExtruder(self, extruder_train, machine_id): changed = False @@ -265,138 +226,6 @@ class ExtruderManager(QObject): if changed: self.extrudersChanged.emit(machine_id) - # ## Creates a container stack for an extruder train. - # # - # # The container stack has an extruder definition at the bottom, which is - # # linked to a machine definition. Then it has a variant profile, a material - # # profile, a quality profile and a user profile, in that order. - # # - # # The resulting container stack is added to the registry. - # # - # # \param extruder_definition The extruder to create the extruder train for. - # # \param machine_definition The machine that the extruder train belongs to. - # # \param position The position of this extruder train in the extruder slots of the machine. - # # \param machine_id The id of the "global" stack this extruder is linked to. - # @deprecated("Use CuraStackBuilder::createExtruderStack", "2.6") - # def createExtruderTrain(self, extruder_definition: DefinitionContainerInterface, machine_definition: DefinitionContainerInterface, - # position, machine_id: str) -> None: - # # Cache some things. - # container_registry = ContainerRegistry.getInstance() - # machine_definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_definition) - # - # # Create a container stack for this extruder. - # extruder_stack_id = container_registry.uniqueName(extruder_definition.getId()) - # container_stack = ContainerStack(extruder_stack_id) - # container_stack.setName(extruder_definition.getName()) # Take over the display name to display the stack with. - # container_stack.addMetaDataEntry("type", "extruder_train") - # container_stack.addMetaDataEntry("machine", machine_id) - # container_stack.addMetaDataEntry("position", position) - # container_stack.addContainer(extruder_definition) - # - # # Find the variant to use for this extruder. - # variant = container_registry.findInstanceContainers(id = "empty_variant")[0] - # if machine_definition.getMetaDataEntry("has_variants"): - # # First add any variant. Later, overwrite with preference if the preference is valid. - # variants = container_registry.findInstanceContainers(definition = machine_definition_id, type = "variant") - # if len(variants) >= 1: - # variant = variants[0] - # preferred_variant_id = machine_definition.getMetaDataEntry("preferred_variant") - # if preferred_variant_id: - # preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, definition = machine_definition_id, type = "variant") - # if len(preferred_variants) >= 1: - # variant = preferred_variants[0] - # else: - # Logger.log("w", "The preferred variant \"%s\" of machine %s doesn't exist or is not a variant profile.", preferred_variant_id, machine_id) - # # And leave it at the default variant. - # container_stack.addContainer(variant) - # - # # Find a material to use for this variant. - # material = container_registry.findInstanceContainers(id = "empty_material")[0] - # if machine_definition.getMetaDataEntry("has_materials"): - # # First add any material. Later, overwrite with preference if the preference is valid. - # machine_has_variant_materials = machine_definition.getMetaDataEntry("has_variant_materials", default = False) - # if machine_has_variant_materials or machine_has_variant_materials == "True": - # materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id, variant = variant.getId()) - # else: - # materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id) - # if len(materials) >= 1: - # material = materials[0] - # preferred_material_id = machine_definition.getMetaDataEntry("preferred_material") - # if preferred_material_id: - # global_stack = ContainerRegistry.getInstance().findContainerStacks(id = machine_id) - # if global_stack: - # approximate_material_diameter = str(round(global_stack[0].getProperty("material_diameter", "value"))) - # else: - # approximate_material_diameter = str(round(machine_definition.getProperty("material_diameter", "value"))) - # - # search_criteria = { "type": "material", "id": preferred_material_id, "approximate_diameter": approximate_material_diameter} - # if machine_definition.getMetaDataEntry("has_machine_materials"): - # search_criteria["definition"] = machine_definition_id - # - # if machine_definition.getMetaDataEntry("has_variants") and variant: - # search_criteria["variant"] = variant.id - # else: - # search_criteria["definition"] = "fdmprinter" - # - # preferred_materials = container_registry.findInstanceContainers(**search_criteria) - # if len(preferred_materials) >= 1: - # # In some cases we get multiple materials. In that case, prefer materials that are marked as read only. - # read_only_preferred_materials = [preferred_material for preferred_material in preferred_materials if preferred_material.isReadOnly()] - # if len(read_only_preferred_materials) >= 1: - # material = read_only_preferred_materials[0] - # else: - # material = preferred_materials[0] - # else: - # Logger.log("w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id) - # # And leave it at the default material. - # container_stack.addContainer(material) - # - # # Find a quality to use for this extruder. - # quality = container_registry.getEmptyInstanceContainer() - # - # search_criteria = { "type": "quality" } - # if machine_definition.getMetaDataEntry("has_machine_quality"): - # search_criteria["definition"] = machine_definition_id - # if machine_definition.getMetaDataEntry("has_materials") and material: - # search_criteria["material"] = material.id - # else: - # search_criteria["definition"] = "fdmprinter" - # - # preferred_quality = machine_definition.getMetaDataEntry("preferred_quality") - # if preferred_quality: - # search_criteria["id"] = preferred_quality - # - # containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) - # if not containers and preferred_quality: - # Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id) - # search_criteria.pop("id", None) - # containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) - # if containers: - # quality = containers[0] - # - # container_stack.addContainer(quality) - # - # empty_quality_changes = container_registry.findInstanceContainers(id = "empty_quality_changes")[0] - # container_stack.addContainer(empty_quality_changes) - # - # user_profile = container_registry.findInstanceContainers(type = "user", extruder = extruder_stack_id) - # if user_profile: # There was already a user profile, loaded from settings. - # user_profile = user_profile[0] - # else: - # user_profile = InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile. - # user_profile.addMetaDataEntry("type", "user") - # user_profile.addMetaDataEntry("extruder", extruder_stack_id) - # from cura.CuraApplication import CuraApplication - # user_profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) - # user_profile.setDefinition(machine_definition) - # container_registry.addContainer(user_profile) - # container_stack.addContainer(user_profile) - # - # # regardless of what the next stack is, we have to set it again, because of signal routing. - # container_stack.setNextStack(Application.getInstance().getGlobalContainerStack()) - # - # container_registry.addContainer(container_stack) - def getAllExtruderValues(self, setting_key): return self.getAllExtruderSettings(setting_key, "value") @@ -678,7 +507,8 @@ class ExtruderManager(QObject): value = extruder.getRawProperty(key, "value") if isinstance(value, SettingFunction): value = value(extruder) - else: #Just a value from global. + else: + # Just a value from global. value = Application.getInstance().getGlobalContainerStack().getProperty(key, "value") return value diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index dacbe2efc5..2de5b6e9b8 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -116,8 +116,7 @@ class MachineSettingsAction(MachineAction): @pyqtSlot(int) def setMachineExtruderCount(self, extruder_count): - # machine_manager = Application.getInstance().getMachineManager() - extruder_manager = ExtruderManager.getInstance() + extruder_manager = Application.getInstance().getExtruderManager() definition_changes_container = self._global_container_stack.definitionChanges if not self._global_container_stack or definition_changes_container == self._empty_container: From 97fd92665440297c9e24698a1a67e1be079f50a9 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 3 Nov 2017 17:46:12 +0100 Subject: [PATCH 43/67] CURA-4526 Add to SimulationView the same behavior than LayerView --- plugins/CuraEngineBackend/CuraEngineBackend.py | 2 +- plugins/CuraEngineBackend/ProcessSlicedLayersJob.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 914aa1dee0..cfbc1f429f 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -588,7 +588,7 @@ class CuraEngineBackend(QObject, Backend): def _onActiveViewChanged(self): if Application.getInstance().getController().getActiveView(): view = Application.getInstance().getController().getActiveView() - if view.getPluginId() == "LayerView": # If switching to layer view, we should process the layers if that hasn't been done yet. + if view.getPluginId() in ("LayerView", "SimulationView"): # If switching to layer view, we should process the layers if that hasn't been done yet. self._layer_view_active = True # There is data and we're not slicing at the moment # if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment. diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index a352564bc2..639ea27135 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py @@ -61,7 +61,7 @@ class ProcessSlicedLayersJob(Job): def run(self): start_time = time() - if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": + if Application.getInstance().getController().getActiveView().getPluginId() in ("LayerView", "SimulationView"): self._progress_message.show() Job.yieldThread() if self._abort_requested: @@ -219,7 +219,7 @@ class ProcessSlicedLayersJob(Job): self._progress_message.setProgress(100) view = Application.getInstance().getController().getActiveView() - if view.getPluginId() == "LayerView": + if view.getPluginId() in ("LayerView", "SimulationView"): view.resetLayerData() if self._progress_message: @@ -232,7 +232,7 @@ class ProcessSlicedLayersJob(Job): def _onActiveViewChanged(self): if self.isRunning(): - if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": + if Application.getInstance().getController().getActiveView().getPluginId() in ("LayerView", "SimulationView"): if not self._progress_message: self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, 0, catalog.i18nc("@info:title", "Information")) if self._progress_message.getProgress() != 100: From 28ce2b1dc6d3382a5d146fa2aa928af18541ce68 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Mon, 6 Nov 2017 09:49:13 +0100 Subject: [PATCH 44/67] Ignore VariSlice plugin --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 570c932d28..ac1e8eba92 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ plugins/ProfileFlattener plugins/cura-god-mode-plugin plugins/cura-big-flame-graph plugins/cura-siemensnx-plugin +plugins/CuraVariSlicePlugin #Build stuff CMakeCache.txt From b7172ad9e36a9109592d8d8641f7c39de088c207 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 7 Nov 2017 17:43:27 +0100 Subject: [PATCH 45/67] Add return values to some functions that cause some crashes --- plugins/LayerView/LayerViewProxy.py | 39 ++++++++++++++++------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/plugins/LayerView/LayerViewProxy.py b/plugins/LayerView/LayerViewProxy.py index 4cf84117da..4cbff65040 100644 --- a/plugins/LayerView/LayerViewProxy.py +++ b/plugins/LayerView/LayerViewProxy.py @@ -23,64 +23,67 @@ class LayerViewProxy(QObject): @pyqtProperty(bool, notify=activityChanged) def layerActivity(self): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): return active_view.getActivity() + return False @pyqtProperty(int, notify=maxLayersChanged) def numLayers(self): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): return active_view.getMaxLayers() + return 0 @pyqtProperty(int, notify=currentLayerChanged) def currentLayer(self): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): return active_view.getCurrentLayer() + return 0 @pyqtProperty(int, notify=currentLayerChanged) def minimumLayer(self): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): return active_view.getMinimumLayer() + return 0 @pyqtProperty(bool, notify=busyChanged) def busy(self): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): return active_view.isBusy() - return False @pyqtProperty(bool, notify=preferencesChanged) def compatibilityMode(self): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): return active_view.getCompatibilityMode() return False @pyqtSlot(int) def setCurrentLayer(self, layer_num): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): active_view.setLayer(layer_num) @pyqtSlot(int) def setMinimumLayer(self, layer_num): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): active_view.setMinimumLayer(layer_num) @pyqtSlot(int) def setLayerViewType(self, layer_view_type): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): active_view.setLayerViewType(layer_view_type) @pyqtSlot(result=int) def getLayerViewType(self): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): return active_view.getLayerViewType() return 0 @@ -88,37 +91,37 @@ class LayerViewProxy(QObject): @pyqtSlot(int, float) def setExtruderOpacity(self, extruder_nr, opacity): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): active_view.setExtruderOpacity(extruder_nr, opacity) @pyqtSlot(int) def setShowTravelMoves(self, show): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): active_view.setShowTravelMoves(show) @pyqtSlot(int) def setShowHelpers(self, show): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): active_view.setShowHelpers(show) @pyqtSlot(int) def setShowSkin(self, show): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): active_view.setShowSkin(show) @pyqtSlot(int) def setShowInfill(self, show): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): active_view.setShowInfill(show) @pyqtProperty(int, notify=globalStackChanged) def extruderCount(self): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): return active_view.getExtruderCount() return 0 @@ -143,7 +146,7 @@ class LayerViewProxy(QObject): def _onActiveViewChanged(self): active_view = self._controller.getActiveView() - if type(active_view) == LayerView.LayerView.LayerView: + if isinstance(active_view, LayerView.LayerView.LayerView): active_view.currentLayerNumChanged.connect(self._onLayerChanged) active_view.maxLayersChanged.connect(self._onMaxLayersChanged) active_view.busyChanged.connect(self._onBusyChanged) From 1ff254194768bcb81888bcee17f8b9cc2bb0d25e Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 9 Nov 2017 10:34:35 +0100 Subject: [PATCH 46/67] Add nozzle color to the default themes --- plugins/CuraEngineBackend/ProcessSlicedLayersJob.py | 1 + resources/themes/cura-dark/theme.json | 1 + resources/themes/cura-light/theme.json | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index 639ea27135..ab93a01204 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py @@ -109,6 +109,7 @@ class ProcessSlicedLayersJob(Job): layer_data.addLayer(abs_layer_number) this_layer = layer_data.getLayer(abs_layer_number) layer_data.setLayerHeight(abs_layer_number, layer.height) + layer_data.setLayerThickness(abs_layer_number, layer.thickness) for p in range(layer.repeatedMessageCount("path_segment")): polygon = layer.getRepeatedMessage("path_segment", p) diff --git a/resources/themes/cura-dark/theme.json b/resources/themes/cura-dark/theme.json index e61c48bffd..8bf1455b60 100644 --- a/resources/themes/cura-dark/theme.json +++ b/resources/themes/cura-dark/theme.json @@ -194,6 +194,7 @@ "layerview_move_combing": [0, 0, 255, 255], "layerview_move_retraction": [128, 128, 255, 255], "layerview_support_interface": [64, 192, 255, 255], + "layerview_nozzle": [181, 166, 66, 120], "material_compatibility_warning": [255, 255, 255, 255], diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index b41ea96846..166fd81dce 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -271,7 +271,8 @@ "layerview_support_infill": [0, 255, 255, 255], "layerview_move_combing": [0, 0, 255, 255], "layerview_move_retraction": [128, 128, 255, 255], - "layerview_support_interface": [64, 192, 255, 255] + "layerview_support_interface": [64, 192, 255, 255], + "layerview_nozzle": [181, 166, 66, 50] }, "sizes": { From aa9f9d5b88ef7e1da0885a82d23ec413bbb563b1 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 10 Nov 2017 16:59:06 +0100 Subject: [PATCH 47/67] Add line thickness and feedrate to the data sent from the backend --- cura/Layer.py | 4 ++-- cura/LayerDataBuilder.py | 12 ++++++++--- cura/LayerPolygon.py | 20 ++++++++++++++++--- plugins/CuraEngineBackend/Cura.proto | 4 ++++ .../ProcessSlicedLayersJob.py | 11 +++++----- plugins/GCodeReader/GCodeReader.py | 6 ++++-- 6 files changed, 42 insertions(+), 15 deletions(-) diff --git a/cura/Layer.py b/cura/Layer.py index d5ef5c9bb4..9cd45380fc 100644 --- a/cura/Layer.py +++ b/cura/Layer.py @@ -47,12 +47,12 @@ class Layer: return result - def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices): + def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices): result_vertex_offset = vertex_offset result_index_offset = index_offset self._element_count = 0 for polygon in self._polygons: - polygon.build(result_vertex_offset, result_index_offset, vertices, colors, line_dimensions, extruders, line_types, indices) + polygon.build(result_vertex_offset, result_index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices) result_vertex_offset += polygon.lineMeshVertexCount() result_index_offset += polygon.lineMeshElementCount() self._element_count += polygon.elementCount diff --git a/cura/LayerDataBuilder.py b/cura/LayerDataBuilder.py index 6e50611e64..d6cc81a4e9 100755 --- a/cura/LayerDataBuilder.py +++ b/cura/LayerDataBuilder.py @@ -20,11 +20,11 @@ class LayerDataBuilder(MeshBuilder): if layer not in self._layers: self._layers[layer] = Layer(layer) - def addPolygon(self, layer, polygon_type, data, line_width): + def addPolygon(self, layer, polygon_type, data, line_width, line_thickness, line_feedrate): if layer not in self._layers: self.addLayer(layer) - p = LayerPolygon(self, polygon_type, data, line_width) + p = LayerPolygon(self, polygon_type, data, line_width, line_thickness, line_feedrate) self._layers[layer].polygons.append(p) def getLayer(self, layer): @@ -64,13 +64,14 @@ class LayerDataBuilder(MeshBuilder): line_dimensions = numpy.empty((vertex_count, 2), numpy.float32) colors = numpy.empty((vertex_count, 4), numpy.float32) indices = numpy.empty((index_count, 2), numpy.int32) + feedrates = numpy.empty((vertex_count), numpy.float32) extruders = numpy.empty((vertex_count), numpy.float32) line_types = numpy.empty((vertex_count), numpy.float32) vertex_offset = 0 index_offset = 0 for layer, data in sorted(self._layers.items()): - ( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices) + ( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices) self._element_counts[layer] = data.elementCount self.addVertices(vertices) @@ -107,6 +108,11 @@ class LayerDataBuilder(MeshBuilder): "value": line_types, "opengl_name": "a_line_type", "opengl_type": "float" + }, + "feedrates": { + "value": feedrates, + "opengl_name": "a_feedrate", + "opengl_type": "float" } } diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py index 7f41351b7f..9766e0c82a 100644 --- a/cura/LayerPolygon.py +++ b/cura/LayerPolygon.py @@ -28,7 +28,8 @@ class LayerPolygon: # \param data new_points # \param line_widths array with line widths # \param line_thicknesses: array with type as index and thickness as value - def __init__(self, extruder, line_types, data, line_widths, line_thicknesses): + # \param line_feedrates array with line feedrates + def __init__(self, extruder, line_types, data, line_widths, line_thicknesses, line_feedrates): self._extruder = extruder self._types = line_types for i in range(len(self._types)): @@ -37,6 +38,7 @@ class LayerPolygon: self._data = data self._line_widths = line_widths self._line_thicknesses = line_thicknesses + self._line_feedrates = line_feedrates self._vertex_begin = 0 self._vertex_end = 0 @@ -84,10 +86,11 @@ class LayerPolygon: # \param vertices : vertex numpy array to be filled # \param colors : vertex numpy array to be filled # \param line_dimensions : vertex numpy array to be filled + # \param feedrates : vertex numpy array to be filled # \param extruders : vertex numpy array to be filled # \param line_types : vertex numpy array to be filled # \param indices : index numpy array to be filled - def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices): + def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices): if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None: self.buildCache() @@ -109,10 +112,13 @@ class LayerPolygon: # Create an array with colors for each vertex and remove the color data for the points that has been thrown away. colors[self._vertex_begin:self._vertex_end, :] = numpy.tile(self._colors, (1, 2)).reshape((-1, 4))[needed_points_list.ravel()] - # Create an array with line widths for each vertex. + # Create an array with line widths and thicknesses for each vertex. line_dimensions[self._vertex_begin:self._vertex_end, 0] = numpy.tile(self._line_widths, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0] line_dimensions[self._vertex_begin:self._vertex_end, 1] = numpy.tile(self._line_thicknesses, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0] + # Create an array with feedrates for each line + feedrates[self._vertex_begin:self._vertex_end] = numpy.tile(self._line_feedrates, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0] + extruders[self._vertex_begin:self._vertex_end] = self._extruder # Convert type per vertex to type per line @@ -166,6 +172,14 @@ class LayerPolygon: @property def lineWidths(self): return self._line_widths + + @property + def lineThicknesses(self): + return self._line_thicknesses + + @property + def lineFeedrates(self): + return self._line_feedrates @property def jumpMask(self): diff --git a/plugins/CuraEngineBackend/Cura.proto b/plugins/CuraEngineBackend/Cura.proto index c2e4e5bb5f..69612210ec 100644 --- a/plugins/CuraEngineBackend/Cura.proto +++ b/plugins/CuraEngineBackend/Cura.proto @@ -61,6 +61,8 @@ message Polygon { Type type = 1; // Type of move bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used) float line_width = 3; // The width of the line being laid down + float line_thickness = 4; // The thickness of the line being laid down + float line_feedrate = 5; // The feedrate of the line being laid down } message LayerOptimized { @@ -82,6 +84,8 @@ message PathSegment { bytes points = 3; // The points defining the line segments, bytes of float[2/3] array of length N+1 bytes line_type = 4; // Type of line segment as an unsigned char array of length 1 or N, where N is the number of line segments in this path bytes line_width = 5; // The widths of the line segments as bytes of a float array of length 1 or N + bytes line_thickness = 6; // The thickness of the line segments as bytes of a float array of length 1 or N + bytes line_feedrate = 7; // The feedrate of the line segments as bytes of a float array of length 1 or N } diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index ab93a01204..30fcf6cced 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py @@ -128,10 +128,11 @@ class ProcessSlicedLayersJob(Job): line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. - # In the future, line_thicknesses should be given by CuraEngine as well. - # Currently the infill layer thickness also translates to line width - line_thicknesses = numpy.zeros(line_widths.shape, dtype="f4") - line_thicknesses[:] = layer.thickness / 1000 # from micrometer to millimeter + line_thicknesses = numpy.fromstring(polygon.line_thickness, dtype="f4") # Convert bytearray to numpy array + line_thicknesses = line_thicknesses.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. + + line_feedrates = numpy.fromstring(polygon.line_feedrate, dtype="f4") # Convert bytearray to numpy array + line_feedrates = line_feedrates.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. # Create a new 3D-array, copy the 2D points over and insert the right height. # This uses manual array creation + copy rather than numpy.insert since this is @@ -146,7 +147,7 @@ class ProcessSlicedLayersJob(Job): new_points[:, 1] = points[:, 2] new_points[:, 2] = -points[:, 1] - this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses) + this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses, line_feedrates) this_poly.buildCache() this_layer.polygons.append(this_poly) diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py index 9107ec3258..c7aea0ddcc 100755 --- a/plugins/GCodeReader/GCodeReader.py +++ b/plugins/GCodeReader/GCodeReader.py @@ -114,9 +114,11 @@ class GCodeReader(MeshReader): line_types = numpy.empty((count - 1, 1), numpy.int32) line_widths = numpy.empty((count - 1, 1), numpy.float32) line_thicknesses = numpy.empty((count - 1, 1), numpy.float32) + line_feedrates = numpy.empty((count - 1, 1), numpy.float32) # TODO: need to calculate actual line width based on E values line_widths[:, 0] = 0.35 # Just a guess - line_thicknesses[:, 0] = layer_thickness + line_thicknesses[:, 0] = layer_thickness # TODO Same for all, but it should be calculated from the layer height differences + line_feedrates[:, 0] = 50 # TODO Now we use 50mm/s as a demo, it should be obtained from the GCode points = numpy.empty((count, 3), numpy.float32) i = 0 for point in path: @@ -127,7 +129,7 @@ class GCodeReader(MeshReader): line_widths[i - 1] = 0.1 i += 1 - this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses) + this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses, line_feedrates) this_poly.buildCache() this_layer.polygons.append(this_poly) From 99319b223cbc49f75b25c6e3e01ccddc3c6a9872 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 14 Nov 2017 15:28:31 +0100 Subject: [PATCH 48/67] CURA-4526 GCodeReader now reads the feedrate and layer thickness --- plugins/GCodeReader/GCodeReader.py | 54 +++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py index c7aea0ddcc..9ff4df4692 100755 --- a/plugins/GCodeReader/GCodeReader.py +++ b/plugins/GCodeReader/GCodeReader.py @@ -40,7 +40,8 @@ class GCodeReader(MeshReader): self._extruder_number = 0 self._clearValues() self._scene_node = None - self._position = namedtuple('Position', ['x', 'y', 'z', 'e']) + # X, Y, Z position, F feedrate and E extruder values are stored + self._position = namedtuple('Position', ['x', 'y', 'z', 'f', 'e']) self._is_layers_in_file = False # Does the Gcode have the layers comment? self._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset] self._current_layer_thickness = 0.2 # default @@ -96,7 +97,7 @@ class GCodeReader(MeshReader): def _createPolygon(self, layer_thickness, path, extruder_offsets): countvalid = 0 for point in path: - if point[3] > 0: + if point[4] > 0: countvalid += 1 if countvalid >= 2: # we know what to do now, no need to count further @@ -117,16 +118,19 @@ class GCodeReader(MeshReader): line_feedrates = numpy.empty((count - 1, 1), numpy.float32) # TODO: need to calculate actual line width based on E values line_widths[:, 0] = 0.35 # Just a guess - line_thicknesses[:, 0] = layer_thickness # TODO Same for all, but it should be calculated from the layer height differences - line_feedrates[:, 0] = 50 # TODO Now we use 50mm/s as a demo, it should be obtained from the GCode + line_thicknesses[:, 0] = layer_thickness points = numpy.empty((count, 3), numpy.float32) i = 0 for point in path: points[i, :] = [point[0] + extruder_offsets[0], point[2], -point[1] - extruder_offsets[1]] if i > 0: - line_types[i - 1] = point[3] - if point[3] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]: + line_feedrates[i - 1] = point[3] + line_types[i - 1] = point[4] + if point[4] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]: line_widths[i - 1] = 0.1 + line_thicknesses[i - 1] = 0.0 # Travels are set as zero thickness lines + # else: + # line_widths[i - 1] = _calculateLineWidth(layer_thickness) i += 1 this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses, line_feedrates) @@ -135,27 +139,43 @@ class GCodeReader(MeshReader): this_layer.polygons.append(this_poly) return True + def _calculateLineWidth(self, current_position, previous_position, layer_thickness): + # Area of the filament + filament_diameter = 2.85 + Af = (filament_diameter / 2) ** 2 * numpy.pi + # Length of the extruded filament + de = current_position.e - previous_position.e + # Volumne of the extruded filament + dVe = de * Af + # Length of the printed line + dX = numpy.sqrt((current_position.x - previous_position.x)**2 + (current_position.y - previous_position.y)**2) + # Area of the printed line. This area is a ellipse + Ae = dVe / dX + # This area is a ellipse with: r1 = layer_thickness, r2 = layer_width + return Ae / (layer_thickness * numpy.pi) + def _gCode0(self, position, params, path): - x, y, z, e = position + x, y, z, f, e = position x = params.x if params.x is not None else x y = params.y if params.y is not None else y z = params.z if params.z is not None else position.z + f = params.f if params.f is not None else position.f if params.e is not None: if params.e > e[self._extruder_number]: - path.append([x, y, z, self._layer_type]) # extrusion + path.append([x, y, z, f, self._layer_type]) # extrusion else: - path.append([x, y, z, LayerPolygon.MoveRetractionType]) # retraction + path.append([x, y, z, f, LayerPolygon.MoveRetractionType]) # retraction e[self._extruder_number] = params.e # Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions # Also, 1.5 is a heuristic for any priming or whatsoever, we skip those. if z > self._previous_z and (z - self._previous_z < 1.5): - self._current_layer_thickness = z - self._previous_z + 0.05 # allow a tiny overlap + self._current_layer_thickness = z - self._previous_z # allow a tiny overlap self._previous_z = z else: - path.append([x, y, z, LayerPolygon.MoveCombingType]) - return self._position(x, y, z, e) + path.append([x, y, z, f, LayerPolygon.MoveCombingType]) + return self._position(x, y, z, f, e) # G0 and G1 should be handled exactly the same. _gCode1 = _gCode0 @@ -166,6 +186,7 @@ class GCodeReader(MeshReader): params.x if params.x is not None else position.x, params.y if params.y is not None else position.y, 0, + position.f, position.e) ## Reset the current position to the values specified. @@ -177,6 +198,7 @@ class GCodeReader(MeshReader): params.x if params.x is not None else position.x, params.y if params.y is not None else position.y, params.z if params.z is not None else position.z, + params.f if params.f is not None else position.f, position.e) def _processGCode(self, G, line, position, path): @@ -184,7 +206,7 @@ class GCodeReader(MeshReader): line = line.split(";", 1)[0] # Remove comments (if any) if func is not None: s = line.upper().split(" ") - x, y, z, e = None, None, None, None + x, y, z, f, e = None, None, None, None, None for item in s[1:]: if len(item) <= 1: continue @@ -196,11 +218,13 @@ class GCodeReader(MeshReader): y = float(item[1:]) if item[0] == "Z": z = float(item[1:]) + if item[0] == "F": + f = float(item[1:]) / 60 if item[0] == "E": e = float(item[1:]) if (x is not None and x < 0) or (y is not None and y < 0): self._center_is_zero = True - params = self._position(x, y, z, e) + params = self._position(x, y, z, f, e) return func(position, params, path) return position @@ -262,7 +286,7 @@ class GCodeReader(MeshReader): Logger.log("d", "Parsing %s..." % file_name) - current_position = self._position(0, 0, 0, [0]) + current_position = self._position(0, 0, 0, 0, [0]) current_path = [] for line in file: From 50308c6e39732fab609d1c5102bebb1e3e7a2227 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 15 Nov 2017 13:03:36 +0100 Subject: [PATCH 49/67] CURA-4526 Calculate the line width from the GCode --- plugins/GCodeReader/GCodeReader.py | 65 +++++++++++++++++------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py index 9ff4df4692..ca4042351d 100755 --- a/plugins/GCodeReader/GCodeReader.py +++ b/plugins/GCodeReader/GCodeReader.py @@ -49,7 +49,9 @@ class GCodeReader(MeshReader): Preferences.getInstance().addPreference("gcodereader/show_caution", True) def _clearValues(self): + self._filament_diameter = 2.85 self._extruder_number = 0 + self._extrusion_length_offset = [0] self._layer_type = LayerPolygon.Inset0Type self._layer_number = 0 self._previous_z = 0 @@ -97,7 +99,7 @@ class GCodeReader(MeshReader): def _createPolygon(self, layer_thickness, path, extruder_offsets): countvalid = 0 for point in path: - if point[4] > 0: + if point[5] > 0: countvalid += 1 if countvalid >= 2: # we know what to do now, no need to count further @@ -116,21 +118,22 @@ class GCodeReader(MeshReader): line_widths = numpy.empty((count - 1, 1), numpy.float32) line_thicknesses = numpy.empty((count - 1, 1), numpy.float32) line_feedrates = numpy.empty((count - 1, 1), numpy.float32) - # TODO: need to calculate actual line width based on E values line_widths[:, 0] = 0.35 # Just a guess line_thicknesses[:, 0] = layer_thickness points = numpy.empty((count, 3), numpy.float32) + extrusion_values = numpy.empty((count, 1), numpy.float32) i = 0 for point in path: points[i, :] = [point[0] + extruder_offsets[0], point[2], -point[1] - extruder_offsets[1]] + extrusion_values[i] = point[4] if i > 0: line_feedrates[i - 1] = point[3] - line_types[i - 1] = point[4] - if point[4] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]: + line_types[i - 1] = point[5] + if point[5] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]: line_widths[i - 1] = 0.1 line_thicknesses[i - 1] = 0.0 # Travels are set as zero thickness lines - # else: - # line_widths[i - 1] = _calculateLineWidth(layer_thickness) + else: + line_widths[i - 1] = self._calculateLineWidth(points[i], points[i-1], extrusion_values[i], extrusion_values[i-1], layer_thickness) i += 1 this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses, line_feedrates) @@ -139,20 +142,27 @@ class GCodeReader(MeshReader): this_layer.polygons.append(this_poly) return True - def _calculateLineWidth(self, current_position, previous_position, layer_thickness): + def _calculateLineWidth(self, current_point, previous_point, current_extrusion, previous_extrusion, layer_thickness): # Area of the filament - filament_diameter = 2.85 - Af = (filament_diameter / 2) ** 2 * numpy.pi + Af = (self._filament_diameter / 2) ** 2 * numpy.pi # Length of the extruded filament - de = current_position.e - previous_position.e + de = current_extrusion - previous_extrusion # Volumne of the extruded filament dVe = de * Af # Length of the printed line - dX = numpy.sqrt((current_position.x - previous_position.x)**2 + (current_position.y - previous_position.y)**2) - # Area of the printed line. This area is a ellipse + dX = numpy.sqrt((current_point[0] - previous_point[0])**2 + (current_point[2] - previous_point[2])**2) + # When the extruder recovers from a retraction, we get zero distance + if dX == 0: + return 0.1 + # Area of the printed line. This area is a rectangle Ae = dVe / dX - # This area is a ellipse with: r1 = layer_thickness, r2 = layer_width - return Ae / (layer_thickness * numpy.pi) + # This area is a rectangle with area equal to layer_thickness * layer_width + line_width = Ae / layer_thickness + + # A threshold is set to avoid weird paths in the GCode + if line_width > 1.2: + return 0.35 + return line_width def _gCode0(self, position, params, path): x, y, z, f, e = position @@ -163,9 +173,9 @@ class GCodeReader(MeshReader): if params.e is not None: if params.e > e[self._extruder_number]: - path.append([x, y, z, f, self._layer_type]) # extrusion + path.append([x, y, z, f, params.e + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion else: - path.append([x, y, z, f, LayerPolygon.MoveRetractionType]) # retraction + path.append([x, y, z, f, params.e + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction e[self._extruder_number] = params.e # Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions @@ -174,7 +184,7 @@ class GCodeReader(MeshReader): self._current_layer_thickness = z - self._previous_z # allow a tiny overlap self._previous_z = z else: - path.append([x, y, z, f, LayerPolygon.MoveCombingType]) + path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveCombingType]) return self._position(x, y, z, f, e) # G0 and G1 should be handled exactly the same. @@ -193,6 +203,8 @@ class GCodeReader(MeshReader): # For example: G92 X10 will set the X to 10 without any physical motion. def _gCode92(self, position, params, path): if params.e is not None: + # Sometimes a G92 E0 is introduced in the middle of the GCode so we need to keep those offsets for calculate the line_width + self._extrusion_length_offset[self._extruder_number] += position.e[self._extruder_number] - params.e position.e[self._extruder_number] = params.e return self._position( params.x if params.x is not None else position.x, @@ -231,6 +243,7 @@ class GCodeReader(MeshReader): def _processTCode(self, T, line, position, path): self._extruder_number = T if self._extruder_number + 1 > len(position.e): + self._extrusion_length_offset.extend([0] * (self._extruder_number - len(position.e) + 1)) position.e.extend([0] * (self._extruder_number - len(position.e) + 1)) return position @@ -249,6 +262,8 @@ class GCodeReader(MeshReader): def read(self, file_name): Logger.log("d", "Preparing to load %s" % file_name) self._cancelled = False + # We obtain the filament diameter from the selected printer to calculate line widths + self._filament_diameter = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") scene_node = SceneNode() # Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no @@ -319,6 +334,7 @@ class GCodeReader(MeshReader): else: Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type) + # When the layer change is reached, the polygon is computed so we have just one layer per layer per extruder if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword: try: layer_number = int(line[len(self._layer_keyword):]) @@ -334,17 +350,12 @@ class GCodeReader(MeshReader): G = self._getInt(line, "G") if G is not None: + # When find a movement, the new posistion is calculated and added to the current_path, but + # don't need to create a polygon until the end of the layer current_position = self._processGCode(G, line, current_position, current_path) - - # < 2 is a heuristic for a movement only, that should not be counted as a layer - if current_position.z > last_z and abs(current_position.z - last_z) < 2: - if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])): - current_path.clear() - if not self._is_layers_in_file: - self._layer_number += 1 - continue + # When changing the extruder, the polygon with the stored paths is computed if line.startswith("T"): T = self._getInt(line, "T") if T is not None: @@ -353,8 +364,8 @@ class GCodeReader(MeshReader): current_position = self._processTCode(T, line, current_position, current_path) - # "Flush" leftovers - if not self._is_layers_in_file and len(current_path) > 1: + # "Flush" leftovers. Last layer paths are still stored + if len(current_path) > 1: if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])): self._layer_number += 1 current_path.clear() From b868f913c42486c8b45d1864c5ce1327da597f75 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 16 Nov 2017 10:27:52 +0100 Subject: [PATCH 50/67] CURA-4577 Adding relative positioning to GCodeReader --- plugins/GCodeReader/GCodeReader.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py index 9107ec3258..e49ff9d8d7 100755 --- a/plugins/GCodeReader/GCodeReader.py +++ b/plugins/GCodeReader/GCodeReader.py @@ -54,6 +54,7 @@ class GCodeReader(MeshReader): self._previous_z = 0 self._layer_data_builder = LayerDataBuilder.LayerDataBuilder() self._center_is_zero = False + self._is_absolute_positioning = True # It can be absolute (G90) or relative (G91) @staticmethod def _getValue(line, code): @@ -166,6 +167,16 @@ class GCodeReader(MeshReader): 0, position.e) + ## Set the absolute positioning + def _gCode90(self, position, params, path): + self._is_absolute_positioning = True + return position + + ## Set the relative positioning + def _gCode91(self, position, params, path): + self._is_absolute_positioning = False + return position + ## Reset the current position to the values specified. # For example: G92 X10 will set the X to 10 without any physical motion. def _gCode92(self, position, params, path): @@ -196,7 +207,7 @@ class GCodeReader(MeshReader): z = float(item[1:]) if item[0] == "E": e = float(item[1:]) - if (x is not None and x < 0) or (y is not None and y < 0): + if self._is_absolute_positioning and ((x is not None and x < 0) or (y is not None and y < 0)): self._center_is_zero = True params = self._position(x, y, z, e) return func(position, params, path) From 15a50511b728a61d9aca9247f88133cc4835d91a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 3 Nov 2017 16:03:28 +0100 Subject: [PATCH 51/67] Don't crash if support density is 0 Found while working on CURA-4523. --- resources/definitions/fdmprinter.def.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index b17769eba2..6628086179 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3495,7 +3495,7 @@ "minimum_value_warning": "support_line_width", "default_value": 2.66, "enabled": "support_enable", - "value": "(support_line_width * 100) / support_infill_rate * (2 if support_pattern == 'grid' else (3 if support_pattern == 'triangles' else 1))", + "value": "0 if support_infill_rate == 0 else (support_line_width * 100) / support_infill_rate * (2 if support_pattern == 'grid' else (3 if support_pattern == 'triangles' else 1))", "limit_to_extruder": "support_infill_extruder_nr", "settable_per_mesh": false, "settable_per_extruder": true @@ -5107,7 +5107,7 @@ "description": "Skip one in every N connection lines to make the support structure easier to break away.", "type": "int", "default_value": 5, - "value": "round(support_skip_zag_per_mm / support_line_distance)", + "value": "0 if support_line_distance == 0 else round(support_skip_zag_per_mm / support_line_distance)", "minimum_value": "1", "minimum_value_warning": "3", "enabled": "support_enable and (support_pattern == 'zigzag') and support_skip_some_zags", From f27494d60acda7af5854a44817008bbda5e81c95 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 17 Nov 2017 10:59:14 +0100 Subject: [PATCH 52/67] Move ironing into shell category It is no longer considered experimental. --- resources/definitions/fdmprinter.def.json | 236 +++++++++++----------- 1 file changed, 118 insertions(+), 118 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 6628086179..bf65baf1d9 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1340,6 +1340,124 @@ "type": "int", "limit_to_extruder": "top_bottom_extruder_nr", "settable_per_mesh": true + }, + "ironing_enabled": + { + "label": "Enable Ironing", + "description": "Go over the top surface one additional time, but without extruding material. This is meant to melt the plastic on top further, creating a smoother surface.", + "type": "bool", + "default_value": false, + "limit_to_extruder": "top_bottom_extruder_nr", + "settable_per_mesh": true + }, + "ironing_only_highest_layer": + { + "label": "Iron Only Highest Layer", + "description": "Only perform ironing on the very last layer of the mesh. This saves time if the lower layers don't need a smooth surface finish.", + "type": "bool", + "default_value": false, + "enabled": "ironing_enabled", + "limit_to_extruder": "top_bottom_extruder_nr", + "settable_per_mesh": true + }, + "ironing_pattern": + { + "label": "Ironing Pattern", + "description": "The pattern to use for ironing top surfaces.", + "type": "enum", + "options": + { + "concentric": "Concentric", + "zigzag": "Zig Zag" + }, + "default_value": "zigzag", + "enabled": "ironing_enabled", + "limit_to_extruder": "top_bottom_extruder_nr", + "settable_per_mesh": true + }, + "ironing_line_spacing": + { + "label": "Ironing Line Spacing", + "description": "The distance between the lines of ironing.", + "type": "float", + "unit": "mm", + "default_value": 0.1, + "minimum_value": "0.001", + "maximum_value_warning": "machine_nozzle_tip_outer_diameter", + "enabled": "ironing_enabled", + "limit_to_extruder": "top_bottom_extruder_nr", + "settable_per_mesh": true + }, + "ironing_flow": + { + "label": "Ironing Flow", + "description": "The amount of material, relative to a normal skin line, to extrude during ironing. Keeping the nozzle filled helps filling some of the crevices of the top surface, but too much results in overextrusion and blips on the side of the surface.", + "type": "float", + "unit": "%", + "default_value": 10.0, + "minimum_value": "0", + "maximum_value_warning": "50", + "enabled": "ironing_enabled", + "limit_to_extruder": "top_bottom_extruder_nr", + "settable_per_mesh": true + }, + "ironing_inset": + { + "label": "Ironing Inset", + "description": "A distance to keep from the edges of the model. Ironing all the way to the edge of the mesh may result in a jagged edge on your print.", + "type": "float", + "unit": "mm", + "default_value": 0.35, + "value": "wall_line_width_0 / 2", + "minimum_value_warning": "0", + "maximum_value_warning": "wall_line_width_0", + "enabled": "ironing_enabled", + "limit_to_extruder": "top_bottom_extruder_nr", + "settable_per_mesh": true + }, + "speed_ironing": + { + "label": "Ironing Speed", + "description": "The speed at which to pass over the top surface.", + "type": "float", + "unit": "mm/s", + "default_value": 20.0, + "value": "speed_topbottom * 20 / 30", + "minimum_value": "0.001", + "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", + "maximum_value_warning": "100", + "enabled": "ironing_enabled", + "limit_to_extruder": "top_bottom_extruder_nr", + "settable_per_mesh": true + }, + "acceleration_ironing": + { + "label": "Ironing Acceleration", + "description": "The acceleration with which ironing is performed.", + "unit": "mm/s²", + "type": "float", + "minimum_value": "0.1", + "minimum_value_warning": "100", + "maximum_value_warning": "10000", + "default_value": 3000, + "value": "acceleration_topbottom", + "enabled": "resolveOrValue('acceleration_enabled') and ironing_enabled", + "limit_to_extruder": "top_bottom_extruder_nr", + "settable_per_mesh": true + }, + "jerk_ironing": + { + "label": "Ironing Jerk", + "description": "The maximum instantaneous velocity change while performing ironing.", + "unit": "mm/s", + "type": "float", + "minimum_value": "0", + "maximum_value_warning": "50", + "default_value": 20, + "value": "jerk_topbottom", + "enabled": "resolveOrValue('jerk_enabled') and ironing_enabled", + "limit_to_extruder": "top_bottom_extruder_nr", + "settable_per_mesh": true } } }, @@ -5813,124 +5931,6 @@ "settable_per_mesh": false, "settable_per_extruder": false, "settable_per_meshgroup": false - }, - "ironing_enabled": - { - "label": "Enable Ironing", - "description": "Go over the top surface one additional time, but without extruding material. This is meant to melt the plastic on top further, creating a smoother surface.", - "type": "bool", - "default_value": false, - "limit_to_extruder": "top_bottom_extruder_nr", - "settable_per_mesh": true - }, - "ironing_only_highest_layer": - { - "label": "Iron Only Highest Layer", - "description": "Only perform ironing on the very last layer of the mesh. This saves time if the lower layers don't need a smooth surface finish.", - "type": "bool", - "default_value": false, - "enabled": "ironing_enabled", - "limit_to_extruder": "top_bottom_extruder_nr", - "settable_per_mesh": true - }, - "ironing_pattern": - { - "label": "Ironing Pattern", - "description": "The pattern to use for ironing top surfaces.", - "type": "enum", - "options": - { - "concentric": "Concentric", - "zigzag": "Zig Zag" - }, - "default_value": "zigzag", - "enabled": "ironing_enabled", - "limit_to_extruder": "top_bottom_extruder_nr", - "settable_per_mesh": true - }, - "ironing_line_spacing": - { - "label": "Ironing Line Spacing", - "description": "The distance between the lines of ironing.", - "type": "float", - "unit": "mm", - "default_value": 0.1, - "minimum_value": "0.001", - "maximum_value_warning": "machine_nozzle_tip_outer_diameter", - "enabled": "ironing_enabled", - "limit_to_extruder": "top_bottom_extruder_nr", - "settable_per_mesh": true - }, - "ironing_flow": - { - "label": "Ironing Flow", - "description": "The amount of material, relative to a normal skin line, to extrude during ironing. Keeping the nozzle filled helps filling some of the crevices of the top surface, but too much results in overextrusion and blips on the side of the surface.", - "type": "float", - "unit": "%", - "default_value": 10.0, - "minimum_value": "0", - "maximum_value_warning": "50", - "enabled": "ironing_enabled", - "limit_to_extruder": "top_bottom_extruder_nr", - "settable_per_mesh": true - }, - "ironing_inset": - { - "label": "Ironing Inset", - "description": "A distance to keep from the edges of the model. Ironing all the way to the edge of the mesh may result in a jagged edge on your print.", - "type": "float", - "unit": "mm", - "default_value": 0.35, - "value": "wall_line_width_0 / 2", - "minimum_value_warning": "0", - "maximum_value_warning": "wall_line_width_0", - "enabled": "ironing_enabled", - "limit_to_extruder": "top_bottom_extruder_nr", - "settable_per_mesh": true - }, - "speed_ironing": - { - "label": "Ironing Speed", - "description": "The speed at which to pass over the top surface.", - "type": "float", - "unit": "mm/s", - "default_value": 20.0, - "value": "speed_topbottom * 20 / 30", - "minimum_value": "0.001", - "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", - "maximum_value_warning": "100", - "enabled": "ironing_enabled", - "limit_to_extruder": "top_bottom_extruder_nr", - "settable_per_mesh": true - }, - "acceleration_ironing": - { - "label": "Ironing Acceleration", - "description": "The acceleration with which ironing is performed.", - "unit": "mm/s²", - "type": "float", - "minimum_value": "0.1", - "minimum_value_warning": "100", - "maximum_value_warning": "10000", - "default_value": 3000, - "value": "acceleration_topbottom", - "enabled": "resolveOrValue('acceleration_enabled') and ironing_enabled", - "limit_to_extruder": "top_bottom_extruder_nr", - "settable_per_mesh": true - }, - "jerk_ironing": - { - "label": "Ironing Jerk", - "description": "The maximum instantaneous velocity change while performing ironing.", - "unit": "mm/s", - "type": "float", - "minimum_value": "0", - "maximum_value_warning": "50", - "default_value": 20, - "value": "jerk_topbottom", - "enabled": "resolveOrValue('jerk_enabled') and ironing_enabled", - "limit_to_extruder": "top_bottom_extruder_nr", - "settable_per_mesh": true } } }, From cd37f91a0a29ed097832d88db9346c520e926f80 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 17 Nov 2017 10:40:13 +0100 Subject: [PATCH 53/67] Remove unnecessary code in BuildVolume CURA-4482 --- cura/BuildVolume.py | 66 +++++++++++---------------------------------- 1 file changed, 15 insertions(+), 51 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 7362a5aff2..50f63e49d5 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -876,15 +876,6 @@ class BuildVolume(SceneNode): return result - ## Private convenience function to get a setting from the adhesion - # extruder. - # - # \param setting_key The key of the setting to get. - # \param property The property to get from the setting. - # \return The property of the specified setting in the adhesion extruder. - def _getSettingFromAdhesionExtruder(self, setting_key, property = "value"): - return self._getSettingFromExtruder(setting_key, "adhesion_extruder_nr", property) - ## Private convenience function to get a setting from every extruder. # # For single extrusion machines, this gets the setting from the global @@ -899,38 +890,6 @@ class BuildVolume(SceneNode): all_values[i] = 0 return all_values - ## Private convenience function to get a setting from the support infill - # extruder. - # - # \param setting_key The key of the setting to get. - # \param property The property to get from the setting. - # \return The property of the specified setting in the support infill - # extruder. - def _getSettingFromSupportInfillExtruder(self, setting_key, property = "value"): - return self._getSettingFromExtruder(setting_key, "support_infill_extruder_nr", property) - - ## Helper function to get a setting from an extruder specified in another - # setting. - # - # \param setting_key The key of the setting to get. - # \param extruder_setting_key The key of the setting that specifies from - # which extruder to get the setting, if there are multiple extruders. - # \param property The property to get from the setting. - # \return The property of the specified setting in the specified extruder. - def _getSettingFromExtruder(self, setting_key, extruder_setting_key, prop = "value"): - extruder_index = self._global_container_stack.getProperty(extruder_setting_key, "value") - extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)] - extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] - - value = extruder_stack.getProperty(setting_key, prop) - setting_type = extruder_stack.getProperty(setting_key, "type") - - # default 0 for numerical values - if not value and (setting_type == "int" or setting_type == "float"): - return 0 - - return value - ## Convenience function to calculate the disallowed radius around the edge. # # This disallowed radius is to allow for space around the models that is @@ -948,29 +907,32 @@ class BuildVolume(SceneNode): return 0.1 # Return a very small value, so we do draw disallowed area's near the edges. adhesion_type = container_stack.getProperty("adhesion_type", "value") - + skirt_brim_line_width = self._global_container_stack.getProperty("skirt_brim_line_width", "value") + initial_layer_line_width_factor = self._global_container_stack.getProperty("initial_layer_line_width_factor", "value") if adhesion_type == "skirt": - skirt_distance = self._getSettingFromAdhesionExtruder("skirt_gap") - skirt_line_count = self._getSettingFromAdhesionExtruder("skirt_line_count") - bed_adhesion_size = skirt_distance + (self._getSettingFromAdhesionExtruder("skirt_brim_line_width") * skirt_line_count) * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor") / 100.0 + skirt_distance = self._global_container_stack.getProperty("skirt_gap", "value") + skirt_line_count = self._global_container_stack.getProperty("skirt_line_count", "value") + + bed_adhesion_size = skirt_distance + (skirt_brim_line_width * skirt_line_count) * initial_layer_line_width_factor / 100.0 for extruder_stack in used_extruders: bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0 # We don't create an additional line for the extruder we're printing the skirt with. - bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0 + bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0 elif adhesion_type == "brim": - bed_adhesion_size = self._getSettingFromAdhesionExtruder("skirt_brim_line_width") * self._getSettingFromAdhesionExtruder("brim_line_count") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor") / 100.0 + brim_line_count = self._global_container_stack.getProperty("brim_line_count", "value") + bed_adhesion_size = skirt_brim_line_width * brim_line_count * initial_layer_line_width_factor / 100.0 for extruder_stack in used_extruders: bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0 # We don't create an additional line for the extruder we're printing the brim with. - bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0 + bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0 elif adhesion_type == "raft": - bed_adhesion_size = self._getSettingFromAdhesionExtruder("raft_margin") + bed_adhesion_size = self._global_container_stack.getProperty("raft_margin", "value") elif adhesion_type == "none": bed_adhesion_size = 0 @@ -979,8 +941,10 @@ class BuildVolume(SceneNode): raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?") support_expansion = 0 - if self._getSettingFromSupportInfillExtruder("support_offset") and self._global_container_stack.getProperty("support_enable", "value"): - support_expansion += self._getSettingFromSupportInfillExtruder("support_offset") + support_enabled = self._global_container_stack.getProperty("support_enable", "value") + support_offset = self._global_container_stack.getProperty("support_offset", "value") + if support_enabled and support_offset: + support_expansion += support_offset farthest_shield_distance = 0 if container_stack.getProperty("draft_shield_enabled", "value"): From 9b102f9a7ef6c5232e2be57f1cd6f2b57c7bdd46 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 17 Nov 2017 11:02:18 +0100 Subject: [PATCH 54/67] Remove unused function in StartSliceJob CURA-4482 --- plugins/CuraEngineBackend/StartSliceJob.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 49dfc71769..196b18e094 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -9,7 +9,6 @@ import time from UM.Job import Job from UM.Application import Application from UM.Logger import Logger -from UM.Decorators import deprecated from UM.Scene.SceneNode import SceneNode from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator @@ -247,20 +246,6 @@ class StartSliceJob(Job): setting.value = str(stack.getProperty(key, "value")).encode("utf-8") Job.yieldThread() - ## Create extruder message from global stack - @deprecated("Extruder stack is always used since version 3.1, even with single extrusion machines", "3.1") - def _buildExtruderMessageFromGlobalStack(self, stack): - message = self._slice_message.addRepeatedMessage("extruders") - - for key in stack.getAllKeys(): - # Do not send settings that are not settable_per_extruder. - if not stack.getProperty(key, "settable_per_extruder"): - continue - setting = message.getMessage("settings").addRepeatedMessage("settings") - setting.name = key - setting.value = str(stack.getProperty(key, "value")).encode("utf-8") - Job.yieldThread() - ## Sends all global settings to the engine. # # The settings are taken from the global stack. This does not include any From aa92d4d0f97bcd36ccd1a7cfff30fd5fc68d843e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 17 Nov 2017 11:46:55 +0100 Subject: [PATCH 55/67] Add to all installation to lib/ folders Some systems have a suffix there to indicate the address size, such as lib64. --- CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 44b4b57f54..9296c4ce4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ find_package(PythonInterp 3.5.0 REQUIRED) install(DIRECTORY resources DESTINATION ${CMAKE_INSTALL_DATADIR}/cura) install(DIRECTORY plugins - DESTINATION lib/cura) + DESTINATION lib${LIB_SUFFIX}/cura) if(NOT APPLE AND NOT WIN32) install(FILES cura_app.py DESTINATION ${CMAKE_INSTALL_BINDIR} @@ -47,16 +47,16 @@ if(NOT APPLE AND NOT WIN32) RENAME cura) if(EXISTS /etc/debian_version) install(DIRECTORY cura - DESTINATION lib/python${PYTHON_VERSION_MAJOR}/dist-packages + DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}/dist-packages FILES_MATCHING PATTERN *.py) install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py - DESTINATION lib/python${PYTHON_VERSION_MAJOR}/dist-packages/cura) + DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}/dist-packages/cura) else() install(DIRECTORY cura - DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages + DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages FILES_MATCHING PATTERN *.py) install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py - DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura) + DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura) endif() install(FILES ${CMAKE_BINARY_DIR}/cura.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) @@ -72,8 +72,8 @@ else() DESTINATION ${CMAKE_INSTALL_BINDIR} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY cura - DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages + DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages FILES_MATCHING PATTERN *.py) install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py - DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura) + DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura) endif() From e246784df268224d0271e6e303858d5b1a5c500d Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 17 Nov 2017 12:43:30 +0100 Subject: [PATCH 56/67] Use parseBool() for metadata bool values CURA-4482 --- cura/Settings/MachineManager.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 7bbbd868f4..0daf54c018 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1175,15 +1175,14 @@ class MachineManager(QObject): @pyqtProperty(bool, notify = globalContainerChanged) def hasMaterials(self) -> bool: if self._global_container_stack: - return bool(self._global_container_stack.getMetaDataEntry("has_materials", False)) + return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)) return False @pyqtProperty(bool, notify = globalContainerChanged) def hasVariants(self) -> bool: if self._global_container_stack: - return bool(self._global_container_stack.getMetaDataEntry("has_variants", False)) - + return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_variants", False)) return False ## Property to indicate if a machine has "specialized" material profiles. @@ -1191,8 +1190,7 @@ class MachineManager(QObject): @pyqtProperty(bool, notify = globalContainerChanged) def filterMaterialsByMachine(self) -> bool: if self._global_container_stack: - return bool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False)) - + return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False)) return False ## Property to indicate if a machine has "specialized" quality profiles. @@ -1200,7 +1198,7 @@ class MachineManager(QObject): @pyqtProperty(bool, notify = globalContainerChanged) def filterQualityByMachine(self) -> bool: if self._global_container_stack: - return bool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False)) + return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False)) return False ## Get the Definition ID of a machine (specified by ID) From 2c39612bc8d06245edb3ffdba26859480c5b0d51 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 17 Nov 2017 12:47:28 +0100 Subject: [PATCH 57/67] Fix UM2 upgrade regarding the variant CURA-4482 UM2 by default doesn't have variants, but if the user enables Olsson Block, the variant option will become available. This commit fixes the following cases: - Make sure that the variant is set on the extruder stack but not the global stack - Extruder stacks don't contain information such as has_variant. Such info should be retrieved from the global stack and not just from the definition container because they can be overriden by other containers. --- cura/Settings/CuraContainerRegistry.py | 5 ++++- cura/Settings/CuraContainerStack.py | 4 +++- cura/Settings/ExtruderStack.py | 5 +++++ plugins/UltimakerMachineActions/UM2UpgradeSelection.py | 4 ++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 526685465e..5cfe867bc2 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -443,7 +443,10 @@ class CuraContainerRegistry(ContainerRegistry): extruder_stack.setUserChanges(user_container) self.addContainer(user_container) - extruder_stack.setVariantById("default") + variant_id = "default" + if machine.variant.getId() != "empty_variant": + variant_id = machine.variant.getId() + extruder_stack.setVariantById(variant_id) extruder_stack.setMaterialById("default") extruder_stack.setQualityById("default") diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index 2d3bf683f6..2a804def4d 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -396,7 +396,9 @@ class CuraContainerStack(ContainerStack): # \note This method assumes the stack has a valid machine definition. def findDefaultVariant(self) -> Optional[ContainerInterface]: definition = self._getMachineDefinition() - if not definition.getMetaDataEntry("has_variants"): + # has_variants can be overridden in other containers and stacks. + # In the case of UM2, it is overridden in the GlobalStack + if not self.getMetaDataEntry("has_variants"): # If the machine does not use variants, we should never set a variant. return None diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py index d8ff6645a8..fe7068b7ea 100644 --- a/cura/Settings/ExtruderStack.py +++ b/cura/Settings/ExtruderStack.py @@ -115,6 +115,11 @@ class ExtruderStack(CuraContainerStack): if has_global_dependencies: self.getNextStack().propertiesChanged.emit(key, properties) + def findDefaultVariant(self): + # The default variant is defined in the machine stack and/or definition, so use the machine stack to find + # the default variant. + return self.getNextStack().findDefaultVariant() + extruder_stack_mime = MimeType( name = "application/x-cura-extruderstack", diff --git a/plugins/UltimakerMachineActions/UM2UpgradeSelection.py b/plugins/UltimakerMachineActions/UM2UpgradeSelection.py index c84033a98e..db277bc485 100644 --- a/plugins/UltimakerMachineActions/UM2UpgradeSelection.py +++ b/plugins/UltimakerMachineActions/UM2UpgradeSelection.py @@ -37,7 +37,7 @@ class UM2UpgradeSelection(MachineAction): def setHasVariants(self, has_variants = True): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: - variant_container = global_container_stack.variant + variant_container = global_container_stack.extruders["0"].variant variant_index = global_container_stack.getContainerIndex(variant_container) if has_variants: @@ -52,7 +52,7 @@ class UM2UpgradeSelection(MachineAction): search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" } containers = self._container_registry.findInstanceContainers(**search_criteria) if containers: - global_container_stack.variant = containers[0] + global_container_stack.extruders["0"].variant = containers[0] else: # The metadata entry is stored in an ini, and ini files are parsed as strings only. # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. From 0f6d65950a8ea017f17d44a019528c55752410ec Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 17 Nov 2017 13:18:44 +0100 Subject: [PATCH 58/67] CURA-4526 Add colors to themes for selected handles --- resources/themes/cura-dark/theme.json | 1 + resources/themes/cura-light/styles.qml | 1 + resources/themes/cura-light/theme.json | 1 + 3 files changed, 3 insertions(+) diff --git a/resources/themes/cura-dark/theme.json b/resources/themes/cura-dark/theme.json index 8bf1455b60..5cfed426e5 100644 --- a/resources/themes/cura-dark/theme.json +++ b/resources/themes/cura-dark/theme.json @@ -133,6 +133,7 @@ "slider_groove_fill": [245, 245, 245, 255], "slider_handle": [255, 255, 255, 255], "slider_handle_hover": [77, 182, 226, 255], + "slider_handle_active": [68, 192, 255, 255], "slider_handle_border": [39, 44, 48, 255], "slider_text_background": [255, 255, 255, 255], diff --git a/resources/themes/cura-light/styles.qml b/resources/themes/cura-light/styles.qml index 6d991c5541..ea9d184926 100755 --- a/resources/themes/cura-light/styles.qml +++ b/resources/themes/cura-light/styles.qml @@ -269,6 +269,7 @@ QtObject { arrowSize: Theme.getSize("button_tooltip_arrow").width color: Theme.getColor("button_tooltip") opacity: control.hovered ? 1.0 : 0.0; + visible: control.text != "" width: control.hovered ? button_tip.width + Theme.getSize("button_tooltip").width : 0 height: Theme.getSize("button_tooltip").height diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 21fd23316d..12f7df906b 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -183,6 +183,7 @@ "slider_groove_fill": [127, 127, 127, 255], "slider_handle": [0, 0, 0, 255], "slider_handle_hover": [77, 182, 226, 255], + "slider_handle_active": [68, 192, 255, 255], "slider_handle_border": [39, 44, 48, 255], "slider_text_background": [255, 255, 255, 255], From 98d48978a860d91457af87430941f57df4292066 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 17 Nov 2017 14:27:46 +0100 Subject: [PATCH 59/67] CURA-4526 Add the SimulationView plugin to Cura and deleting LayerView plugin --- cura/CuraApplication.py | 4 +- .../CuraEngineBackend/CuraEngineBackend.py | 2 +- .../ProcessSlicedLayersJob.py | 6 +- plugins/LayerView/LayerPass.py | 113 ---- plugins/LayerView/LayerViewProxy.py | 154 ----- plugins/LayerView/__init__.py | 25 - .../LayerSlider.qml | 637 +++++++++--------- plugins/SimulationView/NozzleNode.py | 49 ++ plugins/SimulationView/PathSlider.qml | 161 +++++ plugins/SimulationView/SimulationPass.py | 187 +++++ .../SimulationSliderLabel.qml} | 207 +++--- .../SimulationView.py} | 216 ++++-- .../SimulationView.qml} | 344 ++++++++-- plugins/SimulationView/SimulationViewProxy.py | 246 +++++++ plugins/SimulationView/__init__.py | 26 + .../layers.shader | 0 .../layers3d.shader | 29 + plugins/SimulationView/layers3d_shadow.shader | 256 +++++++ plugins/SimulationView/layers_shadow.shader | 156 +++++ .../{LayerView => SimulationView}/plugin.json | 4 +- plugins/SimulationView/resources/nozzle.stl | Bin 0 -> 210284 bytes .../resources/simulation_pause.svg | 79 +++ .../resources/simulation_resume.svg | 82 +++ .../simulationview_composite.shader} | 0 24 files changed, 2162 insertions(+), 821 deletions(-) delete mode 100755 plugins/LayerView/LayerPass.py delete mode 100644 plugins/LayerView/LayerViewProxy.py delete mode 100644 plugins/LayerView/__init__.py rename plugins/{LayerView => SimulationView}/LayerSlider.qml (85%) create mode 100644 plugins/SimulationView/NozzleNode.py create mode 100644 plugins/SimulationView/PathSlider.qml create mode 100644 plugins/SimulationView/SimulationPass.py rename plugins/{LayerView/LayerSliderLabel.qml => SimulationView/SimulationSliderLabel.qml} (88%) rename plugins/{LayerView/LayerView.py => SimulationView/SimulationView.py} (74%) mode change 100755 => 100644 rename plugins/{LayerView/LayerView.qml => SimulationView/SimulationView.qml} (51%) mode change 100755 => 100644 create mode 100644 plugins/SimulationView/SimulationViewProxy.py create mode 100644 plugins/SimulationView/__init__.py rename plugins/{LayerView => SimulationView}/layers.shader (100%) mode change 100755 => 100644 rename plugins/{LayerView => SimulationView}/layers3d.shader (92%) mode change 100755 => 100644 create mode 100644 plugins/SimulationView/layers3d_shadow.shader create mode 100644 plugins/SimulationView/layers_shadow.shader rename plugins/{LayerView => SimulationView}/plugin.json (54%) create mode 100644 plugins/SimulationView/resources/nozzle.stl create mode 100644 plugins/SimulationView/resources/simulation_pause.svg create mode 100644 plugins/SimulationView/resources/simulation_resume.svg rename plugins/{LayerView/layerview_composite.shader => SimulationView/simulationview_composite.shader} (100%) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 41358b83f5..bfd2b0c50a 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -217,7 +217,7 @@ class CuraApplication(QtApplication): "CuraEngineBackend", "UserAgreement", "SolidView", - "LayerView", + "SimulationView", "STLReader", "SelectionTool", "CameraTool", @@ -1383,7 +1383,7 @@ class CuraApplication(QtApplication): extension = os.path.splitext(filename)[1] if extension.lower() in self._non_sliceable_extensions: - self.getController().setActiveView("LayerView") + self.getController().setActiveView("SimulationView") view = self.getController().getActiveView() view.resetLayerData() view.setLayer(9999999) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 44e028093b..a5a1a5b584 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -588,7 +588,7 @@ class CuraEngineBackend(QObject, Backend): def _onActiveViewChanged(self): if Application.getInstance().getController().getActiveView(): view = Application.getInstance().getController().getActiveView() - if view.getPluginId() in ("LayerView", "SimulationView"): # If switching to layer view, we should process the layers if that hasn't been done yet. + if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet. self._layer_view_active = True # There is data and we're not slicing at the moment # if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment. diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index 30fcf6cced..14646cbac1 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py @@ -61,7 +61,7 @@ class ProcessSlicedLayersJob(Job): def run(self): start_time = time() - if Application.getInstance().getController().getActiveView().getPluginId() in ("LayerView", "SimulationView"): + if Application.getInstance().getController().getActiveView().getPluginId() == "SimulationView": self._progress_message.show() Job.yieldThread() if self._abort_requested: @@ -221,7 +221,7 @@ class ProcessSlicedLayersJob(Job): self._progress_message.setProgress(100) view = Application.getInstance().getController().getActiveView() - if view.getPluginId() in ("LayerView", "SimulationView"): + if view.getPluginId() == "SimulationView": view.resetLayerData() if self._progress_message: @@ -234,7 +234,7 @@ class ProcessSlicedLayersJob(Job): def _onActiveViewChanged(self): if self.isRunning(): - if Application.getInstance().getController().getActiveView().getPluginId() in ("LayerView", "SimulationView"): + if Application.getInstance().getController().getActiveView().getPluginId() == "SimulationView": if not self._progress_message: self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, 0, catalog.i18nc("@info:title", "Information")) if self._progress_message.getProgress() != 100: diff --git a/plugins/LayerView/LayerPass.py b/plugins/LayerView/LayerPass.py deleted file mode 100755 index 963c8c75c8..0000000000 --- a/plugins/LayerView/LayerPass.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright (c) 2016 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator -from UM.Resources import Resources -from UM.Scene.SceneNode import SceneNode -from UM.Scene.ToolHandle import ToolHandle -from UM.Application import Application -from UM.PluginRegistry import PluginRegistry - -from UM.View.RenderPass import RenderPass -from UM.View.RenderBatch import RenderBatch -from UM.View.GL.OpenGL import OpenGL - -from cura.Settings.ExtruderManager import ExtruderManager - - -import os.path - -## RenderPass used to display g-code paths. -class LayerPass(RenderPass): - def __init__(self, width, height): - super().__init__("layerview", width, height) - - self._layer_shader = None - self._tool_handle_shader = None - self._gl = OpenGL.getInstance().getBindingsObject() - self._scene = Application.getInstance().getController().getScene() - self._extruder_manager = ExtruderManager.getInstance() - - self._layer_view = None - self._compatibility_mode = None - - def setLayerView(self, layerview): - self._layer_view = layerview - self._compatibility_mode = layerview.getCompatibilityMode() - - def render(self): - if not self._layer_shader: - if self._compatibility_mode: - shader_filename = "layers.shader" - else: - shader_filename = "layers3d.shader" - self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), shader_filename)) - # Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers) - self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex))) - if self._layer_view: - self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getLayerViewType()) - self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities()) - self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves()) - self._layer_shader.setUniformValue("u_show_helpers", self._layer_view.getShowHelpers()) - self._layer_shader.setUniformValue("u_show_skin", self._layer_view.getShowSkin()) - self._layer_shader.setUniformValue("u_show_infill", self._layer_view.getShowInfill()) - else: - #defaults - self._layer_shader.setUniformValue("u_layer_view_type", 1) - self._layer_shader.setUniformValue("u_extruder_opacity", [1, 1, 1, 1]) - self._layer_shader.setUniformValue("u_show_travel_moves", 0) - self._layer_shader.setUniformValue("u_show_helpers", 1) - self._layer_shader.setUniformValue("u_show_skin", 1) - self._layer_shader.setUniformValue("u_show_infill", 1) - - if not self._tool_handle_shader: - self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "toolhandle.shader")) - - self.bind() - - tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay) - - for node in DepthFirstIterator(self._scene.getRoot()): - - if isinstance(node, ToolHandle): - tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh()) - - elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible(): - layer_data = node.callDecoration("getLayerData") - if not layer_data: - continue - - # Render all layers below a certain number as line mesh instead of vertices. - if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())): - start = 0 - end = 0 - element_counts = layer_data.getElementCounts() - for layer in sorted(element_counts.keys()): - if layer > self._layer_view._current_layer_num: - break - if self._layer_view._minimum_layer_num > layer: - start += element_counts[layer] - end += element_counts[layer] - - # This uses glDrawRangeElements internally to only draw a certain range of lines. - batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end)) - batch.addItem(node.getWorldTransformation(), layer_data) - batch.render(self._scene.getActiveCamera()) - - # Create a new batch that is not range-limited - batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid) - - if self._layer_view.getCurrentLayerMesh(): - batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerMesh()) - - if self._layer_view.getCurrentLayerJumps(): - batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerJumps()) - - if len(batch.items) > 0: - batch.render(self._scene.getActiveCamera()) - - # Render toolhandles on top of the layerview - if len(tool_handle_batch.items) > 0: - tool_handle_batch.render(self._scene.getActiveCamera()) - - self.release() diff --git a/plugins/LayerView/LayerViewProxy.py b/plugins/LayerView/LayerViewProxy.py deleted file mode 100644 index 4cbff65040..0000000000 --- a/plugins/LayerView/LayerViewProxy.py +++ /dev/null @@ -1,154 +0,0 @@ -from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty -from UM.FlameProfiler import pyqtSlot -from UM.Application import Application - -import LayerView - - -class LayerViewProxy(QObject): - def __init__(self, parent=None): - super().__init__(parent) - self._current_layer = 0 - self._controller = Application.getInstance().getController() - self._controller.activeViewChanged.connect(self._onActiveViewChanged) - self._onActiveViewChanged() - - currentLayerChanged = pyqtSignal() - maxLayersChanged = pyqtSignal() - activityChanged = pyqtSignal() - globalStackChanged = pyqtSignal() - preferencesChanged = pyqtSignal() - busyChanged = pyqtSignal() - - @pyqtProperty(bool, notify=activityChanged) - def layerActivity(self): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - return active_view.getActivity() - return False - - @pyqtProperty(int, notify=maxLayersChanged) - def numLayers(self): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - return active_view.getMaxLayers() - return 0 - - @pyqtProperty(int, notify=currentLayerChanged) - def currentLayer(self): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - return active_view.getCurrentLayer() - return 0 - - @pyqtProperty(int, notify=currentLayerChanged) - def minimumLayer(self): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - return active_view.getMinimumLayer() - return 0 - - @pyqtProperty(bool, notify=busyChanged) - def busy(self): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - return active_view.isBusy() - return False - - @pyqtProperty(bool, notify=preferencesChanged) - def compatibilityMode(self): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - return active_view.getCompatibilityMode() - return False - - @pyqtSlot(int) - def setCurrentLayer(self, layer_num): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - active_view.setLayer(layer_num) - - @pyqtSlot(int) - def setMinimumLayer(self, layer_num): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - active_view.setMinimumLayer(layer_num) - - @pyqtSlot(int) - def setLayerViewType(self, layer_view_type): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - active_view.setLayerViewType(layer_view_type) - - @pyqtSlot(result=int) - def getLayerViewType(self): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - return active_view.getLayerViewType() - return 0 - - # Opacity 0..1 - @pyqtSlot(int, float) - def setExtruderOpacity(self, extruder_nr, opacity): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - active_view.setExtruderOpacity(extruder_nr, opacity) - - @pyqtSlot(int) - def setShowTravelMoves(self, show): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - active_view.setShowTravelMoves(show) - - @pyqtSlot(int) - def setShowHelpers(self, show): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - active_view.setShowHelpers(show) - - @pyqtSlot(int) - def setShowSkin(self, show): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - active_view.setShowSkin(show) - - @pyqtSlot(int) - def setShowInfill(self, show): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - active_view.setShowInfill(show) - - @pyqtProperty(int, notify=globalStackChanged) - def extruderCount(self): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - return active_view.getExtruderCount() - return 0 - - def _layerActivityChanged(self): - self.activityChanged.emit() - - def _onLayerChanged(self): - self.currentLayerChanged.emit() - self._layerActivityChanged() - - def _onMaxLayersChanged(self): - self.maxLayersChanged.emit() - - def _onBusyChanged(self): - self.busyChanged.emit() - - def _onGlobalStackChanged(self): - self.globalStackChanged.emit() - - def _onPreferencesChanged(self): - self.preferencesChanged.emit() - - def _onActiveViewChanged(self): - active_view = self._controller.getActiveView() - if isinstance(active_view, LayerView.LayerView.LayerView): - active_view.currentLayerNumChanged.connect(self._onLayerChanged) - active_view.maxLayersChanged.connect(self._onMaxLayersChanged) - active_view.busyChanged.connect(self._onBusyChanged) - active_view.globalStackChanged.connect(self._onGlobalStackChanged) - active_view.preferencesChanged.connect(self._onPreferencesChanged) diff --git a/plugins/LayerView/__init__.py b/plugins/LayerView/__init__.py deleted file mode 100644 index da1a5aed19..0000000000 --- a/plugins/LayerView/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2015 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from . import LayerView, LayerViewProxy -from PyQt5.QtQml import qmlRegisterType, qmlRegisterSingletonType - -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") - -def getMetaData(): - return { - "view": { - "name": catalog.i18nc("@item:inlistbox", "Layer view"), - "view_panel": "LayerView.qml", - "weight": 2 - } - } - -def createLayerViewProxy(engine, script_engine): - return LayerViewProxy.LayerViewProxy() - -def register(app): - layer_view = LayerView.LayerView() - qmlRegisterSingletonType(LayerViewProxy.LayerViewProxy, "UM", 1, 0, "LayerView", layer_view.getProxy) - return { "view": LayerView.LayerView() } diff --git a/plugins/LayerView/LayerSlider.qml b/plugins/SimulationView/LayerSlider.qml similarity index 85% rename from plugins/LayerView/LayerSlider.qml rename to plugins/SimulationView/LayerSlider.qml index 9abeb01148..22f9d91340 100644 --- a/plugins/LayerView/LayerSlider.qml +++ b/plugins/SimulationView/LayerSlider.qml @@ -1,312 +1,325 @@ -// 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.2 -import QtQuick.Layouts 1.1 -import QtQuick.Controls.Styles 1.1 - -import UM 1.0 as UM -import Cura 1.0 as Cura - -Item { - id: sliderRoot - - // handle properties - property real handleSize: 10 - property real handleRadius: handleSize / 2 - property real minimumRangeHandleSize: handleSize / 2 - property color upperHandleColor: "black" - property color lowerHandleColor: "black" - property color rangeHandleColor: "black" - property real handleLabelWidth: width - property var activeHandle: upperHandle - - // track properties - property real trackThickness: 4 // width of the slider track - property real trackRadius: trackThickness / 2 - property color trackColor: "white" - property real trackBorderWidth: 1 // width of the slider track border - property color trackBorderColor: "black" - - // value properties - property real maximumValue: 100 - property real minimumValue: 0 - property real minimumRange: 0 // minimum range allowed between min and max values - property bool roundValues: true - property real upperValue: maximumValue - property real lowerValue: minimumValue - - property bool layersVisible: true - - function getUpperValueFromSliderHandle () { - return upperHandle.getValue() - } - - function setUpperValue (value) { - upperHandle.setValue(value) - updateRangeHandle() - } - - function getLowerValueFromSliderHandle () { - return lowerHandle.getValue() - } - - function setLowerValue (value) { - lowerHandle.setValue(value) - updateRangeHandle() - } - - function updateRangeHandle () { - rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height) - } - - // set the active handle to show only one label at a time - function setActiveHandle (handle) { - activeHandle = handle - } - - // slider track - Rectangle { - id: track - - width: sliderRoot.trackThickness - height: sliderRoot.height - sliderRoot.handleSize - radius: sliderRoot.trackRadius - anchors.centerIn: sliderRoot - color: sliderRoot.trackColor - border.width: sliderRoot.trackBorderWidth - border.color: sliderRoot.trackBorderColor - visible: sliderRoot.layersVisible - } - - // Range handle - Item { - id: rangeHandle - - y: upperHandle.y + upperHandle.height - width: sliderRoot.handleSize - height: sliderRoot.minimumRangeHandleSize - anchors.horizontalCenter: sliderRoot.horizontalCenter - visible: sliderRoot.layersVisible - - // set the new value when dragging - function onHandleDragged () { - - upperHandle.y = y - upperHandle.height - lowerHandle.y = y + height - - var upperValue = sliderRoot.getUpperValueFromSliderHandle() - var lowerValue = sliderRoot.getLowerValueFromSliderHandle() - - // set both values after moving the handle position - UM.LayerView.setCurrentLayer(upperValue) - UM.LayerView.setMinimumLayer(lowerValue) - } - - function setValue (value) { - var range = sliderRoot.upperValue - sliderRoot.lowerValue - value = Math.min(value, sliderRoot.maximumValue) - value = Math.max(value, sliderRoot.minimumValue + range) - - UM.LayerView.setCurrentLayer(value) - UM.LayerView.setMinimumLayer(value - range) - } - - Rectangle { - width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth - height: parent.height + sliderRoot.handleSize - anchors.centerIn: parent - color: sliderRoot.rangeHandleColor - } - - MouseArea { - anchors.fill: parent - - drag { - target: parent - axis: Drag.YAxis - minimumY: upperHandle.height - maximumY: sliderRoot.height - (rangeHandle.height + lowerHandle.height) - } - - onPositionChanged: parent.onHandleDragged() - onPressed: sliderRoot.setActiveHandle(rangeHandle) - } - - LayerSliderLabel { - id: rangleHandleLabel - - height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height - x: parent.x - width - UM.Theme.getSize("default_margin").width - anchors.verticalCenter: parent.verticalCenter - target: Qt.point(sliderRoot.width, y + height / 2) - visible: sliderRoot.activeHandle == parent - - // custom properties - maximumValue: sliderRoot.maximumValue - value: sliderRoot.upperValue - busy: UM.LayerView.busy - setValue: rangeHandle.setValue // connect callback functions - } - } - - // Upper handle - Rectangle { - id: upperHandle - - y: sliderRoot.height - (sliderRoot.minimumRangeHandleSize + 2 * sliderRoot.handleSize) - width: sliderRoot.handleSize - height: sliderRoot.handleSize - anchors.horizontalCenter: sliderRoot.horizontalCenter - radius: sliderRoot.handleRadius - color: sliderRoot.upperHandleColor - visible: sliderRoot.layersVisible - - function onHandleDragged () { - - // don't allow the lower handle to be heigher than the upper handle - if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize) { - lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize - } - - // update the range handle - sliderRoot.updateRangeHandle() - - // set the new value after moving the handle position - UM.LayerView.setCurrentLayer(getValue()) - } - - // get the upper value based on the slider position - function getValue () { - var result = y / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) - result = sliderRoot.maximumValue + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumValue)) - result = sliderRoot.roundValues ? Math.round(result) : result - return result - } - - // set the slider position based on the upper value - function setValue (value) { - - UM.LayerView.setCurrentLayer(value) - - var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue) - var newUpperYPosition = Math.round(diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))) - y = newUpperYPosition - - // update the range handle - sliderRoot.updateRangeHandle() - } - - // dragging - MouseArea { - anchors.fill: parent - - drag { - target: parent - axis: Drag.YAxis - minimumY: 0 - maximumY: sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize) - } - - onPositionChanged: parent.onHandleDragged() - onPressed: sliderRoot.setActiveHandle(upperHandle) - } - - LayerSliderLabel { - id: upperHandleLabel - - height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height - x: parent.x - width - UM.Theme.getSize("default_margin").width - anchors.verticalCenter: parent.verticalCenter - target: Qt.point(sliderRoot.width, y + height / 2) - visible: sliderRoot.activeHandle == parent - - // custom properties - maximumValue: sliderRoot.maximumValue - value: sliderRoot.upperValue - busy: UM.LayerView.busy - setValue: upperHandle.setValue // connect callback functions - } - } - - // Lower handle - Rectangle { - id: lowerHandle - - y: sliderRoot.height - sliderRoot.handleSize - width: parent.handleSize - height: parent.handleSize - anchors.horizontalCenter: parent.horizontalCenter - radius: sliderRoot.handleRadius - color: sliderRoot.lowerHandleColor - - visible: slider.layersVisible - - function onHandleDragged () { - - // don't allow the upper handle to be lower than the lower handle - if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize) { - upperHandle.y = y - (upperHandle.heigth + sliderRoot.minimumRangeHandleSize) - } - - // update the range handle - sliderRoot.updateRangeHandle() - - // set the new value after moving the handle position - UM.LayerView.setMinimumLayer(getValue()) - } - - // get the lower value from the current slider position - function getValue () { - var result = (y - (sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)); - result = sliderRoot.maximumValue - sliderRoot.minimumRange + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumRange)) - result = sliderRoot.roundValues ? Math.round(result) : result - return result - } - - // set the slider position based on the lower value - function setValue (value) { - - UM.LayerView.setMinimumLayer(value) - - var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue) - var newLowerYPosition = Math.round((sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize) + diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))) - y = newLowerYPosition - - // update the range handle - sliderRoot.updateRangeHandle() - } - - // dragging - MouseArea { - anchors.fill: parent - - drag { - target: parent - axis: Drag.YAxis - minimumY: upperHandle.height + sliderRoot.minimumRangeHandleSize - maximumY: sliderRoot.height - parent.height - } - - onPositionChanged: parent.onHandleDragged() - onPressed: sliderRoot.setActiveHandle(lowerHandle) - } - - LayerSliderLabel { - id: lowerHandleLabel - - height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height - x: parent.x - width - UM.Theme.getSize("default_margin").width - anchors.verticalCenter: parent.verticalCenter - target: Qt.point(sliderRoot.width, y + height / 2) - visible: sliderRoot.activeHandle == parent - - // custom properties - maximumValue: sliderRoot.maximumValue - value: sliderRoot.lowerValue - busy: UM.LayerView.busy - setValue: lowerHandle.setValue // connect callback functions - } - } -} +// 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.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls.Styles 1.1 + +import UM 1.0 as UM +import Cura 1.0 as Cura + +Item { + id: sliderRoot + + // handle properties + property real handleSize: 10 + property real handleRadius: handleSize / 2 + property real minimumRangeHandleSize: handleSize / 2 + property color upperHandleColor: "black" + property color lowerHandleColor: "black" + property color rangeHandleColor: "black" + property color handleActiveColor: "white" + property real handleLabelWidth: width + property var activeHandle: upperHandle + + // track properties + property real trackThickness: 4 // width of the slider track + property real trackRadius: trackThickness / 2 + property color trackColor: "white" + property real trackBorderWidth: 1 // width of the slider track border + property color trackBorderColor: "black" + + // value properties + property real maximumValue: 100 + property real minimumValue: 0 + property real minimumRange: 0 // minimum range allowed between min and max values + property bool roundValues: true + property real upperValue: maximumValue + property real lowerValue: minimumValue + + property bool layersVisible: true + + function getUpperValueFromSliderHandle () { + return upperHandle.getValue() + } + + function setUpperValue (value) { + upperHandle.setValue(value) + updateRangeHandle() + } + + function getLowerValueFromSliderHandle () { + return lowerHandle.getValue() + } + + function setLowerValue (value) { + lowerHandle.setValue(value) + updateRangeHandle() + } + + function updateRangeHandle () { + rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height) + } + + // set the active handle to show only one label at a time + function setActiveHandle (handle) { + activeHandle = handle + } + + // slider track + Rectangle { + id: track + + width: sliderRoot.trackThickness + height: sliderRoot.height - sliderRoot.handleSize + radius: sliderRoot.trackRadius + anchors.centerIn: sliderRoot + color: sliderRoot.trackColor + border.width: sliderRoot.trackBorderWidth + border.color: sliderRoot.trackBorderColor + visible: sliderRoot.layersVisible + } + + // Range handle + Item { + id: rangeHandle + + y: upperHandle.y + upperHandle.height + width: sliderRoot.handleSize + height: sliderRoot.minimumRangeHandleSize + anchors.horizontalCenter: sliderRoot.horizontalCenter + visible: sliderRoot.layersVisible + + // set the new value when dragging + function onHandleDragged () { + + upperHandle.y = y - upperHandle.height + lowerHandle.y = y + height + + var upperValue = sliderRoot.getUpperValueFromSliderHandle() + var lowerValue = sliderRoot.getLowerValueFromSliderHandle() + + // set both values after moving the handle position + UM.SimulationView.setCurrentLayer(upperValue) + UM.SimulationView.setMinimumLayer(lowerValue) + } + + function setValue (value) { + var range = sliderRoot.upperValue - sliderRoot.lowerValue + value = Math.min(value, sliderRoot.maximumValue) + value = Math.max(value, sliderRoot.minimumValue + range) + + UM.SimulationView.setCurrentLayer(value) + UM.SimulationView.setMinimumLayer(value - range) + } + + Rectangle { + width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth + height: parent.height + sliderRoot.handleSize + anchors.centerIn: parent + color: sliderRoot.rangeHandleColor + } + + MouseArea { + anchors.fill: parent + + drag { + target: parent + axis: Drag.YAxis + minimumY: upperHandle.height + maximumY: sliderRoot.height - (rangeHandle.height + lowerHandle.height) + } + + onPositionChanged: parent.onHandleDragged() + onPressed: sliderRoot.setActiveHandle(rangeHandle) + } + + SimulationSliderLabel { + id: rangleHandleLabel + + height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height + x: parent.x - width - UM.Theme.getSize("default_margin").width + anchors.verticalCenter: parent.verticalCenter + target: Qt.point(sliderRoot.width, y + height / 2) + visible: sliderRoot.activeHandle == parent + + // custom properties + maximumValue: sliderRoot.maximumValue + value: sliderRoot.upperValue + busy: UM.SimulationView.busy + setValue: rangeHandle.setValue // connect callback functions + } + } + + // Upper handle + Rectangle { + id: upperHandle + + y: sliderRoot.height - (sliderRoot.minimumRangeHandleSize + 2 * sliderRoot.handleSize) + width: sliderRoot.handleSize + height: sliderRoot.handleSize + anchors.horizontalCenter: sliderRoot.horizontalCenter + radius: sliderRoot.handleRadius + color: upperHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.upperHandleColor + visible: sliderRoot.layersVisible + + function onHandleDragged () { + + // don't allow the lower handle to be heigher than the upper handle + if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize) { + lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize + } + + // update the range handle + sliderRoot.updateRangeHandle() + + // set the new value after moving the handle position + UM.SimulationView.setCurrentLayer(getValue()) + } + + // get the upper value based on the slider position + function getValue () { + var result = y / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) + result = sliderRoot.maximumValue + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumValue)) + result = sliderRoot.roundValues ? Math.round(result) : result + return result + } + + // set the slider position based on the upper value + function setValue (value) { + + UM.SimulationView.setCurrentLayer(value) + + var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue) + var newUpperYPosition = Math.round(diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))) + y = newUpperYPosition + + // update the range handle + sliderRoot.updateRangeHandle() + } + + Keys.onUpPressed: upperHandleLabel.setValue(upperHandleLabel.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1)) + Keys.onDownPressed: upperHandleLabel.setValue(upperHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1)) + + // dragging + MouseArea { + anchors.fill: parent + + drag { + target: parent + axis: Drag.YAxis + minimumY: 0 + maximumY: sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize) + } + + onPositionChanged: parent.onHandleDragged() + onPressed: { + sliderRoot.setActiveHandle(upperHandle) + upperHandleLabel.forceActiveFocus() + } + } + + SimulationSliderLabel { + id: upperHandleLabel + + height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height + x: parent.x - width - UM.Theme.getSize("default_margin").width + anchors.verticalCenter: parent.verticalCenter + target: Qt.point(sliderRoot.width, y + height / 2) + visible: sliderRoot.activeHandle == parent + + // custom properties + maximumValue: sliderRoot.maximumValue + value: sliderRoot.upperValue + busy: UM.SimulationView.busy + setValue: upperHandle.setValue // connect callback functions + } + } + + // Lower handle + Rectangle { + id: lowerHandle + + y: sliderRoot.height - sliderRoot.handleSize + width: parent.handleSize + height: parent.handleSize + anchors.horizontalCenter: parent.horizontalCenter + radius: sliderRoot.handleRadius + color: lowerHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.lowerHandleColor + + visible: sliderRoot.layersVisible + + function onHandleDragged () { + + // don't allow the upper handle to be lower than the lower handle + if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize) { + upperHandle.y = y - (upperHandle.heigth + sliderRoot.minimumRangeHandleSize) + } + + // update the range handle + sliderRoot.updateRangeHandle() + + // set the new value after moving the handle position + UM.SimulationView.setMinimumLayer(getValue()) + } + + // get the lower value from the current slider position + function getValue () { + var result = (y - (sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)); + result = sliderRoot.maximumValue - sliderRoot.minimumRange + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumRange)) + result = sliderRoot.roundValues ? Math.round(result) : result + return result + } + + // set the slider position based on the lower value + function setValue (value) { + + UM.SimulationView.setMinimumLayer(value) + + var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue) + var newLowerYPosition = Math.round((sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize) + diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))) + y = newLowerYPosition + + // update the range handle + sliderRoot.updateRangeHandle() + } + + Keys.onUpPressed: lowerHandleLabel.setValue(lowerHandleLabel.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1)) + Keys.onDownPressed: lowerHandleLabel.setValue(lowerHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1)) + + // dragging + MouseArea { + anchors.fill: parent + + drag { + target: parent + axis: Drag.YAxis + minimumY: upperHandle.height + sliderRoot.minimumRangeHandleSize + maximumY: sliderRoot.height - parent.height + } + + onPositionChanged: parent.onHandleDragged() + onPressed: { + sliderRoot.setActiveHandle(lowerHandle) + lowerHandleLabel.forceActiveFocus() + } + } + + SimulationSliderLabel { + id: lowerHandleLabel + + height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height + x: parent.x - width - UM.Theme.getSize("default_margin").width + anchors.verticalCenter: parent.verticalCenter + target: Qt.point(sliderRoot.width, y + height / 2) + visible: sliderRoot.activeHandle == parent + + // custom properties + maximumValue: sliderRoot.maximumValue + value: sliderRoot.lowerValue + busy: UM.SimulationView.busy + setValue: lowerHandle.setValue // connect callback functions + } + } +} diff --git a/plugins/SimulationView/NozzleNode.py b/plugins/SimulationView/NozzleNode.py new file mode 100644 index 0000000000..8a29871775 --- /dev/null +++ b/plugins/SimulationView/NozzleNode.py @@ -0,0 +1,49 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from UM.Application import Application +from UM.Math.Color import Color +from UM.Math.Vector import Vector +from UM.PluginRegistry import PluginRegistry +from UM.Scene.SceneNode import SceneNode +from UM.View.GL.OpenGL import OpenGL +from UM.Resources import Resources + +import os + +class NozzleNode(SceneNode): + def __init__(self, parent = None): + super().__init__(parent) + + self._shader = None + self.setCalculateBoundingBox(False) + self._createNozzleMesh() + + def _createNozzleMesh(self): + mesh_file = "resources/nozzle.stl" + try: + path = os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), mesh_file) + except FileNotFoundError: + path = "" + + reader = Application.getInstance().getMeshFileHandler().getReaderForFile(path) + node = reader.read(path) + + if node.getMeshData(): + self.setMeshData(node.getMeshData()) + + def render(self, renderer): + # Avoid to render if it is not visible + if not self.isVisible(): + return False + + if not self._shader: + # We now misuse the platform shader, as it actually supports textures + self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader")) + self._shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("layerview_nozzle").getRgb())) + # Set the opacity to 0, so that the template is in full control. + self._shader.setUniformValue("u_opacity", 0) + + if self.getMeshData(): + renderer.queueNode(self, shader = self._shader, transparent = True) + return True diff --git a/plugins/SimulationView/PathSlider.qml b/plugins/SimulationView/PathSlider.qml new file mode 100644 index 0000000000..0a4af904aa --- /dev/null +++ b/plugins/SimulationView/PathSlider.qml @@ -0,0 +1,161 @@ +// 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.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls.Styles 1.1 + +import UM 1.0 as UM +import Cura 1.0 as Cura + +Item { + id: sliderRoot + + // handle properties + property real handleSize: 10 + property real handleRadius: handleSize / 2 + property color handleColor: "black" + property color handleActiveColor: "white" + property color rangeColor: "black" + property real handleLabelWidth: width + + // track properties + property real trackThickness: 4 // width of the slider track + property real trackRadius: trackThickness / 2 + property color trackColor: "white" + property real trackBorderWidth: 1 // width of the slider track border + property color trackBorderColor: "black" + + // value properties + property real maximumValue: 100 + property bool roundValues: true + property real handleValue: maximumValue + + property bool pathsVisible: true + + function getHandleValueFromSliderHandle () { + return handle.getValue() + } + + function setHandleValue (value) { + handle.setValue(value) + updateRangeHandle() + } + + function updateRangeHandle () { + rangeHandle.width = handle.x - sliderRoot.handleSize + } + + // slider track + Rectangle { + id: track + + width: sliderRoot.width - sliderRoot.handleSize + height: sliderRoot.trackThickness + radius: sliderRoot.trackRadius + anchors.centerIn: sliderRoot + color: sliderRoot.trackColor + border.width: sliderRoot.trackBorderWidth + border.color: sliderRoot.trackBorderColor + visible: sliderRoot.pathsVisible + } + + // Progress indicator + Item { + id: rangeHandle + + x: handle.width + height: sliderRoot.handleSize + width: handle.x - sliderRoot.handleSize + anchors.verticalCenter: sliderRoot.verticalCenter + visible: sliderRoot.pathsVisible + + Rectangle { + height: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth + width: parent.width + sliderRoot.handleSize + anchors.centerIn: parent + color: sliderRoot.rangeColor + } + } + + // Handle + Rectangle { + id: handle + + x: sliderRoot.handleSize + width: sliderRoot.handleSize + height: sliderRoot.handleSize + anchors.verticalCenter: sliderRoot.verticalCenter + radius: sliderRoot.handleRadius + color: handleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.handleColor + visible: sliderRoot.pathsVisible + + function onHandleDragged () { + + // update the range handle + sliderRoot.updateRangeHandle() + + // set the new value after moving the handle position + UM.SimulationView.setCurrentPath(getValue()) + } + + // get the value based on the slider position + function getValue () { + var result = x / (sliderRoot.width - sliderRoot.handleSize) + result = result * sliderRoot.maximumValue + result = sliderRoot.roundValues ? Math.round(result) : result + return result + } + + // set the slider position based on the value + function setValue (value) { + + UM.SimulationView.setCurrentPath(value) + + var diff = value / sliderRoot.maximumValue + var newXPosition = Math.round(diff * (sliderRoot.width - sliderRoot.handleSize)) + x = newXPosition + + // update the range handle + sliderRoot.updateRangeHandle() + } + + Keys.onRightPressed: handleLabel.setValue(handleLabel.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1)) + Keys.onLeftPressed: handleLabel.setValue(handleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1)) + + // dragging + MouseArea { + anchors.fill: parent + + drag { + target: parent + axis: Drag.XAxis + minimumX: 0 + maximumX: sliderRoot.width - sliderRoot.handleSize + } + onPressed: { + handleLabel.forceActiveFocus() + } + + onPositionChanged: parent.onHandleDragged() + } + + SimulationSliderLabel { + id: handleLabel + + height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height + y: parent.y + sliderRoot.handleSize + UM.Theme.getSize("default_margin").height + anchors.horizontalCenter: parent.horizontalCenter + target: Qt.point(x + width / 2, sliderRoot.height) + visible: false + startFrom: 0 + + // custom properties + maximumValue: sliderRoot.maximumValue + value: sliderRoot.handleValue + busy: UM.SimulationView.busy + setValue: handle.setValue // connect callback functions + } + } +} diff --git a/plugins/SimulationView/SimulationPass.py b/plugins/SimulationView/SimulationPass.py new file mode 100644 index 0000000000..4963568935 --- /dev/null +++ b/plugins/SimulationView/SimulationPass.py @@ -0,0 +1,187 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from UM.Math.Color import Color +from UM.Math.Vector import Vector +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.Resources import Resources +from UM.Scene.SceneNode import SceneNode +from UM.Scene.ToolHandle import ToolHandle +from UM.Application import Application +from UM.PluginRegistry import PluginRegistry + +from UM.View.RenderPass import RenderPass +from UM.View.RenderBatch import RenderBatch +from UM.View.GL.OpenGL import OpenGL + +from cura.Settings.ExtruderManager import ExtruderManager + + +import os.path + +## RenderPass used to display g-code paths. +from plugins.SimulationView.NozzleNode import NozzleNode + + +class SimulationPass(RenderPass): + def __init__(self, width, height): + super().__init__("simulationview", width, height) + + self._layer_shader = None + self._layer_shadow_shader = None + self._current_shader = None # This shader will be the shadow or the normal depending if the user wants to see the paths or the layers + self._tool_handle_shader = None + self._nozzle_shader = None + self._old_current_layer = 0 + self._old_current_path = 0 + self._gl = OpenGL.getInstance().getBindingsObject() + self._scene = Application.getInstance().getController().getScene() + self._extruder_manager = ExtruderManager.getInstance() + + self._layer_view = None + self._compatibility_mode = None + + def setSimulationView(self, layerview): + self._layer_view = layerview + self._compatibility_mode = layerview.getCompatibilityMode() + + def render(self): + if not self._layer_shader: + if self._compatibility_mode: + shader_filename = "layers.shader" + shadow_shader_filename = "layers_shadow.shader" + else: + shader_filename = "layers3d.shader" + shadow_shader_filename = "layers3d_shadow.shader" + self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shader_filename)) + self._layer_shadow_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shadow_shader_filename)) + self._current_shader = self._layer_shader + # Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers) + self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex))) + if self._layer_view: + self._layer_shader.setUniformValue("u_max_feedrate", self._layer_view.getMaxFeedrate()) + self._layer_shader.setUniformValue("u_min_feedrate", self._layer_view.getMinFeedrate()) + self._layer_shader.setUniformValue("u_max_thickness", self._layer_view.getMaxThickness()) + self._layer_shader.setUniformValue("u_min_thickness", self._layer_view.getMinThickness()) + self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getSimulationViewType()) + self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities()) + self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves()) + self._layer_shader.setUniformValue("u_show_helpers", self._layer_view.getShowHelpers()) + self._layer_shader.setUniformValue("u_show_skin", self._layer_view.getShowSkin()) + self._layer_shader.setUniformValue("u_show_infill", self._layer_view.getShowInfill()) + else: + #defaults + self._layer_shader.setUniformValue("u_max_feedrate", 1) + self._layer_shader.setUniformValue("u_min_feedrate", 0) + self._layer_shader.setUniformValue("u_max_thickness", 1) + self._layer_shader.setUniformValue("u_min_thickness", 0) + self._layer_shader.setUniformValue("u_layer_view_type", 1) + self._layer_shader.setUniformValue("u_extruder_opacity", [1, 1, 1, 1]) + self._layer_shader.setUniformValue("u_show_travel_moves", 0) + self._layer_shader.setUniformValue("u_show_helpers", 1) + self._layer_shader.setUniformValue("u_show_skin", 1) + self._layer_shader.setUniformValue("u_show_infill", 1) + + if not self._tool_handle_shader: + self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "toolhandle.shader")) + + if not self._nozzle_shader: + self._nozzle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader")) + self._nozzle_shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("layerview_nozzle").getRgb())) + + self.bind() + + tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Solid) + head_position = None # Indicates the current position of the print head + nozzle_node = None + + for node in DepthFirstIterator(self._scene.getRoot()): + + if isinstance(node, ToolHandle): + tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh()) + + + elif isinstance(node, NozzleNode): + nozzle_node = node + nozzle_node.setVisible(False) + + elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible(): + layer_data = node.callDecoration("getLayerData") + if not layer_data: + continue + + # Render all layers below a certain number as line mesh instead of vertices. + if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())): + start = 0 + end = 0 + element_counts = layer_data.getElementCounts() + for layer in sorted(element_counts.keys()): + # In the current layer, we show just the indicated paths + if layer == self._layer_view._current_layer_num: + # We look for the position of the head, searching the point of the current path + index = self._layer_view._current_path_num + offset = 0 + for polygon in layer_data.getLayer(layer).polygons: + # The size indicates all values in the two-dimension array, and the second dimension is + # always size 3 because we have 3D points. + if index >= polygon.data.size // 3 - offset: + index -= polygon.data.size // 3 - offset + offset = 1 # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon + continue + # The head position is calculated and translated + head_position = Vector(polygon.data[index+offset][0], polygon.data[index+offset][1], polygon.data[index+offset][2]) + node.getWorldPosition() + break + break + if self._layer_view._minimum_layer_num > layer: + start += element_counts[layer] + end += element_counts[layer] + + # Calculate the range of paths in the last layer + current_layer_start = end + current_layer_end = end + self._layer_view._current_path_num * 2 # Because each point is used twice + + # This uses glDrawRangeElements internally to only draw a certain range of lines. + # All the layers but the current selected layer are rendered first + if self._old_current_path != self._layer_view._current_path_num: + self._current_shader = self._layer_shadow_shader + if not self._layer_view.isSimulationRunning() and self._old_current_layer != self._layer_view._current_layer_num: + self._current_shader = self._layer_shader + + layers_batch = RenderBatch(self._current_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end)) + layers_batch.addItem(node.getWorldTransformation(), layer_data) + layers_batch.render(self._scene.getActiveCamera()) + + # Current selected layer is rendered + current_layer_batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (current_layer_start, current_layer_end)) + current_layer_batch.addItem(node.getWorldTransformation(), layer_data) + current_layer_batch.render(self._scene.getActiveCamera()) + + self._old_current_layer = self._layer_view._current_layer_num + self._old_current_path = self._layer_view._current_path_num + + # Create a new batch that is not range-limited + batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid) + + if self._layer_view.getCurrentLayerMesh(): + batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerMesh()) + + if self._layer_view.getCurrentLayerJumps(): + batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerJumps()) + + if len(batch.items) > 0: + batch.render(self._scene.getActiveCamera()) + + # The nozzle is drawn once we know the correct position + if self._layer_view.getActivity() and nozzle_node is not None: + if head_position is not None: + nozzle_node.setVisible(True) + nozzle_node.setPosition(head_position) + nozzle_batch = RenderBatch(self._nozzle_shader, type = RenderBatch.RenderType.Solid) + nozzle_batch.addItem(nozzle_node.getWorldTransformation(), mesh = nozzle_node.getMeshData()) + nozzle_batch.render(self._scene.getActiveCamera()) + + # Render toolhandles on top of the layerview + if len(tool_handle_batch.items) > 0: + tool_handle_batch.render(self._scene.getActiveCamera()) + + self.release() diff --git a/plugins/LayerView/LayerSliderLabel.qml b/plugins/SimulationView/SimulationSliderLabel.qml similarity index 88% rename from plugins/LayerView/LayerSliderLabel.qml rename to plugins/SimulationView/SimulationSliderLabel.qml index c989679285..1c8daf867f 100644 --- a/plugins/LayerView/LayerSliderLabel.qml +++ b/plugins/SimulationView/SimulationSliderLabel.qml @@ -1,103 +1,104 @@ -// 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.2 -import QtQuick.Layouts 1.1 -import QtQuick.Controls.Styles 1.1 - -import UM 1.0 as UM -import Cura 1.0 as Cura - -UM.PointingRectangle { - id: sliderLabelRoot - - // custom properties - property real maximumValue: 100 - property real value: 0 - property var setValue // Function - property bool busy: false - - target: Qt.point(parent.width, y + height / 2) - arrowSize: UM.Theme.getSize("default_arrow").width - height: parent.height - width: valueLabel.width + UM.Theme.getSize("default_margin").width - visible: false - - // make sure the text field is focussed when pressing the parent handle - // needed to connect the key bindings when switching active handle - onVisibleChanged: if (visible) valueLabel.forceActiveFocus() - - color: UM.Theme.getColor("tool_panel_background") - borderColor: UM.Theme.getColor("lining") - borderWidth: UM.Theme.getSize("default_lining").width - - Behavior on height { - NumberAnimation { - duration: 50 - } - } - - // catch all mouse events so they're not handled by underlying 3D scene - MouseArea { - anchors.fill: parent - } - - TextField { - id: valueLabel - - anchors { - left: parent.left - leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2) - verticalCenter: parent.verticalCenter - } - - width: 40 * screenScaleFactor - text: sliderLabelRoot.value + 1 // the current handle value, add 1 because layers is an array - horizontalAlignment: TextInput.AlignRight - - // key bindings, work when label is currenctly focused (active handle in LayerSlider) - Keys.onUpPressed: sliderLabelRoot.setValue(sliderLabelRoot.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1)) - Keys.onDownPressed: sliderLabelRoot.setValue(sliderLabelRoot.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1)) - - style: TextFieldStyle { - textColor: UM.Theme.getColor("setting_control_text") - font: UM.Theme.getFont("default") - background: Item { } - } - - onEditingFinished: { - - // Ensure that the cursor is at the first position. On some systems the text isn't fully visible - // Seems to have to do something with different dpi densities that QML doesn't quite handle. - // Another option would be to increase the size even further, but that gives pretty ugly results. - cursorPosition = 0 - - if (valueLabel.text != "") { - // -1 because we need to convert back to an array structure - sliderLabelRoot.setValue(parseInt(valueLabel.text) - 1) - } - } - - validator: IntValidator { - bottom: 1 - top: sliderLabelRoot.maximumValue + 1 // +1 because actual layers is an array - } - } - - BusyIndicator { - id: busyIndicator - - anchors { - left: parent.right - leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2) - verticalCenter: parent.verticalCenter - } - - width: sliderLabelRoot.height - height: width - - visible: sliderLabelRoot.busy - running: sliderLabelRoot.busy - } -} +// 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.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls.Styles 1.1 + +import UM 1.0 as UM +import Cura 1.0 as Cura + +UM.PointingRectangle { + id: sliderLabelRoot + + // custom properties + property real maximumValue: 100 + property real value: 0 + property var setValue // Function + property bool busy: false + property int startFrom: 1 + + target: Qt.point(parent.width, y + height / 2) + arrowSize: UM.Theme.getSize("default_arrow").width + height: parent.height + width: valueLabel.width + UM.Theme.getSize("default_margin").width + visible: false + + // make sure the text field is focussed when pressing the parent handle + // needed to connect the key bindings when switching active handle + onVisibleChanged: if (visible) valueLabel.forceActiveFocus() + + color: UM.Theme.getColor("tool_panel_background") + borderColor: UM.Theme.getColor("lining") + borderWidth: UM.Theme.getSize("default_lining").width + + Behavior on height { + NumberAnimation { + duration: 50 + } + } + + // catch all mouse events so they're not handled by underlying 3D scene + MouseArea { + anchors.fill: parent + } + + TextField { + id: valueLabel + + anchors { + left: parent.left + leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2) + verticalCenter: parent.verticalCenter + } + + width: 40 * screenScaleFactor + text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array + horizontalAlignment: TextInput.AlignRight + + // key bindings, work when label is currenctly focused (active handle in LayerSlider) + Keys.onUpPressed: sliderLabelRoot.setValue(sliderLabelRoot.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1)) + Keys.onDownPressed: sliderLabelRoot.setValue(sliderLabelRoot.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1)) + + style: TextFieldStyle { + textColor: UM.Theme.getColor("setting_control_text") + font: UM.Theme.getFont("default") + background: Item { } + } + + onEditingFinished: { + + // Ensure that the cursor is at the first position. On some systems the text isn't fully visible + // Seems to have to do something with different dpi densities that QML doesn't quite handle. + // Another option would be to increase the size even further, but that gives pretty ugly results. + cursorPosition = 0 + + if (valueLabel.text != "") { + // -startFrom because we need to convert back to an array structure + sliderLabelRoot.setValue(parseInt(valueLabel.text) - startFrom) + } + } + + validator: IntValidator { + bottom:startFrom + top: sliderLabelRoot.maximumValue + startFrom // +startFrom because maybe we want to start in a different value rather than 0 + } + } + + BusyIndicator { + id: busyIndicator + + anchors { + left: parent.right + leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2) + verticalCenter: parent.verticalCenter + } + + width: sliderLabelRoot.height + height: width + + visible: sliderLabelRoot.busy + running: sliderLabelRoot.busy + } +} diff --git a/plugins/LayerView/LayerView.py b/plugins/SimulationView/SimulationView.py old mode 100755 new mode 100644 similarity index 74% rename from plugins/LayerView/LayerView.py rename to plugins/SimulationView/SimulationView.py index 04be97b747..90f64a8224 --- a/plugins/LayerView/LayerView.py +++ b/plugins/SimulationView/SimulationView.py @@ -1,46 +1,46 @@ -# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import sys -from UM.PluginRegistry import PluginRegistry -from UM.View.View import View -from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator -from UM.Resources import Resources -from UM.Event import Event, KeyEvent -from UM.Signal import Signal -from UM.Scene.Selection import Selection -from UM.Math.Color import Color -from UM.Mesh.MeshBuilder import MeshBuilder -from UM.Job import Job -from UM.Preferences import Preferences -from UM.Logger import Logger -from UM.View.GL.OpenGL import OpenGL -from UM.Message import Message -from UM.Application import Application -from UM.View.GL.OpenGLContext import OpenGLContext - -from cura.ConvexHullNode import ConvexHullNode -from cura.Settings.ExtruderManager import ExtruderManager - from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QApplication -from . import LayerViewProxy - +from UM.Application import Application +from UM.Event import Event, KeyEvent +from UM.Job import Job +from UM.Logger import Logger +from UM.Math.Color import Color +from UM.Math.Vector import Vector +from UM.Mesh.MeshBuilder import MeshBuilder +from UM.Message import Message +from UM.PluginRegistry import PluginRegistry +from UM.Preferences import Preferences +from UM.Resources import Resources +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.Scene.SceneNode import SceneNode +from UM.Scene.Selection import Selection +from UM.Signal import Signal +from UM.View.GL.OpenGL import OpenGL +from UM.View.GL.OpenGLContext import OpenGLContext +from UM.View.View import View from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") +from cura.ConvexHullNode import ConvexHullNode +from plugins.SimulationView.NozzleNode import NozzleNode +from . import SimulationPass, SimulationViewProxy -from . import LayerPass +catalog = i18nCatalog("cura") import numpy import os.path ## View used to display g-code paths. -class LayerView(View): - # Must match LayerView.qml +class SimulationView(View): + # Must match SimulationView.qml LAYER_VIEW_TYPE_MATERIAL_TYPE = 0 LAYER_VIEW_TYPE_LINE_TYPE = 1 + LAYER_VIEW_TYPE_FEEDRATE = 2 + LAYER_VIEW_TYPE_THICKNESS = 3 def __init__(self): super().__init__() @@ -54,22 +54,29 @@ class LayerView(View): self._activity = False self._old_max_layers = 0 + self._max_paths = 0 + self._current_path_num = 0 + self._minimum_path_num = 0 + self.currentLayerNumChanged.connect(self._onCurrentLayerNumChanged) + self._busy = False + self._simulation_running = False self._ghost_shader = None self._layer_pass = None self._composite_pass = None self._old_layer_bindings = None - self._layerview_composite_shader = None + self._simulationview_composite_shader = None self._old_composite_shader = None self._global_container_stack = None - self._proxy = LayerViewProxy.LayerViewProxy() + self._proxy = SimulationViewProxy.SimulationViewProxy() self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged) self._resetSettings() self._legend_items = None self._show_travel_moves = False + self._nozzle_node = None Preferences.getInstance().addPreference("view/top_layer_count", 5) Preferences.getInstance().addPreference("view/only_show_top_layers", False) @@ -91,7 +98,7 @@ class LayerView(View): self._compatibility_mode = True # for safety self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"), - title = catalog.i18nc("@info:title", "Layer View")) + title = catalog.i18nc("@info:title", "Simulation View")) def _resetSettings(self): self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed @@ -101,17 +108,24 @@ class LayerView(View): self._show_helpers = 1 self._show_skin = 1 self._show_infill = 1 + self.resetLayerData() def getActivity(self): return self._activity - def getLayerPass(self): + def setActivity(self, activity): + if self._activity == activity: + return + self._activity = activity + self.activityChanged.emit() + + def getSimulationPass(self): if not self._layer_pass: # Currently the RenderPass constructor requires a size > 0 # This should be fixed in RenderPass's constructor. - self._layer_pass = LayerPass.LayerPass(1, 1) + self._layer_pass = SimulationPass.SimulationPass(1, 1) self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")) - self._layer_pass.setLayerView(self) + self._layer_pass.setSimulationView(self) return self._layer_pass def getCurrentLayer(self): @@ -120,13 +134,26 @@ class LayerView(View): def getMinimumLayer(self): return self._minimum_layer_num - def _onSceneChanged(self, node): - self.calculateMaxLayers() - def getMaxLayers(self): return self._max_layers - busyChanged = Signal() + def getCurrentPath(self): + return self._current_path_num + + def getMinimumPath(self): + return self._minimum_path_num + + def getMaxPaths(self): + return self._max_paths + + def getNozzleNode(self): + if not self._nozzle_node: + self._nozzle_node = NozzleNode() + return self._nozzle_node + + def _onSceneChanged(self, node): + self.setActivity(False) + self.calculateMaxLayers() def isBusy(self): return self._busy @@ -136,9 +163,19 @@ class LayerView(View): self._busy = busy self.busyChanged.emit() + def isSimulationRunning(self): + return self._simulation_running + + def setSimulationRunning(self, running): + self._simulation_running = running + def resetLayerData(self): self._current_layer_mesh = None self._current_layer_jumps = None + self._max_feedrate = sys.float_info.min + self._min_feedrate = sys.float_info.max + self._max_thickness = sys.float_info.min + self._min_thickness = sys.float_info.max def beginRendering(self): scene = self.getController().getScene() @@ -186,15 +223,43 @@ class LayerView(View): self.currentLayerNumChanged.emit() + def setPath(self, value): + if self._current_path_num != value: + self._current_path_num = value + if self._current_path_num < 0: + self._current_path_num = 0 + if self._current_path_num > self._max_paths: + self._current_path_num = self._max_paths + if self._current_path_num < self._minimum_path_num: + self._minimum_path_num = self._current_path_num + + self._startUpdateTopLayers() + + self.currentPathNumChanged.emit() + + def setMinimumPath(self, value): + if self._minimum_path_num != value: + self._minimum_path_num = value + if self._minimum_path_num < 0: + self._minimum_path_num = 0 + if self._minimum_path_num > self._max_layers: + self._minimum_path_num = self._max_layers + if self._minimum_path_num > self._current_path_num: + self._current_path_num = self._minimum_path_num + + self._startUpdateTopLayers() + + self.currentPathNumChanged.emit() + ## Set the layer view type # - # \param layer_view_type integer as in LayerView.qml and this class - def setLayerViewType(self, layer_view_type): + # \param layer_view_type integer as in SimulationView.qml and this class + def setSimulationViewType(self, layer_view_type): self._layer_view_type = layer_view_type self.currentLayerNumChanged.emit() - ## Return the layer view type, integer as in LayerView.qml and this class - def getLayerViewType(self): + ## Return the layer view type, integer as in SimulationView.qml and this class + def getSimulationViewType(self): return self._layer_view_type ## Set the extruder opacity @@ -243,9 +308,20 @@ class LayerView(View): def getExtruderCount(self): return self._extruder_count + def getMinFeedrate(self): + return self._min_feedrate + + def getMaxFeedrate(self): + return self._max_feedrate + + def getMinThickness(self): + return self._min_thickness + + def getMaxThickness(self): + return self._max_thickness + def calculateMaxLayers(self): scene = self.getController().getScene() - self._activity = True self._old_max_layers = self._max_layers ## Recalculate num max layers @@ -255,9 +331,16 @@ class LayerView(View): if not layer_data: continue + self.setActivity(True) min_layer_number = sys.maxsize max_layer_number = -sys.maxsize for layer_id in layer_data.getLayers(): + # Store the max and min feedrates and thicknesses for display purposes + for p in layer_data.getLayer(layer_id).polygons: + self._max_feedrate = max(float(p.lineFeedrates.max()), self._max_feedrate) + self._min_feedrate = min(float(p.lineFeedrates.min()), self._min_feedrate) + self._max_thickness = max(float(p.lineThicknesses.max()), self._max_thickness) + self._min_thickness = min(float(p.lineThicknesses.min()), self._min_thickness) if max_layer_number < layer_id: max_layer_number = layer_id if min_layer_number > layer_id: @@ -281,10 +364,32 @@ class LayerView(View): self.maxLayersChanged.emit() self._startUpdateTopLayers() + def calculateMaxPathsOnLayer(self, layer_num): + # Update the currentPath + scene = self.getController().getScene() + for node in DepthFirstIterator(scene.getRoot()): + layer_data = node.callDecoration("getLayerData") + if not layer_data: + continue + + layer = layer_data.getLayer(layer_num) + if layer is None: + return + new_max_paths = layer.lineMeshElementCount() + if new_max_paths > 0 and new_max_paths != self._max_paths: + self._max_paths = new_max_paths + self.maxPathsChanged.emit() + + self.setPath(int(new_max_paths)) + maxLayersChanged = Signal() + maxPathsChanged = Signal() currentLayerNumChanged = Signal() + currentPathNumChanged = Signal() globalStackChanged = Signal() preferencesChanged = Signal() + busyChanged = Signal() + activityChanged = Signal() ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created # as this caused some issues. @@ -308,26 +413,31 @@ class LayerView(View): return True if event.type == Event.ViewActivateEvent: - # Make sure the LayerPass is created - layer_pass = self.getLayerPass() + # Make sure the SimulationPass is created + layer_pass = self.getSimulationPass() self.getRenderer().addRenderPass(layer_pass) + # Make sure the NozzleNode is add to the root + nozzle = self.getNozzleNode() + nozzle.setParent(self.getController().getScene().getRoot()) + nozzle.setVisible(False) + Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() - if not self._layerview_composite_shader: - self._layerview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), "layerview_composite.shader")) + if not self._simulationview_composite_shader: + self._simulationview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), "simulationview_composite.shader")) theme = Application.getInstance().getTheme() - self._layerview_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb())) - self._layerview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb())) + self._simulationview_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb())) + self._simulationview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb())) if not self._composite_pass: self._composite_pass = self.getRenderer().getRenderPass("composite") self._old_layer_bindings = self._composite_pass.getLayerBindings()[:] # make a copy so we can restore to it later - self._composite_pass.getLayerBindings().append("layerview") + self._composite_pass.getLayerBindings().append("simulationview") self._old_composite_shader = self._composite_pass.getCompositeShader() - self._composite_pass.setCompositeShader(self._layerview_composite_shader) + self._composite_pass.setCompositeShader(self._simulationview_composite_shader) elif event.type == Event.ViewDeactivateEvent: self._wireprint_warning_message.hide() @@ -335,6 +445,7 @@ class LayerView(View): if self._global_container_stack: self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) + self._nozzle_node.setParent(None) self.getRenderer().removeRenderPass(self._layer_pass) self._composite_pass.setLayerBindings(self._old_layer_bindings) self._composite_pass.setCompositeShader(self._old_composite_shader) @@ -364,6 +475,9 @@ class LayerView(View): else: self._wireprint_warning_message.hide() + def _onCurrentLayerNumChanged(self): + self.calculateMaxPathsOnLayer(self._current_layer_num) + def _startUpdateTopLayers(self): if not self._compatibility_mode: return @@ -397,7 +511,7 @@ class LayerView(View): self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool( Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")) - self.setLayerViewType(int(float(Preferences.getInstance().getValue("layerview/layer_view_type")))); + self.setSimulationViewType(int(float(Preferences.getInstance().getValue("layerview/layer_view_type")))); for extruder_nr, extruder_opacity in enumerate(Preferences.getInstance().getValue("layerview/extruder_opacities").split("|")): try: diff --git a/plugins/LayerView/LayerView.qml b/plugins/SimulationView/SimulationView.qml old mode 100755 new mode 100644 similarity index 51% rename from plugins/LayerView/LayerView.qml rename to plugins/SimulationView/SimulationView.qml index 7261926bc5..e2e0dc3aed --- a/plugins/LayerView/LayerView.qml +++ b/plugins/SimulationView/SimulationView.qml @@ -13,19 +13,19 @@ Item { id: base width: { - if (UM.LayerView.compatibilityMode) { + if (UM.SimulationView.compatibilityMode) { return UM.Theme.getSize("layerview_menu_size_compatibility").width; } else { return UM.Theme.getSize("layerview_menu_size").width; } } height: { - if (UM.LayerView.compatibilityMode) { + if (UM.SimulationView.compatibilityMode) { return UM.Theme.getSize("layerview_menu_size_compatibility").height; } else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) { - return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.LayerView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height) + return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height) } else { - return UM.Theme.getSize("layerview_menu_size").height + UM.LayerView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height) + return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height) } } @@ -46,7 +46,7 @@ Item anchors.top: parent.top width: parent.width height: parent.height - z: slider.z - 1 + z: layerSlider.z - 1 color: UM.Theme.getColor("tool_panel_background") borderWidth: UM.Theme.getSize("default_lining").width borderColor: UM.Theme.getColor("lining") @@ -61,7 +61,8 @@ Item property bool show_skin: UM.Preferences.getValue("layerview/show_skin") property bool show_infill: UM.Preferences.getValue("layerview/show_infill") // if we are in compatibility mode, we only show the "line type" - property bool show_legend: UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type") == 1 + property bool show_legend: UM.SimulationView.compatibilityMode ? true : UM.Preferences.getValue("layerview/layer_view_type") == 1 + property bool show_gradient: UM.SimulationView.compatibilityMode ? false : UM.Preferences.getValue("layerview/layer_view_type") == 2 || UM.Preferences.getValue("layerview/layer_view_type") == 3 property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers") property int top_layer_count: UM.Preferences.getValue("view/top_layer_count") @@ -79,12 +80,12 @@ Item anchors.left: parent.left text: catalog.i18nc("@label","Color scheme") font: UM.Theme.getFont("default"); - visible: !UM.LayerView.compatibilityMode + visible: !UM.SimulationView.compatibilityMode Layout.fillWidth: true color: UM.Theme.getColor("setting_control_text") } - ListModel // matches LayerView.py + ListModel // matches SimulationView.py { id: layerViewTypes } @@ -97,7 +98,15 @@ Item }) layerViewTypes.append({ text: catalog.i18nc("@label:listbox", "Line Type"), - type_id: 1 // these ids match the switching in the shader + type_id: 1 + }) + layerViewTypes.append({ + text: catalog.i18nc("@label:listbox", "Feedrate"), + type_id: 2 + }) + layerViewTypes.append({ + text: catalog.i18nc("@label:listbox", "Layer thickness"), + type_id: 3 // these ids match the switching in the shader }) } @@ -108,7 +117,7 @@ Item Layout.fillWidth: true Layout.preferredWidth: UM.Theme.getSize("layerview_row").width model: layerViewTypes - visible: !UM.LayerView.compatibilityMode + visible: !UM.SimulationView.compatibilityMode style: UM.Theme.styles.combobox anchors.right: parent.right anchors.rightMargin: 10 * screenScaleFactor @@ -120,14 +129,14 @@ Item Component.onCompleted: { - currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type"); + currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type"); updateLegends(currentIndex); } function updateLegends(type_id) { // update visibility of legends - view_settings.show_legend = UM.LayerView.compatibilityMode || (type_id == 1); + view_settings.show_legend = UM.SimulationView.compatibilityMode || (type_id == 1); } } @@ -139,7 +148,7 @@ Item text: catalog.i18nc("@label","Compatibility Mode") font: UM.Theme.getFont("default") color: UM.Theme.getColor("text") - visible: UM.LayerView.compatibilityMode + visible: UM.SimulationView.compatibilityMode Layout.fillWidth: true Layout.preferredHeight: UM.Theme.getSize("layerview_row").height Layout.preferredWidth: UM.Theme.getSize("layerview_row").width @@ -157,7 +166,7 @@ Item target: UM.Preferences onPreferenceChanged: { - layerTypeCombobox.currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type"); + layerTypeCombobox.currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type"); layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex); view_settings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|"); view_settings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves"); @@ -178,7 +187,7 @@ Item view_settings.extruder_opacities[index] = checked ? 1.0 : 0.0 UM.Preferences.setValue("layerview/extruder_opacities", view_settings.extruder_opacities.join("|")); } - visible: !UM.LayerView.compatibilityMode + visible: !UM.SimulationView.compatibilityMode enabled: index + 1 <= 4 Rectangle { anchors.verticalCenter: parent.verticalCenter @@ -190,7 +199,7 @@ Item radius: width / 2 border.width: UM.Theme.getSize("default_lining").width border.color: UM.Theme.getColor("lining") - visible: !view_settings.show_legend + visible: !view_settings.show_legend & !view_settings.show_gradient } Layout.fillWidth: true Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height @@ -213,28 +222,28 @@ Item Repeater { model: ListModel { - id: typesLegenModel + id: typesLegendModel Component.onCompleted: { - typesLegenModel.append({ + typesLegendModel.append({ label: catalog.i18nc("@label", "Show Travels"), initialValue: view_settings.show_travel_moves, preference: "layerview/show_travel_moves", colorId: "layerview_move_combing" }); - typesLegenModel.append({ + typesLegendModel.append({ label: catalog.i18nc("@label", "Show Helpers"), initialValue: view_settings.show_helpers, preference: "layerview/show_helpers", colorId: "layerview_support" }); - typesLegenModel.append({ + typesLegendModel.append({ label: catalog.i18nc("@label", "Show Shell"), initialValue: view_settings.show_skin, preference: "layerview/show_skin", colorId: "layerview_inset_0" }); - typesLegenModel.append({ + typesLegendModel.append({ label: catalog.i18nc("@label", "Show Infill"), initialValue: view_settings.show_infill, preference: "layerview/show_infill", @@ -285,7 +294,7 @@ Item UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0); } text: catalog.i18nc("@label", "Only Show Top Layers") - visible: UM.LayerView.compatibilityMode + visible: UM.SimulationView.compatibilityMode style: UM.Theme.styles.checkbox } CheckBox { @@ -294,20 +303,20 @@ Item UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1); } text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top") - visible: UM.LayerView.compatibilityMode + visible: UM.SimulationView.compatibilityMode style: UM.Theme.styles.checkbox } Repeater { model: ListModel { - id: typesLegenModelNoCheck + id: typesLegendModelNoCheck Component.onCompleted: { - typesLegenModelNoCheck.append({ + typesLegendModelNoCheck.append({ label: catalog.i18nc("@label", "Top / Bottom"), colorId: "layerview_skin", }); - typesLegenModelNoCheck.append({ + typesLegendModelNoCheck.append({ label: catalog.i18nc("@label", "Inner Wall"), colorId: "layerview_inset_x", }); @@ -336,47 +345,272 @@ Item font: UM.Theme.getFont("default") } } + + // Text for the minimum, maximum and units for the feedrates and layer thickness + Rectangle { + id: gradientLegend + visible: view_settings.show_gradient + width: parent.width + height: UM.Theme.getSize("layerview_row").height + anchors { + topMargin: UM.Theme.getSize("slider_layerview_margin").height + horizontalCenter: parent.horizontalCenter + } + + Label { + text: minText() + anchors.left: parent.left + color: UM.Theme.getColor("setting_control_text") + font: UM.Theme.getFont("default") + + function minText() { + if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) { + // Feedrate selected + if (UM.Preferences.getValue("layerview/layer_view_type") == 2) { + return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2) + } + // Layer thickness selected + if (UM.Preferences.getValue("layerview/layer_view_type") == 3) { + return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2) + } + } + return catalog.i18nc("@label","min") + } + } + + Label { + text: unitsText() + anchors.horizontalCenter: parent.horizontalCenter + color: UM.Theme.getColor("setting_control_text") + font: UM.Theme.getFont("default") + + function unitsText() { + if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) { + // Feedrate selected + if (UM.Preferences.getValue("layerview/layer_view_type") == 2) { + return "mm/s" + } + // Layer thickness selected + if (UM.Preferences.getValue("layerview/layer_view_type") == 3) { + return "mm" + } + } + return "" + } + } + + Label { + text: maxText() + anchors.right: parent.right + color: UM.Theme.getColor("setting_control_text") + font: UM.Theme.getFont("default") + + function maxText() { + if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) { + // Feedrate selected + if (UM.Preferences.getValue("layerview/layer_view_type") == 2) { + return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2) + } + // Layer thickness selected + if (UM.Preferences.getValue("layerview/layer_view_type") == 3) { + return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2) + } + } + return catalog.i18nc("@label","max") + } + } + } + + // Gradient colors for feedrate and thickness + Rectangle { // In QML 5.9 can be changed by LinearGradient + // Invert values because then the bar is rotated 90 degrees + id: gradient + visible: view_settings.show_gradient + anchors.left: parent.right + height: parent.width + width: UM.Theme.getSize("layerview_row").height * 1.5 + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("lining") + transform: Rotation {origin.x: 0; origin.y: 0; angle: 90} + gradient: Gradient { + GradientStop { + position: 0.000 + color: Qt.rgba(1, 0, 0, 1) + } + GradientStop { + position: 0.25 + color: Qt.rgba(0.75, 0.5, 0.25, 1) + } + GradientStop { + position: 0.5 + color: Qt.rgba(0.5, 1, 0.5, 1) + } + GradientStop { + position: 0.75 + color: Qt.rgba(0.25, 0.5, 0.75, 1) + } + GradientStop { + position: 1.0 + color: Qt.rgba(0, 0, 1, 1) + } + } + } } - LayerSlider { - id: slider + Item { + id: slidersBox - width: UM.Theme.getSize("slider_handle").width - height: UM.Theme.getSize("layerview_menu_size").height + width: parent.width + visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity anchors { top: parent.bottom topMargin: UM.Theme.getSize("slider_layerview_margin").height - right: layerViewMenu.right - rightMargin: UM.Theme.getSize("slider_layerview_margin").width + left: parent.left } - // custom properties - upperValue: UM.LayerView.currentLayer - lowerValue: UM.LayerView.minimumLayer - maximumValue: UM.LayerView.numLayers - handleSize: UM.Theme.getSize("slider_handle").width - trackThickness: UM.Theme.getSize("slider_groove").width - trackColor: UM.Theme.getColor("slider_groove") - trackBorderColor: UM.Theme.getColor("slider_groove_border") - upperHandleColor: UM.Theme.getColor("slider_handle") - lowerHandleColor: UM.Theme.getColor("slider_handle") - rangeHandleColor: UM.Theme.getColor("slider_groove_fill") - handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width - layersVisible: UM.LayerView.layerActivity && CuraApplication.platformActivity ? true : false + PathSlider { + id: pathSlider - // update values when layer data changes - Connections { - target: UM.LayerView - onMaxLayersChanged: slider.setUpperValue(UM.LayerView.currentLayer) - onMinimumLayerChanged: slider.setLowerValue(UM.LayerView.minimumLayer) - onCurrentLayerChanged: slider.setUpperValue(UM.LayerView.currentLayer) + width: parent.width + height: UM.Theme.getSize("slider_handle").width + anchors.left: parent.left + visible: !UM.SimulationView.compatibilityMode + + // custom properties + handleValue: UM.SimulationView.currentPath + maximumValue: UM.SimulationView.numPaths + handleSize: UM.Theme.getSize("slider_handle").width + trackThickness: UM.Theme.getSize("slider_groove").width + trackColor: UM.Theme.getColor("slider_groove") + trackBorderColor: UM.Theme.getColor("slider_groove_border") + handleColor: UM.Theme.getColor("slider_handle") + handleActiveColor: UM.Theme.getColor("slider_handle_active") + rangeColor: UM.Theme.getColor("slider_groove_fill") + + // update values when layer data changes + Connections { + target: UM.SimulationView + onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath) + onCurrentPathChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath) + } + + // make sure the slider handlers show the correct value after switching views + Component.onCompleted: { + pathSlider.setHandleValue(UM.SimulationView.currentPath) + } } - // make sure the slider handlers show the correct value after switching views - Component.onCompleted: { - slider.setLowerValue(UM.LayerView.minimumLayer) - slider.setUpperValue(UM.LayerView.currentLayer) + LayerSlider { + id: layerSlider + + width: UM.Theme.getSize("slider_handle").width + height: UM.Theme.getSize("layerview_menu_size").height + + anchors { + top: pathSlider.bottom + topMargin: UM.Theme.getSize("slider_layerview_margin").height + right: parent.right + rightMargin: UM.Theme.getSize("slider_layerview_margin").width + } + + // custom properties + upperValue: UM.SimulationView.currentLayer + lowerValue: UM.SimulationView.minimumLayer + maximumValue: UM.SimulationView.numLayers + handleSize: UM.Theme.getSize("slider_handle").width + trackThickness: UM.Theme.getSize("slider_groove").width + trackColor: UM.Theme.getColor("slider_groove") + trackBorderColor: UM.Theme.getColor("slider_groove_border") + upperHandleColor: UM.Theme.getColor("slider_handle") + lowerHandleColor: UM.Theme.getColor("slider_handle") + rangeHandleColor: UM.Theme.getColor("slider_groove_fill") + handleActiveColor: UM.Theme.getColor("slider_handle_active") + handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width + + // update values when layer data changes + Connections { + target: UM.SimulationView + onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer) + onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer) + onCurrentLayerChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer) + } + + // make sure the slider handlers show the correct value after switching views + Component.onCompleted: { + layerSlider.setLowerValue(UM.SimulationView.minimumLayer) + layerSlider.setUpperValue(UM.SimulationView.currentLayer) + } + } + + // Play simulation button + Button { + id: playButton + implicitWidth: UM.Theme.getSize("button").width * 0.75; + implicitHeight: UM.Theme.getSize("button").height * 0.75; + iconSource: "./resources/simulation_resume.svg" + style: UM.Theme.styles.tool_button + visible: !UM.SimulationView.compatibilityMode + anchors { + horizontalCenter: layerSlider.horizontalCenter + top: layerSlider.bottom + topMargin: UM.Theme.getSize("slider_layerview_margin").width + } + + property var status: 0 // indicates if it's stopped (0) or playing (1) + + onClicked: { + switch(status) { + case 0: { + resumeSimulation() + break + } + case 1: { + pauseSimulation() + break + } + } + } + + function pauseSimulation() { + UM.SimulationView.setSimulationRunning(false) + iconSource = "./resources/simulation_resume.svg" + simulationTimer.stop() + status = 0 + } + + function resumeSimulation() { + UM.SimulationView.setSimulationRunning(true) + iconSource = "./resources/simulation_pause.svg" + simulationTimer.start() + status = 1 + } + } + } + + Timer + { + id: simulationTimer + interval: 250 + running: false + repeat: true + onTriggered: { + var currentPath = UM.SimulationView.currentPath + var numPaths = UM.SimulationView.numPaths + var currentLayer = UM.SimulationView.currentLayer + var numLayers = UM.SimulationView.numLayers + if (currentPath >= numPaths) { + if (currentLayer >= numLayers) { + playButton.pauseSimulation() + } + else { + UM.SimulationView.setCurrentLayer(currentLayer+1) + UM.SimulationView.setCurrentPath(0) + } + } + else { + UM.SimulationView.setCurrentPath(currentPath+1) + } } } } diff --git a/plugins/SimulationView/SimulationViewProxy.py b/plugins/SimulationView/SimulationViewProxy.py new file mode 100644 index 0000000000..21a962104d --- /dev/null +++ b/plugins/SimulationView/SimulationViewProxy.py @@ -0,0 +1,246 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty +from UM.FlameProfiler import pyqtSlot +from UM.Application import Application + +import SimulationView + + +class SimulationViewProxy(QObject): + def __init__(self, parent=None): + super().__init__(parent) + self._current_layer = 0 + self._controller = Application.getInstance().getController() + self._controller.activeViewChanged.connect(self._onActiveViewChanged) + self._onActiveViewChanged() + + currentLayerChanged = pyqtSignal() + currentPathChanged = pyqtSignal() + maxLayersChanged = pyqtSignal() + maxPathsChanged = pyqtSignal() + activityChanged = pyqtSignal() + globalStackChanged = pyqtSignal() + preferencesChanged = pyqtSignal() + busyChanged = pyqtSignal() + + @pyqtProperty(bool, notify=activityChanged) + def layerActivity(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getActivity() + return False + + @pyqtProperty(int, notify=maxLayersChanged) + def numLayers(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getMaxLayers() + return 0 + + @pyqtProperty(int, notify=currentLayerChanged) + def currentLayer(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getCurrentLayer() + return 0 + + @pyqtProperty(int, notify=currentLayerChanged) + def minimumLayer(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getMinimumLayer() + return 0 + + @pyqtProperty(int, notify=maxPathsChanged) + def numPaths(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getMaxPaths() + return 0 + + @pyqtProperty(int, notify=currentPathChanged) + def currentPath(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getCurrentPath() + return 0 + + @pyqtProperty(int, notify=currentPathChanged) + def minimumPath(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getMinimumPath() + return 0 + + @pyqtProperty(bool, notify=busyChanged) + def busy(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.isBusy() + return False + + @pyqtProperty(bool, notify=preferencesChanged) + def compatibilityMode(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getCompatibilityMode() + return False + + @pyqtSlot(int) + def setCurrentLayer(self, layer_num): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + active_view.setLayer(layer_num) + + @pyqtSlot(int) + def setMinimumLayer(self, layer_num): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + active_view.setMinimumLayer(layer_num) + + @pyqtSlot(int) + def setCurrentPath(self, path_num): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + active_view.setPath(path_num) + + @pyqtSlot(int) + def setMinimumPath(self, path_num): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + active_view.setMinimumPath(path_num) + + @pyqtSlot(int) + def setSimulationViewType(self, layer_view_type): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + active_view.setSimulationViewisinstance(layer_view_type) + + @pyqtSlot(result=int) + def getSimulationViewType(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getSimulationViewType() + return 0 + + @pyqtSlot(bool) + def setSimulationRunning(self, running): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + active_view.setSimulationRunning(running) + + @pyqtSlot(result=bool) + def getSimulationRunning(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.isSimulationRunning() + return False + + @pyqtSlot(result=float) + def getMinFeedrate(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getMinFeedrate() + return 0 + + @pyqtSlot(result=float) + def getMaxFeedrate(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getMaxFeedrate() + return 0 + + @pyqtSlot(result=float) + def getMinThickness(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getMinThickness() + return 0 + + @pyqtSlot(result=float) + def getMaxThickness(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getMaxThickness() + return 0 + + # Opacity 0..1 + @pyqtSlot(int, float) + def setExtruderOpacity(self, extruder_nr, opacity): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + active_view.setExtruderOpacity(extruder_nr, opacity) + + @pyqtSlot(int) + def setShowTravelMoves(self, show): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + active_view.setShowTravelMoves(show) + + @pyqtSlot(int) + def setShowHelpers(self, show): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + active_view.setShowHelpers(show) + + @pyqtSlot(int) + def setShowSkin(self, show): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + active_view.setShowSkin(show) + + @pyqtSlot(int) + def setShowInfill(self, show): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + active_view.setShowInfill(show) + + @pyqtProperty(int, notify=globalStackChanged) + def extruderCount(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + return active_view.getExtruderCount() + return 0 + + def _layerActivityChanged(self): + self.activityChanged.emit() + + def _onLayerChanged(self): + self.currentLayerChanged.emit() + self._layerActivityChanged() + + def _onPathChanged(self): + self.currentPathChanged.emit() + self._layerActivityChanged() + + def _onMaxLayersChanged(self): + self.maxLayersChanged.emit() + + def _onMaxPathsChanged(self): + self.maxPathsChanged.emit() + + def _onBusyChanged(self): + self.busyChanged.emit() + + def _onActivityChanged(self): + self.activityChanged.emit() + + def _onGlobalStackChanged(self): + self.globalStackChanged.emit() + + def _onPreferencesChanged(self): + self.preferencesChanged.emit() + + def _onActiveViewChanged(self): + active_view = self._controller.getActiveView() + if isinstance(active_view, SimulationView.SimulationView.SimulationView): + active_view.currentLayerNumChanged.connect(self._onLayerChanged) + active_view.currentPathNumChanged.connect(self._onPathChanged) + active_view.maxLayersChanged.connect(self._onMaxLayersChanged) + active_view.maxPathsChanged.connect(self._onMaxPathsChanged) + active_view.busyChanged.connect(self._onBusyChanged) + active_view.activityChanged.connect(self._onActivityChanged) + active_view.globalStackChanged.connect(self._onGlobalStackChanged) + active_view.preferencesChanged.connect(self._onPreferencesChanged) diff --git a/plugins/SimulationView/__init__.py b/plugins/SimulationView/__init__.py new file mode 100644 index 0000000000..f7ccf41acc --- /dev/null +++ b/plugins/SimulationView/__init__.py @@ -0,0 +1,26 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from PyQt5.QtQml import qmlRegisterSingletonType + +from UM.i18n import i18nCatalog +from . import SimulationViewProxy, SimulationView + +catalog = i18nCatalog("cura") + +def getMetaData(): + return { + "view": { + "name": catalog.i18nc("@item:inlistbox", "Simulation view"), + "view_panel": "SimulationView.qml", + "weight": 2 + } + } + +def createSimulationViewProxy(engine, script_engine): + return SimulationViewProxy.SimulatorViewProxy() + +def register(app): + simulation_view = SimulationView.SimulationView() + qmlRegisterSingletonType(SimulationViewProxy.SimulationViewProxy, "UM", 1, 0, "SimulationView", simulation_view.getProxy) + return { "view": SimulationView.SimulationView()} diff --git a/plugins/LayerView/layers.shader b/plugins/SimulationView/layers.shader old mode 100755 new mode 100644 similarity index 100% rename from plugins/LayerView/layers.shader rename to plugins/SimulationView/layers.shader diff --git a/plugins/LayerView/layers3d.shader b/plugins/SimulationView/layers3d.shader old mode 100755 new mode 100644 similarity index 92% rename from plugins/LayerView/layers3d.shader rename to plugins/SimulationView/layers3d.shader index e8fe425c70..f377fca055 --- a/plugins/LayerView/layers3d.shader +++ b/plugins/SimulationView/layers3d.shader @@ -6,6 +6,10 @@ vertex41core = uniform highp mat4 u_modelMatrix; uniform highp mat4 u_viewProjectionMatrix; uniform lowp float u_active_extruder; + uniform lowp float u_max_feedrate; + uniform lowp float u_min_feedrate; + uniform lowp float u_max_thickness; + uniform lowp float u_min_thickness; uniform lowp int u_layer_view_type; uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible @@ -18,6 +22,8 @@ vertex41core = in highp vec2 a_line_dim; // line width and thickness in highp float a_extruder; in highp float a_line_type; + in highp float a_feedrate; + in highp float a_thickness; out lowp vec4 v_color; @@ -32,6 +38,15 @@ vertex41core = out highp vec3 f_vertex; out highp vec3 f_normal; + vec4 gradientColor(float abs_value, float min_value, float max_value) + { + float value = (abs_value - min_value)/(max_value - min_value); + float red = value; + float green = 1-abs(1-2*value); + float blue = 1-value; + return vec4(red, green, blue, 1.0); + } + void main() { vec4 v1_vertex = a_vertex; @@ -48,6 +63,12 @@ vertex41core = case 1: // "Line type" v_color = a_color; break; + case 2: // "Feedrate" + v_color = gradientColor(a_feedrate, u_min_feedrate, u_max_feedrate); + break; + case 3: // "Layer thickness" + v_color = gradientColor(a_line_dim.y, u_min_thickness, u_max_thickness); + break; } v_vertex = world_space_vert.xyz; @@ -247,6 +268,12 @@ u_show_helpers = 1 u_show_skin = 1 u_show_infill = 1 +u_min_feedrate = 0 +u_max_feedrate = 1 + +u_min_thickness = 0 +u_max_thickness = 1 + [bindings] u_modelViewProjectionMatrix = model_view_projection_matrix u_modelMatrix = model_matrix @@ -262,3 +289,5 @@ a_line_dim = line_dim a_extruder = extruder a_material_color = material_color a_line_type = line_type +a_feedrate = feedrate +a_thickness = thickness diff --git a/plugins/SimulationView/layers3d_shadow.shader b/plugins/SimulationView/layers3d_shadow.shader new file mode 100644 index 0000000000..ad75fcf9d0 --- /dev/null +++ b/plugins/SimulationView/layers3d_shadow.shader @@ -0,0 +1,256 @@ +[shaders] +vertex41core = + #version 410 + uniform highp mat4 u_modelViewProjectionMatrix; + + uniform highp mat4 u_modelMatrix; + uniform highp mat4 u_viewProjectionMatrix; + uniform lowp float u_active_extruder; + uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible + + uniform highp mat4 u_normalMatrix; + + in highp vec4 a_vertex; + in lowp vec4 a_color; + in lowp vec4 a_grayColor; + in lowp vec4 a_material_color; + in highp vec4 a_normal; + in highp vec2 a_line_dim; // line width and thickness + in highp float a_extruder; + in highp float a_line_type; + + out lowp vec4 v_color; + + out highp vec3 v_vertex; + out highp vec3 v_normal; + out lowp vec2 v_line_dim; + out highp int v_extruder; + out highp vec4 v_extruder_opacity; + out float v_line_type; + + out lowp vec4 f_color; + out highp vec3 f_vertex; + out highp vec3 f_normal; + + void main() + { + vec4 v1_vertex = a_vertex; + v1_vertex.y -= a_line_dim.y / 2; // half layer down + + vec4 world_space_vert = u_modelMatrix * v1_vertex; + gl_Position = world_space_vert; + // shade the color depending on the extruder index stored in the alpha component of the color + + v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer + v_vertex = world_space_vert.xyz; + v_normal = (u_normalMatrix * normalize(a_normal)).xyz; + v_line_dim = a_line_dim; + v_extruder = int(a_extruder); + v_line_type = a_line_type; + v_extruder_opacity = u_extruder_opacity; + + // for testing without geometry shader + f_color = v_color; + f_vertex = v_vertex; + f_normal = v_normal; + } + +geometry41core = + #version 410 + + uniform highp mat4 u_viewProjectionMatrix; + uniform int u_show_travel_moves; + uniform int u_show_helpers; + uniform int u_show_skin; + uniform int u_show_infill; + + layout(lines) in; + layout(triangle_strip, max_vertices = 26) out; + + in vec4 v_color[]; + in vec3 v_vertex[]; + in vec3 v_normal[]; + in vec2 v_line_dim[]; + in int v_extruder[]; + in vec4 v_extruder_opacity[]; + in float v_line_type[]; + + out vec4 f_color; + out vec3 f_normal; + out vec3 f_vertex; + + // Set the set of variables and EmitVertex + void myEmitVertex(vec3 vertex, vec4 color, vec3 normal, vec4 pos) { + f_vertex = vertex; + f_color = color; + f_normal = normal; + gl_Position = pos; + EmitVertex(); + } + + void main() + { + vec4 g_vertex_delta; + vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers + vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position + vec3 g_vertex_normal_vert; + vec4 g_vertex_offset_vert; + vec3 g_vertex_normal_horz_head; + vec4 g_vertex_offset_horz_head; + + float size_x; + float size_y; + + if ((v_extruder_opacity[0][v_extruder[0]] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) { + return; + } + // See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType + if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) { + return; + } + if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10))) { + return; + } + if ((u_show_skin == 0) && ((v_line_type[0] == 1) || (v_line_type[0] == 2) || (v_line_type[0] == 3))) { + return; + } + if ((u_show_infill == 0) && (v_line_type[0] == 6)) { + return; + } + + if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { + // fixed size for movements + size_x = 0.05; + } else { + size_x = v_line_dim[1].x / 2 + 0.01; // radius, and make it nicely overlapping + } + size_y = v_line_dim[1].y / 2 + 0.01; + + g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position; + g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z)); + g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); + + g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x)); + + g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //size * g_vertex_normal_horz; + g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); + g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0); + + if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { + // Travels: flat plane with pointy ends + 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)); + 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[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)); + + 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_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(); + + // left side + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz)); + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert)); + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head)); + myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz)); + + EndPrimitive(); + + myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz)); + myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert)); + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head)); + myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz)); + + EndPrimitive(); + + // right side + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz)); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert)); + myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head)); + myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz)); + + EndPrimitive(); + + myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz)); + myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert)); + myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head)); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz)); + + EndPrimitive(); + } + } + +fragment41core = + #version 410 + in lowp vec4 f_color; + in lowp vec3 f_normal; + in lowp vec3 f_vertex; + + out vec4 frag_color; + + uniform mediump vec4 u_ambientColor; + uniform highp vec3 u_lightPosition; + + void main() + { + mediump vec4 finalColor = vec4(0.0); + float alpha = f_color.a; + + finalColor.rgb += f_color.rgb * 0.3; + + highp vec3 normal = normalize(f_normal); + highp vec3 light_dir = normalize(u_lightPosition - f_vertex); + + // Diffuse Component + highp float NdotL = clamp(dot(normal, light_dir), 0.0, 1.0); + finalColor += (NdotL * f_color); + finalColor.a = alpha; // Do not change alpha in any way + + frag_color = finalColor; + } + + +[defaults] +u_active_extruder = 0.0 +u_extruder_opacity = [1.0, 1.0, 1.0, 1.0] + +u_specularColor = [0.4, 0.4, 0.4, 1.0] +u_ambientColor = [0.3, 0.3, 0.3, 0.0] +u_diffuseColor = [1.0, 0.79, 0.14, 1.0] +u_shininess = 20.0 + +u_show_travel_moves = 0 +u_show_helpers = 1 +u_show_skin = 1 +u_show_infill = 1 + +[bindings] +u_modelViewProjectionMatrix = model_view_projection_matrix +u_modelMatrix = model_matrix +u_viewProjectionMatrix = view_projection_matrix +u_normalMatrix = normal_matrix +u_lightPosition = light_0_position + +[attributes] +a_vertex = vertex +a_color = color +a_grayColor = vec4(0.87, 0.12, 0.45, 1.0) +a_normal = normal +a_line_dim = line_dim +a_extruder = extruder +a_material_color = material_color +a_line_type = line_type diff --git a/plugins/SimulationView/layers_shadow.shader b/plugins/SimulationView/layers_shadow.shader new file mode 100644 index 0000000000..972f18c921 --- /dev/null +++ b/plugins/SimulationView/layers_shadow.shader @@ -0,0 +1,156 @@ +[shaders] +vertex = + uniform highp mat4 u_modelViewProjectionMatrix; + uniform lowp float u_active_extruder; + uniform lowp float u_shade_factor; + uniform highp int u_layer_view_type; + + attribute highp float a_extruder; + attribute highp float a_line_type; + attribute highp vec4 a_vertex; + attribute lowp vec4 a_color; + attribute lowp vec4 a_material_color; + + varying lowp vec4 v_color; + varying float v_line_type; + + void main() + { + gl_Position = u_modelViewProjectionMatrix * a_vertex; + // shade the color depending on the extruder index + v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer; + // 8 and 9 are travel moves + // if ((a_line_type != 8.0) && (a_line_type != 9.0)) { + // v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a); + // } + + v_line_type = a_line_type; + } + +fragment = + varying lowp vec4 v_color; + varying float v_line_type; + + uniform int u_show_travel_moves; + uniform int u_show_helpers; + uniform int u_show_skin; + uniform int u_show_infill; + + void main() + { + if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9 + // discard movements + discard; + } + // support: 4, 5, 7, 10 + if ((u_show_helpers == 0) && ( + ((v_line_type >= 3.5) && (v_line_type <= 4.5)) || + ((v_line_type >= 6.5) && (v_line_type <= 7.5)) || + ((v_line_type >= 9.5) && (v_line_type <= 10.5)) || + ((v_line_type >= 4.5) && (v_line_type <= 5.5)) + )) { + discard; + } + // skin: 1, 2, 3 + if ((u_show_skin == 0) && ( + (v_line_type >= 0.5) && (v_line_type <= 3.5) + )) { + discard; + } + // infill: + if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) { + // discard movements + discard; + } + + gl_FragColor = v_color; + } + +vertex41core = + #version 410 + uniform highp mat4 u_modelViewProjectionMatrix; + uniform lowp float u_active_extruder; + uniform lowp float u_shade_factor; + uniform highp int u_layer_view_type; + + in highp float a_extruder; + in highp float a_line_type; + in highp vec4 a_vertex; + in lowp vec4 a_color; + in lowp vec4 a_material_color; + + out lowp vec4 v_color; + out float v_line_type; + + void main() + { + gl_Position = u_modelViewProjectionMatrix * a_vertex; + v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer + // if ((a_line_type != 8) && (a_line_type != 9)) { + // v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a); + // } + + v_line_type = a_line_type; + } + +fragment41core = + #version 410 + in lowp vec4 v_color; + in float v_line_type; + out vec4 frag_color; + + uniform int u_show_travel_moves; + uniform int u_show_helpers; + uniform int u_show_skin; + uniform int u_show_infill; + + void main() + { + if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9 + // discard movements + discard; + } + // helpers: 4, 5, 7, 10 + if ((u_show_helpers == 0) && ( + ((v_line_type >= 3.5) && (v_line_type <= 4.5)) || + ((v_line_type >= 6.5) && (v_line_type <= 7.5)) || + ((v_line_type >= 9.5) && (v_line_type <= 10.5)) || + ((v_line_type >= 4.5) && (v_line_type <= 5.5)) + )) { + discard; + } + // skin: 1, 2, 3 + if ((u_show_skin == 0) && ( + (v_line_type >= 0.5) && (v_line_type <= 3.5) + )) { + discard; + } + // infill: + if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) { + // discard movements + discard; + } + + frag_color = v_color; + } + +[defaults] +u_active_extruder = 0.0 +u_shade_factor = 0.60 +u_layer_view_type = 0 +u_extruder_opacity = [1.0, 1.0, 1.0, 1.0] + +u_show_travel_moves = 0 +u_show_helpers = 1 +u_show_skin = 1 +u_show_infill = 1 + +[bindings] +u_modelViewProjectionMatrix = model_view_projection_matrix + +[attributes] +a_vertex = vertex +a_color = color +a_extruder = extruder +a_line_type = line_type +a_material_color = material_color diff --git a/plugins/LayerView/plugin.json b/plugins/SimulationView/plugin.json similarity index 54% rename from plugins/LayerView/plugin.json rename to plugins/SimulationView/plugin.json index 232fe9c361..0e7bec0626 100644 --- a/plugins/LayerView/plugin.json +++ b/plugins/SimulationView/plugin.json @@ -1,8 +1,8 @@ { - "name": "Layer View", + "name": "Simulation View", "author": "Ultimaker B.V.", "version": "1.0.0", - "description": "Provides the Layer view.", + "description": "Provides the Simulation view.", "api": 4, "i18n-catalog": "cura" } diff --git a/plugins/SimulationView/resources/nozzle.stl b/plugins/SimulationView/resources/nozzle.stl new file mode 100644 index 0000000000000000000000000000000000000000..7f4b22804aab6768ed2b9c0ccdc2f89847e38125 GIT binary patch literal 210284 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%0AbuZy#5DmdXLV_bm} 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 MV-kv + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/plugins/SimulationView/resources/simulation_resume.svg b/plugins/SimulationView/resources/simulation_resume.svg new file mode 100644 index 0000000000..a8ed8e79a3 --- /dev/null +++ b/plugins/SimulationView/resources/simulation_resume.svg @@ -0,0 +1,82 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/plugins/LayerView/layerview_composite.shader b/plugins/SimulationView/simulationview_composite.shader similarity index 100% rename from plugins/LayerView/layerview_composite.shader rename to plugins/SimulationView/simulationview_composite.shader From a0304255d1f9bf9a6b1e220fcef2643df819fd95 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 17 Nov 2017 15:05:08 +0100 Subject: [PATCH 60/67] CURA-4577 Take into account relative movements in GCodes --- plugins/GCodeReader/GCodeReader.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py index e49ff9d8d7..1254efd4f2 100755 --- a/plugins/GCodeReader/GCodeReader.py +++ b/plugins/GCodeReader/GCodeReader.py @@ -136,16 +136,22 @@ class GCodeReader(MeshReader): def _gCode0(self, position, params, path): x, y, z, e = position - x = params.x if params.x is not None else x - y = params.y if params.y is not None else y - z = params.z if params.z is not None else position.z + if self._is_absolute_positioning: + x = params.x if params.x is not None else x + y = params.y if params.y is not None else y + z = params.z if params.z is not None else position.z + else: + x = x + params.x if params.x is not None else x + y = y + params.y if params.y is not None else y + z = z + params.z if params.z is not None else position.z if params.e is not None: - if params.e > e[self._extruder_number]: + new_extrusion_value = params.e if self._is_absolute_positioning else e[self._extruder_number] + params.e + if new_extrusion_value > e[self._extruder_number]: path.append([x, y, z, self._layer_type]) # extrusion else: path.append([x, y, z, LayerPolygon.MoveRetractionType]) # retraction - e[self._extruder_number] = params.e + e[self._extruder_number] = new_extrusion_value # Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions # Also, 1.5 is a heuristic for any priming or whatsoever, we skip those. From 93de0062717bd28d0ea24d6126f346418c7714ea Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Fri, 17 Nov 2017 15:22:29 +0100 Subject: [PATCH 61/67] Remove suffix .curaproject for case "import models" --- cura/PrintInformation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index d3bcc10781..2cf589bfa4 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -305,6 +305,9 @@ class PrintInformation(QObject): # name is "" when I first had some meshes and afterwards I deleted them so the naming should start again if name == "" or (self._base_name == "" and self._base_name != name): + # remove ".curaproject" suffix from (imported) the file name + if name.endswith(".curaproject"): + name = name[:name.rfind(".curaproject")] self._base_name = name self._updateJobName() From fb2ffb7270076877b7dd6403948965df6dd8a755 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Fri, 17 Nov 2017 16:31:16 +0100 Subject: [PATCH 62/67] Simplify relative positioning xyz value updates - CURA-4577 --- plugins/GCodeReader/GCodeReader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py index 1254efd4f2..a0b44bbaaa 100755 --- a/plugins/GCodeReader/GCodeReader.py +++ b/plugins/GCodeReader/GCodeReader.py @@ -141,9 +141,9 @@ class GCodeReader(MeshReader): y = params.y if params.y is not None else y z = params.z if params.z is not None else position.z else: - x = x + params.x if params.x is not None else x - y = y + params.y if params.y is not None else y - z = z + params.z if params.z is not None else position.z + x += params.x if params.x is not None else x + y += params.y if params.y is not None else y + z += params.z if params.z is not None else position.z if params.e is not None: new_extrusion_value = params.e if self._is_absolute_positioning else e[self._extruder_number] + params.e From 568e18f664bdaf8640013400ec9b6b21a2e93955 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 17 Nov 2017 16:30:57 +0100 Subject: [PATCH 63/67] Add Japanese translation of cura.po It's quite bad though so let's see what improvements we can make. --- resources/i18n/ja_JP/cura.po | 4395 ++++++++++++++++++++++++++++++++++ 1 file changed, 4395 insertions(+) create mode 100644 resources/i18n/ja_JP/cura.po diff --git a/resources/i18n/ja_JP/cura.po b/resources/i18n/ja_JP/cura.po new file mode 100644 index 0000000000..c29e0b709b --- /dev/null +++ b/resources/i18n/ja_JP/cura.po @@ -0,0 +1,4395 @@ +# Cura +# Copyright (C) 2017 Ultimaker +# This file is distributed under the same license as the Cura package. +# Ruben Dulek , 2017. +# +msgid "" +msgstr "" +"Project-Id-Version: Cura 3.0\n" +"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n" +"POT-Creation-Date: 2017-08-02 16:53+0000\n" +"PO-Revision-Date: 2017-11-10 20:08+0900\n" +"Language-Team: TEAM\n" +"Language: xx_XX\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" +"Last-Translator: \n" +"X-Generator: Poedit 2.0.4\n" + +#: Manually added for plugins/UM3NetworkPrinting/PrinterInfoBlock.qml +msgctxt "@label:status" +msgid "Print aborted" +msgstr "プリント中止" + +#: Manually added for plugins/UM3NetworkPrinting/PrinterInfoBlock.qml +msgctxt "@label:status" +msgid "Blocked" +msgstr "ブロックされました" + +#: Manually added for plugins/UM3NetworkPrinting/PrinterInfoBlock.qml +msgctxt "@label:status" +msgid "Action required" +msgstr "アクションが必要です。" + +#: Manually added for plugins/UM3NetworkPrinting/PrinterInfoBlock.qml +msgctxt "@label:status" +msgid "Can't start print" +msgstr "プリントを開始できません。" + +#: Manually added for plugins/UM3NetworkPrinting/DiscoverUM3Action.qml +msgctxt "@label" +msgid "This printer is not set up to host a group of Ultimaker 3 printers." +msgstr "Ultimaker3のグループをホストするために設定されていません。" + +#: Manually added for plugins/UM3NetworkPrinting/PrinterInfoBlock.qml +msgctxt "@label" +msgid "Finishes at: " +msgstr "この層で終了します:" + +#: Manually added for plugins/UM3NetworkPrinting/DiscoverUM3Action.qml +msgctxt "@label" +msgid "This printer is the host for a group of %1 Ultimaker 3 printers." +msgstr "このプリンターがUltimaker3のグループのホストプリンターです。" + +#: Manually added for plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py +msgctxt "@info:status" +msgid "Printer '{printer_name}' has finished printing '{job_name}'." +msgstr "プリンター’{printer_name}’が’{job_name}’のプリントを終了しました。" + +#: Manually added for plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py +msgctxt "@info:status" +msgid "Print finished" +msgstr "プリント終了" + +#: Manually added for resources/Cura/Cura.qml +msgctxt "@title:menu menubar:toplevel" +msgid "P&lugins" +msgstr "プラグイン" + +#: Manually added for resources/Cura/Actions.qml +msgctxt "@action:menu" +msgid "Browse plugins..." +msgstr "プラグインをみる" + +#: Manually added for resources/Cura/Actions.qml +msgctxt "@action:menu" +msgid "Installed plugins..." +msgstr "インストールされたプラグイン" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:29 +msgctxt "@action" +msgid "Machine Settings" +msgstr "プリンターの設定" + +#: /home/ruben/Projects/Cura/plugins/XRayView/__init__.py:12 +msgctxt "@item:inlistbox" +msgid "X-Ray view" +msgstr "X-Rayビューイング" + +#: /home/ruben/Projects/Cura/plugins/X3DReader/__init__.py:13 +msgctxt "@item:inlistbox" +msgid "X3D File" +msgstr "X3Dファイル" + +#: /home/ruben/Projects/Cura/plugins/GCodeWriter/__init__.py:16 +msgctxt "@item:inlistbox" +msgid "GCode File" +msgstr "GCodeファイル" + +#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:65 +msgctxt "@action:button" +msgid "Print with Doodle3D WiFi-Box" +msgstr "Doodle3D WiFi-Boxでプリントする" + +#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:66 +msgctxt "@properties:tooltip" +msgid "Print with Doodle3D WiFi-Box" +msgstr "Doodle3D WiFi-Boxでプリント" + +#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:86 +msgctxt "@info:status" +msgid "Connecting to Doodle3D Connect" +msgstr "Doodle3D Connectに接続する" + +#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87 +#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:840 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:822 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:428 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:350 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:367 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/PrintWindow.qml:78 +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:104 +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:99 +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188 +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:371 +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:139 +#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87 +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:251 +msgctxt "@action:button" +msgid "Cancel" +msgstr "キャンセル" + +#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:154 +msgctxt "@info:status" +msgid "Sending data to Doodle3D Connect" +msgstr "Doodle3D Connectにデータを送信" + +#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:161 +msgctxt "@info:status" +msgid "Unable to send data to Doodle3D Connect. Is another job still active?" +msgstr "Doodle3D Connectにデータを送れませんでした。他のジョブはまだアクティブですか?" + +#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:175 +msgctxt "@info:status" +msgid "Storing data on Doodle3D Connect" +msgstr "Doodle3D Connectにデータを保存" + +#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:213 +msgctxt "@info:status" +msgid "File sent to Doodle3D Connect" +msgstr "Doodle3D Connectにファイル送信完了" + +#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214 +msgctxt "@action:button" +msgid "Open Connect.." +msgstr "Connect..を開く" + +#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214 +msgctxt "@info:tooltip" +msgid "Open the Doodle3D Connect web interface" +msgstr "Doodle3D Connect web interfaceを開く" + +#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:35 +msgctxt "@item:inmenu" +msgid "Show Changelog" +msgstr "Changelogの表示" + +#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:20 +#, fuzzy +msgctxt "@item:inmenu" +msgid "Flatten active settings" +msgstr "アクティブ設定を平らにします。" + +#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:32 +#, fuzzy +msgctxt "@info:status" +msgid "Profile has been flattened & activated." +msgstr "プロファイルが平らになり、アクティベートされました。" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:26 +msgctxt "@item:inmenu" +msgid "USB printing" +msgstr "USBプリンティング" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27 +msgctxt "@action:button Preceded by 'Ready to'." +msgid "Print via USB" +msgstr "USBを使ってプリントする" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28 +msgctxt "@info:tooltip" +msgid "Print via USB" +msgstr "USBを使ってプリントする" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:30 +msgctxt "@info:status" +msgid "Connected via USB" +msgstr "USBにて接続する" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:153 +msgctxt "@info:status" +msgid "Unable to start a new job because the printer is busy or not connected." +msgstr "新しいプリントジョブをはじめることができません。プリンターが使用中または接続できていません。" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:153 +msgctxt "@info:title" +msgid "Print Details" +msgstr "プリント詳細" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:456 +msgctxt "@info:status" +msgid "This printer does not support USB printing because it uses UltiGCode flavor." +msgstr "UltiGCodeを使用中のため、USBからのプリントができません。" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:456 +msgctxt "@info:title" +msgid "USB Printing" +msgstr "USBプリント" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:460 +msgctxt "@info:status" +msgid "Unable to start a new job because the printer does not support usb printing." +msgstr "USBでの印刷ができないため、新しいプリントジョブができません。" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:460 +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108 +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:146 +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:158 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:909 +#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1296 +msgctxt "@info:title" +msgid "Warning" +msgstr "警告" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108 +msgctxt "@info" +msgid "Unable to update firmware because there are no printers connected." +msgstr "プリンターが未接続のため、ファームウェアをアップデートできません。" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122 +#, python-format +msgctxt "@info" +msgid "Could not find firmware required for the printer at %s." +msgstr "プリンターに必要なファームウェアを探せませんでした。" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122 +msgctxt "@info:title" +msgid "Printer Firmware" +msgstr "ファームウェア" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23 +msgctxt "@action:button Preceded by 'Ready to'." +msgid "Save to Removable Drive" +msgstr "リムーバブルドライブに保存" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:24 +#, python-brace-format +msgctxt "@item:inlistbox" +msgid "Save to Removable Drive {0}" +msgstr "リムーバブルドライブに保存" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:89 +#, python-brace-format +msgctxt "@info:progress Don't translate the XML tags !" +msgid "Saving to Removable Drive {0}" +msgstr "リムーバブルドライブに保存中" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:89 +msgctxt "@info:title" +msgid "Saving" +msgstr "保存中" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:99 +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:102 +#, python-brace-format +msgctxt "@info:status Don't translate the XML tags or !" +msgid "Could not save to {0}: {1}" +msgstr "保存できませんでした。" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:118 +#, python-brace-format +msgctxt "@info:status Don't translate the tag {device}!" +msgid "Could not find a file name when trying to write to {device}." +msgstr "デバイスに書き出すためのファイル名が見つかりませんでした。" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:131 +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:146 +#, python-brace-format +msgctxt "@info:status" +msgid "Could not save to removable drive {0}: {1}" +msgstr "リムーバブルドライブに保存することができませんでした。" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:132 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:675 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:683 +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:145 +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:152 +#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1305 +msgctxt "@info:title" +msgid "Error" +msgstr "エラー" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:140 +#, python-brace-format +msgctxt "@info:status" +msgid "Saved to Removable Drive {0} as {1}" +msgstr "リムーバブルドライブに保存" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:140 +msgctxt "@info:title" +msgid "File Saved" +msgstr "ファイル保存" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:141 +msgctxt "@action:button" +msgid "Eject" +msgstr "取り出す" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:141 +#, python-brace-format +msgctxt "@action" +msgid "Eject removable device {0}" +msgstr "リムーバブルデバイスを取り出す" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:156 +#, python-brace-format +msgctxt "@info:status" +msgid "Ejected {0}. You can now safely remove the drive." +msgstr "取り出し完了。デバイスを安全に取り外せます。" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:156 +msgctxt "@info:title" +msgid "Safely Remove Hardware" +msgstr "ハードウェアを安全に取り外します。" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:158 +#, python-brace-format +msgctxt "@info:status" +msgid "Failed to eject {0}. Another program may be using the drive." +msgstr "取り出し失敗。他のプログラムがデバイスを使用しているため。" + +#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/WindowsRemovableDrivePlugin.py:68 +msgctxt "@item:intext" +msgid "Removable Drive" +msgstr "リムーバブルドライブ" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:107 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:107 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:49 +msgctxt "@action:button Preceded by 'Ready to'." +msgid "Print over network" +msgstr "ネットワーク上のプリント" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:108 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:108 +msgctxt "@properties:tooltip" +msgid "Print over network" +msgstr "ネットワークのプリント" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:157 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:157 +msgctxt "@info:status" +msgid "Access to the printer requested. Please approve the request on the printer" +msgstr "プリンターへのアクセスが申請されました。プリンタへのリクエストを承認してください。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:157 +msgctxt "@info:title" +msgid "Connection status" +msgstr "コネクションのステータス" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:158 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:158 +msgctxt "@info:status" +msgid "" +msgstr "" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:158 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:161 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:162 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:468 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:500 +msgctxt "@info:title" +msgid "Connection Status" +msgstr "コネクションステータス" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:159 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:159 +msgctxt "@action:button" +msgid "Retry" +msgstr "再試行" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:159 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:159 +msgctxt "@info:tooltip" +msgid "Re-send the access request" +msgstr "アクセスリクエストを再送信" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:161 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:161 +msgctxt "@info:status" +msgid "Access to the printer accepted" +msgstr "プリンターへのアクセスが承認されました。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:162 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:162 +msgctxt "@info:status" +msgid "No access to print with this printer. Unable to send print job." +msgstr "このプリンターへのアクセスが許可されていないため、プリントジョブの送信ができませんでした。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:163 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:163 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:28 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:72 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/UM3InfoComponents.qml:28 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/UM3InfoComponents.qml:72 +msgctxt "@action:button" +msgid "Request Access" +msgstr "アクセスのリクエスト" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:163 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:163 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:27 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:71 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/UM3InfoComponents.qml:27 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/UM3InfoComponents.qml:71 +msgctxt "@info:tooltip" +msgid "Send access request to the printer" +msgstr "アクセスのリクエスト送信" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:375 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:364 +msgctxt "@info:status" +msgid "Connected over the network. Please approve the access request on the printer." +msgstr "ネットワーク上で接続。プリンタへのリクエストを承認してください。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:382 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:371 +msgctxt "@info:status" +msgid "Connected over the network." +msgstr "ネットワーク上で接続" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:395 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:384 +msgctxt "@info:status" +msgid "Connected over the network. No access to control the printer." +msgstr "ネットワーク上で接続。プリントを操作するアクセス権がありません。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:400 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:389 +msgctxt "@info:status" +msgid "Access request was denied on the printer." +msgstr "プリンターへのアクセス権" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:403 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:392 +msgctxt "@info:status" +msgid "Access request failed due to a timeout." +msgstr "時間制限によりアクセスが却下されました。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:467 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:456 +msgctxt "@info:status" +msgid "The connection with the network was lost." +msgstr "ネットワークへの接続が切断されました。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:499 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:487 +msgctxt "@info:status" +msgid "The connection with the printer was lost. Check your printer to see if it is connected." +msgstr "ネットワークへの接続が切断されました。プリンターの接続状態を確認ください。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:649 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:636 +#, python-format +msgctxt "@info:status" +msgid "Unable to start a new print job, printer is busy. Current printer status is %s." +msgstr "プリンターが利用中です。新しいプリントジョブを開始することができませんでした。現在の印刷状況は%です。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:650 +msgctxt "@info:title" +msgid "Printer Status" +msgstr "プリンターのステータス" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:674 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:660 +#, python-brace-format +msgctxt "@info:status" +msgid "Unable to start a new print job. No Printcore loaded in slot {0}" +msgstr "プリントコアがスロットに入っていません。プリントジョブを開始できません。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:682 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:667 +#, python-brace-format +msgctxt "@info:status" +msgid "Unable to start a new print job. No material loaded in slot {0}" +msgstr "フィラメントがスロットに入っていません。プリントジョブを開始できません。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:676 +#, python-brace-format +msgctxt "@label" +msgid "Not enough material for spool {0}." +msgstr "フィラメントの残量が足りません。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:702 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:686 +#, python-brace-format +msgctxt "@label" +msgid "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}" +msgstr "異なるプリントコアが入っています。(Cura:{0}, プリンター{1})エクストルーダー{2}" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:716 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:700 +#, python-brace-format +msgctxt "@label" +msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}" +msgstr "異なるフィラメントが入っています。(Cura:{0}, プリンター{1})エクストルーダー{2}" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:724 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:708 +#, python-brace-format +msgctxt "@label" +msgid "PrintCore {0} is not properly calibrated. XY calibration needs to be performed on the printer." +msgstr "プリントコア{0}が適切にカリブレーションできていません。XYキャリブレーションをプリンターで行ってください。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:729 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:713 +msgctxt "@label" +msgid "Are you sure you wish to print with the selected configuration?" +msgstr "選択された構成にてプリントを開始してもいいですか。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:730 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:714 +msgctxt "@label" +msgid "" +"There is a mismatch between the configuration or calibration of the printer and Cura. For the best result, always slice " +"for the PrintCores and materials that are inserted in your printer." +msgstr "" +"プリンターの設定、キャリブレーションとCuraの構成にミスマッチがあります。プリンターに設置されたプリントコア及びフィラメント" +"を元にCuraをスライスすることで最良の印刷結果を出すことができます。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:736 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:720 +msgctxt "@window:title" +msgid "Mismatched configuration" +msgstr "ミスマッチの構成" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:839 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:821 +msgctxt "@info:status" +msgid "Sending data to printer" +msgstr "プリンターにプリントデータを送信中" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:839 +msgctxt "@info:title" +msgid "Sending Data" +msgstr "プリントデータを送信中" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:908 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:890 +msgctxt "@info:status" +msgid "Unable to send data to printer. Is another job still active?" +msgstr "データをプリンターに送ることができません。他のプリントジョブは進行中ですか?" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1050 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:1034 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:198 +msgctxt "@label:MonitorStatus" +msgid "Aborting print..." +msgstr "プリントを停止します…" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1056 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:1040 +msgctxt "@label:MonitorStatus" +msgid "Print aborted. Please check the printer" +msgstr "プリントを中止しました。プリンターを確認してください。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1062 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:1046 +msgctxt "@label:MonitorStatus" +msgid "Pausing print..." +msgstr "プリントを一時停止します…" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1064 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:1048 +msgctxt "@label:MonitorStatus" +msgid "Resuming print..." +msgstr "プリント再開します…" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1216 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:1191 +msgctxt "@window:title" +msgid "Sync with your printer" +msgstr "プリンターと同期する" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1218 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:1193 +msgctxt "@label" +msgid "Would you like to use your current printer configuration in Cura?" +msgstr "Curaで設定しているプリンタ構成を使用されますか?" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1220 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:1195 +msgctxt "@label" +msgid "" +"The PrintCores and/or materials on your printer differ from those within your current project. For the best result, " +"always slice for the PrintCores and materials that are inserted in your printer." +msgstr "" +"プリンターのプリントコア及びフィラメントが現在のプロジェクトと異なります。最善な印刷結果のために、プリンタに装着しているプ" +"リントコア、フィラメントに合わせてスライスして頂くことをお勧めします。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:19 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.py:19 +msgctxt "@action" +msgid "Connect via Network" +msgstr "ネットワーク上にて接続" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:103 +msgid "This printer is not set up to host a group of connected Ultimaker 3 printers." +msgstr "このプリンターは、繋がっているUltimaker3プリンターをホストするために設定されていません。" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:104 +#, python-brace-format +msgctxt "Count is number of printers." +msgid "This printer is the host for a group of {count} connected Ultimaker 3 printers." +msgstr "このプリンターは{台数}繋がっているUltimaker3プリンターのグループのホストプリンターです。" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:105 +#, python-brace-format +msgid "{printer_name} has finished printing '{job_name}'. Please collect the print and confirm clearing the build plate." +msgstr "{printer_name}は ‘{job_name}’印刷を終了しました。造形物を確認し、ビルドプレートから取り出してください。" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:106 +#, python-brace-format +msgid "" +"{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to " +"start printing." +msgstr "{printer_name} は '{job_name}'.を印刷予定です。印刷を開始するためにジョブに合わせた構成に変更してください。" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:196 +msgctxt "@info:status" +msgid "Sending new jobs (temporarily) blocked, still sending the previous print job." +msgstr "新しいデータの送信 (temporarily) をブロックします、前のプリントジョブが送信中です。" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:212 +msgctxt "@info:status" +msgid "Unable to send new print job: this 3D printer is not (yet) set up to host a group of connected Ultimaker 3 printers." +msgstr "" +"新しいプリントジョブをお送りできません。この3Dプリンターは繋がっているUltimaker3のグループをホストするために設定されていま" +"せん。" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:425 +#, python-brace-format +msgctxt "@info:progress" +msgid "Sending {file_name} to group {cluster_name}" +msgstr "グループに送信中{file_name} {cluster_name}" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:498 +#, python-brace-format +msgctxt "@info:status" +msgid "Sent {file_name} to group {cluster_name}." +msgstr "グループに送信完了{file_name} {cluster_name}." + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:503 +msgctxt "@action:button" +msgid "Show print jobs" +msgstr "プリントジョブを見る" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:504 +msgctxt "@info:tooltip" +msgid "Opens the print jobs interface in your browser." +msgstr "プリントジョブのインターフェイスをブラウザーで開く" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:520 +#, python-brace-format +msgctxt "@info:status" +msgid "Unable to send print job to group {cluster_name}." +msgstr "プリントジョブをグループに送ることができません。{cluster_name}." + +#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:71 +#, python-brace-format +msgctxt "@info Don't translate {machine_name}, since it gets replaced by a printer name!" +msgid "" +"To ensure that your {machine_name} is equipped with the latest features it is recommended to update the firmware " +"regularly. This can be done on the {machine_name} (when connected to the network) or via USB." +msgstr "" +"{machine_name}が最新の機能を得るために、定期的にファームウェアをアップデートすることをお勧めします。{machine_name}(ネット" +"ワーク上で接続)またはUSBにて行ってください。 " + +#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:72 +#, python-format +msgctxt "@info:title The %s gets replaced with the printer name." +msgid "New %s firmware available" +msgstr "新しい利用可能なファームウェアのアップデートがあります。" + +#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:73 +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:149 +msgctxt "@action:button" +msgid "Download" +msgstr "ダウンロード" + +#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:83 +msgctxt "@info" +msgid "Could not access update information." +msgstr "必要なアップデートの情報にアクセスできません。" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:199 +msgctxt "@info:status" +msgid "" +"Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in " +"SolidWorks itself without any problems as well!" +msgstr "" +"ソリッドワークスのファイルを開く際にエラーが発生しました!ソリッドワークスで、問題なく開くことができるか確認してください。" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:31 +msgctxt "@item:inlistbox" +msgid "SolidWorks part file" +msgstr "ソリッドワークスパートファイル" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:35 +msgctxt "@item:inlistbox" +msgid "SolidWorks assembly file" +msgstr "ソリッドワークスアセンブリーファイル" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.py:21 +msgid "Configure" +msgstr "構成" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/CommonComReader.py:135 +#, python-format +msgctxt "@info:status" +msgid "Error while starting %s!" +msgstr " %を開始中にエラーが発生" + +#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:24 +msgid "Modify G-Code" +msgstr "G-codeを修正" + +#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:43 +msgctxt "@info" +msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences." +msgstr "Curaが非特定なスライスされた数字を集めました。プレファレンス内で無効にできます。" + +#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:46 +msgctxt "@info:title" +msgid "Collecting Data" +msgstr "データを収集中" + +#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:48 +msgctxt "@action:button" +msgid "Dismiss" +msgstr "却下する" + +#: /home/ruben/Projects/Cura/plugins/LegacyProfileReader/__init__.py:14 +msgctxt "@item:inlistbox" +msgid "Cura 15.04 profiles" +msgstr "Cura 15.04 プロファイル" + +#: /home/ruben/Projects/Cura/plugins/GCodeProfileReader/__init__.py:14 +#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:14 +msgctxt "@item:inlistbox" +msgid "G-code File" +msgstr "G-codeファイル" + +#: /home/ruben/Projects/Cura/plugins/LayerView/__init__.py:13 +msgctxt "@item:inlistbox" +msgid "Layer view" +msgstr "レイヤービュー" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.py:93 +msgctxt "@info:status" +msgid "Cura does not accurately display layers when Wire Printing is enabled" +msgstr "Curaはワイヤープリンティング設定中には正確にレイヤーを表示しません。" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.py:94 +msgctxt "@info:title" +msgid "Layer View" +msgstr "レイヤービュー" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:14 +msgctxt "@item:inlistbox" +msgid "JPG Image" +msgstr "JPG画像" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:18 +msgctxt "@item:inlistbox" +msgid "JPEG Image" +msgstr "JPEG画像" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:22 +msgctxt "@item:inlistbox" +msgid "PNG Image" +msgstr "PNG画像" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:26 +msgctxt "@item:inlistbox" +msgid "BMP Image" +msgstr "BMP画像" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:30 +msgctxt "@item:inlistbox" +msgid "GIF Image" +msgstr "GIF画像" + +#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269 +msgctxt "@info:status" +msgid "Unable to slice with the current material as it is incompatible with the selected machine or configuration." +msgstr "選ばれたプリンターまたは選ばれたプリント構成が異なるため進行中の材料にてスライスを完了できません。" + +#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269 +#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:297 +#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:307 +#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:316 +msgctxt "@info:title" +msgid "Unable to slice" +msgstr "スライスできません。" + +#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:296 +#, python-brace-format +msgctxt "@info:status" +msgid "Unable to slice with the current settings. The following settings have errors: {0}" +msgstr "現在の設定でスライスが完了できません。以下の設定にエラーがあります。{0}" + +#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:306 +msgctxt "@info:status" +msgid "Unable to slice because the prime tower or prime position(s) are invalid." +msgstr "プライムタワーまたはプライム位置が無効なためスライスできません。" + +#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315 +msgctxt "@info:status" +msgid "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit." +msgstr "" +"モデルのデータがビルトボリュームに入っていないためスライスできるものがありません。スケールやローテーションにて合うように設" +"定してください。" + +#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:65 +#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:238 +msgctxt "@info:status" +msgid "Processing Layers" +msgstr "レイヤーを処理しています。" + +#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:238 +msgctxt "@info:title" +msgid "Information" +msgstr "インフォメーション" + +#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/__init__.py:14 +msgctxt "@label" +msgid "Per Model Settings" +msgstr "各モデル設定" + +#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/__init__.py:15 +msgctxt "@info:tooltip" +msgid "Configure Per Model Settings" +msgstr "各モデル構成設定" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:165 +#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:475 +msgctxt "@title:tab" +msgid "Recommended" +msgstr "推奨" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167 +#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:480 +msgctxt "@title:tab" +msgid "Custom" +msgstr "カスタム" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:30 /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:36 +msgctxt "@item:inlistbox" +msgid "3MF File" +msgstr "3MF ファイル" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:123 +#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1062 +msgctxt "@label" +msgid "Nozzle" +msgstr "ノズル" + +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:28 +msgctxt "@menuitem" +msgid "Browse plugins" +msgstr "プラグインを見る" + +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:163 +#, python-brace-format +msgctxt "@info:status" +msgid "Failed to get plugin ID from {0}" +msgstr "プラグインIDを{0}取得することに失敗しました。" + +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:164 +msgctxt "@info:tile" +msgid "Warning" +msgstr "警告" + +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:202 +msgctxt "@window:title" +msgid "Plugin browser" +msgstr "プラグインブラウザー" + +#: /home/ruben/Projects/Cura/plugins/SolidView/__init__.py:12 +msgctxt "@item:inmenu" +msgid "Solid view" +msgstr "ソリッドビュー" + +#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:18 +msgctxt "@item:inlistbox" +msgid "G File" +msgstr "Gファイル" + +#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:254 +msgctxt "@info:status" +msgid "Parsing G-code" +msgstr "G-codeを解析" + +#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:256 +#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:370 +msgctxt "@info:title" +msgid "G-code Details" +msgstr "G-codeの詳細" + +#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:368 +msgctxt "@info:generic" +msgid "" +"Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code " +"representation may not be accurate." +msgstr "" +"データファイルを送信する前に、プリンターとプリンターの構成設定にそのG-codeが適応しているか確認してください。G-codeの表示が" +"適切でない場合があります。" + +#: /home/ruben/Projects/Cura/plugins/CuraProfileWriter/__init__.py:14 +#: /home/ruben/Projects/Cura/plugins/CuraProfileReader/__init__.py:14 +msgctxt "@item:inlistbox" +msgid "Cura Profile" +msgstr "Curaプロファイル" + +#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30 +msgctxt "@item:inlistbox" +msgid "3MF file" +msgstr "3MFファイル" + +#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:38 +msgctxt "@item:inlistbox" +msgid "Cura Project 3MF file" +msgstr "Curaが3MF fileを算出します。" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UM2UpgradeSelection.py:20 +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelection.py:18 +msgctxt "@action" +msgid "Select upgrades" +msgstr "アップグレードを選択する" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py:12 +msgctxt "@action" +msgid "Upgrade Firmware" +msgstr "ファームウェアをアップグレード" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py:14 +msgctxt "@action" +msgid "Checkup" +msgstr "チェックアップ" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/BedLevelMachineAction.py:15 +msgctxt "@action" +msgid "Level build plate" +msgstr "ビルドプレートを調整する" + +#: /home/ruben/Projects/Cura/cura/PrintInformation.py:88 +msgctxt "@tooltip" +msgid "Outer Wall" +msgstr "アウターウォール" + +#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89 +msgctxt "@tooltip" +msgid "Inner Walls" +msgstr "インナーウォール" + +#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90 +msgctxt "@tooltip" +msgid "Skin" +msgstr "スキン" + +#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91 +msgctxt "@tooltip" +msgid "Infill" +msgstr "インフィル" + +#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92 +msgctxt "@tooltip" +msgid "Support Infill" +msgstr "サポートイルフィル" + +#: /home/ruben/Projects/Cura/cura/PrintInformation.py:93 +msgctxt "@tooltip" +msgid "Support Interface" +msgstr "サポートインターフェイス" + +#: /home/ruben/Projects/Cura/cura/PrintInformation.py:94 +msgctxt "@tooltip" +msgid "Support" +msgstr "サポート" + +#: /home/ruben/Projects/Cura/cura/PrintInformation.py:95 +msgctxt "@tooltip" +msgid "Skirt" +msgstr "スカート" + +#: /home/ruben/Projects/Cura/cura/PrintInformation.py:96 +msgctxt "@tooltip" +msgid "Travel" +msgstr "移動" + +#: /home/ruben/Projects/Cura/cura/PrintInformation.py:97 +msgctxt "@tooltip" +msgid "Retractions" +msgstr "退却" + +#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98 +msgctxt "@tooltip" +msgid "Other" +msgstr "他" + +#: /home/ruben/Projects/Cura/cura/PrintInformation.py:259 +#, python-brace-format +msgctxt "@label" +msgid "Pre-sliced file {0}" +msgstr "スライス前ファイル {0}" + +#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:463 +msgctxt "@item:material" +msgid "No material loaded" +msgstr "フィラメントがロードされていません。" + +#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:470 +msgctxt "@item:material" +msgid "Unknown material" +msgstr "未確認のフィラメント" + +#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:30 +msgctxt "@info:status" +msgid "Finding new location for objects" +msgstr "造形物のために新しい位置を探索中" + +#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:34 +msgctxt "@info:title" +msgid "Finding Location" +msgstr "位置確認" + +#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:89 /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83 +msgctxt "@info:status" +msgid "Unable to find a location within the build volume for all objects" +msgstr "全ての造形物の造形サイズに対し、適切な位置が確認できません" + +#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:90 +msgctxt "@info:title" +msgid "Can't Find Location" +msgstr "位置を確保できません。" + +#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:431 +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:112 +msgctxt "@title:window" +msgid "File Already Exists" +msgstr "すでに存在するファイルです。" + +#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:432 +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:113 +#, python-brace-format +msgctxt "@label Don't translate the XML tag !" +msgid "The file {0} already exists. Are you sure you want to overwrite it?" +msgstr "{0} は既に存在します。ファイルを上書きしますか? " + +#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:815 +msgctxt "@label" +msgid "Custom" +msgstr "カスタム" + +#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:819 +msgctxt "@label" +msgid "Custom Material" +msgstr "カスタムフィラメント" + +#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:108 +msgctxt "@info:status" +msgid "The selected material is incompatible with the selected machine or configuration." +msgstr "選択されたフィラメントはプリンターとそのプリント構成に適応しておりません。" + +#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:109 +#: /home/ruben/Projects/Cura/cura/Settings/MaterialManager.py:24 +msgctxt "@info:title" +msgid "Incompatible Material" +msgstr "不適合フィラメント" + +#: /home/ruben/Projects/Cura/cura/Settings/MaterialManager.py:24 +msgctxt "@info:status Has a cancel button next to it." +msgid "The selected material diameter causes the material to become incompatible with the current printer." +msgstr "選択したフィラメントの直径が、現在選択中のプリンターに適応していません。" + +#: /home/ruben/Projects/Cura/cura/Settings/MaterialManager.py:25 +msgctxt "@action:button" +msgid "Undo" +msgstr "取り消す" + +#: /home/ruben/Projects/Cura/cura/Settings/MaterialManager.py:25 +msgctxt "@action" +msgid "Undo changing the material diameter." +msgstr "フィラメント直径を変更を取り消す" + +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:143 +#, python-brace-format +msgctxt "@info:status Don't translate the XML tags or !" +msgid "Failed to export profile to {0}: {1}" +msgstr "{0}: {1}にプロファイルを書き出すのに失敗しました。" + +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:150 +#, python-brace-format +msgctxt "@info:status Don't translate the XML tag !" +msgid "Failed to export profile to {0}: Writer plugin reported failure." +msgstr " {0}にプロファイルを書き出すことに失敗しました。:ライタープラグイン失敗の報告" + +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:155 +#, python-brace-format +msgctxt "@info:status Don't translate the XML tag !" +msgid "Exported profile to {0}" +msgstr "{0}にプロファイルを書き出しました。" + +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:156 +msgctxt "@info:title" +msgid "Export Details" +msgstr "詳細を書き出す" + +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:182 +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:204 +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:213 +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:247 +#, python-brace-format +msgctxt "@info:status Don't translate the XML tags or !" +msgid "Failed to import profile from {0}: {1}" +msgstr "{0}: {1}からプロファイルを取り込むことに失敗しました。" + +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:215 +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:251 +#, python-brace-format +msgctxt "@info:status" +msgid "Successfully imported profile {0}" +msgstr "プロファイルの取り込み完了" + +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:254 +#, python-brace-format +msgctxt "@info:status" +msgid "Profile {0} has an unknown file type or is corrupted." +msgstr "プロファイル{0}は不特定なファイルまたは破損があります。" + +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:272 +msgctxt "@label" +msgid "Custom profile" +msgstr "カスタムプロファイル" + +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:283 +msgctxt "@info:status" +msgid "Profile is missing a quality type." +msgstr "プロファイルはクオリティータイプが不足しています。" + +#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313 +#, python-brace-format +msgctxt "@info:status" +msgid "Could not find a quality type {0} for the current configuration." +msgstr "進行中のプリント構成にあったクオリティータイプ{0}が見つかりませんでした。" + +#: /home/ruben/Projects/Cura/cura/BuildVolume.py:100 +msgctxt "@info:status" +msgid "" +"The build volume height has been reduced due to the value of the \"Print Sequence\" setting to prevent the gantry from " +"colliding with printed models." +msgstr "プリントシークエンス設定値により、ガントリーと造形物の衝突を避けるため印刷データの高さを低くしました。" + +#: /home/ruben/Projects/Cura/cura/BuildVolume.py:102 +msgctxt "@info:title" +msgid "Build Volume" +msgstr "造形サイズ" + +#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:34 +msgctxt "@info:status" +msgid "Multiplying and placing objects" +msgstr "造形データを増やす、配置する。" + +#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:35 /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83 +msgctxt "@info:title" +msgid "Placing Object" +msgstr "造形データを配置" + +#: /home/ruben/Projects/Cura/cura/CrashHandler.py:54 +msgctxt "@title:window" +msgid "Crash Report" +msgstr "クラッシュ報告" + +#: /home/ruben/Projects/Cura/cura/CrashHandler.py:79 +msgctxt "@label" +msgid "" +"

A fatal exception has occurred that we could not recover from!

\n" +"

Please use the information below to post a bug report at http://github.com/Ultimaker/Cura/issues

\n" +" " +msgstr "" +"

不可解なエラーが発生しリカバリーできませんでした。

\n" +"

この情報をバグとして報告してください。 http://github.com/Ultimaker/" +"Cura/issues

\n" +" " + +#: /home/ruben/Projects/Cura/cura/CrashHandler.py:112 +msgctxt "@action:button" +msgid "Open Web Page" +msgstr "ウェブページを開く" + +#: /home/ruben/Projects/Cura/cura/CuraApplication.py:251 +msgctxt "@info:progress" +msgid "Loading machines..." +msgstr "プリンターを読み込み中…" + +#: /home/ruben/Projects/Cura/cura/CuraApplication.py:619 +msgctxt "@info:progress" +msgid "Setting up scene..." +msgstr "シーンをセットアップ中…" + +#: /home/ruben/Projects/Cura/cura/CuraApplication.py:661 +msgctxt "@info:progress" +msgid "Loading interface..." +msgstr "インターフェイスを読み込み中…" + +#: /home/ruben/Projects/Cura/cura/CuraApplication.py:824 +#, python-format +msgctxt "" +"@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## " +"mm." +msgid "%(width).1f x %(depth).1f x %(height).1f mm" +msgstr "%(幅).1f x %(奥行き).1f x %(高さ).1f mm" + +#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1295 +#, python-brace-format +msgctxt "@info:status" +msgid "Only one G-code file can be loaded at a time. Skipped importing {0}" +msgstr "一度に一つのG-codeしか読み取れません。取り込みをスキップしました{0}。" + +#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1304 +#, python-brace-format +msgctxt "@info:status" +msgid "Can't open any other file if G-code is loading. Skipped importing {0}" +msgstr "G-codeを読み込み中は他のファイルを開くことができません。取り込みをスキップしました{0}。" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:59 +msgctxt "@title" +msgid "Machine Settings" +msgstr "プリンターの設定" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:77 +msgctxt "@title:tab" +msgid "Printer" +msgstr "プリンター" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:96 +msgctxt "@label" +msgid "Printer Settings" +msgstr "プリンターの設定" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:107 +msgctxt "@label" +msgid "X (Width)" +msgstr "X(幅)" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:108 +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:118 +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:128 +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:234 +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:287 +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:299 +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:390 +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400 +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412 +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:839 +msgctxt "@label" +msgid "mm" +msgstr "mm" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:117 +msgctxt "@label" +msgid "Y (Depth)" +msgstr "Y (奥行き)" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:127 +msgctxt "@label" +msgid "Z (Height)" +msgstr "Z (高さ)" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:139 +msgctxt "@label" +msgid "Build plate shape" +msgstr "ビルドプレート形" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:148 +msgctxt "@option:check" +msgid "Origin at center" +msgstr "センターを出します。" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:156 +msgctxt "@option:check" +msgid "Heated bed" +msgstr "ヒーテッドドベッド" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:167 +msgctxt "@label" +msgid "Gcode flavor" +msgstr "Gcodeフレーバー" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:180 +msgctxt "@label" +msgid "Printhead Settings" +msgstr "プリントヘッド設定" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:190 +msgctxt "@label" +msgid "X min" +msgstr "X分" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:191 +msgctxt "@tooltip" +msgid "" +"Distance from the left of the printhead to the center of the nozzle. Used to prevent colissions between previous prints " +"and the printhead when printing \"One at a Time\"." +msgstr "" +"プリントヘッド左側からノズルの中心までの距離。印刷時に前の造形物とプリントヘッドとの衝突を避けるために “1プリントづつ”印刷" +"を使用。" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:200 +msgctxt "@label" +msgid "Y min" +msgstr "Y分" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:201 +msgctxt "@tooltip" +msgid "" +"Distance from the front of the printhead to the center of the nozzle. Used to prevent colissions between previous prints " +"and the printhead when printing \"One at a Time\"." +msgstr "" +"プリントヘッド前部からノズルの中心までの距離。印刷時に前の造形物とプリントヘッドとの衝突を避けるために “1プリントづつ”印刷" +"を使用。" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:210 +msgctxt "@label" +msgid "X max" +msgstr "最大X" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:211 +msgctxt "@tooltip" +msgid "" +"Distance from the right of the printhead to the center of the nozzle. Used to prevent colissions between previous prints " +"and the printhead when printing \"One at a Time\"." +msgstr "" +"プリントヘッド右側からノズルの中心までの距離。印刷時に前の造形物とプリントヘッドとの衝突を避けるために “1プリントづつ”印刷" +"を使用。" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:220 +msgctxt "@label" +msgid "Y max" +msgstr "最大Y" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:221 +msgctxt "@tooltip" +msgid "" +"Distance from the rear of the printhead to the center of the nozzle. Used to prevent colissions between previous prints " +"and the printhead when printing \"One at a Time\"." +msgstr "" +"プリントヘッド後部からノズルの中心までの距離。印刷時に前の造形物とプリントヘッドとの衝突を避けるために “1プリントづつ”印刷" +"を使用。" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:233 +msgctxt "@label" +msgid "Gantry height" +msgstr "ガントリーの高さ" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235 +msgctxt "@tooltip" +msgid "" +"The height difference between the tip of the nozzle and the gantry system (X and Y axes). Used to prevent collisions " +"between previous prints and the gantry when printing \"One at a Time\"." +msgstr "" +"(X 軸及びY軸)ノズルの先端とガントリーシステムの高さに相違があります。印刷時に前の造形物とプリントヘッドとの衝突を避けるた" +"めに “1プリントづつ”印刷を使用。" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:254 +msgctxt "@label" +msgid "Number of Extruders" +msgstr "エクストルーダーの数" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:288 +msgctxt "@tooltip" +msgid "" +"The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or " +"the profile." +msgstr "プリンターに対応したフィラメントの直径。正確な直径はフィラメント及びまたはプロファイルに変動します。" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:290 +msgctxt "@label" +msgid "Material diameter" +msgstr "フィラメント直径" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:298 +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:389 +msgctxt "@label" +msgid "Nozzle size" +msgstr "ノズルサイズ" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:316 +msgctxt "@label" +msgid "Start Gcode" +msgstr "G-codeをスタートします。" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:326 +msgctxt "@tooltip" +msgid "Gcode commands to be executed at the very start." +msgstr "G-codeが最初に起動するようにします。" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:335 +msgctxt "@label" +msgid "End Gcode" +msgstr "G-codeを終了" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:345 +msgctxt "@tooltip" +msgid "Gcode commands to be executed at the very end." +msgstr "G-codeが最後にに起動するようにします。" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:377 +msgctxt "@label" +msgid "Nozzle Settings" +msgstr "ノズル設定" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:399 +msgctxt "@label" +msgid "Nozzle offset X" +msgstr "ノズルオフセットX" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:411 +msgctxt "@label" +msgid "Nozzle offset Y" +msgstr "ノズルオフセットY" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:432 +msgctxt "@label" +msgid "Extruder Start Gcode" +msgstr "エクストルーダーがGcodeを開始します。" + +#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:450 +msgctxt "@label" +msgid "Extruder End Gcode" +msgstr "エクストルーダーがGcodeを終了します。" + +#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:18 +msgctxt "@label" +msgid "Changelog" +msgstr "Changelogの表示" + +#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37 +#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:107 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:55 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/UM3InfoComponents.qml:55 +#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:446 +#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:357 +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:80 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:125 +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:146 /home/ruben/Projects/Cura/resources/qml/EngineLog.qml:38 +msgctxt "@action:button" +msgid "Close" +msgstr "やめる" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:22 +msgctxt "@title:window" +msgid "Firmware Update" +msgstr "ファームウェアアップデート" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:42 +msgctxt "@label" +msgid "Firmware update completed." +msgstr "ファームウェアアップデート完了" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:47 +msgctxt "@label" +msgid "Starting firmware update, this may take a while." +msgstr "ファームウェアアップデートを開始します、時間が少しかかるかもしれません。" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:52 +msgctxt "@label" +msgid "Updating firmware." +msgstr "ファームウェアアップデート中" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:61 +msgctxt "@label" +msgid "Firmware update failed due to an unknown error." +msgstr "不特定なエラーの発生によりファームウェアアップデート失敗しました。" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:64 +msgctxt "@label" +msgid "Firmware update failed due to an communication error." +msgstr "コミュニケーションエラーによりファームウェアアップデート失敗しました。" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:67 +msgctxt "@label" +msgid "Firmware update failed due to an input/output error." +msgstr "インプット/アウトプットエラーによりファームウェアアップデート失敗しました。" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:70 +msgctxt "@label" +msgid "Firmware update failed due to missing firmware." +msgstr "ファームウェアが見つからず、ファームウェアアップデート失敗しました。" + +#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:73 +msgctxt "@label" +msgid "Unknown error code: %1" +msgstr "不特定エラーコード: %1" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:55 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:55 +msgctxt "@title:window" +msgid "Connect to Networked Printer" +msgstr "ネットワーク上で繋がったプリンターに接続" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:65 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:65 +msgctxt "@label" +msgid "" +"To print directly to your printer over the network, please make sure your printer is connected to the network using a " +"network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can " +"still use a USB drive to transfer g-code files to your printer.\n" +"\n" +"Select your printer from the list below:" +msgstr "" +"ネットワーク接続にて直接プリントするためには、必ずケーブルまたはWifiネットワークにて繋がっていることを確認してください。" +"Curaをプリンターに接続していない場合でも、USBメモリを使って直接プリンターにg-codeファイルをトランスファーできます。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:75 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:75 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:44 +msgctxt "@action:button" +msgid "Add" +msgstr "追加" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:85 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:85 +msgctxt "@action:button" +msgid "Edit" +msgstr "編集" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:96 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:50 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:95 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:190 +msgctxt "@action:button" +msgid "Remove" +msgstr "取り除く" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:104 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:104 +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:35 +msgctxt "@action:button" +msgid "Refresh" +msgstr "更新" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:196 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:196 +msgctxt "@label" +msgid "If your printer is not listed, read the network printing troubleshooting guide" +msgstr "" +"お持ちのプリンターがリストにない場合、ネットワークプリンティングトラブルシューティングガイドを読んでくだ" +"さい。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:223 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:223 +msgctxt "@label" +msgid "Type" +msgstr "タイプ" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:235 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:235 +msgctxt "@label" +msgid "Ultimaker 3" +msgstr "Ultimaker3" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:238 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:238 +msgctxt "@label" +msgid "Ultimaker 3 Extended" +msgstr "Ultimaker 3 Extended" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:241 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:241 +msgctxt "@label" +msgid "Unknown" +msgstr "不明" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:254 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:254 +msgctxt "@label" +msgid "Firmware version" +msgstr "ファームウェアバージョン" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:266 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:266 +msgctxt "@label" +msgid "Address" +msgstr "アドレス" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:280 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:297 +msgctxt "@label" +msgid "The printer at this address has not yet responded." +msgstr "このアドレスのプリンターは応答していません。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:285 +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:38 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:302 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/UM3InfoComponents.qml:38 +msgctxt "@action:button" +msgid "Connect" +msgstr "接続" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:299 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:316 +msgctxt "@title:window" +msgid "Printer Address" +msgstr "プリンターアドレス" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:329 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:346 +msgctxt "@alabel" +msgid "Enter the IP address or hostname of your printer on the network." +msgstr "ネットワーク内のプリンターのIPアドレスまたはホストネームを入力してください。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:359 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:376 +msgctxt "@action:button" +msgid "Ok" +msgstr "OK" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:37 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/UM3InfoComponents.qml:37 +msgctxt "@info:tooltip" +msgid "Connect to a printer" +msgstr "プリンターにつなぐ" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:116 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/UM3InfoComponents.qml:116 +msgctxt "@info:tooltip" +msgid "Load the configuration of the printer into Cura" +msgstr "プリンターの構成をCuraに取り入れる。" + +#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:117 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/UM3InfoComponents.qml:117 +msgctxt "@action:button" +msgid "Activate Configuration" +msgstr "プリント構成をアクティベートする" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:284 +msgctxt "@label" +msgid "This printer is not set up to host a group of connected Ultimaker 3 printers" +msgstr "このプリンターは、繋がっているUltimaker3プリンターをホストするために設定されていません。" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:287 +msgctxt "@label" +msgid "This printer is the host for a group of %1 connected Ultimaker 3 printers" +msgstr "このプリンターは繋がっているUltimaker3プリンターのグループのホストです。" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/PrintWindow.qml:24 +msgctxt "@title:window" +msgid "Print over network" +msgstr "ネットワーク上のプリント" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/PrintWindow.qml:92 +msgctxt "@action:button" +msgid "Print" +msgstr "プリント" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterMonitorItem.qml:50 +msgctxt "@label: arg 1 is group name" +msgid "%1 is not set up to host a group of connected Ultimaker 3 printers" +msgstr "%1は、繋がっているUltimaker3プリンターのグループをホストするために設定されていません。" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterMonitorItem.qml:311 +msgctxt "@label:status" +msgid "Printing" +msgstr "プリント中" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterMonitorItem.qml:315 +msgctxt "@label:status" +msgid "Reserved" +msgstr "予約済み" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterMonitorItem.qml:317 +msgctxt "@label:status" +msgid "Finished" +msgstr "終了" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterMonitorItem.qml:320 +msgctxt "@label:status" +msgid "Preparing" +msgstr "準備中" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterMonitorItem.qml:327 +msgctxt "@label:status" +msgid "Available" +msgstr "利用可能" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterMonitorItem.qml:367 +msgctxt "@label" +msgid "Completed on: " +msgstr "完了:" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterMonitorItem.qml:371 +msgctxt "@label" +msgid "Clear build plate" +msgstr "ビルドプレートをクリアにする" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterMonitorItem.qml:375 +msgctxt "@label" +msgid "Preparing to print" +msgstr "プリントの準備をする" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterMonitorItem.qml:379 +msgctxt "@label" +msgid "Not accepting print jobs" +msgstr "プリントデータを確認できない" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/OpenPanelButton.qml:14 +msgctxt "@info:tooltip" +msgid "Opens the print jobs page with your default web browser." +msgstr "デフォルトのウェブブラウザーにてプリントジョブを開く" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/OpenPanelButton.qml:15 +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterControlItem.qml:154 +msgctxt "@action:button" +msgid "View print jobs" +msgstr "プリントジョブを見る" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterControlItem.qml:36 +msgctxt "@label" +msgid "PRINTER GROUP" +msgstr "プリンターグループ" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterControlItem.qml:69 +msgctxt "@title" +msgid "Print jobs" +msgstr "プリントジョブ" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterControlItem.qml:100 +msgctxt "@label" +msgid "Printing" +msgstr "プリント中" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterControlItem.qml:118 +msgctxt "@label" +msgid "Queued" +msgstr "順番を待つ" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterControlItem.qml:135 +msgctxt "@label" +msgid "Waiting for configuration change" +msgstr "プリント構成の変更を待っている" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterControlItem.qml:194 +msgctxt "@label:title" +msgid "Printers" +msgstr "複数のプリンター" + +#: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/ClusterControlItem.qml:247 +msgctxt "@action:button" +msgid "View printers" +msgstr "複数のプリンターをみる" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:20 +msgctxt "@title:window" +msgid "Cura SolidWorks Plugin Configuration" +msgstr "Curaソリッドワークスプラグインコンフィグレーション" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:44 +msgctxt "@action:label" +msgid "Default quality of the exported STL:" +msgstr "書き出されたSTLのクオリティーデフォルト" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:79 +msgctxt "@option:curaSolidworksStlQuality" +msgid "Always ask" +msgstr "毎回確認" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:80 +msgctxt "@option:curaSolidworksStlQuality" +msgid "Always use Fine quality" +msgstr "常にファインクオリティーを使用する" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:81 +msgctxt "@option:curaSolidworksStlQuality" +msgid "Always use Coarse quality" +msgstr "常に粗めのクオリティーを使用する" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:92 +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:88 +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181 +msgctxt "@action:button" +msgid "OK" +msgstr "OK" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:20 +msgctxt "@title:window" +msgid "Import SolidWorks File as STL..." +msgstr "ソリッドワークスのファイルをSTLとして取り込む" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:43 +msgctxt "@info:tooltip" +msgid "Quality of the Exported STL" +msgstr "書き出されたSTLのクオリティー" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:48 +msgctxt "@action:label" +msgid "Quality" +msgstr "クオリティー" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:62 +msgctxt "@option:curaSolidworksStlQuality" +msgid "Coarse" +msgstr "粗い" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:63 +msgctxt "@option:curaSolidworksStlQuality" +msgid "Fine" +msgstr "ファイン" + +#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:78 +#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:81 +msgctxt "@text:window" +msgid "Remember my choice" +msgstr "選択を記憶させる" + +#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:18 +msgctxt "@title:window" +msgid "Post Processing Plugin" +msgstr "プラグイン処理後" + +#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:49 +msgctxt "@label" +msgid "Post Processing Scripts" +msgstr "スクリプトの処理後" + +#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:218 +msgctxt "@action" +msgid "Add a script" +msgstr "スクリプトを加える" + +#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:264 +msgctxt "@label" +msgid "Settings" +msgstr "設定" + +#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:456 +msgctxt "@info:tooltip" +msgid "Change active post-processing scripts" +msgstr "処理したスクリプトを変更する" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:76 +msgctxt "@label" +msgid "Color scheme" +msgstr "カラースキーム" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:91 +msgctxt "@label:listbox" +msgid "Material Color" +msgstr "フィラメントの色" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:95 +msgctxt "@label:listbox" +msgid "Line Type" +msgstr "ラインタイプ" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:135 +msgctxt "@label" +msgid "Compatibility Mode" +msgstr "コンパティビリティモード" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:216 +msgctxt "@label" +msgid "Show Travels" +msgstr "移動を表示する" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:222 +msgctxt "@label" +msgid "Show Helpers" +msgstr "ヘルプを表示する" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:228 +msgctxt "@label" +msgid "Show Shell" +msgstr "シェルを表示する" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:234 +msgctxt "@label" +msgid "Show Infill" +msgstr "インフィルを表示する" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:283 +msgctxt "@label" +msgid "Only Show Top Layers" +msgstr "トップのレイヤーを表示する" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:292 +msgctxt "@label" +msgid "Show 5 Detailed Layers On Top" +msgstr "トップの5レイヤーの詳細を表示する" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:303 +msgctxt "@label" +msgid "Top / Bottom" +msgstr "トップ/ボトム" + +#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:307 +msgctxt "@label" +msgid "Inner Wall" +msgstr "インナーウォール" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:19 +msgctxt "@title:window" +msgid "Convert Image..." +msgstr "画像を変換する…" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:33 +msgctxt "@info:tooltip" +msgid "The maximum distance of each pixel from \"Base.\"" +msgstr "“ベース”から各ピクセルへの最大距離" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:38 +msgctxt "@action:label" +msgid "Height (mm)" +msgstr "高さ(mm)" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:56 +msgctxt "@info:tooltip" +msgid "The base height from the build plate in millimeters." +msgstr "ミリメートルでビルドプレートからベースの高さ" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:61 +msgctxt "@action:label" +msgid "Base (mm)" +msgstr "ベース(mm)" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:79 +msgctxt "@info:tooltip" +msgid "The width in millimeters on the build plate." +msgstr "ビルドプレート上の幅ミリメートル" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:84 +msgctxt "@action:label" +msgid "Width (mm)" +msgstr "幅(mm)" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:103 +msgctxt "@info:tooltip" +msgid "The depth in millimeters on the build plate" +msgstr "ビルドプレート上の奥行きミリメートル" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:108 +msgctxt "@action:label" +msgid "Depth (mm)" +msgstr "深さ(mm)" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:126 +msgctxt "@info:tooltip" +msgid "" +"By default, white pixels represent high points on the mesh and black pixels represent low points on the mesh. Change this " +"option to reverse the behavior such that black pixels represent high points on the mesh and white pixels represent low " +"points on the mesh." +msgstr "" +"デフォルトで、白ピクセルはメッシュの高いポイントを表し、黒ピクセルはメッシュの低いポイントを表します。このオプションをリ" +"バースするために変更し、黒ピクセルがメッシュの高いポイントを表し、白ピクセルがメッシュの低いポイントを表すようにする。" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:139 +msgctxt "@item:inlistbox" +msgid "Lighter is higher" +msgstr "薄いほうを高く" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:139 +msgctxt "@item:inlistbox" +msgid "Darker is higher" +msgstr "暗いほうを高く" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:149 +msgctxt "@info:tooltip" +msgid "The amount of smoothing to apply to the image." +msgstr "画像に適応したスムージング量" + +#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:154 +msgctxt "@action:label" +msgid "Smoothing" +msgstr "スムージング" + +#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:208 +msgctxt "@action:button" +msgid "Select settings" +msgstr "設定を選択する" + +#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:248 +msgctxt "@title:window" +msgid "Select Settings to Customize for this model" +msgstr "このモデルをカスタマイズする設定を選択する" + +#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:272 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:91 +msgctxt "@label:textbox" +msgid "Filter..." +msgstr "フィルター…" + +#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:296 +msgctxt "@label:checkbox" +msgid "Show all" +msgstr "すべて表示する" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:13 +msgctxt "@title:window" +msgid "Open Project" +msgstr "プロジェクトを開く" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:54 +msgctxt "@action:ComboBox option" +msgid "Update existing" +msgstr "existingをアップデートする" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:55 +msgctxt "@action:ComboBox option" +msgid "Create new" +msgstr "新しいものを作成する" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:66 +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:70 +msgctxt "@action:title" +msgid "Summary - Cura Project" +msgstr "サマリーCuraプロジェクト" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:88 +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:88 +msgctxt "@action:label" +msgid "Printer settings" +msgstr "プリンターの設定" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:104 +msgctxt "@info:tooltip" +msgid "How should the conflict in the machine be resolved?" +msgstr "このプリンターの問題をどのように解決すればいいか?" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:124 +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:97 +msgctxt "@action:label" +msgid "Type" +msgstr "タイプ" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:140 +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:197 +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:289 +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:112 +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:188 +msgctxt "@action:label" +msgid "Name" +msgstr "ネーム" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:161 +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:164 +msgctxt "@action:label" +msgid "Profile settings" +msgstr "プロファイル設定" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:177 +msgctxt "@info:tooltip" +msgid "How should the conflict in the profile be resolved?" +msgstr "このプロファイルの問題をどのように解決すればいいか?" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:212 +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:172 +msgctxt "@action:label" +msgid "Not in profile" +msgstr "プロファイル内にない" + +# Can’t edit the Japanese text +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:217 +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:177 +#, fuzzy +msgctxt "@action:label" +msgid "%1 override" +msgid_plural "%1 overrides" +msgstr[0] "プロファイルを無効にする" +msgstr[1] "" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:228 +msgctxt "@action:label" +msgid "Derivative from" +msgstr "次から引き出す" + +# can’t inset the japanese text +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:233 +msgctxt "@action:label" +msgid "%1, %2 override" +msgid_plural "%1, %2 overrides" +msgstr[0] "" +msgstr[1] "" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:249 +msgctxt "@action:label" +msgid "Material settings" +msgstr "フィラメント設定" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:265 +msgctxt "@info:tooltip" +msgid "How should the conflict in the material be resolved?" +msgstr "このフィラメントの問題をどのように解決すればいいか?" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:308 +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:207 +msgctxt "@action:label" +msgid "Setting visibility" +msgstr "視野設定" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:317 +msgctxt "@action:label" +msgid "Mode" +msgstr "モード" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:332 +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:216 +msgctxt "@action:label" +msgid "Visible settings:" +msgstr "ビジブル設定:" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:337 +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:221 +msgctxt "@action:label" +msgid "%1 out of %2" +msgstr "%2のうち%1" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:363 +msgctxt "@action:warning" +msgid "Loading a project will clear all models on the buildplate" +msgstr "プロジェクトを入れることでビルド上のモデルがすべて消滅します。" + +#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:381 +msgctxt "@action:button" +msgid "Open" +msgstr "開く" + +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:11 +msgctxt "@title:window" +msgid "Find & Update plugins" +msgstr "探す&アップデートプラグイン" + +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:27 +msgctxt "@label" +msgid "Here you can find a list of Third Party plugins." +msgstr "サードパーティのプラグインのリストをここで見つけることができます。" + +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:145 +msgctxt "@action:button" +msgid "Upgrade" +msgstr "アップグレード" + +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:147 +msgctxt "@action:button" +msgid "Installed" +msgstr "インストールした" + +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:199 +msgctxt "@title:window" +msgid "Plugin License Agreement" +msgstr "プラグインライセンス同意書" + +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:220 +msgctxt "@label" +msgid "" +" plugin contains a license.\n" +"You need to accept this license to install this plugin.\n" +"Do you agree with the terms below?" +msgstr "" +"プラグインがライセンスを保持しています。\n" +"このライセンスを承認しプラグインをインストールしてください。\n" +"下記項目に賛成しますか?" + +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:242 +msgctxt "@action:button" +msgid "Accept" +msgstr "承認する" + +#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:253 +msgctxt "@action:button" +msgid "Decline" +msgstr "拒否する" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml:25 +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml:25 +msgctxt "@title" +msgid "Select Printer Upgrades" +msgstr "プリンターアップグレードを選択する" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml:37 +msgctxt "@label" +msgid "Please select any upgrades made to this Ultimaker 2." +msgstr "このUltimaker2に施したアップグレードを選択してください。" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml:45 +msgctxt "@label" +msgid "Olsson Block" +msgstr "Olsson Block" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/BedLevelMachineAction.qml:27 +msgctxt "@title" +msgid "Build Plate Leveling" +msgstr "ビルドプレートのレベリング" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/BedLevelMachineAction.qml:38 +msgctxt "@label" +msgid "" +"To make sure your prints will come out great, you can now adjust your buildplate. When you click 'Move to Next Position' " +"the nozzle will move to the different positions that can be adjusted." +msgstr "プリントの成功率を上げるために、ビルドプレートを今調整できます。’次のポジションに移動’をクリックすると" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/BedLevelMachineAction.qml:47 +msgctxt "@label" +msgid "" +"For every position; insert a piece of paper under the nozzle and adjust the print build plate height. The print build " +"plate height is right when the paper is slightly gripped by the tip of the nozzle." +msgstr "すべてのポジションに;" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/BedLevelMachineAction.qml:62 +msgctxt "@action:button" +msgid "Start Build Plate Leveling" +msgstr "ビルドプレートのレベリングを開始する" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/BedLevelMachineAction.qml:74 +msgctxt "@action:button" +msgid "Move to Next Position" +msgstr "次のポジションに移動" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:27 +msgctxt "@title" +msgid "Upgrade Firmware" +msgstr "ファームウェアをアップグレード" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:38 +msgctxt "@label" +msgid "" +"Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates " +"the temperature and ultimately makes your printer work." +msgstr "" +"ファームウェアとは直接お持ちの3Dプリンターを動かすソフトウェアです。このファームウェアはステップモーターを操作し、温度を管" +"理し、プリンターとして成すべき点を補います。" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:48 +msgctxt "@label" +msgid "The firmware shipping with new printers works, but new versions tend to have more features and improvements." +msgstr "" +"配達時のファームウェアで動かすことはできますが、新しいバージョンの方がより改善され、便利なフィーチャーがついてきます。" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:62 +msgctxt "@action:button" +msgid "Automatically upgrade Firmware" +msgstr "自動でファームウェアをアップグレード" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:72 +msgctxt "@action:button" +msgid "Upload custom Firmware" +msgstr "カスタムファームウェアをアップロードする" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:83 +msgctxt "@title:window" +msgid "Select custom firmware" +msgstr "カスタムファームウェアを選択する。" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml:37 +msgctxt "@label" +msgid "Please select any upgrades made to this Ultimaker Original" +msgstr "このUltimaker Originalに施されたアップグレートを選択する" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml:45 +msgctxt "@label" +msgid "Heated Build Plate (official kit or self-built)" +msgstr "ヒーティッドビルドプレート(オフィシャルキットまたはセルフビルド)" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:27 +msgctxt "@title" +msgid "Check Printer" +msgstr "プリンターチェック" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:39 +msgctxt "@label" +msgid "" +"It's a good idea to do a few sanity checks on your Ultimaker. You can skip this step if you know your machine is " +"functional" +msgstr "" +"お持ちのUltimkaerにてサニティーチェックを数回行うことは推奨します。もしプリンター機能に問題ない場合はこの項目をスキップし" +"てください。" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:53 +msgctxt "@action:button" +msgid "Start Printer Check" +msgstr "プリンターチェックを開始する" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:80 +msgctxt "@label" +msgid "Connection: " +msgstr "コネクション:" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:89 +msgctxt "@info:status" +msgid "Connected" +msgstr "接続済" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:89 +msgctxt "@info:status" +msgid "Not connected" +msgstr "プリンターにつながっていません。" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:99 +#, fuzzy +msgctxt "@label" +msgid "Min endstop X: " +msgstr "エンドストップ X:" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:109 +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:130 +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:151 +msgctxt "@info:status" +msgid "Works" +msgstr "作品" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:109 +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:130 +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:151 +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:173 +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:234 +msgctxt "@info:status" +msgid "Not checked" +msgstr "チェックされていません。" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:120 +#, fuzzy +msgctxt "@label" +msgid "Min endstop Y: " +msgstr "エンドストップ Y:" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:141 +#, fuzzy +msgctxt "@label" +msgid "Min endstop Z: " +msgstr "エンドストップ Z:" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:163 +msgctxt "@label" +msgid "Nozzle temperature check: " +msgstr "ノズル温度チェック:" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:187 +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:248 +msgctxt "@action:button" +msgid "Stop Heating" +msgstr "ヒーティングストップ" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:187 +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:248 +msgctxt "@action:button" +msgid "Start Heating" +msgstr "ヒーティング開始" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:223 +msgctxt "@label" +msgid "Build plate temperature check:" +msgstr "ビルドプレートの温度チェック:" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:234 +msgctxt "@info:status" +msgid "Checked" +msgstr "チェック済" + +#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:284 +msgctxt "@label" +msgid "Everything is in order! You're done with your CheckUp." +msgstr "すべてに異常はありません。チェックアップを終了しました。" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:88 +msgctxt "@label:MonitorStatus" +msgid "Not connected to a printer" +msgstr "プリンターにつながっていません。" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:90 +msgctxt "@label:MonitorStatus" +msgid "Printer does not accept commands" +msgstr "今プリンタはコマンドを処理できません。" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:96 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:196 +msgctxt "@label:MonitorStatus" +msgid "In maintenance. Please check the printer" +msgstr "メンテナンス。プリンターをチェックしてください。" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:101 +msgctxt "@label:MonitorStatus" +msgid "Lost connection with the printer" +msgstr "プリンターへの接続が切断されました。" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:103 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:186 +msgctxt "@label:MonitorStatus" +msgid "Printing..." +msgstr "プリント中" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:106 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:188 +msgctxt "@label:MonitorStatus" +msgid "Paused" +msgstr "一時停止しました" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:109 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:190 +msgctxt "@label:MonitorStatus" +msgid "Preparing..." +msgstr "準備中" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:111 +msgctxt "@label:MonitorStatus" +msgid "Please remove the print" +msgstr "造形物を取り出してください。" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:237 +msgctxt "@label:" +msgid "Resume" +msgstr "再開" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241 +msgctxt "@label:" +msgid "Pause" +msgstr "一時停止" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:270 +msgctxt "@label:" +msgid "Abort Print" +msgstr "プリント中止" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:280 +msgctxt "@window:title" +msgid "Abort print" +msgstr "プリント中止" + +#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:282 +msgctxt "@label" +msgid "Are you sure you want to abort the print?" +msgstr "本当にプリントを中止してもいいですか。" + +#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:15 +msgctxt "@title:window" +msgid "Discard or Keep changes" +msgstr "変更を取り消すか保存するか" + +#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:57 +msgctxt "@text:window" +msgid "" +"You have customized some profile settings.\n" +"Would you like to keep or discard those settings?" +msgstr "プロファイル設定をカスタマイズしました。この設定をキープしますか、キャンセルしますか。" + +#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:110 +msgctxt "@title:column" +msgid "Profile settings" +msgstr "プロファイル設定" + +#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:117 +msgctxt "@title:column" +msgid "Default" +msgstr "デフォルト" + +#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:124 +msgctxt "@title:column" +msgid "Customized" +msgstr "カスタマイズ" + +#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:593 +msgctxt "@option:discardOrKeep" +msgid "Always ask me this" +msgstr "毎回確認する" + +#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:594 +msgctxt "@option:discardOrKeep" +msgid "Discard and never ask again" +msgstr "取り消し、再度確認しない。" + +#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595 +msgctxt "@option:discardOrKeep" +msgid "Keep and never ask again" +msgstr "キープし、再度確認しない。" + +#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:196 +msgctxt "@action:button" +msgid "Discard" +msgstr "取り消す" + +#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:209 +msgctxt "@action:button" +msgid "Keep" +msgstr "キープする" + +#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:222 +msgctxt "@action:button" +msgid "Create New Profile" +msgstr "新しいプロファイルを作る" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:44 +msgctxt "@title" +msgid "Information" +msgstr "インフォメーション" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:68 +msgctxt "@label" +msgid "Display Name" +msgstr "ディスプレイ名" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:78 +msgctxt "@label" +msgid "Brand" +msgstr "ブランド" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:92 +msgctxt "@label" +msgid "Material Type" +msgstr "フィラメントタイプ" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:105 +msgctxt "@label" +msgid "Color" +msgstr "色" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:139 +msgctxt "@label" +msgid "Properties" +msgstr "プロパティ" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141 +msgctxt "@label" +msgid "Density" +msgstr "密度" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:156 +msgctxt "@label" +msgid "Diameter" +msgstr "直径" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:185 +msgctxt "@label" +msgid "Filament Cost" +msgstr "フィラメントコスト" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201 +msgctxt "@label" +msgid "Filament weight" +msgstr "フィラメントの重さ" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218 +msgctxt "@label" +msgid "Filament length" +msgstr "フィラメントの長さ" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:227 +msgctxt "@label" +msgid "Cost per Meter" +msgstr "毎メーターコスト" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:241 +msgctxt "@label" +msgid "This material is linked to %1 and shares some of its properties." +msgstr "このフィラメントは %1にリンクすプロパティーを共有する。" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:248 +msgctxt "@label" +msgid "Unlink Material" +msgstr "フィラメントをリンクを外す" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:259 +msgctxt "@label" +msgid "Description" +msgstr "記述" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:272 +msgctxt "@label" +msgid "Adhesion Information" +msgstr "接着のインフォメーション" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:298 +msgctxt "@label" +msgid "Print settings" +msgstr "プリント設定" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:14 +msgctxt "@title:tab" +msgid "Setting Visibility" +msgstr "視野設定" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:44 +msgctxt "@label:textbox" +msgid "Check all" +msgstr "すべて確認する" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfileTab.qml:40 +msgctxt "@info:status" +msgid "Calculated" +msgstr "計算された" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfileTab.qml:53 +msgctxt "@title:column" +msgid "Setting" +msgstr "設定" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfileTab.qml:60 +msgctxt "@title:column" +msgid "Profile" +msgstr "プロファイル" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfileTab.qml:67 +msgctxt "@title:column" +msgid "Current" +msgstr "現在" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfileTab.qml:75 +msgctxt "@title:column" +msgid "Unit" +msgstr "ユニット" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:14 +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:436 +msgctxt "@title:tab" +msgid "General" +msgstr "一般" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:128 +msgctxt "@label" +msgid "Interface" +msgstr "インターフェイス" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:139 +msgctxt "@label" +msgid "Language:" +msgstr "言語:" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:205 +msgctxt "@label" +msgid "Currency:" +msgstr "通貨:" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:219 +msgctxt "@label" +msgid "Theme:" +msgstr "テーマ:" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:279 +msgctxt "@label" +msgid "You will need to restart the application for these changes to have effect." +msgstr "それらの変更を有効にするためにはアプリケーションを再起動しなけらばなりません。" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:296 +msgctxt "@info:tooltip" +msgid "Slice automatically when changing settings." +msgstr "セッティングを変更すると自動にスライスします。" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:304 +msgctxt "@option:check" +msgid "Slice automatically" +msgstr "自動的にスライスする" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:318 +msgctxt "@label" +msgid "Viewport behavior" +msgstr "ビューポイント機能" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:326 +msgctxt "@info:tooltip" +msgid "Highlight unsupported areas of the model in red. Without support these areas will not print properly." +msgstr "赤でサポートができないエリアをハイライトしてください。サポートがない場合、正確にプリントができない場合があります。" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:335 +msgctxt "@option:check" +msgid "Display overhang" +msgstr "ディスプレイオーバーハング" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:342 +msgctxt "@info:tooltip" +msgid "Moves the camera so the model is in the center of the view when a model is selected" +msgstr "モデルの選択時にモデルがカメラの中心に見えるようにカメラを移動する" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:347 +msgctxt "@action:button" +msgid "Center camera when item is selected" +msgstr "アイテムを選択するとカメラが中心にきます" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:356 +msgctxt "@info:tooltip" +msgid "Should the default zoom behavior of cura be inverted?" +msgstr "Curaのデフォルトのズーム機能は変更できるべきか?" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:361 +msgctxt "@action:button" +msgid "Invert the direction of camera zoom." +msgstr "カメラのズーム方向を反転する" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:370 +msgctxt "@info:tooltip" +msgid "Should zooming move in the direction of the mouse?" +msgstr "ズームはマウスの方向に動くべきか?" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:375 +msgctxt "@action:button" +msgid "Zoom toward mouse direction" +msgstr "マウスの方向にズームする" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384 +msgctxt "@info:tooltip" +msgid "Should models on the platform be moved so that they no longer intersect?" +msgstr "交差を避けるためにプラットホーム上のモデルを移動するべきですか?" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389 +msgctxt "@option:check" +msgid "Ensure models are kept apart" +msgstr "モデルの距離が離れているように確認する" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:397 +msgctxt "@info:tooltip" +msgid "Should models on the platform be moved down to touch the build plate?" +msgstr "プラットホーム上のモデルはブルドプレートに触れるように下げるべきか?" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:402 +msgctxt "@option:check" +msgid "Automatically drop models to the build plate" +msgstr "自動的にモデルをビルドプレートに落とす" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:414 +msgctxt "@info:tooltip" +msgid "Show caution message in gcode reader." +msgstr "gcodeリーダーに注意メッセージを表示する" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:423 +msgctxt "@option:check" +msgid "Caution message in gcode reader" +msgstr "gcodeリーダーの注意メッセージ" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430 +msgctxt "@info:tooltip" +msgid "Should layer be forced into compatibility mode?" +msgstr "レイヤーはコンパティビリティモードに強制されるべきか?" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:435 +msgctxt "@option:check" +msgid "Force layer view compatibility mode (restart required)" +msgstr "レイヤービューコンパティビリティモードを強制する。(再起動が必要)" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:451 +msgctxt "@label" +msgid "Opening and saving files" +msgstr "ファイルを開くまた保存" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:457 +msgctxt "@info:tooltip" +msgid "Should models be scaled to the build volume if they are too large?" +msgstr "モデルがビルドボリュームに対して大きすぎる場合はスケールされるべきか?" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:462 +msgctxt "@option:check" +msgid "Scale large models" +msgstr "大きなモデルをスケールする" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:471 +msgctxt "@info:tooltip" +msgid "" +"An model may appear extremely small if its unit is for example in meters rather than millimeters. Should these models be " +"scaled up?" +msgstr "" +"ユニット値がミリメートルではなくメートルの場合、モデルが極端に小さく現れる場合があります。モデルはスケールアップされるべき" +"ですか?" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:476 +msgctxt "@option:check" +msgid "Scale extremely small models" +msgstr "極端に小さなモデルをスケールアップする" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:485 +msgctxt "@info:tooltip" +msgid "Should a prefix based on the printer name be added to the print job name automatically?" +msgstr "プリンター名の敬称はプリントジョブの名前に自動的に加えられるべきか?" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490 +msgctxt "@option:check" +msgid "Add machine prefix to job name" +msgstr "プリンターの敬称をジョブネームに加える" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:499 +msgctxt "@info:tooltip" +msgid "Should a summary be shown when saving a project file?" +msgstr "プロジェクトファイルを保存時にサマリーを表示するべきか?" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:503 +msgctxt "@option:check" +msgid "Show summary dialog when saving project" +msgstr "プロジェクトを保存時にダイアログサマリーを表示する" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512 +msgctxt "@info:tooltip" +msgid "Default behavior when opening a project file" +msgstr "プロジェクトファイルを開く際のデフォルト機能" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520 +msgctxt "@window:text" +msgid "Default behavior when opening a project file: " +msgstr "プロジェクトファイル開く際のデフォルト機能:" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:533 +msgctxt "@option:openProject" +msgid "Always ask" +msgstr "いつもお尋ねください。" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:534 +msgctxt "@option:openProject" +msgid "Always open as a project" +msgstr "常にプロジェクトとして開く" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535 +msgctxt "@option:openProject" +msgid "Always import models" +msgstr "常にモデルを取り込む" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571 +msgctxt "@info:tooltip" +msgid "" +"When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want " +"to keep your modifications or not, or you can choose a default behaviour and never show that dialog again." +msgstr "" +"プロファイル内を変更し異なるプロファイルにしました、どこの変更点を保持、破棄したいのダイアログが表示されます、また何度もダ" +"イアログが表示されないようにデフォルト機能を選ぶことができます。" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:580 +msgctxt "@label" +msgid "Override Profile" +msgstr "プロファイルを無効にする" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:629 +msgctxt "@label" +msgid "Privacy" +msgstr "プライバシー" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636 +msgctxt "@info:tooltip" +msgid "Should Cura check for updates when the program is started?" +msgstr "Curaのプログラム開始時にアップデートがあるかチェックしますか?" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:641 +msgctxt "@option:check" +msgid "Check for updates on start" +msgstr "スタート時にアップデートあるかどうかのチェック" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:651 +msgctxt "@info:tooltip" +msgid "" +"Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally " +"identifiable information is sent or stored." +msgstr "" +"プリンターの不明なデータをUltimakerにおくりますか?メモ、モデル、IPアドレス、個人的な情報は送信されたり保存されたりはしま" +"せん。" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:656 +msgctxt "@option:check" +msgid "Send (anonymous) print information" +msgstr " (不特定な) プリントインフォメーションを送信" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:15 +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:441 +msgctxt "@title:tab" +msgid "Printers" +msgstr "プリンター" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:37 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:51 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:137 +msgctxt "@action:button" +msgid "Activate" +msgstr "アクティベート" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:57 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:102 +msgctxt "@action:button" +msgid "Rename" +msgstr "名を変える" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:151 +msgctxt "@label" +msgid "Printer type:" +msgstr "プリンタータイプ:" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:160 +msgctxt "@label" +msgid "Connection:" +msgstr "コネクション:" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:166 +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:52 +msgctxt "@info:status" +msgid "The printer is not connected." +msgstr "このプリンターはつながっていません。" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:172 +msgctxt "@label" +msgid "State:" +msgstr "ステート:" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:192 +msgctxt "@label:MonitorStatus" +msgid "Waiting for someone to clear the build plate" +msgstr "ビルドプレートの掃除を待つ" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:201 +msgctxt "@label:MonitorStatus" +msgid "Waiting for a printjob" +msgstr "プリントジョブの待機中" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:15 +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:445 +msgctxt "@title:tab" +msgid "Profiles" +msgstr "プロファイル" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:29 +msgctxt "@label" +msgid "Protected profiles" +msgstr "保護されたプロファイル" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:29 +msgctxt "@label" +msgid "Custom profiles" +msgstr "カスタムプロファイル" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:64 +msgctxt "@label" +msgid "Create" +msgstr "作成する" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:80 +msgctxt "@label" +msgid "Duplicate" +msgstr "複製" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:113 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:201 +msgctxt "@action:button" +msgid "Import" +msgstr "取り込む" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:119 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:212 +msgctxt "@action:button" +msgid "Export" +msgstr "書き出す" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:126 +msgctxt "@label %1 is printer name" +msgid "Printer: %1" +msgstr "プリンター: %1" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:165 +msgctxt "@action:button" +msgid "Update profile with current settings/overrides" +msgstr "プロファイルを現在のセッティング/" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:173 +msgctxt "@action:button" +msgid "Discard current changes" +msgstr "今の変更を破棄する" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:190 +msgctxt "@action:label" +msgid "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below." +msgstr "" +"このプロファイルはプリンターによりデフォルトを使用、従いこのプロファイルはセッティング/書き換えが以下のリストにありませ" +"ん。" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:197 +msgctxt "@action:label" +msgid "Your current settings match the selected profile." +msgstr "設定は選択したプロファイルにマッチしています。" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:215 +msgctxt "@title:tab" +msgid "Global Settings" +msgstr "グローバル設定" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:258 +msgctxt "@title:window" +msgid "Rename Profile" +msgstr "プロファイル名を変える" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:271 +msgctxt "@title:window" +msgid "Create Profile" +msgstr "プロファイルを作る" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:285 +msgctxt "@title:window" +msgid "Duplicate Profile" +msgstr "プロファイルを複製する" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:299 +msgctxt "@window:title" +msgid "Import Profile" +msgstr "プロファイルを取り込む" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:307 +msgctxt "@title:window" +msgid "Import Profile" +msgstr "プロファイルを取り込む" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:335 +msgctxt "@title:window" +msgid "Export Profile" +msgstr "プロファイルを書き出す" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:15 +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:443 +msgctxt "@title:tab" +msgid "Materials" +msgstr "マテリアル" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:116 +msgctxt "@action:label %1 is printer name, %2 is how this printer names variants, %3 is variant name" +msgid "Printer: %1, %2: %3" +msgstr "プリンター: %1, %2: %3" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:120 +msgctxt "@action:label %1 is printer name" +msgid "Printer: %1" +msgstr "プリンター:%1" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:149 +msgctxt "@action:button" +msgid "Create" +msgstr "作成する" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:168 +msgctxt "@action:button" +msgid "Duplicate" +msgstr "複製" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:319 +msgctxt "@title:window" +msgid "Import Material" +msgstr "フィラメントを取り込む" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:320 +msgctxt "@info:status Don't translate the XML tags or !" +msgid "Could not import material %1: %2" +msgstr " %1: %2フィラメントを取り込むことができない。" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:324 +msgctxt "@info:status Don't translate the XML tag !" +msgid "Successfully imported material %1" +msgstr "フィラメントの取り込みに成功しました。" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:343 +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:358 +msgctxt "@title:window" +msgid "Export Material" +msgstr "フィラメントを書き出す" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:362 +msgctxt "@info:status Don't translate the XML tags and !" +msgid "Failed to export material to %1: %2" +msgstr "フィラメントの書き出しに失敗しました <ファイルネーム>%1: <メッセージ>%2" + +#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:368 +msgctxt "@info:status Don't translate the XML tag !" +msgid "Successfully exported material to %1" +msgstr "無事に%1にフィラメントを書き出しました。" + +#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18 /home/ruben/Projects/Cura/resources/qml/Cura.qml:766 +msgctxt "@title:window" +msgid "Add Printer" +msgstr "プリンターを追加する" + +#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:185 +msgctxt "@label" +msgid "Printer Name:" +msgstr "プリンター名:" + +#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:208 +msgctxt "@action:button" +msgid "Add Printer" +msgstr "プリンターについて" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:15 +msgctxt "@title:window" +msgid "About Cura" +msgstr "Curaについて" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:56 +msgctxt "@label" +msgid "End-to-end solution for fused filament 3D printing." +msgstr "熱溶解積層型3Dプリンティングのエンドtoエンドソリューション" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69 +msgctxt "@info:credit" +msgid "" +"Cura is developed by Ultimaker B.V. in cooperation with the community.\n" +"Cura proudly uses the following open source projects:" +msgstr "CuraはUltimakerB.Vのコミュニティの協力によって開発され、Curaはオープンソースで使えることを誇りに思います:" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118 +msgctxt "@label" +msgid "Graphical user interface" +msgstr "グラフィックユーザーインターフェイス" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:119 +msgctxt "@label" +msgid "Application framework" +msgstr "アプリケーションフレームワーク" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:120 +msgctxt "@label" +msgid "GCode generator" +msgstr "Gcodeジェネレーター" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:121 +msgctxt "@label" +msgid "Interprocess communication library" +msgstr "インタープロセスコミュニケーションライブラリー" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:123 +msgctxt "@label" +msgid "Programming language" +msgstr "プログラミング用語" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:124 +msgctxt "@label" +msgid "GUI framework" +msgstr "GUIフレームワーク" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:125 +msgctxt "@label" +msgid "GUI framework bindings" +msgstr "GUIフレームワークバインディング" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:126 +msgctxt "@label" +msgid "C/C++ Binding library" +msgstr "C/C++ バインディングライブラリー" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:127 +msgctxt "@label" +msgid "Data interchange format" +msgstr "データインターフェイスフォーマット" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:128 +msgctxt "@label" +msgid "Support library for scientific computing" +msgstr "サイエンスコンピューティングを操作するためのライブラリーサポート" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:129 +msgctxt "@label" +msgid "Support library for faster math" +msgstr "ファターマスを操作するためのライブラリーサポート" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:130 +msgctxt "@label" +msgid "Support library for handling STL files" +msgstr "STLファイルを操作するためのライブラリーサポート" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:131 +msgctxt "@label" +msgid "Support library for handling 3MF files" +msgstr "3MFファイルを操作するためのライブラリーサポート" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132 +msgctxt "@label" +msgid "Serial communication library" +msgstr "シリアルコミュニケーションライブラリー" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133 +msgctxt "@label" +msgid "ZeroConf discovery library" +msgstr "ZeroConfディスカバリーライブラリー" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134 +msgctxt "@label" +msgid "Polygon clipping library" +msgstr "ポリゴンクリッピングライブラリー" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:136 +msgctxt "@label" +msgid "Font" +msgstr "フォント" + +#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137 +msgctxt "@label" +msgid "SVG icons" +msgstr "SVGアイコン" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:41 +msgctxt "@label" +msgid "Profile:" +msgstr "プロファイル:" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:97 +msgctxt "@tooltip" +msgid "" +"Some setting/override values are different from the values stored in the profile.\n" +"\n" +"Click to open the profile manager." +msgstr "" +"いくらかの設定プロファイルにある値とことなる場合無効にします。\n" +"プロファイルマネージャーをクリックして開いてください。" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:145 +msgctxt "@label:textbox" +msgid "Search..." +msgstr "検索…" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:476 +msgctxt "@action:menu" +msgid "Copy value to all extruders" +msgstr "すべてのエクストルーダーの値をコピーする" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:491 +msgctxt "@action:menu" +msgid "Hide this setting" +msgstr "この設定を非表示にする" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:501 +msgctxt "@action:menu" +msgid "Don't show this setting" +msgstr "この設定を表示しない" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:505 +msgctxt "@action:menu" +msgid "Keep this setting visible" +msgstr "常に見えるように設定する" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:524 +msgctxt "@action:menu" +msgid "Configure setting visiblity..." +msgstr "視野のセッティングを構成する" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:123 +msgctxt "@label" +msgid "" +"Some hidden settings use values different from their normal calculated value.\n" +"\n" +"Click to make these settings visible." +msgstr "" +"いくらかの非表示設定は通常の計算された値と異なる値を使用します。\n" +"表示されるようにクリックしてください。" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:62 +msgctxt "@label Header for list of settings." +msgid "Affects" +msgstr "影響" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:67 +msgctxt "@label Header for list of settings." +msgid "Affected By" +msgstr "次によって影響を受ける" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157 +msgctxt "@label" +msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders" +msgstr "この設定は常に全てのエクストルーダーに共有されています。ここですべてのエクストルーダーの数値を変更できます。" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:160 +msgctxt "@label" +msgid "The value is resolved from per-extruder values " +msgstr "この値は各エクストルーダーの値から取得します。" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:186 +msgctxt "@label" +msgid "" +"This setting has a value that is different from the profile.\n" +"\n" +"Click to restore the value of the profile." +msgstr "" +"この設定にプロファイルと異なった値があります。\n" +"プロファイルの値を戻すためにクリックしてください。" + +#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:284 +msgctxt "@label" +msgid "" +"This setting is normally calculated, but it currently has an absolute value set.\n" +"\n" +"Click to restore the calculated value." +msgstr "" +"このセッティングは通常計算されます、今は絶対値に固定されています。\n" +"計算された値に変更するためにクリックを押してください。" + +#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:118 +msgctxt "@label:listbox" +msgid "Print Setup" +msgstr "プリントセットアップ" + +#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:118 +msgctxt "@label:listbox" +msgid "" +"Print Setup disabled\n" +"G-code files cannot be modified" +msgstr "" +"プリントセットアップが無効\n" +"G-codeファイルを修正することができません。" + +#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:326 +msgctxt "@label" +msgid "00h 00min" +msgstr "00h 00min" + +#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:344 +msgctxt "@tooltip" +msgid "Time information" +msgstr "Time information" + +#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:370 +msgctxt "@description" +msgid "Print time" +msgstr "プリント時間" + +#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:409 +msgctxt "@label" +msgid "%1m / ~ %2g / ~ %4 %3" +msgstr "%1分 / ~ %2g / ~ %4 %3" + +#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:414 +msgctxt "@label" +msgid "%1m / ~ %2g" +msgstr "%1分/ ~ %2g" + +#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:476 +msgctxt "@tooltip" +msgid "" +"Recommended Print Setup

Print with the recommended settings for the selected printer, material and " +"quality." +msgstr "" +"おすすめプリントセットアップ

選択されたプリンターにておすすめの設定、フィラメント、質にてプリントしてくだ" +"さい。 " + +#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:481 +msgctxt "@tooltip" +msgid "Custom Print Setup

Print with finegrained control over every last bit of the slicing process." +msgstr "カスタムプリントセットアップ

スライス処理のきめ細かなコントロールにてプリントする" + +#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:35 +msgctxt "@title:menuitem %1 is the automatically selected material" +msgid "Automatic: %1" +msgstr "自動: %1" + +#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:12 +msgctxt "@title:menu menubar:toplevel" +msgid "&View" +msgstr "&ビュー" + +#: /home/ruben/Projects/Cura/resources/qml/Menus/NozzleMenu.qml:26 +msgctxt "@title:menuitem %1 is the nozzle currently loaded in the printer" +msgid "Automatic: %1" +msgstr "自動: %1" + +# can’t enter japanese texts +#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:25 +msgctxt "@label" +msgid "Print Selected Model With:" +msgid_plural "Print Selected Models With:" +msgstr[0] "" +msgstr[1] "" + +# can’t eneter japanese texts +#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:83 +msgctxt "@title:window" +msgid "Multiply Selected Model" +msgid_plural "Multiply Selected Models" +msgstr[0] "" +msgstr[1] "" + +#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:108 +msgctxt "@label" +msgid "Number of Copies" +msgstr "コピーの数" + +#: /home/ruben/Projects/Cura/resources/qml/Menus/RecentFilesMenu.qml:13 +msgctxt "@title:menu menubar:file" +msgid "Open &Recent" +msgstr "最近開いたファイルを開く" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:33 +msgctxt "@info:status" +msgid "No printer connected" +msgstr "接続中のプリンターはありません。" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:90 /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:157 +msgctxt "@label" +msgid "Extruder" +msgstr "エクストルーダー" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:120 +msgctxt "@tooltip" +msgid "" +"The target temperature of the hotend. The hotend will heat up or cool down towards this temperature. If this is 0, the " +"hotend heating is turned off." +msgstr "" +"ホットエンドの目標温度。ホットエンドはこの温度に向けて上がったり下がったりします。これが0の場合、ホットエンドの加熱はオフ" +"になっています。" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:152 +msgctxt "@tooltip" +msgid "The current temperature of this extruder." +msgstr "現在のエクストルーダーの温度" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:187 +msgctxt "@tooltip" +msgid "The colour of the material in this extruder." +msgstr "エクストルーダーのマテリアルの色" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:219 +msgctxt "@tooltip" +msgid "The material in this extruder." +msgstr "エクストルーダー入ったフィラメント" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:251 +msgctxt "@tooltip" +msgid "The nozzle inserted in this extruder." +msgstr "ノズルが入ったエクストルーダー" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:282 +msgctxt "@label" +msgid "Build plate" +msgstr "ビルドプレート" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:311 +msgctxt "@tooltip" +msgid "" +"The target temperature of the heated bed. The bed will heat up or cool down towards this temperature. If this is 0, the " +"bed heating is turned off." +msgstr "" +"ヒーティッドベッドの目標温度。ベッドはこの温度に向けて上がったり下がったりします。これが0の場合、ベッドの加熱はオフになっ" +"ています。" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:343 +msgctxt "@tooltip" +msgid "The current temperature of the heated bed." +msgstr "現在のヒーティッドベッドの温度" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:422 +msgctxt "@tooltip of temperature input" +msgid "The temperature to pre-heat the bed to." +msgstr "ベッドのプリヒート温度" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:617 +msgctxt "@button Cancel pre-heating" +msgid "Cancel" +msgstr "キャンセル" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:617 +msgctxt "@button" +msgid "Pre-heat" +msgstr "プレヒート" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:644 +msgctxt "@tooltip of pre-heat" +msgid "" +"Heat the bed in advance before printing. You can continue adjusting your print while it is heating, and you won't have to " +"wait for the bed to heat up when you're ready to print." +msgstr "" +"プリント開始前にベッドを加熱します。加熱中もプリントの調整を行えます、またべットが加熱するまでプリント開始を待つ必要もあり" +"ません。" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:677 +msgctxt "@label" +msgid "Active print" +msgstr "プリントをアクティベートする" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:682 +msgctxt "@label" +msgid "Job Name" +msgstr "ジョブネーム" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:688 +msgctxt "@label" +msgid "Printing Time" +msgstr "プリント時間" + +#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:694 +msgctxt "@label" +msgid "Estimated time left" +msgstr "残り時間" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:67 +msgctxt "@action:inmenu" +msgid "Toggle Fu&ll Screen" +msgstr "留め金 フルスクリーン" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:74 +msgctxt "@action:inmenu menubar:edit" +msgid "&Undo" +msgstr "&取り消す" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:84 +msgctxt "@action:inmenu menubar:edit" +msgid "&Redo" +msgstr "&やりなおす" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:94 +msgctxt "@action:inmenu menubar:file" +msgid "&Quit" +msgstr "&やめる" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:102 +msgctxt "@action:inmenu" +msgid "Configure Cura..." +msgstr "Curaを構成する…" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:109 +msgctxt "@action:inmenu menubar:printer" +msgid "&Add Printer..." +msgstr "&プリンターを追加する" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:115 +msgctxt "@action:inmenu menubar:printer" +msgid "Manage Pr&inters..." +msgstr "プリンターを管理する" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:122 +msgctxt "@action:inmenu" +msgid "Manage Materials..." +msgstr "フィラメントを管理する" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:130 +msgctxt "@action:inmenu menubar:profile" +msgid "&Update profile with current settings/overrides" +msgstr "&現在の設定/無効にプロファイルをアップデートする" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:138 +msgctxt "@action:inmenu menubar:profile" +msgid "&Discard current changes" +msgstr "&変更を破棄する" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:150 +msgctxt "@action:inmenu menubar:profile" +msgid "&Create profile from current settings/overrides..." +msgstr "&今の設定/無効からプロファイルを作成する…" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:156 +msgctxt "@action:inmenu menubar:profile" +msgid "Manage Profiles..." +msgstr "プロファイルを管理する" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:163 +msgctxt "@action:inmenu menubar:help" +msgid "Show Online &Documentation" +msgstr "オンラインドキュメントを表示する" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:171 +msgctxt "@action:inmenu menubar:help" +msgid "Report a &Bug" +msgstr "報告&バグ" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:179 +msgctxt "@action:inmenu menubar:help" +msgid "&About..." +msgstr "アバウト..." + +# can’t enter japanese text +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:186 +msgctxt "@action:inmenu menubar:edit" +msgid "Delete &Selected Model" +msgid_plural "Delete &Selected Models" +msgstr[0] "" +msgstr[1] "" + +# can’t enter japanese text +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:196 +msgctxt "@action:inmenu menubar:edit" +msgid "Center Selected Model" +msgid_plural "Center Selected Models" +msgstr[0] "" +msgstr[1] "" + +# can’t edit japanese text +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:205 +msgctxt "@action:inmenu menubar:edit" +msgid "Multiply Selected Model" +msgid_plural "Multiply Selected Models" +msgstr[0] "" +msgstr[1] "" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:214 +msgctxt "@action:inmenu" +msgid "Delete Model" +msgstr "モデルを消去する" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:222 +msgctxt "@action:inmenu" +msgid "Ce&nter Model on Platform" +msgstr "プラットホームの中心にモデルを配置" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:228 +msgctxt "@action:inmenu menubar:edit" +msgid "&Group Models" +msgstr "&モデルグループ" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:238 +msgctxt "@action:inmenu menubar:edit" +msgid "Ungroup Models" +msgstr "モデルを非グループ化" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:248 +msgctxt "@action:inmenu menubar:edit" +msgid "&Merge Models" +msgstr "&モデルの合体" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:258 +msgctxt "@action:inmenu" +msgid "&Multiply Model..." +msgstr "&モデルを増倍する…" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:265 +msgctxt "@action:inmenu menubar:edit" +msgid "&Select All Models" +msgstr "&すべてのモデル選択" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:275 +msgctxt "@action:inmenu menubar:edit" +msgid "&Clear Build Plate" +msgstr "&ビルドプレート上のクリア" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:285 +msgctxt "@action:inmenu menubar:file" +msgid "Re&load All Models" +msgstr "すべてのモデルを読み込む" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:294 +msgctxt "@action:inmenu menubar:edit" +msgid "Arrange All Models" +msgstr "すべてのモデルをアレンジする" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:302 +msgctxt "@action:inmenu menubar:edit" +msgid "Arrange Selection" +msgstr "選択をアレンジする" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:309 +msgctxt "@action:inmenu menubar:edit" +msgid "Reset All Model Positions" +msgstr "すべてのモデルのポジションをリセットする" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:316 +msgctxt "@action:inmenu menubar:edit" +msgid "Reset All Model &Transformations" +msgstr "すべてのモデル&変更点をリセットする" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:323 +msgctxt "@action:inmenu menubar:file" +msgid "&Open File(s)..." +msgstr "&ファイルを開く(s)…" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:331 +msgctxt "@action:inmenu menubar:file" +msgid "&New Project..." +msgstr "&新しいプロジェクト…" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:338 +msgctxt "@action:inmenu menubar:help" +msgid "Show Engine &Log..." +msgstr "エンジン&ログを表示する" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:346 +msgctxt "@action:inmenu menubar:help" +msgid "Show Configuration Folder" +msgstr "コンフィグレーションのフォルダーを表示する" + +#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:353 +msgctxt "@action:menu" +msgid "Configure setting visibility..." +msgstr "視野のセッティングを構成する" + +#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26 +msgctxt "@label:PrintjobStatus" +msgid "Please load a 3D model" +msgstr "3Dモデルをロードしてください。" + +#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:32 +msgctxt "@label:PrintjobStatus" +msgid "Ready to slice" +msgstr "スライスの準備ができました。" + +#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:34 +msgctxt "@label:PrintjobStatus" +msgid "Slicing..." +msgstr "スライス中…" + +#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36 +msgctxt "@label:PrintjobStatus %1 is target operation" +msgid "Ready to %1" +msgstr "準備に%1" + +#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38 +msgctxt "@label:PrintjobStatus" +msgid "Unable to Slice" +msgstr "スライスできません。" + +#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40 +msgctxt "@label:PrintjobStatus" +msgid "Slicing unavailable" +msgstr "スライスが利用不可能" + +#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:147 +msgctxt "@label:Printjob" +msgid "Prepare" +msgstr "準備する" + +#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:147 +msgctxt "@label:Printjob" +msgid "Cancel" +msgstr "キャンセル" + +#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:287 +msgctxt "@info:tooltip" +msgid "Select the active output device" +msgstr "アクティブなアウトプットデバイスを選択する" + +#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:19 +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:593 +msgctxt "@title:window" +msgid "Open file(s)" +msgstr "ファイルを開く(s)" + +#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:64 +msgctxt "@text:window" +msgid "" +"We have found one or more project file(s) within the files you have selected. You can open only one project file at a " +"time. We suggest to only import models from those files. Would you like to proceed?" +msgstr "" +"選択したファイルの中に複数のプロジェクトが存在します。1ファイルのみ一度に開けます。ファイルからモデルを先に取り込むことを" +"お勧めします。続けますか?" + +#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:99 +msgctxt "@action:button" +msgid "Import all as models" +msgstr "すべてをモデルとして取り入れる" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:19 +msgctxt "@title:window" +msgid "Cura" +msgstr "Cura" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:85 +msgctxt "@title:menu menubar:toplevel" +msgid "&File" +msgstr "&ファイル" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:102 +msgctxt "@action:inmenu menubar:file" +msgid "&Save Selection to File" +msgstr "&ファイルに選択したものを保存" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:111 +msgctxt "@title:menu menubar:file" +msgid "Save &As..." +msgstr "名前をつけて保存" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:122 +msgctxt "@title:menu menubar:file" +msgid "Save project" +msgstr "プロジェクトを保存" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:145 +msgctxt "@title:menu menubar:toplevel" +msgid "&Edit" +msgstr "&編集" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162 +msgctxt "@title:menu" +msgid "&View" +msgstr "&ビュー" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:167 +msgctxt "@title:menu" +msgid "&Settings" +msgstr "&設定" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:169 +msgctxt "@title:menu menubar:toplevel" +msgid "&Printer" +msgstr "&プリンター" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:179 /home/ruben/Projects/Cura/resources/qml/Cura.qml:191 +msgctxt "@title:menu" +msgid "&Material" +msgstr "&フィラメント" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:180 /home/ruben/Projects/Cura/resources/qml/Cura.qml:192 +msgctxt "@title:menu" +msgid "&Profile" +msgstr "&プロファイル" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184 +msgctxt "@action:inmenu" +msgid "Set as Active Extruder" +msgstr "アクティブエクストルーダーとしてセットする" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:202 +msgctxt "@title:menu menubar:toplevel" +msgid "E&xtensions" +msgstr "拡張子" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:235 +msgctxt "@title:menu menubar:toplevel" +msgid "P&references" +msgstr "プレファレンス" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:243 +msgctxt "@title:menu menubar:toplevel" +msgid "&Help" +msgstr "ヘルプ" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:325 +msgctxt "@action:button" +msgid "Open File" +msgstr "ファイルを開く" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:439 +msgctxt "@title:tab" +msgid "Settings" +msgstr "設定" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:475 +msgctxt "@title:window" +msgid "New project" +msgstr "新しいプロジェクト…" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:476 +msgctxt "@info:question" +msgid "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings." +msgstr "新しいプロジェクトを開始しますか?この作業では保存していない設定やビルドプレートをクリアします。" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:694 +msgctxt "@window:title" +msgid "Install Plugin" +msgstr "プラグインをインストール" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:701 +msgctxt "@title:window" +msgid "Open File(s)" +msgstr "ファイルを開く(s)" + +#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:704 +msgctxt "@text:window" +msgid "" +"We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. " +"If you want to open a G-Code file, please just select only one." +msgstr "" +"選択したファイルの中に複数のG-codeが存在します。1つのG-codeのみ一度に開けます。G-codeファイルを開く場合は、1点のみ選んでく" +"ださい。" + +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:14 +msgctxt "@title:window" +msgid "Save Project" +msgstr "プロジェクトを保存" + +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:134 +msgctxt "@action:label" +msgid "Extruder %1" +msgstr "エクストルーダー" + +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:144 +msgctxt "@action:label" +msgid "%1 & material" +msgstr "フィラメント" + +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:240 +msgctxt "@action:label" +msgid "Don't show project summary on save again" +msgstr "保存中のプロジェクトサマリーを非表示にする" + +#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:262 +msgctxt "@action:button" +msgid "Save" +msgstr "保存" + +#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:65 +msgctxt "@title:tab" +msgid "Prepare" +msgstr "準備する" + +#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:79 +msgctxt "@title:tab" +msgid "Monitor" +msgstr "モニター" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:50 +msgctxt "@label" +msgid "Layer Height" +msgstr "レイヤーの高さ" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:62 +msgctxt "@label" +msgid "Print Speed" +msgstr "プリントスピード" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:73 +msgctxt "@label" +msgid "Slower" +msgstr "ゆっくり" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:85 +msgctxt "@label" +msgid "Faster" +msgstr "早く" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:416 +msgctxt "@label" +msgid "Infill" +msgstr "インフィル" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:590 +msgctxt "@label" +msgid "Gradual infill will gradually increase the amount of infill towards the top." +msgstr "グラデュアルインフィルはトップに向かうに従ってインフィルの量を増やします。" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:602 +msgctxt "@label" +msgid "Enable gradual" +msgstr "グラデュアルを有効にする" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:668 +msgctxt "@label" +msgid "Generate Support" +msgstr "サポートを生成します。" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:702 +msgctxt "@label" +msgid "" +"Generate structures to support parts of the model which have overhangs. Without these structures, such parts would " +"collapse during printing." +msgstr "" +"オーバーハングがあるモデルにサポートを生成します。このサポート構造なしでは、プリント中にオーバーハングのパーツが崩壊してし" +"まいます。" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:718 +msgctxt "@label" +msgid "Support Extruder" +msgstr "サポートエクストルーダー" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:769 +msgctxt "@label" +msgid "" +"Select which extruder to use for support. This will build up supporting structures below the model to prevent the model " +"from sagging or printing in mid air." +msgstr "" +"サポートに使うエクストルーダーを選択してください。モデルの垂れや中空プリントを避けるためにモデルの下にサポート構造を生成し" +"ます。" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:796 +msgctxt "@label" +msgid "Build Plate Adhesion" +msgstr "ビルドプレートの接着" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:843 +msgctxt "@label" +msgid "" +"Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards." +msgstr "ブリムまたはラフトのプリントの有効化。それぞれ、プリントの周り、また造形物の下に底面を加え切り取りやすくします。" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:883 +msgctxt "@label" +msgid "Need help improving your prints?
Read the Ultimaker Troubleshooting Guides" +msgstr "プリントにヘルプが必要ですか?
読んでください。 Ultimakerトラブルシューティングガイド" + +# can’t enter japanese +#: /home/ruben/Projects/Cura/resources/qml/ExtruderButton.qml:16 +msgctxt "@label %1 is filled in with the name of an extruder" +msgid "Print Selected Model with %1" +msgid_plural "Print Selected Models with %1" +msgstr[0] "" +msgstr[1] "" + +#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:20 +msgctxt "@title:window" +msgid "Open project file" +msgstr "プロジェクトを開く" + +#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:71 +msgctxt "@text:window" +msgid "This is a Cura project file. Would you like to open it as a project or import the models from it?" +msgstr "これはCuraのプロジェクトファイルです。プロジェクトとしてあけますか、それともモデルのみ取り込みますか?" + +#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:95 +msgctxt "@action:button" +msgid "Open as project" +msgstr "プロジェクトを開く" + +#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:114 +msgctxt "@action:button" +msgid "Import models" +msgstr "モデルを取り込む" + +#: /home/ruben/Projects/Cura/resources/qml/EngineLog.qml:15 +msgctxt "@title:window" +msgid "Engine Log" +msgstr "エンジンログ" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:261 +msgctxt "@label" +msgid "Material" +msgstr "フィラメント" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:374 +msgctxt "@label" +msgid "Check material compatibility" +msgstr "マテリアルのコンパティビリティを確認" + +#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:394 +msgctxt "@tooltip" +msgid "Click to check the material compatibility on Ultimaker.com." +msgstr "Ultimaker.comにてマテリアルのコンパティビリティを調べるためにクリック" + +#: MachineSettingsAction/plugin.json +msgctxt "description" +msgid "Provides a way to change machine settings (such as build volume, nozzle size, etc)" +msgstr "プリンターの設定を変更(印刷ボリューム、ノズルサイズ、その他)" + +#: MachineSettingsAction/plugin.json +msgctxt "name" +msgid "Machine Settings action" +msgstr "プリンターの設定アクション" + +#: XRayView/plugin.json +msgctxt "description" +msgid "Provides the X-Ray view." +msgstr "X-Rayビューイング" + +#: XRayView/plugin.json +msgctxt "name" +msgid "X-Ray View" +msgstr "X-Rayビュー" + +#: X3DReader/plugin.json +msgctxt "description" +msgid "Provides support for reading X3D files." +msgstr "X3Dファイルを読むこむためのサポートを供給する" + +#: X3DReader/plugin.json +msgctxt "name" +msgid "X3D Reader" +msgstr "X3Dリーダー" + +#: GCodeWriter/plugin.json +msgctxt "description" +msgid "Writes GCode to a file." +msgstr "ファイルにGCodeを書く" + +#: GCodeWriter/plugin.json +msgctxt "name" +msgid "GCode Writer" +msgstr "GCodeライター" + +#: cura-god-mode-plugin/src/GodMode/plugin.json +msgctxt "description" +msgid "Dump the contents of all settings to a HTML file." +msgstr "HTMLファイルに設定内容を放置する" + +#: cura-god-mode-plugin/src/GodMode/plugin.json +msgctxt "name" +msgid "God Mode" +msgstr "Godモード" + +#: Doodle3D-cura-plugin/Doodle3D/plugin.json +msgctxt "description" +msgid "Accepts G-Code and sends them over WiFi to a Doodle3D WiFi-Box." +msgstr "G-codeを承認し、Doodle3D WiFi-ボックスにWifi上にて送る" + +#: Doodle3D-cura-plugin/Doodle3D/plugin.json +msgctxt "name" +msgid "Doodle3D WiFi-Box" +msgstr "Doodle3D WiFi-Box" + +#: ChangeLogPlugin/plugin.json +msgctxt "description" +msgid "Shows changes since latest checked version." +msgstr "最新の更新バージョンの変更点を表示する" + +#: ChangeLogPlugin/plugin.json +msgctxt "name" +msgid "Changelog" +msgstr "Changelog" + +#: ProfileFlattener/plugin.json +msgctxt "description" +msgid "Create a flattend quality changes profile." +msgstr "プロファイルを変更するフラットエンドクオリティーを作成する" + +#: ProfileFlattener/plugin.json +msgctxt "name" +msgid "Profile flatener" +msgstr "プロファイルフラットナー" + +#: USBPrinting/plugin.json +msgctxt "description" +msgid "Accepts G-Code and sends them to a printer. Plugin can also update firmware." +msgstr "G-codeを承認し、プリンターに送信する。またプラグインはファームウェアをアップデートできます。" + +#: USBPrinting/plugin.json +msgctxt "name" +msgid "USB printing" +msgstr "USBプリンティング" + +#: RemovableDriveOutputDevice/plugin.json +msgctxt "description" +msgid "Provides removable drive hotplugging and writing support." +msgstr "取り外し可能なドライブホットプラギング及びサポートの書き出しの供給" + +#: RemovableDriveOutputDevice/plugin.json +msgctxt "name" +msgid "Removable Drive Output Device Plugin" +msgstr "取り外し可能なドライブアウトプットデバイスプラグイン" + +#: UM3NetworkPrinting/plugin.json +msgctxt "description" +msgid "Manages network connections to Ultimaker 3 printers" +msgstr "Ultimaker3のプリンターのネットワーク接続を管理する。" + +#: UM3NetworkPrinting/plugin.json +msgctxt "name" +msgid "UM3 Network Connection" +msgstr "UM3ネットワークコネクション" + +#: CuraPrintClusterUpload/plugin.json +msgctxt "name" +msgid "UM3 Network Connection (Cluster)" +msgstr "UM3 ネットワークコネクション(クラスター)" + +#: FirmwareUpdateChecker/plugin.json +msgctxt "description" +msgid "Checks for firmware updates." +msgstr "ファームウェアアップデートをチェックする" + +#: FirmwareUpdateChecker/plugin.json +msgctxt "name" +msgid "Firmware Update Checker" +msgstr "ファームウェアアップデートチェッカー" + +#: CuraSolidWorksPlugin/plugin.json +msgctxt "description" +msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura" +msgstr "ソリッドワークスにて特定のファイルを開くことが可能です。その後変換され、Curaに取り込めます。" + +#: CuraSolidWorksPlugin/plugin.json +msgctxt "name" +msgid "SolidWorks Integration" +msgstr "ソリッドワークスインタグレーション" + +#: PostProcessingPlugin/plugin.json +msgctxt "description" +msgid "Extension that allows for user created scripts for post processing" +msgstr "後処理のためにユーザーが作成したスクリプト用拡張子" + +#: PostProcessingPlugin/plugin.json +msgctxt "name" +msgid "Post Processing" +msgstr "後処理" + +#: AutoSave/plugin.json +msgctxt "description" +msgid "Automatically saves Preferences, Machines and Profiles after changes." +msgstr "変更後プリンターやプロファイルプレファレンスを自動的に保存します、" + +#: AutoSave/plugin.json +msgctxt "name" +msgid "Auto Save" +msgstr "自動保存" + +#: SliceInfoPlugin/plugin.json +msgctxt "description" +msgid "Submits anonymous slice info. Can be disabled through preferences." +msgstr "不特定なスライス情報を提出。プレファレンスの中で無効になる可能性もある。" + +#: SliceInfoPlugin/plugin.json +msgctxt "name" +msgid "Slice info" +msgstr "スライスインフォメーション" + +#: XmlMaterialProfile/plugin.json +msgctxt "description" +msgid "Provides capabilities to read and write XML-based material profiles." +msgstr "XMLベースフィラメントのプロファイルを読み書きするための機能を供給する。" + +#: XmlMaterialProfile/plugin.json +msgctxt "name" +msgid "Material Profiles" +msgstr "フィラメントプロファイル" + +#: LegacyProfileReader/plugin.json +msgctxt "description" +msgid "Provides support for importing profiles from legacy Cura versions." +msgstr "レガシーCura Versionsからプロファイルを取り込むためのサポートを供給する" + +#: LegacyProfileReader/plugin.json +msgctxt "name" +msgid "Legacy Cura Profile Reader" +msgstr "レガシーCuraプロファイルリーダー" + +#: GCodeProfileReader/plugin.json +msgctxt "description" +msgid "Provides support for importing profiles from g-code files." +msgstr "g-codeファイルからプロファイルを読み込むサポートを供給する。" + +#: GCodeProfileReader/plugin.json +msgctxt "name" +msgid "GCode Profile Reader" +msgstr "GCodeプロファイルリーダー" + +#: LayerView/plugin.json +msgctxt "description" +msgid "Provides the Layer view." +msgstr "レイヤービューを供給する" + +#: LayerView/plugin.json +msgctxt "name" +msgid "Layer View" +msgstr "レイヤービュー" + +#: VersionUpgrade/VersionUpgrade25to26/plugin.json +msgctxt "description" +msgid "Upgrades configurations from Cura 2.5 to Cura 2.6." +msgstr "Cura 2.5 からCura 2.6のコンフィグレーションアップグレート" + +#: VersionUpgrade/VersionUpgrade25to26/plugin.json +msgctxt "name" +msgid "Version Upgrade 2.5 to 2.6" +msgstr "2.5から2.6にバージョンアップグレート" + +#: VersionUpgrade/VersionUpgrade27to30/plugin.json +msgctxt "description" +msgid "Upgrades configurations from Cura 2.7 to Cura 3.0." +msgstr "Cura 2.7からCura 3.0のコンフィグレーションアップグレート" + +#: VersionUpgrade/VersionUpgrade27to30/plugin.json +msgctxt "name" +msgid "Version Upgrade 2.7 to 3.0" +msgstr "2.7から3.0にバージョンアップグレート" + +#: VersionUpgrade/VersionUpgrade26to27/plugin.json +msgctxt "description" +msgid "Upgrades configurations from Cura 2.6 to Cura 2.7." +msgstr "Cura 2.6 からCura 2.7のコンフィグレーションアップグレート" + +#: VersionUpgrade/VersionUpgrade26to27/plugin.json +msgctxt "name" +msgid "Version Upgrade 2.6 to 2.7" +msgstr "2.6から2.7にバージョンアップグレート" + +#: VersionUpgrade/VersionUpgrade21to22/plugin.json +msgctxt "description" +msgid "Upgrades configurations from Cura 2.1 to Cura 2.2." +msgstr "Cura 2.1 からCura 2.2のコンフィグレーションアップグレート" + +#: VersionUpgrade/VersionUpgrade21to22/plugin.json +msgctxt "name" +msgid "Version Upgrade 2.1 to 2.2" +msgstr "2.1 から2.2にバージョンアップグレート" + +#: VersionUpgrade/VersionUpgrade22to24/plugin.json +msgctxt "description" +msgid "Upgrades configurations from Cura 2.2 to Cura 2.4." +msgstr "Cura 2.2 からCura 2.4のコンフィグレーションアップグレート" + +#: VersionUpgrade/VersionUpgrade22to24/plugin.json +msgctxt "name" +msgid "Version Upgrade 2.2 to 2.4" +msgstr "2.2 から2.4にバージョンアップグレート" + +#: ImageReader/plugin.json +msgctxt "description" +msgid "Enables ability to generate printable geometry from 2D image files." +msgstr "2Dの画像ファイルからプリント可能なジオメトリーを生成を可能にする" + +#: ImageReader/plugin.json +msgctxt "name" +msgid "Image Reader" +msgstr "画像リーダー" + +#: CuraEngineBackend/plugin.json +msgctxt "description" +msgid "Provides the link to the CuraEngine slicing backend." +msgstr "CuraEngineスライシングバックエンドにリンクを供給する" + +#: CuraEngineBackend/plugin.json +msgctxt "name" +msgid "CuraEngine Backend" +msgstr "Curaエンジンバックエンド" + +#: PerObjectSettingsTool/plugin.json +msgctxt "description" +msgid "Provides the Per Model Settings." +msgstr "各モデル設定を与える" + +#: PerObjectSettingsTool/plugin.json +msgctxt "name" +msgid "Per Model Settings Tool" +msgstr "各モデル設定ツール" + +#: 3MFReader/plugin.json +msgctxt "description" +msgid "Provides support for reading 3MF files." +msgstr "3MFファイルを読むこむためのサポートを供給する" + +#: 3MFReader/plugin.json +msgctxt "name" +msgid "3MF Reader" +msgstr "3MFリーダー" + +#: PluginBrowser/plugin.json +msgctxt "description" +msgid "Find, manage and install new plugins." +msgstr "探す、新しいプラグインを管理、インストール" + +#: PluginBrowser/plugin.json +msgctxt "name" +msgid "Plugin Browser" +msgstr "プラグインブラウザー" + +#: SolidView/plugin.json +msgctxt "description" +msgid "Provides a normal solid mesh view." +msgstr "ノーマルなソリットメッシュビューを供給する" + +#: SolidView/plugin.json +msgctxt "name" +msgid "Solid View" +msgstr "ソリッドビュー" + +#: GCodeReader/plugin.json +msgctxt "description" +msgid "Allows loading and displaying G-code files." +msgstr "G-codeファイルの読み込み、表示を許可する" + +#: GCodeReader/plugin.json +msgctxt "name" +msgid "G-code Reader" +msgstr "G-codeリーダー" + +#: CuraProfileWriter/plugin.json +msgctxt "description" +msgid "Provides support for exporting Cura profiles." +msgstr "Curaプロファイルを書き出すためのサポートを供給する" + +#: CuraProfileWriter/plugin.json +msgctxt "name" +msgid "Cura Profile Writer" +msgstr "Curaプロファイルライター" + +#: 3MFWriter/plugin.json +msgctxt "description" +msgid "Provides support for writing 3MF files." +msgstr "3MFファイルを読むこむためのサポートを供給する" + +#: 3MFWriter/plugin.json +msgctxt "name" +msgid "3MF Writer" +msgstr "3MFリーダー" + +#: UltimakerMachineActions/plugin.json +msgctxt "description" +msgid "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc)" +msgstr "Ultimakerのプリンターのアクションを供給する(ベッドレベリングウィザード、アップグレードの選択、他)" + +#: UltimakerMachineActions/plugin.json +msgctxt "name" +msgid "Ultimaker machine actions" +msgstr "Ultimkerプリンターのアクション" + +#: CuraProfileReader/plugin.json +msgctxt "description" +msgid "Provides support for importing Cura profiles." +msgstr "Curaプロファイルを取り込むためのサポートを供給する" + +#: CuraProfileReader/plugin.json +msgctxt "name" +msgid "Cura Profile Reader" +msgstr "Curaプロファイルリーダー" From 7a42c5bcceb389b7ecb5e1cbd0f4847a78b49c2b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 17 Nov 2017 16:38:02 +0100 Subject: [PATCH 64/67] Fix technical mistakes of Japanese translation There's a lot of keys that weren't added to the Japanese translation. Our program crashes on these because it can't find where it needs to fill in the argument. --- resources/i18n/ja_JP/cura.po | 120 +++++++++++++++++------------------ 1 file changed, 57 insertions(+), 63 deletions(-) diff --git a/resources/i18n/ja_JP/cura.po b/resources/i18n/ja_JP/cura.po index c29e0b709b..1b77006b0c 100644 --- a/resources/i18n/ja_JP/cura.po +++ b/resources/i18n/ja_JP/cura.po @@ -8,15 +8,15 @@ msgstr "" "Project-Id-Version: Cura 3.0\n" "Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n" "POT-Creation-Date: 2017-08-02 16:53+0000\n" -"PO-Revision-Date: 2017-11-10 20:08+0900\n" +"PO-Revision-Date: 2017-11-17 15:52+0100\n" "Language-Team: TEAM\n" "Language: xx_XX\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" +"Plural-Forms: nplurals=1; plural=0;\n" "Last-Translator: \n" -"X-Generator: Poedit 2.0.4\n" +"X-Generator: Poedit 1.8.7.1\n" #: Manually added for plugins/UM3NetworkPrinting/PrinterInfoBlock.qml msgctxt "@label:status" @@ -51,7 +51,7 @@ msgstr "この層で終了します:" #: Manually added for plugins/UM3NetworkPrinting/DiscoverUM3Action.qml msgctxt "@label" msgid "This printer is the host for a group of %1 Ultimaker 3 printers." -msgstr "このプリンターがUltimaker3のグループのホストプリンターです。" +msgstr "このプリンターはUltimaker3 %1グループのホストプリンターです。" #: Manually added for plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py msgctxt "@info:status" @@ -86,7 +86,7 @@ msgstr "プリンターの設定" #: /home/ruben/Projects/Cura/plugins/XRayView/__init__.py:12 msgctxt "@item:inlistbox" msgid "X-Ray view" -msgstr "X-Rayビューイング" +msgstr "透視ビューイング" #: /home/ruben/Projects/Cura/plugins/X3DReader/__init__.py:13 msgctxt "@item:inlistbox" @@ -243,7 +243,7 @@ msgstr "プリンターが未接続のため、ファームウェアをアップ #, python-format msgctxt "@info" msgid "Could not find firmware required for the printer at %s." -msgstr "プリンターに必要なファームウェアを探せませんでした。" +msgstr "プリンター(%s)に必要なファームウェアを探せませんでした。" #: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122 msgctxt "@info:title" @@ -259,13 +259,13 @@ msgstr "リムーバブルドライブに保存" #, python-brace-format msgctxt "@item:inlistbox" msgid "Save to Removable Drive {0}" -msgstr "リムーバブルドライブに保存" +msgstr "リムーバブルドライブ{0}に保存" #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:89 #, python-brace-format msgctxt "@info:progress Don't translate the XML tags !" msgid "Saving to Removable Drive {0}" -msgstr "リムーバブルドライブに保存中" +msgstr "リムーバブルドライブ{0}に保存中" #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:89 msgctxt "@info:title" @@ -277,20 +277,20 @@ msgstr "保存中" #, python-brace-format msgctxt "@info:status Don't translate the XML tags or !" msgid "Could not save to {0}: {1}" -msgstr "保存できませんでした。" +msgstr "{0}を保存できませんでした: {1}" #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:118 #, python-brace-format msgctxt "@info:status Don't translate the tag {device}!" msgid "Could not find a file name when trying to write to {device}." -msgstr "デバイスに書き出すためのファイル名が見つかりませんでした。" +msgstr "デバイス{device}に書き出すためのファイル名が見つかりませんでした。" #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:131 #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:146 #, python-brace-format msgctxt "@info:status" msgid "Could not save to removable drive {0}: {1}" -msgstr "リムーバブルドライブに保存することができませんでした。" +msgstr "リムーバブルドライブ{0}に保存することができませんでした: {1}" #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:132 #: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:675 @@ -306,7 +306,7 @@ msgstr "エラー" #, python-brace-format msgctxt "@info:status" msgid "Saved to Removable Drive {0} as {1}" -msgstr "リムーバブルドライブに保存" +msgstr "リムーバブルドライブ{0}に {1}として保存" #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:140 msgctxt "@info:title" @@ -322,13 +322,13 @@ msgstr "取り出す" #, python-brace-format msgctxt "@action" msgid "Eject removable device {0}" -msgstr "リムーバブルデバイスを取り出す" +msgstr "リムーバブルデバイス{0}を取り出す" #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:156 #, python-brace-format msgctxt "@info:status" msgid "Ejected {0}. You can now safely remove the drive." -msgstr "取り出し完了。デバイスを安全に取り外せます。" +msgstr "{0}取り出し完了。デバイスを安全に取り外せます。" #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:156 msgctxt "@info:title" @@ -339,7 +339,7 @@ msgstr "ハードウェアを安全に取り外します。" #, python-brace-format msgctxt "@info:status" msgid "Failed to eject {0}. Another program may be using the drive." -msgstr "取り出し失敗。他のプログラムがデバイスを使用しているため。" +msgstr "{0}取り出し失敗。他のプログラムがデバイスを使用しているため。" #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/WindowsRemovableDrivePlugin.py:68 msgctxt "@item:intext" @@ -476,7 +476,8 @@ msgstr "ネットワークへの接続が切断されました。プリンター #, python-format msgctxt "@info:status" msgid "Unable to start a new print job, printer is busy. Current printer status is %s." -msgstr "プリンターが利用中です。新しいプリントジョブを開始することができませんでした。現在の印刷状況は%です。" +msgstr "" +"プリンターが利用中です。新しいプリントジョブを開始することができませんでした。現在のプリンターのステータスは%sです。" #: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:650 msgctxt "@info:title" @@ -488,42 +489,42 @@ msgstr "プリンターのステータス" #, python-brace-format msgctxt "@info:status" msgid "Unable to start a new print job. No Printcore loaded in slot {0}" -msgstr "プリントコアがスロットに入っていません。プリントジョブを開始できません。" +msgstr "プリントコアがスロット{0}に入っていません。プリントジョブを開始できません。" #: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:682 #: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:667 #, python-brace-format msgctxt "@info:status" msgid "Unable to start a new print job. No material loaded in slot {0}" -msgstr "フィラメントがスロットに入っていません。プリントジョブを開始できません。" +msgstr "フィラメントがスロット{0}に入っていません。プリントジョブを開始できません。" #: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692 #: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:676 #, python-brace-format msgctxt "@label" msgid "Not enough material for spool {0}." -msgstr "フィラメントの残量が足りません。" +msgstr "フィラメント{0}の残量が足りません。" #: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:702 #: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:686 #, python-brace-format msgctxt "@label" msgid "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}" -msgstr "異なるプリントコアが入っています。(Cura:{0}, プリンター{1})エクストルーダー{2}" +msgstr "異なるプリントコアが入っています(Cura:{0}, プリンター{1})エクストルーダー{2}" #: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:716 #: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:700 #, python-brace-format msgctxt "@label" msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}" -msgstr "異なるフィラメントが入っています。(Cura:{0}, プリンター{1})エクストルーダー{2}" +msgstr "異なるフィラメントが入っています(Cura:{0}, プリンター{1})エクストルーダー{2}" #: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:724 #: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:708 #, python-brace-format msgctxt "@label" msgid "PrintCore {0} is not properly calibrated. XY calibration needs to be performed on the printer." -msgstr "プリントコア{0}が適切にカリブレーションできていません。XYキャリブレーションをプリンターで行ってください。" +msgstr "プリントコア{0}が適切にキャリブレーションできていません。XYキャリブレーションをプリンターで行ってください。" #: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:729 #: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkPrinterOutputDevice.py:713 @@ -625,7 +626,7 @@ msgstr "このプリンターは、繋がっているUltimaker3プリンター #, python-brace-format msgctxt "Count is number of printers." msgid "This printer is the host for a group of {count} connected Ultimaker 3 printers." -msgstr "このプリンターは{台数}繋がっているUltimaker3プリンターのグループのホストプリンターです。" +msgstr "このプリンターは{count}繋がっているUltimaker3プリンターのグループのホストプリンターです。" #: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/NetworkClusterPrinterOutputDevice.py:105 #, python-brace-format @@ -693,7 +694,7 @@ msgstr "" #, python-format msgctxt "@info:title The %s gets replaced with the printer name." msgid "New %s firmware available" -msgstr "新しい利用可能なファームウェアのアップデートがあります。" +msgstr "新しい利用可能な%sファームウェアのアップデートがあります。" #: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:73 #: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:149 @@ -732,7 +733,7 @@ msgstr "構成" #, python-format msgctxt "@info:status" msgid "Error while starting %s!" -msgstr " %を開始中にエラーが発生" +msgstr "%sを開始中にエラーが発生" #: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:24 msgid "Modify G-Code" @@ -821,7 +822,7 @@ msgstr "スライスできません。" #, python-brace-format msgctxt "@info:status" msgid "Unable to slice with the current settings. The following settings have errors: {0}" -msgstr "現在の設定でスライスが完了できません。以下の設定にエラーがあります。{0}" +msgstr "現在の設定でスライスが完了できません。以下の設定にエラーがあります: {0}" #: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:306 msgctxt "@info:status" @@ -888,7 +889,7 @@ msgstr "プラグインを見る" #, python-brace-format msgctxt "@info:status" msgid "Failed to get plugin ID from {0}" -msgstr "プラグインIDを{0}取得することに失敗しました。" +msgstr "{0}からプラグインIDを取得することに失敗しました。" #: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:164 msgctxt "@info:tile" @@ -1069,7 +1070,7 @@ msgstr "すでに存在するファイルです。" #, python-brace-format msgctxt "@label Don't translate the XML tag !" msgid "The file {0} already exists. Are you sure you want to overwrite it?" -msgstr "{0} は既に存在します。ファイルを上書きしますか? " +msgstr "{0} は既に存在します。ファイルを上書きしますか?" #: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:815 msgctxt "@label" @@ -1111,7 +1112,7 @@ msgstr "フィラメント直径を変更を取り消す" #, python-brace-format msgctxt "@info:status Don't translate the XML tags or !" msgid "Failed to export profile to {0}: {1}" -msgstr "{0}: {1}にプロファイルを書き出すのに失敗しました。" +msgstr "{0}にプロファイルを書き出すのに失敗しました: {1}" #: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:150 #, python-brace-format @@ -1144,7 +1145,7 @@ msgstr "{0}: {1}からプロファイル #, python-brace-format msgctxt "@info:status" msgid "Successfully imported profile {0}" -msgstr "プロファイルの取り込み完了" +msgstr "プロファイル {0}の取り込み完了" #: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:254 #, python-brace-format @@ -1204,7 +1205,7 @@ msgid "" " " msgstr "" "

不可解なエラーが発生しリカバリーできませんでした。

\n" -"

この情報をバグとして報告してください。 http://github.com/Ultimaker/" +"

この情報をバグとして報告してください。 http://github.com/Ultimaker/" "Cura/issues

\n" " " @@ -1234,19 +1235,19 @@ msgctxt "" "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## " "mm." msgid "%(width).1f x %(depth).1f x %(height).1f mm" -msgstr "%(幅).1f x %(奥行き).1f x %(高さ).1f mm" +msgstr "%(width).1f x %(depth).1f x %(height).1f mm" #: /home/ruben/Projects/Cura/cura/CuraApplication.py:1295 #, python-brace-format msgctxt "@info:status" msgid "Only one G-code file can be loaded at a time. Skipped importing {0}" -msgstr "一度に一つのG-codeしか読み取れません。取り込みをスキップしました{0}。" +msgstr "一度に一つのG-codeしか読み取れません。{0}の取り込みをスキップしました。" #: /home/ruben/Projects/Cura/cura/CuraApplication.py:1304 #, python-brace-format msgctxt "@info:status" msgid "Can't open any other file if G-code is loading. Skipped importing {0}" -msgstr "G-codeを読み込み中は他のファイルを開くことができません。取り込みをスキップしました{0}。" +msgstr "G-codeを読み込み中は他のファイルを開くことができません。{0}の取り込みをスキップしました。" #: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:59 msgctxt "@title" @@ -1571,8 +1572,8 @@ msgstr "更新" msgctxt "@label" msgid "If your printer is not listed, read the network printing troubleshooting guide" msgstr "" -"お持ちのプリンターがリストにない場合、ネットワークプリンティングトラブルシューティングガイドを読んでくだ" -"さい。" +"お持ちのプリンターがリストにない場合、ネットワーク・プリンティング・トラブルシューティング・ガイドを読ん" +"でください。" #: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:223 #: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:223 @@ -1668,7 +1669,7 @@ msgstr "このプリンターは、繋がっているUltimaker3プリンター #: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/DiscoverUM3Action.qml:287 msgctxt "@label" msgid "This printer is the host for a group of %1 connected Ultimaker 3 printers" -msgstr "このプリンターは繋がっているUltimaker3プリンターのグループのホストです。" +msgstr "このプリンターは繋がっているUltimaker3プリンターの%1グループのホストです。" #: /home/ruben/Projects/Cura/plugins/CuraPrintClusterUpload/PrintWindow.qml:24 msgctxt "@title:window" @@ -2087,12 +2088,10 @@ msgstr "プロファイル内にない" # Can’t edit the Japanese text #: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:217 #: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:177 -#, fuzzy msgctxt "@action:label" msgid "%1 override" msgid_plural "%1 overrides" -msgstr[0] "プロファイルを無効にする" -msgstr[1] "" +msgstr[0] "%1個の設定を上書き" #: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:228 msgctxt "@action:label" @@ -2100,12 +2099,13 @@ msgid "Derivative from" msgstr "次から引き出す" # can’t inset the japanese text +# %1: print quality profile name +# %2: number of overridden ssettings #: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:233 msgctxt "@action:label" msgid "%1, %2 override" msgid_plural "%1, %2 overrides" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%2の%1個の設定を上書き" #: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:249 msgctxt "@action:label" @@ -2138,7 +2138,7 @@ msgstr "ビジブル設定:" #: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:221 msgctxt "@action:label" msgid "%1 out of %2" -msgstr "%2のうち%1" +msgstr "%2のうち%1" #: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:363 msgctxt "@action:warning" @@ -3063,12 +3063,12 @@ msgstr "フィラメントを取り込む" #: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:320 msgctxt "@info:status Don't translate the XML tags or !" msgid "Could not import material %1: %2" -msgstr " %1: %2フィラメントを取り込むことができない。" +msgstr " %1フィラメントを取り込むことができない: %2" #: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:324 msgctxt "@info:status Don't translate the XML tag !" msgid "Successfully imported material %1" -msgstr "フィラメントの取り込みに成功しました。" +msgstr "フィラメント%1の取り込みに成功しました。" #: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:343 #: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:358 @@ -3079,12 +3079,12 @@ msgstr "フィラメントを書き出す" #: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:362 msgctxt "@info:status Don't translate the XML tags and !" msgid "Failed to export material to %1: %2" -msgstr "フィラメントの書き出しに失敗しました <ファイルネーム>%1: <メッセージ>%2" +msgstr "フィラメントの書き出しに失敗しました %1: %2" #: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:368 msgctxt "@info:status Don't translate the XML tag !" msgid "Successfully exported material to %1" -msgstr "無事に%1にフィラメントを書き出しました。" +msgstr "フィラメントの%1への書き出しが完了ました。" #: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18 /home/ruben/Projects/Cura/resources/qml/Cura.qml:766 msgctxt "@title:window" @@ -3359,7 +3359,7 @@ msgstr "カスタムプリントセットアップ

スライス #: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:35 msgctxt "@title:menuitem %1 is the automatically selected material" msgid "Automatic: %1" -msgstr "自動: %1" +msgstr "自動選択: %1" #: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:12 msgctxt "@title:menu menubar:toplevel" @@ -3369,7 +3369,7 @@ msgstr "&ビュー" #: /home/ruben/Projects/Cura/resources/qml/Menus/NozzleMenu.qml:26 msgctxt "@title:menuitem %1 is the nozzle currently loaded in the printer" msgid "Automatic: %1" -msgstr "自動: %1" +msgstr "自動選択: %1" # can’t enter japanese texts #: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:25 @@ -3377,7 +3377,6 @@ msgctxt "@label" msgid "Print Selected Model With:" msgid_plural "Print Selected Models With:" msgstr[0] "" -msgstr[1] "" # can’t eneter japanese texts #: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:83 @@ -3385,7 +3384,6 @@ msgctxt "@title:window" msgid "Multiply Selected Model" msgid_plural "Multiply Selected Models" msgstr[0] "" -msgstr[1] "" #: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:108 msgctxt "@label" @@ -3580,7 +3578,6 @@ msgctxt "@action:inmenu menubar:edit" msgid "Delete &Selected Model" msgid_plural "Delete &Selected Models" msgstr[0] "" -msgstr[1] "" # can’t enter japanese text #: /home/ruben/Projects/Cura/resources/qml/Actions.qml:196 @@ -3588,7 +3585,6 @@ msgctxt "@action:inmenu menubar:edit" msgid "Center Selected Model" msgid_plural "Center Selected Models" msgstr[0] "" -msgstr[1] "" # can’t edit japanese text #: /home/ruben/Projects/Cura/resources/qml/Actions.qml:205 @@ -3596,7 +3592,6 @@ msgctxt "@action:inmenu menubar:edit" msgid "Multiply Selected Model" msgid_plural "Multiply Selected Models" msgstr[0] "" -msgstr[1] "" #: /home/ruben/Projects/Cura/resources/qml/Actions.qml:214 msgctxt "@action:inmenu" @@ -3706,7 +3701,7 @@ msgstr "スライス中…" #: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36 msgctxt "@label:PrintjobStatus %1 is target operation" msgid "Ready to %1" -msgstr "準備に%1" +msgstr "%1の準備完了" #: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38 msgctxt "@label:PrintjobStatus" @@ -3875,12 +3870,12 @@ msgstr "プロジェクトを保存" #: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:134 msgctxt "@action:label" msgid "Extruder %1" -msgstr "エクストルーダー" +msgstr "エクストルーダー%1" #: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:144 msgctxt "@action:label" msgid "%1 & material" -msgstr "フィラメント" +msgstr "%1とフィラメント" #: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:240 msgctxt "@action:label" @@ -3979,15 +3974,14 @@ msgstr "ブリムまたはラフトのプリントの有効化。それぞれ、 #: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:883 msgctxt "@label" msgid "Need help improving your prints?
Read the Ultimaker Troubleshooting Guides" -msgstr "プリントにヘルプが必要ですか?
読んでください。 Ultimakerトラブルシューティングガイド" +msgstr "プリントにヘルプが必要ですか?
Ultimakerトラブルシューティングガイドを読んでください。" # can’t enter japanese #: /home/ruben/Projects/Cura/resources/qml/ExtruderButton.qml:16 msgctxt "@label %1 is filled in with the name of an extruder" msgid "Print Selected Model with %1" msgid_plural "Print Selected Models with %1" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "選択したモデルを%1で印刷する" #: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:20 msgctxt "@title:window" @@ -4022,7 +4016,7 @@ msgstr "フィラメント" #: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:374 msgctxt "@label" msgid "Check material compatibility" -msgstr "マテリアルのコンパティビリティを確認" +msgstr "マテリアルのコンパティビリティを確認" #: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:394 msgctxt "@tooltip" @@ -4042,12 +4036,12 @@ msgstr "プリンターの設定アクション" #: XRayView/plugin.json msgctxt "description" msgid "Provides the X-Ray view." -msgstr "X-Rayビューイング" +msgstr "透視ビューイング" #: XRayView/plugin.json msgctxt "name" msgid "X-Ray View" -msgstr "X-Rayビュー" +msgstr "透視ビュー" #: X3DReader/plugin.json msgctxt "description" From 217fb606c474ad1d1a120e666a87e2ad3db323bf Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 20 Nov 2017 09:04:07 +0100 Subject: [PATCH 65/67] CURA-4577 Fix the previous change --- plugins/GCodeReader/GCodeReader.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py index a0b44bbaaa..1b2795800e 100755 --- a/plugins/GCodeReader/GCodeReader.py +++ b/plugins/GCodeReader/GCodeReader.py @@ -139,11 +139,11 @@ class GCodeReader(MeshReader): if self._is_absolute_positioning: x = params.x if params.x is not None else x y = params.y if params.y is not None else y - z = params.z if params.z is not None else position.z + z = params.z if params.z is not None else z else: - x += params.x if params.x is not None else x - y += params.y if params.y is not None else y - z += params.z if params.z is not None else position.z + x += params.x if params.x is not None else 0 + y += params.y if params.y is not None else 0 + z += params.z if params.z is not None else 0 if params.e is not None: new_extrusion_value = params.e if self._is_absolute_positioning else e[self._extruder_number] + params.e From 56db10d9cb9315f20dc1ba92223dd76725db0df1 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 20 Nov 2017 11:24:25 +0100 Subject: [PATCH 66/67] CURA-4526 Change simulation behavior when clicking on Play button --- plugins/SimulationView/SimulationView.qml | 41 ++++++++++++++++++----- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/plugins/SimulationView/SimulationView.qml b/plugins/SimulationView/SimulationView.qml index e2e0dc3aed..4c7d99deec 100644 --- a/plugins/SimulationView/SimulationView.qml +++ b/plugins/SimulationView/SimulationView.qml @@ -583,7 +583,6 @@ Item UM.SimulationView.setSimulationRunning(true) iconSource = "./resources/simulation_pause.svg" simulationTimer.start() - status = 1 } } } @@ -599,18 +598,42 @@ Item var numPaths = UM.SimulationView.numPaths var currentLayer = UM.SimulationView.currentLayer var numLayers = UM.SimulationView.numLayers - if (currentPath >= numPaths) { - if (currentLayer >= numLayers) { - playButton.pauseSimulation() - } - else { - UM.SimulationView.setCurrentLayer(currentLayer+1) + // When the user plays the simulation, if the path slider is at the end of this layer, we start + // the simulation at the beginning of the current layer. + if (playButton.status == 0) + { + if (currentPath >= numPaths) + { UM.SimulationView.setCurrentPath(0) } + else + { + UM.SimulationView.setCurrentPath(currentPath+1) + } } - else { - UM.SimulationView.setCurrentPath(currentPath+1) + // If the simulation is already playing and we reach the end of a layer, then it automatically + // starts at the beginning of the next layer. + else + { + if (currentPath >= numPaths) + { + // At the end of the model, the simulation stops + if (currentLayer >= numLayers) + { + playButton.pauseSimulation() + } + else + { + UM.SimulationView.setCurrentLayer(currentLayer+1) + UM.SimulationView.setCurrentPath(0) + } + } + else + { + UM.SimulationView.setCurrentPath(currentPath+1) + } } + playButton.status = 1 } } } From 20c21d6e79259804ba3684f193514cec8d5b5587 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Mon, 20 Nov 2017 13:52:22 +0100 Subject: [PATCH 67/67] Added "Signal.disconect" after changing views CURA-4526 --- plugins/SimulationView/SimulationViewProxy.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/SimulationView/SimulationViewProxy.py b/plugins/SimulationView/SimulationViewProxy.py index 21a962104d..2bd707293f 100644 --- a/plugins/SimulationView/SimulationViewProxy.py +++ b/plugins/SimulationView/SimulationViewProxy.py @@ -16,6 +16,8 @@ class SimulationViewProxy(QObject): self._controller.activeViewChanged.connect(self._onActiveViewChanged) self._onActiveViewChanged() + self.is_simulationView_selected = False + currentLayerChanged = pyqtSignal() currentPathChanged = pyqtSignal() maxLayersChanged = pyqtSignal() @@ -236,6 +238,19 @@ class SimulationViewProxy(QObject): def _onActiveViewChanged(self): active_view = self._controller.getActiveView() if isinstance(active_view, SimulationView.SimulationView.SimulationView): + + # remove other connection if once the SimulationView was created. + if self.is_simulationView_selected: + active_view.currentLayerNumChanged.disconnect(self._onLayerChanged) + active_view.currentPathNumChanged.disconnect(self._onPathChanged) + active_view.maxLayersChanged.disconnect(self._onMaxLayersChanged) + active_view.maxPathsChanged.disconnect(self._onMaxPathsChanged) + active_view.busyChanged.disconnect(self._onBusyChanged) + active_view.activityChanged.disconnect(self._onActivityChanged) + active_view.globalStackChanged.disconnect(self._onGlobalStackChanged) + active_view.preferencesChanged.disconnect(self._onPreferencesChanged) + + self.is_simulationView_selected = True active_view.currentLayerNumChanged.connect(self._onLayerChanged) active_view.currentPathNumChanged.connect(self._onPathChanged) active_view.maxLayersChanged.connect(self._onMaxLayersChanged)