diff --git a/.gitignore b/.gitignore index 22d42783f2..1db07180c0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ resources/firmware resources/materials LC_MESSAGES .cache +*.qmlc + +#MacOS +.DS_Store # Editors and IDEs. *kdev* @@ -19,6 +23,7 @@ LC_MESSAGES *~ *.qm .idea +cura.desktop # Eclipse+PyDev .project @@ -33,4 +38,23 @@ plugins/Doodle3D-cura-plugin plugins/GodMode plugins/PostProcessingPlugin plugins/X3GWriter +plugins/FlatProfileExporter +plugins/ProfileFlattener +plugins/cura-god-mode-plugin +plugins/cura-big-flame-graph + +#Build stuff +CMakeCache.txt +CMakeFiles +CPackSourceConfig.cmake +Testing/ +CTestTestfile.cmake +Makefile* +junit-pytest-* +CuraVersion.py +cmake_install.cmake + +#Debug +*.gcode +run.sh diff --git a/cura.desktop.in b/cura.desktop.in index ceb5c99f08..1f6d295dd2 100644 --- a/cura.desktop.in +++ b/cura.desktop.in @@ -1,5 +1,4 @@ [Desktop Entry] -Version=1 Name=Cura Name[de]=Cura GenericName=3D Printing Software diff --git a/cura/Arrange.py b/cura/Arrange.py index 2348535efc..0d1f2e0c06 100755 --- a/cura/Arrange.py +++ b/cura/Arrange.py @@ -114,7 +114,7 @@ class Arrange: self._priority_unique_values.sort() ## Return the amount of "penalty points" for polygon, which is the sum of priority - # 999999 if occupied + # None if occupied # \param x x-coordinate to check shape # \param y y-coordinate # \param shape_arr the ShapeArray object to place @@ -128,9 +128,9 @@ class Arrange: offset_x:offset_x + shape_arr.arr.shape[1]] try: if numpy.any(occupied_slice[numpy.where(shape_arr.arr == 1)]): - return 999999 + return None except IndexError: # out of bounds if you try to place an object outside - return 999999 + return None prio_slice = self._priority[ offset_y:offset_y + shape_arr.arr.shape[0], offset_x:offset_x + shape_arr.arr.shape[1]] @@ -157,7 +157,7 @@ class Arrange: # array to "world" coordinates penalty_points = self.checkShape(projected_x, projected_y, shape_arr) - if penalty_points != 999999: + if penalty_points is not None: return LocationSuggestion(x = projected_x, y = projected_y, penalty_points = penalty_points, priority = priority) return LocationSuggestion(x = None, y = None, penalty_points = None, priority = priority) # No suitable location found :-( diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 16a11fbc1c..c87e07a974 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -25,6 +25,8 @@ catalog = i18nCatalog("cura") import numpy import math +from typing import List + # Setting for clearance around the prime PRIME_CLEARANCE = 6.5 @@ -129,7 +131,7 @@ class BuildVolume(SceneNode): ## Updates the listeners that listen for changes in per-mesh stacks. # # \param node The node for which the decorators changed. - def _updateNodeListeners(self, node): + def _updateNodeListeners(self, node: SceneNode): per_mesh_stack = node.callDecoration("getStack") if per_mesh_stack: per_mesh_stack.propertyChanged.connect(self._onSettingPropertyChanged) @@ -139,21 +141,25 @@ class BuildVolume(SceneNode): self._updateDisallowedAreasAndRebuild() def setWidth(self, width): - if width: self._width = width + if width is not None: + self._width = width def setHeight(self, height): - if height: self._height = height + if height is not None: + self._height = height def setDepth(self, depth): - if depth: self._depth = depth + if depth is not None: + self._depth = depth - def setShape(self, shape): - if shape: self._shape = shape + def setShape(self, shape: str): + if shape: + self._shape = shape - def getDisallowedAreas(self): + def getDisallowedAreas(self) -> List[Polygon]: return self._disallowed_areas - def setDisallowedAreas(self, areas): + def setDisallowedAreas(self, areas: List[Polygon]): self._disallowed_areas = areas def render(self, renderer): @@ -196,7 +202,6 @@ class BuildVolume(SceneNode): return for node in nodes: - # Need to check group nodes later if node.callDecoration("isGroup"): group_nodes.append(node) # Keep list of affected group_nodes @@ -412,10 +417,10 @@ class BuildVolume(SceneNode): self.updateNodeBoundaryCheck() - def getBoundingBox(self): + def getBoundingBox(self) -> AxisAlignedBox: return self._volume_aabb - def getRaftThickness(self): + def getRaftThickness(self) -> float: return self._raft_thickness def _updateRaftThickness(self): @@ -428,7 +433,8 @@ class BuildVolume(SceneNode): self._global_container_stack.getProperty("raft_interface_thickness", "value") + self._global_container_stack.getProperty("raft_surface_layers", "value") * self._global_container_stack.getProperty("raft_surface_thickness", "value") + - self._global_container_stack.getProperty("raft_airgap", "value")) + self._global_container_stack.getProperty("raft_airgap", "value") - + self._global_container_stack.getProperty("layer_0_z_overlap", "value")) # Rounding errors do not matter, we check if raft_thickness has changed at all if old_raft_thickness != self._raft_thickness: @@ -492,7 +498,7 @@ class BuildVolume(SceneNode): self._engine_ready = True self.rebuild() - def _onSettingPropertyChanged(self, setting_key, property_name): + def _onSettingPropertyChanged(self, setting_key: str, property_name: str): if property_name != "value": return @@ -525,7 +531,7 @@ class BuildVolume(SceneNode): if rebuild_me: self.rebuild() - def hasErrors(self): + def hasErrors(self) -> bool: return self._has_errors ## Calls _updateDisallowedAreas and makes sure the changes appear in the @@ -557,7 +563,7 @@ class BuildVolume(SceneNode): used_extruders = [self._global_container_stack] result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added. - prime_areas = self._computeDisallowedAreasPrime(disallowed_border_size, used_extruders) + prime_areas = self._computeDisallowedAreasPrimeBlob(disallowed_border_size, used_extruders) prime_disallowed_areas = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking. #Check if prime positions intersect with disallowed areas. @@ -595,20 +601,21 @@ class BuildVolume(SceneNode): result_areas[extruder_id].append(polygon) #Don't perform the offset on these. # Add prime tower location as disallowed area. - prime_tower_collision = False - prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders) - for extruder_id in prime_tower_areas: - for prime_tower_area in prime_tower_areas[extruder_id]: - for area in result_areas[extruder_id]: - if prime_tower_area.intersectsPolygon(area) is not None: - prime_tower_collision = True + if len(used_extruders) > 1: #No prime tower in single-extrusion. + prime_tower_collision = False + prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders) + for extruder_id in prime_tower_areas: + for prime_tower_area in prime_tower_areas[extruder_id]: + for area in result_areas[extruder_id]: + if prime_tower_area.intersectsPolygon(area) is not None: + prime_tower_collision = True + break + if prime_tower_collision: #Already found a collision. break - if prime_tower_collision: #Already found a collision. - break - if not prime_tower_collision: - result_areas[extruder_id].extend(prime_tower_areas[extruder_id]) - else: - self._error_areas.extend(prime_tower_areas[extruder_id]) + if not prime_tower_collision: + result_areas[extruder_id].extend(prime_tower_areas[extruder_id]) + else: + self._error_areas.extend(prime_tower_areas[extruder_id]) self._has_errors = len(self._error_areas) > 0 @@ -652,7 +659,7 @@ class BuildVolume(SceneNode): return result - ## Computes the disallowed areas for the prime locations. + ## Computes the disallowed areas for the prime blobs. # # These are special because they are not subject to things like brim or # travel avoidance. They do get a dilute with the border size though @@ -663,17 +670,18 @@ class BuildVolume(SceneNode): # \param used_extruders The extruder stacks to generate disallowed areas # for. # \return A dictionary with for each used extruder ID the prime areas. - def _computeDisallowedAreasPrime(self, border_size, used_extruders): + def _computeDisallowedAreasPrimeBlob(self, border_size, used_extruders): result = {} machine_width = self._global_container_stack.getProperty("machine_width", "value") machine_depth = self._global_container_stack.getProperty("machine_depth", "value") for extruder in used_extruders: + prime_blob_enabled = extruder.getProperty("prime_blob_enable", "value") prime_x = extruder.getProperty("extruder_prime_pos_x", "value") prime_y = - extruder.getProperty("extruder_prime_pos_y", "value") - #Ignore extruder prime position if it is not set - if prime_x == 0 and prime_y == 0: + #Ignore extruder prime position if it is not set or if blob is disabled + if (prime_x == 0 and prime_y == 0) or not prime_blob_enabled: result[extruder.getId()] = [] continue @@ -944,9 +952,9 @@ class BuildVolume(SceneNode): return max(min(value, max_value), min_value) _skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist"] - _raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap"] + _raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"] _extra_z_settings = ["retraction_hop_enabled", "retraction_hop"] - _prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z"] + _prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z", "prime_blob_enable"] _tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"] _ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"] _distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts"] diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py index da72ffdbe3..44150cb76f 100644 --- a/cura/ConvexHullDecorator.py +++ b/cura/ConvexHullDecorator.py @@ -59,7 +59,8 @@ class ConvexHullDecorator(SceneNodeDecorator): hull = self._compute2DConvexHull() if self._global_stack and self._node: - if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"): + # Parent can be None if node is just loaded. + if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and (self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")): hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), numpy.float32))) hull = self._add2DAdhesionMargin(hull) return hull @@ -79,7 +80,7 @@ class ConvexHullDecorator(SceneNodeDecorator): return None if self._global_stack: - if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"): + if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and (self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")): head_with_fans = self._compute2DConvexHeadMin() head_with_fans_with_adhesion_margin = self._add2DAdhesionMargin(head_with_fans) return head_with_fans_with_adhesion_margin @@ -93,8 +94,7 @@ class ConvexHullDecorator(SceneNodeDecorator): return None if self._global_stack: - if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"): - + if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and (self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")): # Printing one at a time and it's not an object in a group return self._compute2DConvexHull() return None @@ -328,11 +328,10 @@ class ConvexHullDecorator(SceneNodeDecorator): return self.__isDescendant(root, node.getParent()) _affected_settings = [ - "adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", - "raft_surface_thickness", "raft_airgap", "raft_margin", "print_sequence", + "adhesion_type", "raft_margin", "print_sequence", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"] ## Settings that change the convex hull. # # If these settings change, the convex hull should be recalculated. - _influencing_settings = {"xy_offset", "mold_enabled", "mold_width"} \ No newline at end of file + _influencing_settings = {"xy_offset", "mold_enabled", "mold_width"} diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index b658f88824..4048b409a7 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -48,35 +48,32 @@ def show(exception_type, value, tb): dialog = QDialog() dialog.setMinimumWidth(640) dialog.setMinimumHeight(640) - dialog.setWindowTitle(catalog.i18nc("@title:window", "Oops!")) + dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report")) layout = QVBoxLayout(dialog) - label = QLabel(dialog) - pixmap = QPixmap() - - try: - data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read() - pixmap.loadFromData(data) - except: - try: - from UM.Resources import Resources - path = Resources.getPath(Resources.Images, "kitten.jpg") - pixmap.load(path) - except: - pass - - pixmap = pixmap.scaled(150, 150) - label.setPixmap(pixmap) - label.setAlignment(Qt.AlignCenter) - layout.addWidget(label) + #label = QLabel(dialog) + #pixmap = QPixmap() + #try: + # data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read() + # pixmap.loadFromData(data) + #except: + # try: + # from UM.Resources import Resources + # path = Resources.getPath(Resources.Images, "kitten.jpg") + # pixmap.load(path) + # except: + # pass + #pixmap = pixmap.scaled(150, 150) + #label.setPixmap(pixmap) + #label.setAlignment(Qt.AlignCenter) + #layout.addWidget(label) label = QLabel(dialog) layout.addWidget(label) #label.setScaledContents(True) label.setText(catalog.i18nc("@label", """
A fatal exception has occurred that we could not recover from!
-We hope this picture of a kitten helps you recover from the shock.
Please use the information below to post a bug report at http://github.com/Ultimaker/Cura/issues
""")) diff --git a/cura/CuraActions.py b/cura/CuraActions.py index df26a9a9a6..eeebd3b6b2 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -1,10 +1,23 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + from PyQt5.QtCore import QObject, QUrl from PyQt5.QtGui import QDesktopServices from UM.FlameProfiler import pyqtSlot from UM.Event import CallFunctionEvent from UM.Application import Application +from UM.Math.Vector import Vector +from UM.Scene.Selection import Selection +from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator +from UM.Operations.GroupedOperation import GroupedOperation +from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation +from UM.Operations.SetTransformOperation import SetTransformOperation +from cura.SetParentOperation import SetParentOperation +from cura.MultiplyObjectsJob import MultiplyObjectsJob +from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation +from cura.Settings.ExtruderManager import ExtruderManager class CuraActions(QObject): def __init__(self, parent = None): @@ -23,5 +36,84 @@ class CuraActions(QObject): event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {}) Application.getInstance().functionEvent(event) + ## Center all objects in the selection + @pyqtSlot() + def centerSelection(self) -> None: + operation = GroupedOperation() + for node in Selection.getAllSelectedObjects(): + current_node = node + while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): + current_node = current_node.getParent() + + center_operation = SetTransformOperation(current_node, Vector()) + operation.addOperation(center_operation) + operation.push() + + ## Multiply all objects in the selection + # + # \param count The number of times to multiply the selection. + @pyqtSlot(int) + def multiplySelection(self, count: int) -> None: + job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, 8) + job.start() + + ## Delete all selected objects. + @pyqtSlot() + def deleteSelection(self) -> None: + if not Application.getInstance().getController().getToolsEnabled(): + return + + removed_group_nodes = [] + op = GroupedOperation() + nodes = Selection.getAllSelectedObjects() + for node in nodes: + op.addOperation(RemoveSceneNodeOperation(node)) + group_node = node.getParent() + if group_node and group_node.callDecoration("isGroup") and group_node not in removed_group_nodes: + remaining_nodes_in_group = list(set(group_node.getChildren()) - set(nodes)) + if len(remaining_nodes_in_group) == 1: + removed_group_nodes.append(group_node) + op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent())) + op.addOperation(RemoveSceneNodeOperation(group_node)) + op.push() + + ## Set the extruder that should be used to print the selection. + # + # \param extruder_id The ID of the extruder stack to use for the selected objects. + @pyqtSlot(str) + def setExtruderForSelection(self, extruder_id: str) -> None: + operation = GroupedOperation() + + nodes_to_change = [] + for node in Selection.getAllSelectedObjects(): + # Do not change any nodes that already have the right extruder set. + if node.callDecoration("getActiveExtruder") == extruder_id: + continue + + # If the node is a group, apply the active extruder to all children of the group. + if node.callDecoration("isGroup"): + for grouped_node in BreadthFirstIterator(node): + if grouped_node.callDecoration("getActiveExtruder") == extruder_id: + continue + + if grouped_node.callDecoration("isGroup"): + continue + + nodes_to_change.append(grouped_node) + continue + + nodes_to_change.append(node) + + if not nodes_to_change: + # If there are no changes to make, we still need to reset the selected extruders. + # This is a workaround for checked menu items being deselected while still being + # selected. + ExtruderManager.getInstance().resetSelectedObjectExtruders() + return + + for node in nodes_to_change: + operation.addOperation(SetObjectExtruderOperation(node, extruder_id)) + operation.push() + def _openUrl(self, url): - QDesktopServices.openUrl(url) \ No newline at end of file + QDesktopServices.openUrl(url) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index af23fcb4cf..b85c1a2f54 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -26,6 +26,7 @@ from UM.Message import Message from UM.i18n import i18nCatalog from UM.Workspace.WorkspaceReader import WorkspaceReader from UM.Platform import Platform +from UM.Decorators import deprecated from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation @@ -68,6 +69,8 @@ from cura.Settings.ContainerSettingsModel import ContainerSettingsModel from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler from cura.Settings.QualitySettingsModel import QualitySettingsModel from cura.Settings.ContainerManager import ContainerManager +from cura.Settings.GlobalStack import GlobalStack +from cura.Settings.ExtruderStack import ExtruderStack from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS from UM.FlameProfiler import pyqtSlot @@ -96,6 +99,11 @@ if not MYPY: class CuraApplication(QtApplication): + # SettingVersion represents the set of settings available in the machine/extruder definitions. + # You need to make sure that this version number needs to be increased if there is any non-backwards-compatible + # changes of the settings. + SettingVersion = 1 + class ResourceTypes: QmlFiles = Resources.UserType + 1 Firmware = Resources.UserType + 2 @@ -105,10 +113,15 @@ class CuraApplication(QtApplication): UserInstanceContainer = Resources.UserType + 6 MachineStack = Resources.UserType + 7 ExtruderStack = Resources.UserType + 8 + DefinitionChangesContainer = Resources.UserType + 9 Q_ENUMS(ResourceTypes) def __init__(self): + # this list of dir names will be used by UM to detect an old cura directory + for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]: + Resources.addExpectedDirNameInData(dir_name) + Resources.addSearchPath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura", "resources")) if not hasattr(sys, "frozen"): Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")) @@ -146,6 +159,7 @@ class CuraApplication(QtApplication): Resources.addStorageType(self.ResourceTypes.UserInstanceContainer, "user") Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders") Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances") + Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer) @@ -153,17 +167,18 @@ class CuraApplication(QtApplication): ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack) + ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer) ## Initialise the version upgrade manager with Cura's storage paths. import UM.VersionUpgradeManager #Needs to be here to prevent circular dependencies. UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions( { - ("quality", InstanceContainer.Version): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"), + ("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"), ("machine_stack", ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"), ("extruder_train", ContainerStack.Version): (self.ResourceTypes.ExtruderStack, "application/x-uranium-extruderstack"), ("preferences", Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences"), - ("user", InstanceContainer.Version): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer") + ("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer") } ) @@ -214,6 +229,7 @@ class CuraApplication(QtApplication): self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity) self.getController().toolOperationStopped.connect(self._onToolOperationStopped) + self.getController().contextMenuRequested.connect(self._onContextMenuRequested) Resources.addType(self.ResourceTypes.QmlFiles, "qml") Resources.addType(self.ResourceTypes.Firmware, "firmware") @@ -283,6 +299,7 @@ class CuraApplication(QtApplication): z_seam_y infill infill_sparse_density + gradual_infill_steps material material_print_temperature material_bed_temperature @@ -320,6 +337,7 @@ class CuraApplication(QtApplication): blackmagic print_sequence infill_mesh + cutting_mesh experimental """.replace("\n", ";").replace(" ", "")) @@ -407,7 +425,7 @@ class CuraApplication(QtApplication): elif instance_type == "variant": path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name) elif instance_type == "definition_changes": - path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name) + path = Resources.getStoragePath(self.ResourceTypes.DefinitionChangesContainer, file_name) if path: instance.setPath(path) @@ -430,16 +448,18 @@ class CuraApplication(QtApplication): mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack)) file_name = urllib.parse.quote_plus(stack.getId()) + "." + mime_type.preferredSuffix - stack_type = stack.getMetaDataEntry("type", None) + path = None - if not stack_type or stack_type == "machine": + if isinstance(stack, GlobalStack): path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name) - elif stack_type == "extruder_train": + elif isinstance(stack, ExtruderStack): path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name) - if path: - stack.setPath(path) - with SaveFile(path, "wt") as f: - f.write(data) + else: + path = Resources.getStoragePath(Resources.ContainerStacks, file_name) + + stack.setPath(path) + with SaveFile(path, "wt") as f: + f.write(data) @pyqtSlot(str, result = QUrl) @@ -803,6 +823,7 @@ class CuraApplication(QtApplication): # Remove all selected objects from the scene. @pyqtSlot() + @deprecated("Moved to CuraActions", "2.6") def deleteSelection(self): if not self.getController().getToolsEnabled(): return @@ -823,6 +844,7 @@ class CuraApplication(QtApplication): ## Remove an object from the scene. # Note that this only removes an object if it is selected. @pyqtSlot("quint64") + @deprecated("Use deleteSelection instead", "2.6") def deleteObject(self, object_id): if not self.getController().getToolsEnabled(): return @@ -850,13 +872,22 @@ class CuraApplication(QtApplication): # \param count number of copies # \param min_offset minimum offset to other objects. @pyqtSlot("quint64", int) + @deprecated("Use CuraActions::multiplySelection", "2.6") def multiplyObject(self, object_id, count, min_offset = 8): - job = MultiplyObjectsJob(object_id, count, min_offset) + node = self.getController().getScene().findObject(object_id) + if not node: + node = Selection.getSelectedObject(0) + + while node.getParent() and node.getParent().callDecoration("isGroup"): + node = node.getParent() + + job = MultiplyObjectsJob([node], count, min_offset) job.start() return ## Center object on platform. @pyqtSlot("quint64") + @deprecated("Use CuraActions::centerSelection", "2.6") def centerObject(self, object_id): node = self.getController().getScene().findObject(object_id) if not node and object_id != 0: # Workaround for tool handles overlapping the selected object @@ -984,7 +1015,9 @@ class CuraApplication(QtApplication): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) if not node.isSelectable(): continue # i.e. node with layer data - nodes.append(node) + # Skip nodes that are too big + if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth: + nodes.append(node) self.arrange(nodes, fixed_nodes = []) ## Arrange Selection @@ -1255,6 +1288,8 @@ class CuraApplication(QtApplication): arranger = Arrange.create(scene_root = root) min_offset = 8 + self.fileLoaded.emit(filename) + for node in nodes: node.setSelectable(True) node.setName(os.path.basename(filename)) @@ -1278,13 +1313,18 @@ class CuraApplication(QtApplication): # If there is no convex hull for the node, start calculating it and continue. if not node.getDecorator(ConvexHullDecorator): node.addDecorator(ConvexHullDecorator()) + for child in node.getAllChildren(): + if not child.getDecorator(ConvexHullDecorator): + child.addDecorator(ConvexHullDecorator()) if node.callDecoration("isSliceable"): - # Find node location - offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset) + # Only check position if it's not already blatantly obvious that it won't fit. + if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth: + # Find node location + offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset) - # Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher - node,_ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10) + # Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher + node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10) op = AddSceneNodeOperation(node, scene.getRoot()) op.push() @@ -1309,3 +1349,13 @@ class CuraApplication(QtApplication): except Exception as e: Logger.log("e", "Could not check file %s: %s", file_url, e) return False + + def _onContextMenuRequested(self, x: float, y: float) -> None: + # Ensure we select the object if we request a context menu over an object without having a selection. + if not Selection.hasSelection(): + node = self.getController().getScene().findObject(self.getRenderer().getRenderPass("selection").getIdAtPosition(x, y)) + if node: + while(node.getParent() and node.getParent().callDecoration("isGroup")): + node = node.getParent() + + Selection.add(node) diff --git a/cura/MultiplyObjectsJob.py b/cura/MultiplyObjectsJob.py index 870f165487..a795e0bc10 100644 --- a/cura/MultiplyObjectsJob.py +++ b/cura/MultiplyObjectsJob.py @@ -24,9 +24,9 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation class MultiplyObjectsJob(Job): - def __init__(self, object_id, count, min_offset = 8): + def __init__(self, objects, count, min_offset = 8): super().__init__() - self._object_id = object_id + self._objects = objects self._count = count self._min_offset = min_offset @@ -35,33 +35,42 @@ class MultiplyObjectsJob(Job): dismissable=False, progress=0) status_message.show() scene = Application.getInstance().getController().getScene() - node = scene.findObject(self._object_id) - if not node and self._object_id != 0: # Workaround for tool handles overlapping the selected object - node = Selection.getSelectedObject(0) - - # If object is part of a group, multiply group - current_node = node - while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): - current_node = current_node.getParent() + total_progress = len(self._objects) * self._count + current_progress = 0 root = scene.getRoot() arranger = Arrange.create(scene_root=root) - offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset) nodes = [] - found_solution_for_all = True - for i in range(self._count): - # We do place the nodes one by one, as we want to yield in between. - node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr) - if not solution_found: - found_solution_for_all = False - new_location = node.getPosition() - new_location = new_location.set(z = 100 - i * 20) - node.setPosition(new_location) + for node in self._objects: + # If object is part of a group, multiply group + current_node = node + while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): + current_node = current_node.getParent() + + node_too_big = False + if node.getBoundingBox().width < 300 or node.getBoundingBox().depth < 300: + offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset) + else: + node_too_big = True + + found_solution_for_all = True + for i in range(self._count): + # We do place the nodes one by one, as we want to yield in between. + if not node_too_big: + node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr) + if node_too_big or not solution_found: + found_solution_for_all = False + new_location = node.getPosition() + new_location = new_location.set(z = 100 - i * 20) + node.setPosition(new_location) + + nodes.append(node) + current_progress += 1 + status_message.setProgress((current_progress / total_progress) * 100) + Job.yieldThread() - nodes.append(node) Job.yieldThread() - status_message.setProgress((i + 1) / self._count * 100) if nodes: op = GroupedOperation() @@ -72,4 +81,4 @@ class MultiplyObjectsJob(Job): if not found_solution_for_all: no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects")) - no_full_solution_message.show() \ No newline at end of file + no_full_solution_message.show() diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index 1eb7aaa7dd..47672a9823 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -31,8 +31,8 @@ catalog = i18nCatalog("cura") # - This triggers a new slice with the current settings - this is the "current settings pass". # - When the slice is done, we update the current print time and material amount. # - If the source of the slice was not a Setting change, we start the second slice pass, the "low quality settings pass". Otherwise we stop here. -# - When that is done, we update the minimum print time and start the final slice pass, the "high quality settings pass". -# - When the high quality pass is done, we update the maximum print time. +# - When that is done, we update the minimum print time and start the final slice pass, the "Extra Fine settings pass". +# - When the Extra Fine pass is done, we update the maximum print time. # # This class also mangles the current machine name and the filename of the first loaded mesh into a job name. # This job name is requested by the JobSpecs qml file. @@ -52,6 +52,19 @@ class PrintInformation(QObject): super().__init__(parent) self._current_print_time = Duration(None, self) + self._print_times_per_feature = { + "none": Duration(None, self), + "inset_0": Duration(None, self), + "inset_x": Duration(None, self), + "skin": Duration(None, self), + "support": Duration(None, self), + "skirt": Duration(None, self), + "infill": Duration(None, self), + "support_infill": Duration(None, self), + "travel": Duration(None, self), + "retract": Duration(None, self), + "support_interface": Duration(None, self) + } self._material_lengths = [] self._material_weights = [] @@ -93,6 +106,10 @@ class PrintInformation(QObject): def currentPrintTime(self): return self._current_print_time + @pyqtProperty("QVariantMap", notify = currentPrintTimeChanged) + def printTimesPerFeature(self): + return self._print_times_per_feature + materialLengthsChanged = pyqtSignal() @pyqtProperty("QVariantList", notify = materialLengthsChanged) @@ -111,12 +128,16 @@ class PrintInformation(QObject): def materialCosts(self): return self._material_costs - def _onPrintDurationMessage(self, total_time, material_amounts): - if total_time != total_time: # Check for NaN. Engine can sometimes give us weird values. - Logger.log("w", "Received NaN for print duration message") - self._current_print_time.setDuration(0) - else: - self._current_print_time.setDuration(total_time) + def _onPrintDurationMessage(self, time_per_feature, material_amounts): + total_time = 0 + for feature, time in time_per_feature.items(): + if time != time: # Check for NaN. Engine can sometimes give us weird values. + self._print_times_per_feature[feature].setDuration(0) + Logger.log("w", "Received NaN for print duration message") + continue + total_time += time + self._print_times_per_feature[feature].setDuration(time) + self._current_print_time.setDuration(total_time) self.currentPrintTimeChanged.emit() @@ -183,7 +204,10 @@ class PrintInformation(QObject): def _onActiveMaterialChanged(self): if self._active_material_container: - self._active_material_container.metaDataChanged.disconnect(self._onMaterialMetaDataChanged) + try: + self._active_material_container.metaDataChanged.disconnect(self._onMaterialMetaDataChanged) + except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that is already disconnected. + pass active_material_id = Application.getInstance().getMachineManager().activeMaterialId active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id=active_material_id) diff --git a/cura/QualityManager.py b/cura/QualityManager.py index d7b2c7d705..e4a09ca8ea 100644 --- a/cura/QualityManager.py +++ b/cura/QualityManager.py @@ -3,7 +3,7 @@ # This collects a lot of quality and quality changes related code which was split between ContainerManager # and the MachineManager and really needs to usable from both. -from typing import List +from typing import List, Optional, Dict, TYPE_CHECKING from UM.Application import Application from UM.Settings.ContainerRegistry import ContainerRegistry @@ -11,14 +11,18 @@ from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.InstanceContainer import InstanceContainer from cura.Settings.ExtruderManager import ExtruderManager +if TYPE_CHECKING: + from cura.Settings.GlobalStack import GlobalStack + from cura.Settings.ExtruderStack import ExtruderStack + from UM.Settings.DefinitionContainer import DefinitionContainerInterface class QualityManager: ## Get the singleton instance for this class. @classmethod - def getInstance(cls): + def getInstance(cls) -> "QualityManager": # Note: Explicit use of class name to prevent issues with inheritance. - if QualityManager.__instance is None: + if not QualityManager.__instance: QualityManager.__instance = cls() return QualityManager.__instance @@ -27,12 +31,12 @@ class QualityManager: ## Find a quality by name for a specific machine definition and materials. # # \param quality_name - # \param machine_definition (Optional) \type{ContainerInstance} If nothing is + # \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is # specified then the currently selected machine definition is used. - # \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then + # \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then # the current set of selected materials is used. - # \return the matching quality container \type{ContainerInstance} - def findQualityByName(self, quality_name, machine_definition=None, material_containers=None): + # \return the matching quality container \type{InstanceContainer} + def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None) -> Optional[InstanceContainer]: criteria = {"type": "quality", "name": quality_name} result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria) @@ -46,12 +50,10 @@ class QualityManager: ## Find a quality changes container by name. # # \param quality_changes_name \type{str} the name of the quality changes container. - # \param machine_definition (Optional) \type{ContainerInstance} If nothing is - # specified then the currently selected machine definition is used. - # \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then - # the current set of selected materials is used. - # \return the matching quality changes containers \type{List[ContainerInstance]} - def findQualityChangesByName(self, quality_changes_name, machine_definition=None): + # \param machine_definition (Optional) \type{DefinitionContainer} If nothing is + # specified then the currently selected machine definition is used.. + # \return the matching quality changes containers \type{List[InstanceContainer]} + def findQualityChangesByName(self, quality_changes_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None): criteria = {"type": "quality_changes", "name": quality_changes_name} result = self._getFilteredContainersForStack(machine_definition, [], **criteria) @@ -62,7 +64,7 @@ class QualityManager: # \param machine_definition \type{DefinitionContainer} # \param material_containers \type{List[InstanceContainer]} # \return \type{List[str]} - def findAllQualityTypesForMachineAndMaterials(self, machine_definition, material_containers): + def findAllQualityTypesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[str]: # Determine the common set of quality types which can be # applied to all of the materials for this machine. quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0]) @@ -76,9 +78,9 @@ class QualityManager: ## Fetches a dict of quality types names to quality profiles for a combination of machine and material. # # \param machine_definition \type{DefinitionContainer} the machine definition. - # \param material \type{ContainerInstance} the material. - # \return \type{Dict[str, ContainerInstance]} the dict of suitable quality type names mapping to qualities. - def __fetchQualityTypeDictForMaterial(self, machine_definition, material): + # \param material \type{InstanceContainer} the material. + # \return \type{Dict[str, InstanceContainer]} the dict of suitable quality type names mapping to qualities. + def __fetchQualityTypeDictForMaterial(self, machine_definition: "DefinitionContainerInterface", material: InstanceContainer) -> Dict[str, InstanceContainer]: qualities = self.findAllQualitiesForMachineMaterial(machine_definition, material) quality_type_dict = {} for quality in qualities: @@ -88,12 +90,12 @@ class QualityManager: ## Find a quality container by quality type. # # \param quality_type \type{str} the name of the quality type to search for. - # \param machine_definition (Optional) \type{ContainerInstance} If nothing is + # \param machine_definition (Optional) \type{InstanceContainer} If nothing is # specified then the currently selected machine definition is used. - # \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then + # \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then # the current set of selected materials is used. - # \return the matching quality container \type{ContainerInstance} - def findQualityByQualityType(self, quality_type, machine_definition=None, material_containers=None, **kwargs): + # \return the matching quality container \type{InstanceContainer} + def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None, **kwargs) -> InstanceContainer: criteria = kwargs criteria["type"] = "quality" if quality_type: @@ -110,9 +112,9 @@ class QualityManager: ## Find all suitable qualities for a combination of machine and material. # # \param machine_definition \type{DefinitionContainer} the machine definition. - # \param material_container \type{ContainerInstance} the material. - # \return \type{List[ContainerInstance]} the list of suitable qualities. - def findAllQualitiesForMachineMaterial(self, machine_definition, material_container): + # \param material_container \type{InstanceContainer} the material. + # \return \type{List[InstanceContainer]} the list of suitable qualities. + def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]: criteria = {"type": "quality" } result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria) if not result: @@ -125,7 +127,7 @@ class QualityManager: # # \param machine_definition \type{DefinitionContainer} the machine definition. # \return \type{List[InstanceContainer]} the list of quality changes - def findAllQualityChangesForMachine(self, machine_definition: DefinitionContainer) -> List[InstanceContainer]: + def findAllQualityChangesForMachine(self, machine_definition: "DefinitionContainerInterface") -> List[InstanceContainer]: if machine_definition.getMetaDataEntry("has_machine_quality"): definition_id = machine_definition.getId() else: @@ -141,19 +143,19 @@ class QualityManager: # Only one quality per quality type is returned. i.e. if there are 2 qualities with quality_type=normal # then only one of then is returned (at random). # - # \param global_container_stack \type{ContainerStack} the global machine definition - # \param extruder_stacks \type{List[ContainerStack]} the list of extruder stacks + # \param global_container_stack \type{GlobalStack} the global machine definition + # \param extruder_stacks \type{List[ExtruderStack]} the list of extruder stacks # \return \type{List[InstanceContainer]} the list of the matching qualities. The quality profiles # return come from the first extruder in the given list of extruders. - def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack, extruder_stacks): + def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack: "GlobalStack", extruder_stacks: List["ExtruderStack"]) -> List[InstanceContainer]: global_machine_definition = global_container_stack.getBottom() if extruder_stacks: # Multi-extruder machine detected. - materials = [stack.findContainer(type="material") for stack in extruder_stacks] + materials = [stack.material for stack in extruder_stacks] else: # Machine with one extruder. - materials = [global_container_stack.findContainer(type="material")] + materials = [global_container_stack.material] quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials) @@ -170,7 +172,7 @@ class QualityManager: # This tries to find a generic or basic version of the given material. # \param material_container \type{InstanceContainer} the material # \return \type{List[InstanceContainer]} a list of the basic materials or an empty list if one could not be found. - def _getBasicMaterials(self, material_container): + def _getBasicMaterials(self, material_container: InstanceContainer): base_material = material_container.getMetaDataEntry("material") material_container_definition = material_container.getDefinition() if material_container_definition and material_container_definition.getMetaDataEntry("has_machine_quality"): @@ -192,7 +194,7 @@ class QualityManager: def _getFilteredContainers(self, **kwargs): return self._getFilteredContainersForStack(None, None, **kwargs) - def _getFilteredContainersForStack(self, machine_definition=None, material_containers=None, **kwargs): + def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_containers: List[InstanceContainer] = None, **kwargs): # Fill in any default values. if machine_definition is None: machine_definition = Application.getInstance().getGlobalContainerStack().getBottom() @@ -202,7 +204,8 @@ class QualityManager: if material_containers is None: active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() - material_containers = [stack.findContainer(type="material") for stack in active_stacks] + if active_stacks: + material_containers = [stack.material for stack in active_stacks] criteria = kwargs filter_by_material = False @@ -216,12 +219,11 @@ class QualityManager: filter_by_material = whole_machine_definition.getMetaDataEntry("has_materials") else: criteria["definition"] = "fdmprinter" - + material_ids = set() # Stick the material IDs in a set if material_containers is None or len(material_containers) == 0: filter_by_material = False else: - material_ids = set() for material_instance in material_containers: if material_instance is not None: # Add the parent material too. @@ -245,7 +247,7 @@ class QualityManager: # an extruder definition. # \return \type{DefinitionContainer} the parent machine definition. If the given machine # definition doesn't have a parent then it is simply returned. - def getParentMachineDefinition(self, machine_definition: DefinitionContainer) -> DefinitionContainer: + def getParentMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface": container_registry = ContainerRegistry.getInstance() machine_entry = machine_definition.getMetaDataEntry("machine") @@ -274,8 +276,8 @@ class QualityManager: # # \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or # an extruder definition. - # \return \type{DefinitionContainer} - def getWholeMachineDefinition(self, machine_definition): + # \return \type{DefinitionContainerInterface} + def getWholeMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface": machine_entry = machine_definition.getMetaDataEntry("machine") if machine_entry is None: # This already is a 'global' machine definition. diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index bac11f78cf..b5c76f1f17 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -1,13 +1,15 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. import os.path import urllib +import uuid from typing import Dict, Union from PyQt5.QtCore import QObject, QUrl, QVariant from UM.FlameProfiler import pyqtSlot from PyQt5.QtWidgets import QMessageBox +from UM.Util import parseBool from UM.PluginRegistry import PluginRegistry from UM.SaveFile import SaveFile @@ -429,7 +431,7 @@ class ContainerManager(QObject): for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): # Find the quality_changes container for this stack and merge the contents of the top container into it. - quality_changes = stack.findContainer(type = "quality_changes") + quality_changes = stack.qualityChanges if not quality_changes or quality_changes.isReadOnly(): Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId()) continue @@ -482,8 +484,8 @@ class ContainerManager(QObject): # Go through the active stacks and create quality_changes containers from the user containers. for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): user_container = stack.getTop() - quality_container = stack.findContainer(type = "quality") - quality_changes_container = stack.findContainer(type = "quality_changes") + quality_container = stack.quality + quality_changes_container = stack.qualityChanges if not quality_container or not quality_changes_container: Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId()) continue @@ -604,7 +606,7 @@ class ContainerManager(QObject): machine_definition = global_stack.getBottom() active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() - material_containers = [stack.findContainer(type="material") for stack in active_stacks] + material_containers = [stack.material for stack in active_stacks] result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name, QualityManager.getInstance().getParentMachineDefinition(machine_definition), @@ -671,6 +673,9 @@ class ContainerManager(QObject): return new_change_instances + ## Create a duplicate of a material, which has the same GUID and base_file metadata + # + # \return \type{str} the id of the newly created container. @pyqtSlot(str, result = str) def duplicateMaterial(self, material_id: str) -> str: containers = self._container_registry.findInstanceContainers(id=material_id) @@ -693,6 +698,104 @@ class ContainerManager(QObject): duplicated_container.deserialize(f.read()) duplicated_container.setDirty(True) self._container_registry.addContainer(duplicated_container) + return self._getMaterialContainerIdForActiveMachine(new_id) + + ## Create a new material by cloning Generic PLA and setting the GUID to something unqiue + # + # \return \type{str} the id of the newly created container. + @pyqtSlot(result = str) + def createMaterial(self) -> str: + # Ensure all settings are saved. + Application.getInstance().saveSettings() + + containers = self._container_registry.findInstanceContainers(id="generic_pla") + if not containers: + Logger.log("d", "Unable to create a new material by cloning generic_pla, because it doesn't exist.") + return "" + + # Create a new ID & container to hold the data. + new_id = self._container_registry.uniqueName("custom_material") + container_type = type(containers[0]) # Could be either a XMLMaterialProfile or a InstanceContainer + duplicated_container = container_type(new_id) + + # Instead of duplicating we load the data from the basefile again. + # This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile + # are also correctly created. + with open(containers[0].getPath(), encoding="utf-8") as f: + duplicated_container.deserialize(f.read()) + + duplicated_container.setMetaDataEntry("GUID", str(uuid.uuid4())) + duplicated_container.setMetaDataEntry("brand", catalog.i18nc("@label", "Custom")) + duplicated_container.setMetaDataEntry("material", catalog.i18nc("@label", "Custom")) + duplicated_container.setName(catalog.i18nc("@label", "Custom Material")) + + self._container_registry.addContainer(duplicated_container) + return self._getMaterialContainerIdForActiveMachine(new_id) + + ## Find the id of a material container based on the new material + # Utilty function that is shared between duplicateMaterial and createMaterial + # + # \param base_file \type{str} the id of the created container. + def _getMaterialContainerIdForActiveMachine(self, base_file): + global_stack = Application.getInstance().getGlobalContainerStack() + if not global_stack: + return base_file + + has_machine_materials = parseBool(global_stack.getMetaDataEntry("has_machine_materials", default = False)) + has_variant_materials = parseBool(global_stack.getMetaDataEntry("has_variant_materials", default = False)) + has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False)) + if has_machine_materials or has_variant_materials: + if has_variants: + materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId) + else: + materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId()) + + if materials: + return materials[0].getId() + + Logger.log("w", "Unable to find a suitable container based on %s for the current machine .", base_file) + return "" # do not activate a new material if a container can not be found + + return base_file + + ## Get a list of materials that have the same GUID as the reference material + # + # \param material_id \type{str} the id of the material for which to get the linked materials. + # \return \type{list} a list of names of materials with the same GUID + @pyqtSlot(str, result = "QStringList") + def getLinkedMaterials(self, material_id: str): + containers = self._container_registry.findInstanceContainers(id=material_id) + if not containers: + Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id) + return [] + + material_container = containers[0] + material_base_file = material_container.getMetaDataEntry("base_file", "") + material_guid = material_container.getMetaDataEntry("GUID", "") + if not material_guid: + Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id) + return [] + + containers = self._container_registry.findInstanceContainers(type = "material", GUID = material_guid) + linked_material_names = [] + for container in containers: + if container.getId() in [material_id, material_base_file] or container.getMetaDataEntry("base_file") != container.getId(): + continue + + linked_material_names.append(container.getName()) + return linked_material_names + + ## Unlink a material from all other materials by creating a new GUID + # \param material_id \type{str} the id of the material to create a new GUID for. + @pyqtSlot(str) + def unlinkMaterial(self, material_id: str): + containers = self._container_registry.findInstanceContainers(id=material_id) + if not containers: + Logger.log("d", "Unable to make the material with id %s unique, because it doesn't exist.", material_id) + return "" + + containers[0].setMetaDataEntry("GUID", str(uuid.uuid4())) + ## Get the singleton instance for this class. @classmethod @@ -815,6 +918,8 @@ class ContainerManager(QObject): quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0]) else: quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition)) + from cura.CuraApplication import CuraApplication + quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) return quality_changes diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 3982418070..7961e8db31 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. import os @@ -6,6 +6,7 @@ import os.path import re 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.InstanceContainer import InstanceContainer @@ -16,8 +17,12 @@ from UM.Platform import Platform from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with. from UM.Util import parseBool -from cura.Settings.ExtruderManager import ExtruderManager -from cura.Settings.ContainerManager import ContainerManager +from . import ExtruderStack +from . import GlobalStack +from .ContainerManager import ContainerManager +from .ExtruderManager import ExtruderManager + +from cura.CuraApplication import CuraApplication from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -26,6 +31,28 @@ class CuraContainerRegistry(ContainerRegistry): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + ## Overridden from ContainerRegistry + # + # Adds a container to the registry. + # + # This will also try to convert a ContainerStack to either Extruder or + # 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. + required_setting_version = CuraApplication.SettingVersion + actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0)) + if required_setting_version != actual_setting_version: + Logger.log("w", "Instance container {container_id} is outdated. Its setting version is {actual_setting_version} but it should be {required_setting_version}.".format(container_id = container.getId(), actual_setting_version = actual_setting_version, required_setting_version = required_setting_version)) + return #Don't add. + + super().addContainer(container) + ## 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 @@ -212,6 +239,11 @@ class CuraContainerRegistry(ContainerRegistry): # If it hasn't returned by now, none of the plugins loaded the profile successfully. return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)} + @override(ContainerRegistry) + def load(self): + super().load() + self._fixupExtruders() + def _configureProfile(self, profile, id_seed, new_name): profile.setReadOnly(False) profile.setDirty(True) # Ensure the profiles are correctly saved @@ -284,3 +316,47 @@ class CuraContainerRegistry(ContainerRegistry): if global_container_stack: return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False)) return False + + ## Convert an "old-style" pure ContainerStack to either an Extruder or Global stack. + def _convertContainerStack(self, container): + assert type(container) == ContainerStack + + container_type = container.getMetaDataEntry("type") + if container_type not in ("extruder_train", "machine"): + # It is not an extruder or machine, so do nothing with the stack + return container + + Logger.log("d", "Converting ContainerStack {stack} to {type}", stack = container.getId(), type = container_type) + + new_stack = None + if container_type == "extruder_train": + new_stack = ExtruderStack.ExtruderStack(container.getId()) + else: + new_stack = GlobalStack.GlobalStack(container.getId()) + + container_contents = container.serialize() + new_stack.deserialize(container_contents) + + # Delete the old configuration file so we do not get double stacks + if os.path.isfile(container.getPath()): + os.remove(container.getPath()) + + return new_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): + extruder_stacks = self.findContainers(ExtruderStack.ExtruderStack) + for extruder_stack in extruder_stacks: + if extruder_stack.getNextStack(): + # Has the right next stack, so ignore it. + continue + + machines = ContainerRegistry.getInstance().findContainerStacks(id=extruder_stack.getMetaDataEntry("machine", "")) + if machines: + extruder_stack.setNextStack(machines[0]) + else: + Logger.log("w", "Could not find machine {machine} for extruder {extruder}", machine = extruder_stack.getMetaDataEntry("machine"), extruder = extruder_stack.getId()) diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py new file mode 100755 index 0000000000..d14f2d04c9 --- /dev/null +++ b/cura/Settings/CuraContainerStack.py @@ -0,0 +1,616 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +import os.path + +from typing import Any, Optional + +from PyQt5.QtCore import pyqtProperty, pyqtSignal +from UM.FlameProfiler import pyqtSlot + +from UM.Decorators import override +from UM.Logger import Logger +from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError +from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.Interfaces import ContainerInterface + +from . import Exceptions + + +## Base class for Cura related stacks that want to enforce certain containers are available. +# +# This class makes sure that the stack has the following containers set: user changes, quality +# changes, quality, material, variant, definition changes and finally definition. Initially, +# these will be equal to the empty instance container. +# +# The container types are determined based on the following criteria: +# - user: An InstanceContainer with the metadata entry "type" set to "user". +# - quality changes: An InstanceContainer with the metadata entry "type" set to "quality_changes". +# - quality: An InstanceContainer with the metadata entry "type" set to "quality". +# - material: An InstanceContainer with the metadata entry "type" set to "material". +# - variant: An InstanceContainer with the metadata entry "type" set to "variant". +# - definition changes: An InstanceContainer with the metadata entry "type" set to "definition_changes". +# - definition: A DefinitionContainer. +# +# Internally, this class ensures the mentioned containers are always there and kept in a specific order. +# This also means that operations on the stack that modifies the container ordering is prohibited and +# will raise an exception. +class CuraContainerStack(ContainerStack): + def __init__(self, container_id: str, *args, **kwargs): + super().__init__(container_id, *args, **kwargs) + + self._empty_instance_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + + self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] + + self.containersChanged.connect(self._onContainersChanged) + + # This is emitted whenever the containersChanged signal from the ContainerStack base class is emitted. + pyqtContainersChanged = pyqtSignal() + + ## Set the user changes container. + # + # \param new_user_changes The new user changes container. It is expected to have a "type" metadata entry with the value "user". + def setUserChanges(self, new_user_changes: InstanceContainer) -> None: + self.replaceContainer(_ContainerIndexes.UserChanges, new_user_changes) + + ## Get the user changes container. + # + # \return The user changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(InstanceContainer, fset = setUserChanges, notify = pyqtContainersChanged) + def userChanges(self) -> InstanceContainer: + return self._containers[_ContainerIndexes.UserChanges] + + ## Set the quality changes container. + # + # \param new_quality_changes The new quality changes container. It is expected to have a "type" metadata entry with the value "quality_changes". + def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit = False) -> None: + self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit) + + ## Set the quality changes container by an ID. + # + # This will search for the specified container and set it. If no container was found, an error will be raised. + # + # \param new_quality_changes_id The ID of the new quality changes container. + # + # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. + def setQualityChangesById(self, new_quality_changes_id: str) -> None: + quality_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_changes_id) + if quality_changes: + self.setQualityChanges(quality_changes[0]) + else: + raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_changes_id)) + + ## Get the quality changes container. + # + # \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(InstanceContainer, fset = setQualityChanges, notify = pyqtContainersChanged) + def qualityChanges(self) -> InstanceContainer: + return self._containers[_ContainerIndexes.QualityChanges] + + ## Set the quality container. + # + # \param new_quality The new quality container. It is expected to have a "type" metadata entry with the value "quality". + def setQuality(self, new_quality: InstanceContainer, postpone_emit = False) -> None: + self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit) + + ## Set the quality container by an ID. + # + # This will search for the specified container and set it. If no container was found, an error will be raised. + # There is a special value for ID, which is "default". The "default" value indicates the quality should be set + # to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultQuality + # for details. + # + # \param new_quality_id The ID of the new quality container. + # + # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. + def setQualityById(self, new_quality_id: str) -> None: + quality = self._empty_instance_container + if new_quality_id == "default": + new_quality = self.findDefaultQuality() + if new_quality: + quality = new_quality + else: + qualities = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_id) + if qualities: + quality = qualities[0] + else: + raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_id)) + + self.setQuality(quality) + + ## Get the quality container. + # + # \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(InstanceContainer, fset = setQuality, notify = pyqtContainersChanged) + def quality(self) -> InstanceContainer: + return self._containers[_ContainerIndexes.Quality] + + ## Set the material container. + # + # \param new_quality_changes The new material container. It is expected to have a "type" metadata entry with the value "quality_changes". + def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None: + self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit) + + ## Set the material container by an ID. + # + # This will search for the specified container and set it. If no container was found, an error will be raised. + # There is a special value for ID, which is "default". The "default" value indicates the quality should be set + # to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultMaterial + # for details. + # + # \param new_quality_changes_id The ID of the new material container. + # + # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. + def setMaterialById(self, new_material_id: str) -> None: + material = self._empty_instance_container + if new_material_id == "default": + new_material = self.findDefaultMaterial() + if new_material: + material = new_material + else: + materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id) + if materials: + material = materials[0] + else: + raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_material_id)) + + self.setMaterial(material) + + ## Get the material container. + # + # \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(InstanceContainer, fset = setMaterial, notify = pyqtContainersChanged) + def material(self) -> InstanceContainer: + return self._containers[_ContainerIndexes.Material] + + ## Set the variant container. + # + # \param new_quality_changes The new variant container. It is expected to have a "type" metadata entry with the value "quality_changes". + def setVariant(self, new_variant: InstanceContainer) -> None: + self.replaceContainer(_ContainerIndexes.Variant, new_variant) + + ## Set the variant container by an ID. + # + # This will search for the specified container and set it. If no container was found, an error will be raised. + # There is a special value for ID, which is "default". The "default" value indicates the quality should be set + # to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultVariant + # for details. + # + # \param new_quality_changes_id The ID of the new variant container. + # + # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. + def setVariantById(self, new_variant_id: str) -> None: + variant = self._empty_instance_container + if new_variant_id == "default": + new_variant = self.findDefaultVariant() + if new_variant: + variant = new_variant + else: + variants = ContainerRegistry.getInstance().findInstanceContainers(id = new_variant_id) + if variants: + variant = variants[0] + else: + raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_variant_id)) + + self.setVariant(variant) + + ## Get the variant container. + # + # \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(InstanceContainer, fset = setVariant, notify = pyqtContainersChanged) + def variant(self) -> InstanceContainer: + return self._containers[_ContainerIndexes.Variant] + + ## Set the definition changes container. + # + # \param new_quality_changes The new definition changes container. It is expected to have a "type" metadata entry with the value "quality_changes". + def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None: + self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes) + + ## Set the definition changes container by an ID. + # + # \param new_quality_changes_id The ID of the new definition changes container. + # + # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. + def setDefinitionChangesById(self, new_definition_changes_id: str) -> None: + new_definition_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_definition_changes_id) + if new_definition_changes: + self.setDefinitionChanges(new_definition_changes[0]) + else: + raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_changes_id)) + + ## Get the definition changes container. + # + # \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(InstanceContainer, fset = setDefinitionChanges, notify = pyqtContainersChanged) + def definitionChanges(self) -> InstanceContainer: + return self._containers[_ContainerIndexes.DefinitionChanges] + + ## Set the definition container. + # + # \param new_quality_changes The new definition container. It is expected to have a "type" metadata entry with the value "quality_changes". + def setDefinition(self, new_definition: DefinitionContainer) -> None: + self.replaceContainer(_ContainerIndexes.Definition, new_definition) + + ## Set the definition container by an ID. + # + # \param new_quality_changes_id The ID of the new definition container. + # + # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. + def setDefinitionById(self, new_definition_id: str) -> None: + new_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = new_definition_id) + if new_definition: + self.setDefinition(new_definition[0]) + else: + raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_id)) + + ## Get the definition container. + # + # \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(DefinitionContainer, fset = setDefinition, notify = pyqtContainersChanged) + def definition(self) -> DefinitionContainer: + return self._containers[_ContainerIndexes.Definition] + + @override(ContainerStack) + def getBottom(self) -> "DefinitionContainer": + return self.definition + + @override(ContainerStack) + def getTop(self) -> "InstanceContainer": + return self.userChanges + + ## Check whether the specified setting has a 'user' value. + # + # A user value here is defined as the setting having a value in either + # the UserChanges or QualityChanges container. + # + # \return True if the setting has a user value, False if not. + @pyqtSlot(str, result = bool) + def hasUserValue(self, key: str) -> bool: + if self._containers[_ContainerIndexes.UserChanges].hasProperty(key, "value"): + return True + + if self._containers[_ContainerIndexes.QualityChanges].hasProperty(key, "value"): + return True + + return False + + ## Set a property of a setting. + # + # This will set a property of a specified setting. Since the container stack does not contain + # any settings itself, it is required to specify a container to set the property on. The target + # container is matched by container type. + # + # \param key The key of the setting to set. + # \param property_name The name of the property to set. + # \param new_value The new value to set the property to. + # \param target_container The type of the container to set the property of. Defaults to "user". + def setProperty(self, key: str, property_name: str, new_value: Any, target_container: str = "user") -> None: + container_index = _ContainerIndexes.TypeIndexMap.get(target_container, -1) + if container_index != -1: + self._containers[container_index].setProperty(key, property_name, new_value) + else: + raise IndexError("Invalid target container {type}".format(type = target_container)) + + ## Overridden from ContainerStack + # + # Since we have a fixed order of containers in the stack and this method would modify the container + # ordering, we disallow this operation. + @override(ContainerStack) + def addContainer(self, container: ContainerInterface) -> None: + raise Exceptions.InvalidOperationError("Cannot add a container to Global stack") + + ## Overridden from ContainerStack + # + # Since we have a fixed order of containers in the stack and this method would modify the container + # ordering, we disallow this operation. + @override(ContainerStack) + def insertContainer(self, index: int, container: ContainerInterface) -> None: + raise Exceptions.InvalidOperationError("Cannot insert a container into Global stack") + + ## Overridden from ContainerStack + # + # Since we have a fixed order of containers in the stack and this method would modify the container + # ordering, we disallow this operation. + @override(ContainerStack) + def removeContainer(self, index: int = 0) -> None: + raise Exceptions.InvalidOperationError("Cannot remove a container from Global stack") + + ## Overridden from ContainerStack + # + # Replaces the container at the specified index with another container. + # This version performs checks to make sure the new container has the expected metadata and type. + # + # \throws Exception.InvalidContainerError Raised when trying to replace a container with a container that has an incorrect type. + @override(ContainerStack) + def replaceContainer(self, index: int, container: ContainerInterface, postpone_emit: bool = False) -> None: + expected_type = _ContainerIndexes.IndexTypeMap[index] + if expected_type == "definition": + if not isinstance(container, DefinitionContainer): + raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not a DefinitionContainer".format(index = index)) + elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type: + raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type.".format(index = index, type = expected_type, actual_type = container.getMetaDataEntry("type"))) + + super().replaceContainer(index, container, postpone_emit) + + ## Overridden from ContainerStack + # + # This deserialize will make sure the internal list of containers matches with what we expect. + # It will first check to see if the container at a certain index already matches with what we + # expect. If it does not, it will search for a matching container with the correct type. Should + # no container with the correct type be found, it will use the empty container. + # + # \throws InvalidContainerStackError Raised when no definition can be found for the stack. + @override(ContainerStack) + def deserialize(self, contents: str) -> None: + super().deserialize(contents) + + new_containers = self._containers.copy() + while len(new_containers) < len(_ContainerIndexes.IndexTypeMap): + new_containers.append(self._empty_instance_container) + + # Validate and ensure the list of containers matches with what we expect + for index, type_name in _ContainerIndexes.IndexTypeMap.items(): + try: + container = new_containers[index] + except IndexError: + container = None + + if type_name == "definition": + if not container or not isinstance(container, DefinitionContainer): + definition = self.findContainer(container_type = DefinitionContainer) + if not definition: + raise InvalidContainerStackError("Stack {id} does not have a definition!".format(id = self._id)) + + new_containers[index] = definition + continue + + if not container or container.getMetaDataEntry("type") != type_name: + actual_container = self.findContainer(type = type_name) + if actual_container: + new_containers[index] = actual_container + else: + new_containers[index] = self._empty_instance_container + + self._containers = new_containers + + ## Find the variant that should be used as "default" variant. + # + # This will search for variants that match the current definition and pick the preferred one, + # if specified by the machine definition. + # + # The following criteria are used to find the default variant: + # - If the machine definition does not have a metadata entry "has_variants" set to True, return None + # - The definition of the variant should be the same as the machine definition for this stack. + # - The container should have a metadata entry "type" with value "variant". + # - If the machine definition has a metadata entry "preferred_variant", filter the variant IDs based on that. + # + # \return The container that should be used as default, or None if nothing was found or the machine does not use variants. + # + # \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"): + # If the machine does not use variants, we should never set a variant. + return None + + # First add any variant. Later, overwrite with preference if the preference is valid. + variant = None + definition_id = self._findInstanceContainerDefinitionId(definition) + variants = ContainerRegistry.getInstance().findInstanceContainers(definition = definition_id, type = "variant") + if variants: + variant = variants[0] + + preferred_variant_id = definition.getMetaDataEntry("preferred_variant") + if preferred_variant_id: + preferred_variants = ContainerRegistry.getInstance().findInstanceContainers(id = preferred_variant_id, definition = definition_id, type = "variant") + if preferred_variants: + variant = preferred_variants[0] + else: + Logger.log("w", "The preferred variant \"{variant}\" of stack {stack} does not exist or is not a variant.", variant = preferred_variant_id, stack = self.id) + # And leave it at the default variant. + + if variant: + return variant + + Logger.log("w", "Could not find a valid default variant for stack {stack}", stack = self.id) + return None + + ## Find the material that should be used as "default" material. + # + # This will search for materials that match the current definition and pick the preferred one, + # if specified by the machine definition. + # + # The following criteria are used to find the default material: + # - If the machine definition does not have a metadata entry "has_materials" set to True, return None + # - If the machine definition has a metadata entry "has_machine_materials", the definition of the material should + # be the same as the machine definition for this stack. Otherwise, the definition should be "fdmprinter". + # - The container should have a metadata entry "type" with value "material". + # - If the machine definition has a metadata entry "has_variants" and set to True, the "variant" metadata entry of + # the material should be the same as the ID of the variant in the stack. Only applies if "has_machine_materials" is also True. + # - If the stack currently has a material set, try to find a material that matches the current material by name. + # - Otherwise, if the machine definition has a metadata entry "preferred_material", try to find a material that matches the specified ID. + # + # \return The container that should be used as default, or None if nothing was found or the machine does not use materials. + def findDefaultMaterial(self) -> Optional[ContainerInterface]: + definition = self._getMachineDefinition() + if not definition.getMetaDataEntry("has_materials"): + # Machine does not use materials, never try to set it. + return None + + search_criteria = {"type": "material"} + if definition.getMetaDataEntry("has_machine_materials"): + search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition) + + if definition.getMetaDataEntry("has_variants"): + search_criteria["variant"] = self.variant.id + else: + search_criteria["definition"] = "fdmprinter" + + if self.material != self._empty_instance_container: + search_criteria["name"] = self.material.name + else: + preferred_material = definition.getMetaDataEntry("preferred_material") + if preferred_material: + search_criteria["id"] = preferred_material + + materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) + if not materials: + Logger.log("w", "The preferred material \"{material}\" could not be found for stack {stack}", material = preferred_material, stack = self.id) + # We failed to find any materials matching the specified criteria, drop some specific criteria and try to find + # a material that sort-of matches what we want. + search_criteria.pop("variant", None) + search_criteria.pop("id", None) + search_criteria.pop("name", None) + materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) + + if materials: + return materials[0] + + Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id) + return None + + ## Find the quality that should be used as "default" quality. + # + # This will search for qualities that match the current definition and pick the preferred one, + # if specified by the machine definition. + # + # \return The container that should be used as default, or None if nothing was found. + def findDefaultQuality(self) -> Optional[ContainerInterface]: + definition = self._getMachineDefinition() + registry = ContainerRegistry.getInstance() + material_container = self.material if self.material != self._empty_instance_container else None + + search_criteria = {"type": "quality"} + + if definition.getMetaDataEntry("has_machine_quality"): + search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition) + + if definition.getMetaDataEntry("has_materials") and material_container: + search_criteria["material"] = material_container.id + else: + search_criteria["definition"] = "fdmprinter" + + if self.quality != self._empty_instance_container: + search_criteria["name"] = self.quality.name + else: + preferred_quality = definition.getMetaDataEntry("preferred_quality") + if preferred_quality: + search_criteria["id"] = preferred_quality + + containers = registry.findInstanceContainers(**search_criteria) + if containers: + return containers[0] + + if "material" in search_criteria: + # First check if we can solve our material not found problem by checking if we can find quality containers + # that are assigned to the parents of this material profile. + try: + inherited_files = material_container.getInheritedFiles() + except AttributeError: # Material_container does not support inheritance. + inherited_files = [] + + if inherited_files: + for inherited_file in inherited_files: + # Extract the ID from the path we used to load the file. + search_criteria["material"] = os.path.basename(inherited_file).split(".")[0] + containers = registry.findInstanceContainers(**search_criteria) + if containers: + return containers[0] + + # We still weren't able to find a quality for this specific material. + # Try to find qualities for a generic version of the material. + material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"} + if definition.getMetaDataEntry("has_machine_quality"): + if self.material != self._empty_instance_container: + material_search_criteria["definition"] = material_container.getDefinition().id + + if definition.getMetaDataEntry("has_variants"): + material_search_criteria["variant"] = material_container.getMetaDataEntry("variant") + else: + material_search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition) + + if definition.getMetaDataEntry("has_variants") and self.variant != self._empty_instance_container: + material_search_criteria["variant"] = self.variant.id + else: + material_search_criteria["definition"] = "fdmprinter" + material_containers = registry.findInstanceContainers(**material_search_criteria) + # Try all materials to see if there is a quality profile available. + for material_container in material_containers: + search_criteria["material"] = material_container.getId() + + containers = registry.findInstanceContainers(**search_criteria) + if containers: + return containers[0] + + if "name" in search_criteria or "id" in search_criteria: + # If a quality by this name can not be found, try a wider set of search criteria + search_criteria.pop("name", None) + search_criteria.pop("id", None) + + containers = registry.findInstanceContainers(**search_criteria) + if containers: + return containers[0] + + return None + + ## protected: + + # Helper to make sure we emit a PyQt signal on container changes. + def _onContainersChanged(self, container: Any) -> None: + self.pyqtContainersChanged.emit() + + # Helper that can be overridden to get the "machine" definition, that is, the definition that defines the machine + # and its properties rather than, for example, the extruder. Defaults to simply returning the definition property. + def _getMachineDefinition(self) -> DefinitionContainer: + return self.definition + + ## Find the ID that should be used when searching for instance containers for a specified definition. + # + # This handles the situation where the definition specifies we should use a different definition when + # searching for instance containers. + # + # \param machine_definition The definition to find the "quality definition" for. + # + # \return The ID of the definition container to use when searching for instance containers. + @classmethod + def _findInstanceContainerDefinitionId(cls, machine_definition: DefinitionContainer) -> str: + quality_definition = machine_definition.getMetaDataEntry("quality_definition") + if not quality_definition: + return machine_definition.id + + definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition) + if not definitions: + Logger.log("w", "Unable to find parent definition {parent} for machine {machine}", parent = quality_definition, machine = machine_definition.id) + return machine_definition.id + + return cls._findInstanceContainerDefinitionId(definitions[0]) + +## private: + +# Private helper class to keep track of container positions and their types. +class _ContainerIndexes: + UserChanges = 0 + QualityChanges = 1 + Quality = 2 + Material = 3 + Variant = 4 + DefinitionChanges = 5 + Definition = 6 + + # Simple hash map to map from index to "type" metadata entry + IndexTypeMap = { + UserChanges: "user", + QualityChanges: "quality_changes", + Quality: "quality", + Material: "material", + Variant: "variant", + DefinitionChanges: "definition_changes", + Definition: "definition", + } + + # Reverse lookup: type -> index + TypeIndexMap = dict([(v, k) for k, v in IndexTypeMap.items()]) diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py new file mode 100644 index 0000000000..b84b1f4634 --- /dev/null +++ b/cura/Settings/CuraStackBuilder.py @@ -0,0 +1,155 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from UM.Logger import Logger + +from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.ContainerRegistry import ContainerRegistry + +from .GlobalStack import GlobalStack +from .ExtruderStack import ExtruderStack +from typing import Optional + + +## Contains helper functions to create new machines. +class CuraStackBuilder: + ## Create a new instance of a machine. + # + # \param name The name of the new machine. + # \param definition_id The ID of the machine definition to use. + # + # \return The new global stack or None if an error occurred. + @classmethod + def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]: + registry = ContainerRegistry.getInstance() + definitions = registry.findDefinitionContainers(id = definition_id) + if not definitions: + Logger.log("w", "Definition {definition} was not found!", definition = definition_id) + return None + + machine_definition = definitions[0] + name = registry.createUniqueName("machine", "", name, machine_definition.name) + + new_global_stack = cls.createGlobalStack( + new_stack_id = name, + definition = machine_definition, + quality = "default", + material = "default", + variant = "default", + ) + + 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 + ) + + return new_global_stack + + ## Create a new Extruder stack + # + # \param new_stack_id The ID of the new stack. + # \param definition The definition to base the new stack on. + # \param machine_definition The machine definition to use for the user container. + # \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm" + # + # \return A new Global stack instance with the specified parameters. + @classmethod + def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainer, machine_definition: DefinitionContainer, **kwargs) -> ExtruderStack: + stack = ExtruderStack(new_stack_id) + stack.setName(definition.getName()) + stack.setDefinition(definition) + stack.addMetaDataEntry("position", definition.getMetaDataEntry("position")) + + user_container = InstanceContainer(new_stack_id + "_user") + user_container.addMetaDataEntry("type", "user") + user_container.addMetaDataEntry("extruder", new_stack_id) + from cura.CuraApplication import CuraApplication + user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) + user_container.setDefinition(machine_definition) + + stack.setUserChanges(user_container) + + if "next_stack" in kwargs: + stack.setNextStack(kwargs["next_stack"]) + + # Important! The order here matters, because that allows the stack to + # assume the material and variant have already been set. + if "definition_changes" in kwargs: + stack.setDefinitionChangesById(kwargs["definition_changes"]) + + if "variant" in kwargs: + stack.setVariantById(kwargs["variant"]) + + if "material" in kwargs: + stack.setMaterialById(kwargs["material"]) + + if "quality" in kwargs: + stack.setQualityById(kwargs["quality"]) + + if "quality_changes" in kwargs: + stack.setQualityChangesById(kwargs["quality_changes"]) + + # Only add the created containers to the registry after we have set all the other + # properties. This makes the create operation more transactional, since any problems + # setting properties will not result in incomplete containers being added. + registry = ContainerRegistry.getInstance() + registry.addContainer(stack) + registry.addContainer(user_container) + + return stack + + ## Create a new Global stack + # + # \param new_stack_id The ID of the new stack. + # \param definition The definition to base the new stack on. + # \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm" + # + # \return A new Global stack instance with the specified parameters. + @classmethod + def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainer, **kwargs) -> GlobalStack: + stack = GlobalStack(new_stack_id) + stack.setDefinition(definition) + + user_container = InstanceContainer(new_stack_id + "_user") + user_container.addMetaDataEntry("type", "user") + user_container.addMetaDataEntry("machine", new_stack_id) + from cura.CuraApplication import CuraApplication + user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) + user_container.setDefinition(definition) + + stack.setUserChanges(user_container) + + # Important! The order here matters, because that allows the stack to + # assume the material and variant have already been set. + if "definition_changes" in kwargs: + stack.setDefinitionChangesById(kwargs["definition_changes"]) + + if "variant" in kwargs: + stack.setVariantById(kwargs["variant"]) + + if "material" in kwargs: + stack.setMaterialById(kwargs["material"]) + + if "quality" in kwargs: + stack.setQualityById(kwargs["quality"]) + + if "quality_changes" in kwargs: + stack.setQualityChangesById(kwargs["quality_changes"]) + + registry = ContainerRegistry.getInstance() + registry.addContainer(stack) + registry.addContainer(user_container) + + return stack diff --git a/cura/Settings/Exceptions.py b/cura/Settings/Exceptions.py new file mode 100644 index 0000000000..a30059b2e7 --- /dev/null +++ b/cura/Settings/Exceptions.py @@ -0,0 +1,22 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + + +## Raised when trying to perform an operation like add on a stack that does not allow that. +class InvalidOperationError(Exception): + pass + + +## Raised when trying to replace a container with a container that does not have the expected type. +class InvalidContainerError(Exception): + pass + + +## Raised when trying to add an extruder to a Global stack that already has the maximum number of extruders. +class TooManyExtrudersError(Exception): + pass + + +## Raised when an extruder has no next stack set. +class NoGlobalStackError(Exception): + pass diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 07a32143c1..10934c2d31 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant #For communicating data and events to Qt. @@ -6,14 +6,22 @@ from UM.FlameProfiler import pyqtSlot 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.SettingFunction import SettingFunction from UM.Settings.ContainerStack import ContainerStack from UM.Settings.DefinitionContainer import DefinitionContainer -from typing import Optional +from typing import Optional, List, TYPE_CHECKING, Union + +if TYPE_CHECKING: + from cura.Settings.ExtruderStack import ExtruderStack + from cura.Settings.GlobalStack import GlobalStack + ## Manages all existing extruder stacks. # @@ -32,12 +40,15 @@ 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. + self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders. self._active_extruder_index = 0 + 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 @@ -75,7 +86,7 @@ class ExtruderManager(QObject): for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]: extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position] if extruder.getId() == id: - return extruder.findContainer(type = "quality_changes").getId() + return extruder.qualityChanges.getId() ## The instance of the singleton pattern. # @@ -117,7 +128,50 @@ class ExtruderManager(QObject): except IndexError: return "" - def getActiveExtruderStack(self) -> ContainerStack: + ## Emitted whenever the selectedObjectExtruders property changes. + selectedObjectExtrudersChanged = pyqtSignal() + + ## Provides a list of extruder IDs used by the current selected objects. + @pyqtProperty("QVariantList", notify = selectedObjectExtrudersChanged) + def selectedObjectExtruders(self) -> List[str]: + if not self._selected_object_extruders: + object_extruders = set() + + # First, build a list of the actual selected objects (including children of groups, excluding group nodes) + selected_nodes = [] + for node in Selection.getAllSelectedObjects(): + if node.callDecoration("isGroup"): + for grouped_node in BreadthFirstIterator(node): + if grouped_node.callDecoration("isGroup"): + continue + + selected_nodes.append(grouped_node) + else: + selected_nodes.append(node) + + # Then, figure out which nodes are used by those selected nodes. + for node in selected_nodes: + extruder = node.callDecoration("getActiveExtruder") + if extruder: + object_extruders.add(extruder) + else: + global_stack = Application.getInstance().getGlobalContainerStack() + if global_stack.getId() in self._extruder_trains: + object_extruders.add(self._extruder_trains[global_stack.getId()]["0"].getId()) + + self._selected_object_extruders = list(object_extruders) + + return self._selected_object_extruders + + ## Reset the internal list used for the selectedObjectExtruders property + # + # This will trigger a recalculation of the extruders used for the + # selection. + def resetSelectedObjectExtruders(self) -> None: + self._selected_object_extruders = [] + self.selectedObjectExtrudersChanged.emit() + + def getActiveExtruderStack(self) -> Optional["ExtruderStack"]: global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: @@ -127,7 +181,7 @@ class ExtruderManager(QObject): return None ## Get an extruder stack by index - def getExtruderStack(self, index): + def getExtruderStack(self, index) -> Optional["ExtruderStack"]: global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: if global_container_stack.getId() in self._extruder_trains: @@ -136,7 +190,7 @@ class ExtruderManager(QObject): return None ## Get all extruder stacks - def getExtruderStacks(self): + def getExtruderStacks(self) -> List["ExtruderStack"]: result = [] for i in range(self.extruderCount): result.append(self.getExtruderStack(i)) @@ -147,6 +201,7 @@ class ExtruderManager(QObject): # # \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: DefinitionContainer, machine_id: str) -> None: changed = False machine_definition_id = machine_definition.getId() @@ -199,6 +254,7 @@ class ExtruderManager(QObject): # \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: DefinitionContainer, machine_definition: DefinitionContainer, position, machine_id: str) -> None: # Cache some things. @@ -244,7 +300,13 @@ class ExtruderManager(QObject): material = materials[0] preferred_material_id = machine_definition.getMetaDataEntry("preferred_material") if preferred_material_id: - search_criteria = { "type": "material", "id": preferred_material_id} + global_stack = ContainerRegistry.getInstance().findContainerStacks(id = machine_id) + if global_stack: + approximate_material_diameter = round(global_stack[0].getProperty("material_diameter", "value")) + else: + approximate_material_diameter = 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 @@ -301,6 +363,8 @@ class ExtruderManager(QObject): 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) @@ -340,7 +404,7 @@ class ExtruderManager(QObject): # list. # # \return A list of extruder stacks. - def getUsedExtruderStacks(self): + def getUsedExtruderStacks(self) -> List["ContainerStack"]: global_stack = Application.getInstance().getGlobalContainerStack() container_registry = ContainerRegistry.getInstance() @@ -394,19 +458,16 @@ class ExtruderManager(QObject): ## Removes the container stack and user profile for the extruders for a specific machine. # # \param machine_id The machine to remove the extruders for. - def removeMachineExtruders(self, machine_id): + def removeMachineExtruders(self, machine_id: str): for extruder in self.getMachineExtruders(machine_id): - containers = ContainerRegistry.getInstance().findInstanceContainers(type = "user", extruder = extruder.getId()) - for container in containers: - ContainerRegistry.getInstance().removeContainer(container.getId()) + ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId()) ContainerRegistry.getInstance().removeContainer(extruder.getId()) ## Returns extruders for a specific machine. # # \param machine_id The machine to get the extruders of. - def getMachineExtruders(self, machine_id): + def getMachineExtruders(self, machine_id: str): if machine_id not in self._extruder_trains: - Logger.log("w", "Tried to get the extruder trains for machine %s, which doesn't exist.", machine_id) return [] return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]] @@ -414,7 +475,7 @@ class ExtruderManager(QObject): # # The first element is the global container stack, followed by any extruder stacks. # \return \type{List[ContainerStack]} - def getActiveGlobalAndExtruderStacks(self): + def getActiveGlobalAndExtruderStacks(self) -> Optional[List[Union["ExtruderStack", "GlobalStack"]]]: global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack: return None @@ -426,23 +487,24 @@ class ExtruderManager(QObject): ## Returns the list of active extruder stacks. # # \return \type{List[ContainerStack]} a list of - def getActiveExtruderStacks(self): + def getActiveExtruderStacks(self) -> List["ExtruderStack"]: global_stack = Application.getInstance().getGlobalContainerStack() result = [] - if global_stack: + 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]) return result def __globalContainerStackChanged(self) -> None: - self._addCurrentMachineExtruders() global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack and global_container_stack.getBottom() and global_container_stack.getBottom().getId() != self._global_container_stack_definition_id: self._global_container_stack_definition_id = global_container_stack.getBottom().getId() self.globalContainerStackDefinitionChanged.emit() self.activeExtruderChanged.emit() + self.resetSelectedObjectExtruders() + ## Adds the extruders of the currently active machine. def _addCurrentMachineExtruders(self) -> None: global_stack = Application.getInstance().getGlobalContainerStack() @@ -463,6 +525,10 @@ class ExtruderManager(QObject): result = [] 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 + value = extruder.getRawProperty(key, "value") if value is None: @@ -521,18 +587,6 @@ class ExtruderManager(QObject): @staticmethod def getResolveOrValue(key): global_stack = Application.getInstance().getGlobalContainerStack() + resolved_value = global_stack.getProperty(key, "value") - resolved_value = global_stack.getProperty(key, "resolve") - if resolved_value is not None: - user_container = global_stack.findContainer({"type": "user"}) - quality_changes_container = global_stack.findContainer({"type": "quality_changes"}) - if user_container.hasProperty(key, "value") or quality_changes_container.hasProperty(key, "value"): - # Normal case - value = global_stack.getProperty(key, "value") - else: - # We have a resolved value and we're using it because of no user and quality_changes value - value = resolved_value - else: - value = global_stack.getRawProperty(key, "value") - - return value + return resolved_value diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py new file mode 100644 index 0000000000..18a9969828 --- /dev/null +++ b/cura/Settings/ExtruderStack.py @@ -0,0 +1,85 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from typing import Any + +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot + +from UM.Decorators import override +from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase +from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Settings.Interfaces import ContainerInterface + +from . import Exceptions +from .CuraContainerStack import CuraContainerStack +from .ExtruderManager import ExtruderManager + +## Represents an Extruder and its related containers. +# +# +class ExtruderStack(CuraContainerStack): + def __init__(self, container_id, *args, **kwargs): + super().__init__(container_id, *args, **kwargs) + + self.addMetaDataEntry("type", "extruder_train") # For backward compatibility + + ## Overridden from ContainerStack + # + # This will set the next stack and ensure that we register this stack as an extruder. + @override(ContainerStack) + def setNextStack(self, stack: ContainerStack) -> None: + super().setNextStack(stack) + stack.addExtruder(self) + self.addMetaDataEntry("machine", stack.id) + + # For backward compatibility: Register the extruder with the Extruder Manager + ExtruderManager.getInstance().registerExtruder(self, stack.id) + + @classmethod + def getLoadingPriority(cls) -> int: + return 3 + + ## Overridden from ContainerStack + # + # It will perform a few extra checks when trying to get properties. + # + # The two extra checks it currently does is to ensure a next stack is set and to bypass + # the extruder when the property is not settable per extruder. + # + # \throws Exceptions.NoGlobalStackError Raised when trying to get a property from an extruder without + # having a next stack set. + @override(ContainerStack) + def getProperty(self, key: str, property_name: str) -> Any: + if not self._next_stack: + raise Exceptions.NoGlobalStackError("Extruder {id} is missing the next stack!".format(id = self.id)) + + if not super().getProperty(key, "settable_per_extruder"): + return self.getNextStack().getProperty(key, property_name) + + return super().getProperty(key, property_name) + + @override(CuraContainerStack) + def _getMachineDefinition(self) -> ContainerInterface: + if not self.getNextStack(): + raise Exceptions.NoGlobalStackError("Extruder {id} is missing the next stack!".format(id = self.id)) + + return self.getNextStack()._getMachineDefinition() + + @override(CuraContainerStack) + def deserialize(self, contents: str) -> None: + super().deserialize(contents) + stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", "")) + if stacks: + self.setNextStack(stacks[0]) + +extruder_stack_mime = MimeType( + name = "application/x-cura-extruderstack", + comment = "Cura Extruder Stack", + suffixes = ["extruder.cfg"] +) + +MimeTypeDatabase.addMimeType(extruder_stack_mime) +ContainerRegistry.addContainerTypeByName(ExtruderStack, "extruder_stack", extruder_stack_mime.name) diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index 7f4a77eb5f..7491f8aa93 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -1,11 +1,11 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty +from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer import UM.Qt.ListModel from UM.Application import Application - +import UM.FlameProfiler from cura.Settings.ExtruderManager import ExtruderManager ## Model that holds extruders. @@ -33,6 +33,12 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): # The ID of the definition of the extruder. DefinitionRole = Qt.UserRole + 5 + # The material of the extruder. + MaterialRole = Qt.UserRole + 6 + + # The variant of the extruder. + VariantRole = Qt.UserRole + 7 + ## List of colours to display if there is no material or the material has no known # colour. defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"] @@ -49,6 +55,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): self.addRoleName(self.ColorRole, "color") self.addRoleName(self.IndexRole, "index") self.addRoleName(self.DefinitionRole, "definition") + self.addRoleName(self.MaterialRole, "material") + self.addRoleName(self.VariantRole, "variant") + + 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._add_global = False self._simple_names = False @@ -103,28 +116,33 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): active_extruder_stack.containersChanged.connect(self._onExtruderStackContainersChanged) self._active_extruder_stack = active_extruder_stack - def _onExtruderStackContainersChanged(self, container): - # The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name - self._updateExtruders() + # Update when there is an empty container or material change + if container.getMetaDataEntry("type") == "material" or container.getMetaDataEntry("type") is None: + # 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): + self._update_extruder_timer.start() + ## Update the list of extruders. # # This should be called whenever the list of extruders changes. - def _updateExtruders(self): + @UM.FlameProfiler.profile + def __updateExtruders(self): changed = False if self.rowCount() != 0: changed = True items = [] - global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: if self._add_global: - material = global_container_stack.findContainer({ "type": "material" }) + material = global_container_stack.material color = material.getMetaDataEntry("color_code", default = self.defaultColors[0]) if material else self.defaultColors[0] item = { "id": global_container_stack.getId(), @@ -136,15 +154,20 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): items.append(item) changed = True + machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value") manager = ExtruderManager.getInstance() for extruder in manager.getMachineExtruders(global_container_stack.getId()): - extruder_name = extruder.getName() - material = extruder.findContainer({ "type": "material" }) position = extruder.getMetaDataEntry("position", default = "0") # Get the position try: position = int(position) 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. @@ -152,7 +175,9 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): "name": extruder_name, "color": color, "index": position, - "definition": extruder.getBottom().getId() + "definition": extruder.getBottom().getId(), + "material": material.getName() if material else "", + "variant": variant.getName() if variant else "", } items.append(item) changed = True diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py new file mode 100755 index 0000000000..cee141cf93 --- /dev/null +++ b/cura/Settings/GlobalStack.py @@ -0,0 +1,123 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from typing import Any + +from PyQt5.QtCore import pyqtProperty + +from UM.Decorators import override + +from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase +from UM.Settings.ContainerStack import ContainerStack +from UM.Settings.SettingInstance import InstanceState +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Logger import Logger + +from . import Exceptions +from .CuraContainerStack import CuraContainerStack + +## Represents the Global or Machine stack and its related containers. +# +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._extruders = [] + + # 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 + # if the resolve function tried to access the same property it is a resolve for. + self._resolving_settings = set() + + ## Get the list of extruders of this stack. + # + # \return The extruders registered with this stack. + @pyqtProperty("QVariantList") + def extruders(self) -> list: + return self._extruders + + @classmethod + def getLoadingPriority(cls) -> int: + return 2 + + ## Add an extruder to the list of extruders of this stack. + # + # \param extruder The extruder to add. + # + # \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 and len(self._extruders) + 1 > extruder_count: + Logger.log("w", "Adding extruder {meta} to {id} but its extruder count is {count}".format(id = self.id, count = extruder_count, meta = str(extruder.getMetaData()))) + + self._extruders.append(extruder) + + ## Overridden from ContainerStack + # + # This will return the value of the specified property for the specified setting, + # unless the property is "value" and that setting has a "resolve" function set. + # When a resolve is set, it will instead try and execute the resolve first and + # then fall back to the normal "value" property. + # + # \param key The setting key to get the property of. + # \param property_name The property to get the value of. + # + # \return The value of the property for the specified setting, or None if not found. + @override(ContainerStack) + def getProperty(self, key: str, property_name: str) -> Any: + if not self.definition.findDefinitions(key = key): + return None + + if self._shouldResolve(key, property_name): + self._resolving_settings.add(key) + resolve = super().getProperty(key, "resolve") + self._resolving_settings.remove(key) + if resolve is not None: + return resolve + + return super().getProperty(key, property_name) + + ## Overridden from ContainerStack + # + # This will simply raise an exception since the Global stack cannot have a next stack. + @override(ContainerStack) + def setNextStack(self, next_stack: ContainerStack) -> None: + raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!") + + # protected: + + # Determine whether or not we should try to get the "resolve" property instead of the + # requested property. + def _shouldResolve(self, key: str, property_name: str) -> bool: + if property_name is not "value": + # Do not try to resolve anything but the "value" property + return False + + if key in self._resolving_settings: + # To prevent infinite recursion, if getProperty is called with the same key as + # we are already trying to resolve, we should not try to resolve again. Since + # this can happen multiple times when trying to resolve a value, we need to + # track all settings that are being resolved. + return False + + setting_state = super().getProperty(key, "state") + if setting_state is not None and setting_state != InstanceState.Default: + # When the user has explicitly set a value, we should ignore any resolve and + # just return that value. + return False + + return True + + +## private: +global_stack_mime = MimeType( + name = "application/x-cura-globalstack", + comment = "Cura Global Stack", + suffixes = ["global.cfg"] +) + +MimeTypeDatabase.addMimeType(global_stack_mime) +ContainerRegistry.addContainerTypeByName(GlobalStack, "global_stack", global_stack_mime.name) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 638b475094..0eafd33e94 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -15,26 +15,45 @@ from UM.Message import Message from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerStack import ContainerStack from UM.Settings.InstanceContainer import InstanceContainer -from UM.Settings.SettingDefinition import SettingDefinition from UM.Settings.SettingFunction import SettingFunction -from UM.Settings.Validator import ValidatorState from UM.Signal import postponeSignals +import UM.FlameProfiler from cura.QualityManager import QualityManager from cura.PrinterOutputDevice import PrinterOutputDevice from cura.Settings.ExtruderManager import ExtruderManager +from .CuraStackBuilder import CuraStackBuilder + from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from UM.Settings.DefinitionContainer import DefinitionContainer + from cura.Settings.CuraContainerStack import CuraContainerStack + from cura.Settings.GlobalStack import GlobalStack + import os + class MachineManager(QObject): def __init__(self, parent = None): super().__init__(parent) - self._active_container_stack = None # type: ContainerStack - self._global_container_stack = None # type: ContainerStack + self._active_container_stack = None # type: CuraContainerStack + self._global_container_stack = None # type: GlobalStack + + self._error_check_timer = QTimer() + self._error_check_timer.setInterval(250) + self._error_check_timer.setSingleShot(True) + self._error_check_timer.timeout.connect(self._updateStacksHaveErrors) + + self._instance_container_timer = QTimer() + self._instance_container_timer.setInterval(250) + self._instance_container_timer.setSingleShot(True) + self._instance_container_timer.timeout.connect(self.__onInstanceContainersChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) ## When the global container is changed, active material probably needs to be updated. @@ -43,10 +62,12 @@ class MachineManager(QObject): self.globalContainerChanged.connect(self.activeQualityChanged) self._stacks_have_errors = None - self._empty_variant_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_variant")[0] - self._empty_material_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_material")[0] - self._empty_quality_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality")[0] - self._empty_quality_changes_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality_changes")[0] + + self._empty_variant_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + self._empty_material_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + self._empty_quality_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + self._empty_quality_changes_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + self._onGlobalContainerChanged() ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) @@ -83,11 +104,6 @@ class MachineManager(QObject): self._material_incompatible_message = Message(catalog.i18nc("@info:status", "The selected material is incompatible with the selected machine or configuration.")) - self._error_check_timer = QTimer() - self._error_check_timer.setInterval(250) - self._error_check_timer.setSingleShot(True) - self._error_check_timer.timeout.connect(self._updateStacksHaveErrors) - globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value) activeMaterialChanged = pyqtSignal() activeVariantChanged = pyqtSignal() @@ -123,7 +139,7 @@ class MachineManager(QObject): return self._printer_output_devices @pyqtProperty(int, constant=True) - def totalNumberOfSettings(self): + def totalNumberOfSettings(self) -> int: return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys()) def _onHotendIdChanged(self, index: Union[str, int], hotend_id: str) -> None: @@ -147,7 +163,7 @@ class MachineManager(QObject): else: Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.getBottom().getId(), hotend_id)) - def _onMaterialIdChanged(self, index, material_id): + def _onMaterialIdChanged(self, index: Union[str, int], material_id: str): if not self._global_container_stack: return @@ -223,14 +239,22 @@ class MachineManager(QObject): def _onGlobalContainerChanged(self): if self._global_container_stack: - self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged) - self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged) - self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) - - material = self._global_container_stack.findContainer({"type": "material"}) + try: + self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged) + except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that was already disconnected. + pass + try: + self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged) + except TypeError: + pass + try: + self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) + except TypeError: + pass + material = self._global_container_stack.material material.nameChanged.disconnect(self._onMaterialNameChanged) - quality = self._global_container_stack.findContainer({"type": "quality"}) + quality = self._global_container_stack.quality quality.nameChanged.disconnect(self._onQualityNameChanged) if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1: @@ -253,26 +277,25 @@ class MachineManager(QObject): # 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.findContainer(type = "variant") + global_variant = self._global_container_stack.variant if global_variant != self._empty_variant_container: - self._global_container_stack.replaceContainer(self._global_container_stack.getContainerIndex(global_variant), self._empty_variant_container) + self._global_container_stack.setVariant(self._empty_variant_container) - global_material = self._global_container_stack.findContainer(type = "material") + global_material = self._global_container_stack.material if global_material != self._empty_material_container: - self._global_container_stack.replaceContainer(self._global_container_stack.getContainerIndex(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.findContainer({"type": "material"}) + material = self._global_container_stack.material material.nameChanged.connect(self._onMaterialNameChanged) - quality = self._global_container_stack.findContainer({"type": "quality"}) + quality = self._global_container_stack.quality quality.nameChanged.connect(self._onQualityNameChanged) - - self._updateStacksHaveErrors() + self._error_check_timer.start() ## Update self._stacks_valid according to _checkStacksForErrors and emit if change. def _updateStacksHaveErrors(self): @@ -289,23 +312,23 @@ class MachineManager(QObject): if not self._active_container_stack: self._active_container_stack = self._global_container_stack - self._updateStacksHaveErrors() + self._error_check_timer.start() if old_active_container_stack != self._active_container_stack: # Many methods and properties related to the active quality actually depend # on _active_container_stack. If it changes, then the properties change. self.activeQualityChanged.emit() - def _onInstanceContainersChanged(self, container): - container_type = container.getMetaDataEntry("type") - + def __onInstanceContainersChanged(self): + self.activeQualityChanged.emit() self.activeVariantChanged.emit() self.activeMaterialChanged.emit() - self.activeQualityChanged.emit() + self._error_check_timer.start() - self._updateStacksHaveErrors() + def _onInstanceContainersChanged(self, container): + self._instance_container_timer.start() - def _onPropertyChanged(self, key, property_name): + def _onPropertyChanged(self, key: str, property_name: str): if property_name == "value": # Notify UI items, such as the "changed" star in profile pull down menu. self.activeStackValueChanged.emit() @@ -322,40 +345,11 @@ class MachineManager(QObject): @pyqtSlot(str, str) def addMachine(self, name: str, definition_id: str) -> None: - container_registry = ContainerRegistry.getInstance() - definitions = container_registry.findDefinitionContainers(id = definition_id) - if definitions: - definition = definitions[0] - name = self._createUniqueName("machine", "", name, definition.getName()) - new_global_stack = ContainerStack(name) - new_global_stack.addMetaDataEntry("type", "machine") - container_registry.addContainer(new_global_stack) - - variant_instance_container = self._updateVariantContainer(definition) - material_instance_container = self._updateMaterialContainer(definition, variant_instance_container) - quality_instance_container = self._updateQualityContainer(definition, variant_instance_container, material_instance_container) - - current_settings_instance_container = InstanceContainer(name + "_current_settings") - current_settings_instance_container.addMetaDataEntry("machine", name) - current_settings_instance_container.addMetaDataEntry("type", "user") - current_settings_instance_container.setDefinition(definitions[0]) - container_registry.addContainer(current_settings_instance_container) - - new_global_stack.addContainer(definition) - if variant_instance_container: - new_global_stack.addContainer(variant_instance_container) - if material_instance_container: - new_global_stack.addContainer(material_instance_container) - if quality_instance_container: - new_global_stack.addContainer(quality_instance_container) - - new_global_stack.addContainer(self._empty_quality_changes_container) - new_global_stack.addContainer(current_settings_instance_container) - - ExtruderManager.getInstance().addMachineExtruders(definition, new_global_stack.getId()) - - Application.getInstance().setGlobalContainerStack(new_global_stack) - + new_stack = CuraStackBuilder.createMachine(name, definition_id) + if new_stack: + Application.getInstance().setGlobalContainerStack(new_stack) + 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, ...) @@ -418,7 +412,7 @@ class MachineManager(QObject): ## Delete a user setting from the global stack and all extruder stacks. # \param key \type{str} the name of the key to delete @pyqtSlot(str) - def clearUserSettingAllCurrentStacks(self, key): + def clearUserSettingAllCurrentStacks(self, key: str): if not self._global_container_stack: return @@ -474,6 +468,10 @@ class MachineManager(QObject): return "" + @pyqtProperty("QObject", notify = globalContainerChanged) + def activeMachine(self) -> "GlobalStack": + return self._global_container_stack + @pyqtProperty(str, notify = activeStackChanged) def activeStackId(self) -> str: if self._active_container_stack: @@ -484,7 +482,7 @@ class MachineManager(QObject): @pyqtProperty(str, notify = activeMaterialChanged) def activeMaterialName(self) -> str: if self._active_container_stack: - material = self._active_container_stack.findContainer({"type":"material"}) + material = self._active_container_stack.material if material: return material.getName() @@ -495,18 +493,29 @@ class MachineManager(QObject): result = [] if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None: for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): - variant_container = stack.findContainer({"type": "variant"}) + variant_container = stack.variant if variant_container and variant_container != self._empty_variant_container: result.append(variant_container.getName()) return result + @pyqtProperty("QVariantList", notify = activeVariantChanged) + def activeMaterialIds(self): + result = [] + if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None: + for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): + variant_container = stack.findContainer({"type": "variant"}) + if variant_container and variant_container != self._empty_variant_container: + result.append(variant_container.getId()) + + return result + @pyqtProperty("QVariantList", notify = activeMaterialChanged) def activeMaterialNames(self): result = [] if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None: for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): - material_container = stack.findContainer(type="material") + material_container = stack.material if material_container and material_container != self._empty_material_container: result.append(material_container.getName()) return result @@ -514,12 +523,28 @@ class MachineManager(QObject): @pyqtProperty(str, notify=activeMaterialChanged) def activeMaterialId(self) -> str: if self._active_container_stack: - material = self._active_container_stack.findContainer({"type": "material"}) + material = self._active_container_stack.material if material: return material.getId() return "" + @pyqtProperty("QVariantMap", notify = activeVariantChanged) + def allActiveVariantIds(self): + if not self._global_container_stack: + return {} + + result = {} + + for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): + variant_container = stack.variant + if not variant_container: + continue + + result[stack.getId()] = variant_container.getId() + + return result + @pyqtProperty("QVariantMap", notify = activeMaterialChanged) def allActiveMaterialIds(self): if not self._global_container_stack: @@ -528,7 +553,7 @@ class MachineManager(QObject): result = {} for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): - material_container = stack.findContainer(type = "material") + material_container = stack.material if not material_container: continue @@ -543,31 +568,31 @@ class MachineManager(QObject): # \return The layer height of the currently active quality profile. If # there is no quality profile, this returns 0. @pyqtProperty(float, notify=activeQualityChanged) - def activeQualityLayerHeight(self): + def activeQualityLayerHeight(self) -> float: if not self._global_container_stack: return 0 - quality_changes = self._global_container_stack.findContainer({"type": "quality_changes"}) + quality_changes = self._global_container_stack.qualityChanges if quality_changes: value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality_changes.getId()) if isinstance(value, SettingFunction): value = value(self._global_container_stack) return value - quality = self._global_container_stack.findContainer({"type": "quality"}) + quality = self._global_container_stack.quality if quality: value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality.getId()) if isinstance(value, SettingFunction): value = value(self._global_container_stack) return value - return 0 #No quality profile. + return 0 # No quality profile. ## Get the Material ID associated with the currently active material # \returns MaterialID (string) if found, empty string otherwise @pyqtProperty(str, notify=activeQualityChanged) def activeQualityMaterialId(self) -> str: if self._active_container_stack: - quality = self._active_container_stack.findContainer({"type": "quality"}) + quality = self._active_container_stack.quality if quality: material_id = quality.getMetaDataEntry("material") if material_id: @@ -582,50 +607,50 @@ class MachineManager(QObject): return "" @pyqtProperty(str, notify=activeQualityChanged) - def activeQualityName(self): + def activeQualityName(self) -> str: if self._active_container_stack and self._global_container_stack: - quality = self._global_container_stack.findContainer({"type": "quality_changes"}) - if quality and quality != self._empty_quality_changes_container: + quality = self._global_container_stack.qualityChanges + if quality and not isinstance(quality, type(self._empty_quality_changes_container)): return quality.getName() - quality = self._active_container_stack.findContainer({"type": "quality"}) + quality = self._active_container_stack.quality if quality: return quality.getName() return "" @pyqtProperty(str, notify=activeQualityChanged) - def activeQualityId(self): + def activeQualityId(self) -> str: if self._active_container_stack: - quality = self._active_container_stack.findContainer({"type": "quality_changes"}) - if quality and quality != self._empty_quality_changes_container: + quality = self._active_container_stack.qualityChanges + if quality and not isinstance(quality, type(self._empty_quality_changes_container)): return quality.getId() - quality = self._active_container_stack.findContainer({"type": "quality"}) + quality = self._active_container_stack.quality if quality: return quality.getId() return "" @pyqtProperty(str, notify=activeQualityChanged) - def globalQualityId(self): + def globalQualityId(self) -> str: if self._global_container_stack: - quality = self._global_container_stack.findContainer({"type": "quality_changes"}) - if quality and quality != self._empty_quality_changes_container: + quality = self._global_container_stack.qualityChanges + if quality and not isinstance(quality, type(self._empty_quality_changes_container)): return quality.getId() - quality = self._global_container_stack.findContainer({"type": "quality"}) + quality = self._global_container_stack.quality if quality: return quality.getId() return "" @pyqtProperty(str, notify = activeQualityChanged) - def activeQualityType(self): + def activeQualityType(self) -> str: if self._active_container_stack: - quality = self._active_container_stack.findContainer(type = "quality") + quality = self._active_container_stack.quality if quality: return quality.getMetaDataEntry("quality_type") return "" @pyqtProperty(bool, notify = activeQualityChanged) - def isActiveQualitySupported(self): + def isActiveQualitySupported(self) -> bool: if self._active_container_stack: - quality = self._active_container_stack.findContainer(type = "quality") + quality = self._active_container_stack.quality if quality: return Util.parseBool(quality.getMetaDataEntry("supported", True)) return False @@ -637,25 +662,25 @@ class MachineManager(QObject): # \todo Ideally, this method would be named activeQualityId(), and the other one # would be named something like activeQualityOrQualityChanges() for consistency @pyqtProperty(str, notify = activeQualityChanged) - def activeQualityContainerId(self): + def activeQualityContainerId(self) -> str: # We're using the active stack instead of the global stack in case the list of qualities differs per extruder if self._global_container_stack: - quality = self._active_container_stack.findContainer(type = "quality") + quality = self._active_container_stack.quality if quality: return quality.getId() return "" @pyqtProperty(str, notify = activeQualityChanged) - def activeQualityChangesId(self): + def activeQualityChangesId(self) -> str: if self._active_container_stack: - changes = self._active_container_stack.findContainer(type = "quality_changes") - if changes: + changes = self._active_container_stack.qualityChanges + if changes and changes.getId() != "empty": return changes.getId() return "" ## Check if a container is read_only @pyqtSlot(str, result = bool) - def isReadOnly(self, container_id) -> bool: + def isReadOnly(self, container_id: str) -> bool: containers = ContainerRegistry.getInstance().findInstanceContainers(id = container_id) if not containers or not self._active_container_stack: return True @@ -663,7 +688,7 @@ 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): + def copyValueToExtruders(self, key: str): if not self._active_container_stack or self._global_container_stack.getProperty("machine_extruder_count", "value") <= 1: return @@ -677,7 +702,7 @@ class MachineManager(QObject): ## Set the active material by switching out a container # Depending on from/to material+current variant, a quality profile is chosen and set. @pyqtSlot(str) - def setActiveMaterial(self, material_id): + def setActiveMaterial(self, material_id: str): with postponeSignals(*self._getContainerChangedSignals(), compress = True): containers = ContainerRegistry.getInstance().findInstanceContainers(id = material_id) if not containers or not self._active_container_stack: @@ -686,21 +711,20 @@ class MachineManager(QObject): Logger.log("d", "Attempting to change the active material to %s", material_id) - old_material = self._active_container_stack.findContainer({"type": "material"}) - old_quality = self._active_container_stack.findContainer({"type": "quality"}) - old_quality_changes = self._active_container_stack.findContainer({"type": "quality_changes"}) + old_material = self._active_container_stack.material + old_quality = self._active_container_stack.quality + old_quality_changes = self._active_container_stack.qualityChanges if not old_material: Logger.log("w", "While trying to set the active material, no material was found to replace it.") return - if old_quality_changes.getId() == "empty_quality_changes": + if old_quality_changes and isinstance(old_quality_changes, type(self._empty_quality_changes_container)): old_quality_changes = None self.blurSettings.emit() old_material.nameChanged.disconnect(self._onMaterialNameChanged) - material_index = self._active_container_stack.getContainerIndex(old_material) - self._active_container_stack.replaceContainer(material_index, material_container) + self._active_container_stack.material = material_container Logger.log("d", "Active material changed") material_container.nameChanged.connect(self._onMaterialNameChanged) @@ -727,7 +751,7 @@ class MachineManager(QObject): candidate_quality = quality_manager.findQualityByQualityType(quality_type, quality_manager.getWholeMachineDefinition(machine_definition), [material_container]) - if not candidate_quality or candidate_quality.getId() == "empty_quality": + if not candidate_quality or isinstance(candidate_quality, type(self._empty_quality_changes_container)): # Fall back to a quality (which must be compatible with all other extruders) new_qualities = quality_manager.findAllUsableQualitiesForMachineAndExtruders( self._global_container_stack, ExtruderManager.getInstance().getExtruderStacks()) @@ -743,31 +767,30 @@ class MachineManager(QObject): self.setActiveQuality(new_quality_id) @pyqtSlot(str) - def setActiveVariant(self, variant_id): + def setActiveVariant(self, variant_id: str): with postponeSignals(*self._getContainerChangedSignals(), compress = True): containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_id) if not containers or not self._active_container_stack: return Logger.log("d", "Attempting to change the active variant to %s", variant_id) - old_variant = self._active_container_stack.findContainer({"type": "variant"}) - old_material = self._active_container_stack.findContainer({"type": "material"}) + old_variant = self._active_container_stack.variant + old_material = self._active_container_stack.material if old_variant: self.blurSettings.emit() - variant_index = self._active_container_stack.getContainerIndex(old_variant) - self._active_container_stack.replaceContainer(variant_index, containers[0]) - Logger.log("d", "Active variant changed") + self._active_container_stack.variant = containers[0] + Logger.log("d", "Active variant changed to {active_variant_id}".format(active_variant_id = containers[0].getId())) preferred_material = None if old_material: preferred_material_name = old_material.getName() - self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), containers[0], preferred_material_name).id) + self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), self._global_container_stack, containers[0], preferred_material_name).id) else: Logger.log("w", "While trying to set the active variant, no variant was found to replace.") ## set the active quality # \param quality_id The quality_id of either a quality or a quality_changes @pyqtSlot(str) - def setActiveQuality(self, quality_id): + def setActiveQuality(self, quality_id: str): with postponeSignals(*self._getContainerChangedSignals(), compress = True): self.blurSettings.emit() @@ -804,8 +827,8 @@ class MachineManager(QObject): name_changed_connect_stacks.append(stack_quality) name_changed_connect_stacks.append(stack_quality_changes) - self._replaceQualityOrQualityChangesInStack(stack, stack_quality) - self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes) + self._replaceQualityOrQualityChangesInStack(stack, stack_quality, postpone_emit=True) + self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes, postpone_emit=True) # Send emits that are postponed in replaceContainer. # Here the stacks are finished replacing and every value can be resolved based on the current state. @@ -825,7 +848,8 @@ class MachineManager(QObject): # # \param quality_name \type{str} the name of the quality. # \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes". - def determineQualityAndQualityChangesForQualityType(self, quality_type): + @UM.FlameProfiler.profile + def determineQualityAndQualityChangesForQualityType(self, quality_type: str): quality_manager = QualityManager.getInstance() result = [] empty_quality_changes = self._empty_quality_changes_container @@ -841,7 +865,7 @@ class MachineManager(QObject): stacks = [global_container_stack] for stack in stacks: - material = stack.findContainer(type="material") + material = stack.material 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 @@ -862,7 +886,7 @@ class MachineManager(QObject): # # \param quality_changes_name \type{str} the name of the quality changes. # \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes". - def _determineQualityAndQualityChangesForQualityChanges(self, quality_changes_name): + def _determineQualityAndQualityChangesForQualityChanges(self, quality_changes_name: str): result = [] quality_manager = QualityManager.getInstance() @@ -878,7 +902,7 @@ class MachineManager(QObject): else: Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name) return None - material = global_container_stack.findContainer(type="material") + 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. @@ -901,7 +925,7 @@ class MachineManager(QObject): else: quality_changes = global_quality_changes - material = stack.findContainer(type="material") + material = stack.material quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) if not quality: #No quality profile found for this quality type. quality = self._empty_quality_container @@ -918,44 +942,44 @@ class MachineManager(QObject): return result - def _replaceQualityOrQualityChangesInStack(self, stack, container, postpone_emit = False): + def _replaceQualityOrQualityChangesInStack(self, stack: "CuraContainerStack", container: "InstanceContainer", postpone_emit = False): # Disconnect the signal handling from the old container. - old_container = stack.findContainer(type=container.getMetaDataEntry("type")) - if old_container: - old_container.nameChanged.disconnect(self._onQualityNameChanged) - else: - Logger.log("e", "Could not find container of type %s in stack %s while replacing quality (changes) with container %s", container.getMetaDataEntry("type"), stack.getId(), container.getId()) - return - - # Swap in the new container into the stack. - stack.replaceContainer(stack.getContainerIndex(old_container), container, postpone_emit = postpone_emit) - - # Attach the needed signal handling. - container.nameChanged.connect(self._onQualityNameChanged) + container_type = container.getMetaDataEntry("type") + if container_type == "quality": + stack.quality.nameChanged.disconnect(self._onQualityNameChanged) + stack.setQuality(container, postpone_emit = postpone_emit) + stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged) + elif container_type == "quality_changes" or container_type is None: + # If the container is an empty container, we need to change the quality_changes. + # Quality can never be set to empty. + stack.qualityChanges.nameChanged.disconnect(self._onQualityNameChanged) + stack.setQualityChanges(container, postpone_emit = postpone_emit) + stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged) + self._onQualityNameChanged() def _askUserToKeepOrClearCurrentSettings(self): Application.getInstance().discardOrKeepProfileChanges() @pyqtProperty(str, notify = activeVariantChanged) - def activeVariantName(self): + def activeVariantName(self) -> str: if self._active_container_stack: - variant = self._active_container_stack.findContainer({"type": "variant"}) + variant = self._active_container_stack.variant if variant: return variant.getName() return "" @pyqtProperty(str, notify = activeVariantChanged) - def activeVariantId(self): + def activeVariantId(self) -> str: if self._active_container_stack: - variant = self._active_container_stack.findContainer({"type": "variant"}) + variant = self._active_container_stack.variant if variant: return variant.getId() return "" @pyqtProperty(str, notify = globalContainerChanged) - def activeDefinitionId(self): + def activeDefinitionId(self) -> str: if self._global_container_stack: definition = self._global_container_stack.getBottom() if definition: @@ -964,7 +988,7 @@ class MachineManager(QObject): return "" @pyqtProperty(str, notify=globalContainerChanged) - def activeDefinitionName(self): + def activeDefinitionName(self) -> str: if self._global_container_stack: definition = self._global_container_stack.getBottom() if definition: @@ -976,7 +1000,7 @@ class MachineManager(QObject): # \returns DefinitionID (string) if found, empty string otherwise # \sa getQualityDefinitionId @pyqtProperty(str, notify = globalContainerChanged) - def activeQualityDefinitionId(self): + def activeQualityDefinitionId(self) -> str: if self._global_container_stack: return self.getQualityDefinitionId(self._global_container_stack.getBottom()) return "" @@ -985,16 +1009,16 @@ class MachineManager(QObject): # This is normally the id of the definition itself, but machines can specify a different definition to inherit qualities from # \param definition (DefinitionContainer) machine definition # \returns DefinitionID (string) if found, empty string otherwise - def getQualityDefinitionId(self, definition): + def getQualityDefinitionId(self, definition: "DefinitionContainer") -> str: return QualityManager.getInstance().getParentMachineDefinition(definition).getId() ## Get the Variant ID to use to select quality profiles for the currently active variant # \returns VariantID (string) if found, empty string otherwise # \sa getQualityVariantId @pyqtProperty(str, notify = activeVariantChanged) - def activeQualityVariantId(self): + def activeQualityVariantId(self) -> str: if self._active_container_stack: - variant = self._active_container_stack.findContainer({"type": "variant"}) + variant = self._active_container_stack.variant if variant: return self.getQualityVariantId(self._global_container_stack.getBottom(), variant) return "" @@ -1003,9 +1027,9 @@ class MachineManager(QObject): # This is normally the id of the variant itself, but machines can specify a different definition # to inherit qualities from, which has consequences for the variant to use as well # \param definition (DefinitionContainer) machine definition - # \param variant (DefinitionContainer) variant definition + # \param variant (InstanceContainer) variant definition # \returns VariantID (string) if found, empty string otherwise - def getQualityVariantId(self, definition, variant): + def getQualityVariantId(self, definition: "DefinitionContainer", variant: "InstanceContainer") -> str: variant_id = variant.getId() definition_id = definition.getId() quality_definition_id = self.getQualityDefinitionId(definition) @@ -1017,7 +1041,7 @@ class MachineManager(QObject): ## Gets how the active definition calls variants # Caveat: per-definition-variant-title is currently not translated (though the fallback is) @pyqtProperty(str, notify = globalContainerChanged) - def activeDefinitionVariantsName(self): + def activeDefinitionVariantsName(self) -> str: fallback_title = catalog.i18nc("@label", "Nozzle") if self._global_container_stack: return self._global_container_stack.getBottom().getMetaDataEntry("variants_name", fallback_title) @@ -1025,7 +1049,7 @@ class MachineManager(QObject): return fallback_title @pyqtSlot(str, str) - def renameMachine(self, machine_id, new_name): + 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()) @@ -1033,7 +1057,7 @@ class MachineManager(QObject): self.globalContainerChanged.emit() @pyqtSlot(str) - def removeMachine(self, machine_id): + def removeMachine(self, machine_id: str): # If the machine that is being removed is the currently active machine, set another machine as the active machine. activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id) @@ -1051,14 +1075,14 @@ class MachineManager(QObject): @pyqtProperty(bool, notify = globalContainerChanged) - def hasMaterials(self): + def hasMaterials(self) -> bool: if self._global_container_stack: return bool(self._global_container_stack.getMetaDataEntry("has_materials", False)) return False @pyqtProperty(bool, notify = globalContainerChanged) - def hasVariants(self): + def hasVariants(self) -> bool: if self._global_container_stack: return bool(self._global_container_stack.getMetaDataEntry("has_variants", False)) @@ -1067,7 +1091,7 @@ class MachineManager(QObject): ## Property to indicate if a machine has "specialized" material profiles. # Some machines have their own material profiles that "override" the default catch all profiles. @pyqtProperty(bool, notify = globalContainerChanged) - def filterMaterialsByMachine(self): + def filterMaterialsByMachine(self) -> bool: if self._global_container_stack: return bool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False)) @@ -1076,7 +1100,7 @@ class MachineManager(QObject): ## Property to indicate if a machine has "specialized" quality profiles. # Some machines have their own quality profiles that "override" the default catch all profiles. @pyqtProperty(bool, notify = globalContainerChanged) - def filterQualityByMachine(self): + def filterQualityByMachine(self) -> bool: if self._global_container_stack: return bool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False)) return False @@ -1085,7 +1109,7 @@ class MachineManager(QObject): # \param machine_id string machine id to get the definition ID of # \returns DefinitionID (string) if found, None otherwise @pyqtSlot(str, result = str) - def getDefinitionByMachineId(self, machine_id): + def getDefinitionByMachineId(self, machine_id: str) -> str: containers = ContainerRegistry.getInstance().findContainerStacks(id=machine_id) if containers: return containers[0].getBottom().getId() @@ -1094,27 +1118,12 @@ class MachineManager(QObject): def createMachineManager(engine=None, script_engine=None): return MachineManager() - def _updateVariantContainer(self, definition): - if not definition.getMetaDataEntry("has_variants"): - return self._empty_variant_container - machine_definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(definition) - containers = [] - preferred_variant = definition.getMetaDataEntry("preferred_variant") - if preferred_variant: - containers = ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = machine_definition_id, id = preferred_variant) - if not containers: - containers = ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = machine_definition_id) - - if containers: - return containers[0] - - return self._empty_variant_container - - def _updateMaterialContainer(self, definition, variant_container = None, preferred_material_name = None): + def _updateMaterialContainer(self, definition: "DefinitionContainer", stack: "ContainerStack", variant_container: Optional["InstanceContainer"] = None, preferred_material_name: Optional[str] = None): if not definition.getMetaDataEntry("has_materials"): return self._empty_material_container - search_criteria = { "type": "material" } + approximate_material_diameter = round(stack.getProperty("material_diameter", "value")) + search_criteria = { "type": "material", "approximate_diameter": approximate_material_diameter } if definition.getMetaDataEntry("has_machine_materials"): search_criteria["definition"] = self.getQualityDefinitionId(definition) @@ -1146,110 +1155,6 @@ class MachineManager(QObject): Logger.log("w", "Unable to find a material container with provided criteria, returning an empty one instead.") return self._empty_material_container - def _updateQualityContainer(self, definition, variant_container, material_container = None, preferred_quality_name = None): - container_registry = ContainerRegistry.getInstance() - search_criteria = { "type": "quality" } - - if definition.getMetaDataEntry("has_machine_quality"): - search_criteria["definition"] = self.getQualityDefinitionId(definition) - - if definition.getMetaDataEntry("has_materials") and material_container: - search_criteria["material"] = material_container.id - else: - search_criteria["definition"] = "fdmprinter" - - if preferred_quality_name and preferred_quality_name != "empty": - search_criteria["name"] = preferred_quality_name - else: - preferred_quality = definition.getMetaDataEntry("preferred_quality") - if preferred_quality: - search_criteria["id"] = preferred_quality - - containers = container_registry.findInstanceContainers(**search_criteria) - if containers: - return containers[0] - - if "material" in search_criteria: - # First check if we can solve our material not found problem by checking if we can find quality containers - # that are assigned to the parents of this material profile. - try: - inherited_files = material_container.getInheritedFiles() - except AttributeError: # Material_container does not support inheritance. - inherited_files = [] - - if inherited_files: - for inherited_file in inherited_files: - # Extract the ID from the path we used to load the file. - search_criteria["material"] = os.path.basename(inherited_file).split(".")[0] - containers = container_registry.findInstanceContainers(**search_criteria) - if containers: - return containers[0] - # We still weren't able to find a quality for this specific material. - # Try to find qualities for a generic version of the material. - material_search_criteria = { "type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"} - if definition.getMetaDataEntry("has_machine_quality"): - if material_container: - material_search_criteria["definition"] = material_container.getDefinition().id - - if definition.getMetaDataEntry("has_variants"): - material_search_criteria["variant"] = material_container.getMetaDataEntry("variant") - else: - material_search_criteria["definition"] = self.getQualityDefinitionId(definition) - - if definition.getMetaDataEntry("has_variants") and variant_container: - material_search_criteria["variant"] = self.getQualityVariantId(definition, variant_container) - else: - material_search_criteria["definition"] = "fdmprinter" - material_containers = container_registry.findInstanceContainers(**material_search_criteria) - # Try all materials to see if there is a quality profile available. - for material_container in material_containers: - search_criteria["material"] = material_container.getId() - - containers = container_registry.findInstanceContainers(**search_criteria) - if containers: - return containers[0] - - if "name" in search_criteria or "id" in search_criteria: - # If a quality by this name can not be found, try a wider set of search criteria - search_criteria.pop("name", None) - search_criteria.pop("id", None) - - containers = container_registry.findInstanceContainers(**search_criteria) - if containers: - return containers[0] - - # Notify user that we were unable to find a matching quality - message = Message(catalog.i18nc("@info:status", "Unable to find a quality profile for this combination. Default settings will be used instead.")) - message.show() - return self._empty_quality_container - - ## Finds a quality-changes container to use if any other container - # changes. - # - # \param quality_type The quality type to find a quality-changes for. - # \param preferred_quality_changes_name The name of the quality-changes to - # pick, if any such quality-changes profile is available. - def _updateQualityChangesContainer(self, quality_type, preferred_quality_changes_name = None): - container_registry = ContainerRegistry.getInstance() # Cache. - search_criteria = { "type": "quality_changes" } - - search_criteria["quality"] = quality_type - if preferred_quality_changes_name: - search_criteria["name"] = preferred_quality_changes_name - - # Try to search with the name in the criteria first, since we prefer to have the correct name. - containers = container_registry.findInstanceContainers(**search_criteria) - if containers: # Found one! - return containers[0] - - if "name" in search_criteria: - del search_criteria["name"] # Not found, then drop the name requirement (if we had one) and search again. - containers = container_registry.findInstanceContainers(**search_criteria) - if containers: - return containers[0] - - return self._empty_quality_changes_container # Didn't find anything with the required quality_type. - def _onMachineNameChanged(self): self.globalContainerChanged.emit() diff --git a/cura/Settings/ProfilesModel.py b/cura/Settings/ProfilesModel.py index 404bb569a5..9056273216 100644 --- a/cura/Settings/ProfilesModel.py +++ b/cura/Settings/ProfilesModel.py @@ -32,9 +32,9 @@ class ProfilesModel(InstanceContainersModel): ## Get the singleton instance for this class. @classmethod - def getInstance(cls): + def getInstance(cls) -> "ProfilesModel": # Note: Explicit use of class name to prevent issues with inheritance. - if ProfilesModel.__instance is None: + if not ProfilesModel.__instance: ProfilesModel.__instance = cls() return ProfilesModel.__instance diff --git a/cura/Settings/SetObjectExtruderOperation.py b/cura/Settings/SetObjectExtruderOperation.py new file mode 100644 index 0000000000..31c996529a --- /dev/null +++ b/cura/Settings/SetObjectExtruderOperation.py @@ -0,0 +1,27 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from UM.Scene.SceneNode import SceneNode +from UM.Operations.Operation import Operation + +from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator + +## Simple operation to set the extruder a certain object should be printed with. +class SetObjectExtruderOperation(Operation): + def __init__(self, node: SceneNode, extruder_id: str) -> None: + self._node = node + self._extruder_id = extruder_id + self._previous_extruder_id = None + self._decorator_added = False + + def undo(self): + if self._previous_extruder_id: + self._node.callDecoration("setActiveExtruder", self._previous_extruder_id) + + def redo(self): + stack = self._node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway. + if not stack: + self._node.addDecorator(SettingOverrideDecorator()) + + self._previous_extruder_id = self._node.callDecoration("getActiveExtruder") + self._node.callDecoration("setActiveExtruder", self._extruder_id) diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py index 2f81526813..ff0d1d81c0 100644 --- a/cura/Settings/SettingInheritanceManager.py +++ b/cura/Settings/SettingInheritanceManager.py @@ -109,10 +109,13 @@ class SettingInheritanceManager(QObject): self._settings_with_inheritance_warning.remove(key) settings_with_inheritance_warning_changed = True - # Find the topmost parent (Assumed to be a category) parent = definitions[0].parent - while parent.parent is not None: - parent = parent.parent + # Find the topmost parent (Assumed to be a category) + if parent is not None: + while parent.parent is not None: + parent = parent.parent + else: + parent = definitions[0] # Already at a category if parent.key not in self._settings_with_inheritance_warning and has_overwritten_inheritance: # Category was not in the list yet, so needs to be added now. diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index 76c155cb99..d754b6bc6d 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -109,6 +109,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): def setActiveExtruder(self, extruder_stack_id): self._extruder_stack = extruder_stack_id self._updateNextStack() + ExtruderManager.getInstance().resetSelectedObjectExtruders() self.activeExtruderChanged.emit() def getStack(self): diff --git a/cura/ShapeArray.py b/cura/ShapeArray.py index 534fa78e4d..95d0201c38 100755 --- a/cura/ShapeArray.py +++ b/cura/ShapeArray.py @@ -43,8 +43,10 @@ class ShapeArray: transform_x = transform._data[0][3] transform_y = transform._data[2][3] hull_verts = node.callDecoration("getConvexHull") + # For one_at_a_time printing you need the convex hull head. + hull_head_verts = node.callDecoration("getConvexHullHead") or hull_verts - offset_verts = hull_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset)) + offset_verts = hull_head_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset)) offset_points = copy.deepcopy(offset_verts._points) # x, y offset_points[:, 0] = numpy.add(offset_points[:, 0], -transform_x) offset_points[:, 1] = numpy.add(offset_points[:, 1], -transform_y) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py old mode 100644 new mode 100755 index a0ce679464..c0b8e73a8c --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -1,3 +1,6 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + from UM.Workspace.WorkspaceReader import WorkspaceReader from UM.Application import Application @@ -15,7 +18,10 @@ from .WorkspaceDialog import WorkspaceDialog import xml.etree.ElementTree as ET from cura.Settings.ExtruderManager import ExtruderManager +from cura.Settings.ExtruderStack import ExtruderStack +from cura.Settings.GlobalStack import GlobalStack +from configparser import ConfigParser import zipfile import io import configparser @@ -31,10 +37,20 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._dialog = WorkspaceDialog() self._3mf_mesh_reader = None self._container_registry = ContainerRegistry.getInstance() - self._definition_container_suffix = ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix + + # suffixes registered with the MineTypes don't start with a dot '.' + self._definition_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix self._material_container_suffix = None # We have to wait until all other plugins are loaded before we can set it - self._instance_container_suffix = ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix - self._container_stack_suffix = ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix + self._instance_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix + self._container_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix + self._extruder_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ExtruderStack).preferredSuffix + self._global_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(GlobalStack).preferredSuffix + + # Certain instance container types are ignored because we make the assumption that only we make those types + # of containers. They are: + # - quality + # - variant + self._ignored_instance_container_types = {"quality", "variant"} self._resolve_strategies = {} @@ -47,6 +63,49 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._id_mapping[old_id] = self._container_registry.uniqueName(old_id) return self._id_mapping[old_id] + ## Separates the given file list into a list of GlobalStack files and a list of ExtruderStack files. + # + # In old versions, extruder stack files have the same suffix as container stack files ".stack.cfg". + # + def _determineGlobalAndExtruderStackFiles(self, project_file_name, file_list): + archive = zipfile.ZipFile(project_file_name, "r") + + global_stack_file_list = [name for name in file_list if name.endswith(self._global_stack_suffix)] + extruder_stack_file_list = [name for name in file_list if name.endswith(self._extruder_stack_suffix)] + + # separate container stack files and extruder stack files + files_to_determine = [name for name in file_list if name.endswith(self._container_stack_suffix)] + for file_name in files_to_determine: + # FIXME: HACK! + # We need to know the type of the stack file, but we can only know it if we deserialize it. + # The default ContainerStack.deserialize() will connect signals, which is not desired in this case. + # Since we know that the stack files are INI files, so we directly use the ConfigParser to parse them. + serialized = archive.open(file_name).read().decode("utf-8") + stack_config = ConfigParser() + stack_config.read_string(serialized) + + # sanity check + if not stack_config.has_option("metadata", "type"): + Logger.log("e", "%s in %s doesn't seem to be valid stack file", file_name, project_file_name) + continue + + stack_type = stack_config.get("metadata", "type") + if stack_type == "extruder_train": + extruder_stack_file_list.append(file_name) + elif stack_type == "machine": + global_stack_file_list.append(file_name) + else: + Logger.log("w", "Unknown container stack type '%s' from %s in %s", + stack_type, file_name, project_file_name) + + if len(global_stack_file_list) != 1: + raise RuntimeError("More than one global stack file found: [%s]" % str(global_stack_file_list)) + + return global_stack_file_list[0], extruder_stack_file_list + + ## read some info so we can make decisions + # \param file_name + # \param show_dialog In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog. def preRead(self, file_name, show_dialog=True, *args, **kwargs): self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name) if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted: @@ -59,51 +118,52 @@ class ThreeMFWorkspaceReader(WorkspaceReader): machine_type = "" variant_type_name = i18n_catalog.i18nc("@label", "Nozzle") - num_extruders = 0 # Check if there are any conflicts, so we can ask the user. archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] - container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)] - self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None} - machine_conflict = False - quality_changes_conflict = False - for container_stack_file in container_stack_files: - container_id = self._stripFileToId(container_stack_file) - serialized = archive.open(container_stack_file).read().decode("utf-8") - if machine_name == "": - machine_name = self._getMachineNameFromSerializedStack(serialized) - stacks = self._container_registry.findContainerStacks(id=container_id) - if stacks: - # Check if there are any changes at all in any of the container stacks. - id_list = self._getContainerIdListFromSerialized(serialized) - for index, container_id in enumerate(id_list): - if stacks[0].getContainer(index).getId() != container_id: - machine_conflict = True - Job.yieldThread() + # A few lists of containers in this project files. + # When loading the global stack file, it may be associated with those containers, which may or may not be + # in Cura already, so we need to provide them as alternative search lists. + definition_container_list = [] + instance_container_list = [] + material_container_list = [] + + # + # Read definition containers + # + machine_definition_container_count = 0 + extruder_definition_container_count = 0 definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] - for definition_container_file in definition_container_files: - container_id = self._stripFileToId(definition_container_file) + for each_definition_container_file in definition_container_files: + container_id = self._stripFileToId(each_definition_container_file) definitions = self._container_registry.findDefinitionContainers(id=container_id) if not definitions: definition_container = DefinitionContainer(container_id) - definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8")) + definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8")) else: definition_container = definitions[0] + definition_container_list.append(definition_container) - if definition_container.getMetaDataEntry("type") != "extruder": + definition_container_type = definition_container.getMetaDataEntry("type") + if definition_container_type == "machine": machine_type = definition_container.getName() variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name) + + machine_definition_container_count += 1 + elif definition_container_type == "extruder": + extruder_definition_container_count += 1 else: - num_extruders += 1 + Logger.log("w", "Unknown definition container type %s for %s", + definition_container_type, each_definition_container_file) Job.yieldThread() - - if num_extruders == 0: - num_extruders = 1 # No extruder stacks found, which means there is one extruder - - extruders = num_extruders * [""] + # sanity check + if machine_definition_container_count != 1: + msg = "Expecting one machine definition container but got %s" % machine_definition_container_count + Logger.log("e", msg) + raise RuntimeError(msg) material_labels = [] material_conflict = False @@ -119,18 +179,25 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if materials and not materials[0].isReadOnly(): # Only non readonly materials can be in conflict material_conflict = True Job.yieldThread() + # Check if any quality_changes instance container is in conflict. instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] quality_name = "" quality_type = "" num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes + num_settings_overriden_by_definition_changes = 0 # How many settings are changed by the definition changes num_user_settings = 0 - for instance_container_file in instance_container_files: - container_id = self._stripFileToId(instance_container_file) + quality_changes_conflict = False + definition_changes_conflict = False + + for each_instance_container_file in instance_container_files: + container_id = self._stripFileToId(each_instance_container_file) instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string - instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) + instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8")) + instance_container_list.append(instance_container) + container_type = instance_container.getMetaDataEntry("type") if container_type == "quality_changes": quality_name = instance_container.getName() @@ -141,16 +208,41 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Check if there really is a conflict by comparing the values if quality_changes[0] != instance_container: quality_changes_conflict = True - elif container_type == "quality": - # If the quality name is not set (either by quality or changes, set it now) - # Quality changes should always override this (as they are "on top") - if quality_name == "": - quality_name = instance_container.getName() - quality_type = instance_container.getName() + elif container_type == "definition_changes": + definition_name = instance_container.getName() + num_settings_overriden_by_definition_changes += len(instance_container._instances) + definition_changes = self._container_registry.findDefinitionContainers(id = container_id) + if definition_changes: + if definition_changes[0] != instance_container: + definition_changes_conflict = True elif container_type == "user": num_user_settings += len(instance_container._instances) + elif container_type in self._ignored_instance_container_types: + # Ignore certain instance container types + Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type) + continue Job.yieldThread() + + # Load ContainerStack files and ExtruderStack files + global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles( + file_name, cura_file_names) + self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None} + machine_conflict = False + for container_stack_file in [global_stack_file] + extruder_stack_files: + container_id = self._stripFileToId(container_stack_file) + serialized = archive.open(container_stack_file).read().decode("utf-8") + if machine_name == "": + machine_name = self._getMachineNameFromSerializedStack(serialized) + stacks = self._container_registry.findContainerStacks(id = container_id) + if stacks: + # Check if there are any changes at all in any of the container stacks. + id_list = self._getContainerIdListFromSerialized(serialized) + for index, container_id in enumerate(id_list): + if stacks[0].getContainer(index).getId() != container_id: + machine_conflict = True + Job.yieldThread() + num_visible_settings = 0 try: temp_preferences = Preferences() @@ -171,9 +263,17 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if not show_dialog: return WorkspaceReader.PreReadResult.accepted + # prepare data for the dialog + num_extruders = extruder_definition_container_count + if num_extruders == 0: + num_extruders = 1 # No extruder stacks found, which means there is one extruder + + extruders = num_extruders * [""] + # Show the dialog, informing the user what is about to happen. self._dialog.setMachineConflict(machine_conflict) self._dialog.setQualityChangesConflict(quality_changes_conflict) + self._dialog.setDefinitionChangesConflict(definition_changes_conflict) self._dialog.setMaterialConflict(material_conflict) self._dialog.setNumVisibleSettings(num_visible_settings) self._dialog.setQualityName(quality_name) @@ -196,9 +296,58 @@ class ThreeMFWorkspaceReader(WorkspaceReader): return WorkspaceReader.PreReadResult.cancelled self._resolve_strategies = self._dialog.getResult() + # + # There can be 3 resolve strategies coming from the dialog: + # - new: create a new container + # - override: override the existing container + # - None: There is no conflict, which means containers with the same IDs may or may not be there already. + # If they are there, there is no conflict between the them. + # In this case, you can either create a new one, or safely override the existing one. + # + # Default values + for k, v in self._resolve_strategies.items(): + if v is None: + self._resolve_strategies[k] = "new" return WorkspaceReader.PreReadResult.accepted + ## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack. + def _overrideExtruderStack(self, global_stack, extruder_index, extruder_file_content): + extruder_stack = global_stack.extruders[extruder_index] + machine_extruder_count = len(global_stack.extruders) + + old_extruder_stack_id = extruder_stack.getId() + + # HACK: There are two cases: + # - the new ExtruderStack has the same ID as the one we are overriding + # - they don't have the same ID + # In the second case, directly overriding the existing ExtruderStack will leave the old stack file + # in the Cura directory, and this will cause a problem when we restart Cura. So, we always delete + # the existing file first. + self._container_registry._deleteFiles(extruder_stack) + + # override the given extruder stack + extruder_stack.deserialize(extruder_file_content) + # HACK: The deserialize() of ExtruderStack will add itself to the GlobalStack, which is redundant here. + # So we need to remove the new entries in the GlobalStack. + global_stack._extruders = global_stack._extruders[:machine_extruder_count] + + # HACK: clean and fill the container query cache again + if old_extruder_stack_id in self._container_registry._id_container_cache: + del self._container_registry._id_container_cache[old_extruder_stack_id] + new_extruder_stack_id = extruder_stack.getId() + self._container_registry._id_container_cache[new_extruder_stack_id] = extruder_stack + + # return the new ExtruderStack + return extruder_stack + + ## Read the project file + # Add all the definitions / materials / quality changes that do not exist yet. Then it loads + # all the stacks into the container registry. In some cases it will reuse the container for the global stack. + # It handles old style project files containing .stack.cfg as well as new style project files + # containing global.cfg / extruder.cfg + # + # \param file_name def read(self, file_name): archive = zipfile.ZipFile(file_name, "r") @@ -232,6 +381,24 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # We do this so that if something goes wrong, it's easier to clean up. containers_to_add = [] + global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names) + + global_stack = None + extruder_stacks = [] + extruder_stacks_added = [] + container_stacks_added = [] + + containers_added = [] + + global_stack_id_original = self._stripFileToId(global_stack_file) + global_stack_id_new = global_stack_id_original + global_stack_need_rename = False + if self._resolve_strategies["machine"] == "new": + # We need a new id if the id already exists + if self._container_registry.findContainerStacks(id = global_stack_id_original): + global_stack_id_new = self.getNewId(global_stack_id_original) + global_stack_need_rename = True + # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few # TODO: cases that the container loaded is the same (most notable in materials & definitions). # TODO: It might be possible that we need to add smarter checking in the future. @@ -240,7 +407,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] for definition_container_file in definition_container_files: container_id = self._stripFileToId(definition_container_file) - definitions = self._container_registry.findDefinitionContainers(id=container_id) + definitions = self._container_registry.findDefinitionContainers(id = container_id) if not definitions: definition_container = DefinitionContainer(container_id) definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8")) @@ -257,222 +424,379 @@ class ThreeMFWorkspaceReader(WorkspaceReader): material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)] for material_container_file in material_container_files: container_id = self._stripFileToId(material_container_file) - materials = self._container_registry.findInstanceContainers(id=container_id) + materials = self._container_registry.findInstanceContainers(id = container_id) + if not materials: material_container = xml_material_profile(container_id) material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) containers_to_add.append(material_container) else: - if not materials[0].isReadOnly(): # Only create new materials if they are not read only. + material_container = materials[0] + if not material_container.isReadOnly(): # Only create new materials if they are not read only. if self._resolve_strategies["material"] == "override": - materials[0].deserialize(archive.open(material_container_file).read().decode("utf-8")) + material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) elif self._resolve_strategies["material"] == "new": # Note that we *must* deserialize it with a new ID, as multiple containers will be # auto created & added. material_container = xml_material_profile(self.getNewId(container_id)) material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) containers_to_add.append(material_container) - material_containers.append(material_container) + + material_containers.append(material_container) Job.yieldThread() Logger.log("d", "Workspace loading is checking instance containers...") # Get quality_changes and user profiles saved in the workspace instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] user_instance_containers = [] - quality_changes_instance_containers = [] + quality_and_definition_changes_instance_containers = [] for instance_container_file in instance_container_files: container_id = self._stripFileToId(instance_container_file) + serialized = archive.open(instance_container_file).read().decode("utf-8") + + # HACK! we ignore the "metadata/type = quality" instance containers! + parser = configparser.ConfigParser() + parser.read_string(serialized) + if not parser.has_option("metadata", "type"): + Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file) + continue + if parser.get("metadata", "type") == "quality": + continue + instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string - instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) + instance_container.deserialize(serialized) container_type = instance_container.getMetaDataEntry("type") Job.yieldThread() - if container_type == "user": + + if container_type in self._ignored_instance_container_types: + # Ignore certain instance container types + Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type) + continue + elif container_type == "user": # Check if quality changes already exists. - user_containers = self._container_registry.findInstanceContainers(id=container_id) + user_containers = self._container_registry.findInstanceContainers(id = container_id) if not user_containers: containers_to_add.append(instance_container) else: if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None: - user_containers[0].deserialize(archive.open(instance_container_file).read().decode("utf-8")) + instance_container = user_containers[0] + instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) + instance_container.setDirty(True) elif self._resolve_strategies["machine"] == "new": # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. extruder_id = instance_container.getMetaDataEntry("extruder", None) if extruder_id: - new_id = self.getNewId(extruder_id) + "_current_settings" + new_extruder_id = self.getNewId(extruder_id) + new_id = new_extruder_id + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) - instance_container.setMetaDataEntry("extruder", self.getNewId(extruder_id)) + instance_container.setMetaDataEntry("extruder", new_extruder_id) containers_to_add.append(instance_container) machine_id = instance_container.getMetaDataEntry("machine", None) if machine_id: - new_id = self.getNewId(machine_id) + "_current_settings" + new_machine_id = self.getNewId(machine_id) + new_id = new_machine_id + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) - instance_container.setMetaDataEntry("machine", self.getNewId(machine_id)) + instance_container.setMetaDataEntry("machine", new_machine_id) containers_to_add.append(instance_container) user_instance_containers.append(instance_container) - elif container_type == "quality_changes": + elif container_type in ("quality_changes", "definition_changes"): # Check if quality changes already exists. - quality_changes = self._container_registry.findInstanceContainers(id = container_id) - if not quality_changes: + changes_containers = self._container_registry.findInstanceContainers(id = container_id) + if not changes_containers: + # no existing containers with the same ID, so we can safely add the new one containers_to_add.append(instance_container) else: - if self._resolve_strategies["quality_changes"] == "override": - quality_changes[0].deserialize(archive.open(instance_container_file).read().decode("utf-8")) - elif self._resolve_strategies["quality_changes"] is None: + # we have found existing container with the same ID, so we need to resolve according to the + # selected strategy. + if self._resolve_strategies[container_type] == "override": + instance_container = changes_containers[0] + instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) + instance_container.setDirty(True) + + elif self._resolve_strategies[container_type] == "new": + # TODO: how should we handle the case "new" for quality_changes and definition_changes? + + new_changes_container_id = self.getNewId(instance_container.getId()) + instance_container._id = new_changes_container_id + instance_container.setName(new_changes_container_id) + + # TODO: we don't know the following is correct or not, need to verify + # AND REFACTOR!!! + if self._resolve_strategies["machine"] == "new": + # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. + extruder_id = instance_container.getMetaDataEntry("extruder", None) + if extruder_id: + new_extruder_id = self.getNewId(extruder_id) + instance_container.setMetaDataEntry("extruder", new_extruder_id) + + machine_id = instance_container.getMetaDataEntry("machine", None) + if machine_id: + new_machine_id = self.getNewId(machine_id) + instance_container.setMetaDataEntry("machine", new_machine_id) + + containers_to_add.append(instance_container) + + elif self._resolve_strategies[container_type] is None: # The ID already exists, but nothing in the values changed, so do nothing. pass - quality_changes_instance_containers.append(instance_container) + quality_and_definition_changes_instance_containers.append(instance_container) else: - continue + existing_container = self._container_registry.findInstanceContainers(id = container_id) + if not existing_container: + containers_to_add.append(instance_container) + if global_stack_need_rename: + if instance_container.getMetaDataEntry("machine"): + instance_container.setMetaDataEntry("machine", global_stack_id_new) # Add all the containers right before we try to add / serialize the stack for container in containers_to_add: self._container_registry.addContainer(container) container.setDirty(True) + containers_added.append(container) # Get the stack(s) saved in the workspace. Logger.log("d", "Workspace loading is checking stacks containers...") - container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)] - global_stack = None - extruder_stacks = [] - container_stacks_added = [] - try: - for container_stack_file in container_stack_files: - container_id = self._stripFileToId(container_stack_file) - # Check if a stack by this ID already exists; - container_stacks = self._container_registry.findContainerStacks(id=container_id) + # -- + # load global stack file + try: + # Check if a stack by this ID already exists; + container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original) + if container_stacks: + stack = container_stacks[0] + + if self._resolve_strategies["machine"] == "override": + # TODO: HACK + # There is a machine, check if it has authentication data. If so, keep that data. + network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id") + network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key") + container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8")) + if network_authentication_id: + container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id) + if network_authentication_key: + container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key) + elif self._resolve_strategies["machine"] == "new": + stack = GlobalStack(global_stack_id_new) + stack.deserialize(archive.open(global_stack_file).read().decode("utf-8")) + + # Ensure a unique ID and name + stack._id = global_stack_id_new + + # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the + # bound machine also needs to change. + if stack.getMetaDataEntry("machine", None): + stack.setMetaDataEntry("machine", global_stack_id_new) + + # Only machines need a new name, stacks may be non-unique + stack.setName(self._container_registry.uniqueName(stack.getName())) + container_stacks_added.append(stack) + self._container_registry.addContainer(stack) + else: + Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"]) + else: + # no existing container stack, so we create a new one + stack = GlobalStack(global_stack_id_new) + # Deserialize stack by converting read data from bytes to string + stack.deserialize(archive.open(global_stack_file).read().decode("utf-8")) + container_stacks_added.append(stack) + self._container_registry.addContainer(stack) + containers_added.append(stack) + + global_stack = stack + Job.yieldThread() + 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. + for container in containers_added: + self._container_registry.removeContainer(container.getId()) + return + + # -- + # load extruder stack files + try: + for index, extruder_stack_file in enumerate(extruder_stack_files): + container_id = self._stripFileToId(extruder_stack_file) + extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8") + + container_stacks = self._container_registry.findContainerStacks(id = container_id) if container_stacks: + # this container stack already exists, try to resolve stack = container_stacks[0] + if self._resolve_strategies["machine"] == "override": - # TODO: HACK - # There is a machine, check if it has authenticationd data. If so, keep that data. - network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id") - network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key") - container_stacks[0].deserialize(archive.open(container_stack_file).read().decode("utf-8")) - if network_authentication_id: - container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id) - if network_authentication_key: - container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key) + # NOTE: This is the same code as those in the lower part + # deserialize new extruder stack over the current ones + stack = self._overrideExtruderStack(global_stack, index, extruder_file_content) + elif self._resolve_strategies["machine"] == "new": + # create a new extruder stack from this one new_id = self.getNewId(container_id) - stack = ContainerStack(new_id) - stack.deserialize(archive.open(container_stack_file).read().decode("utf-8")) + stack = ExtruderStack(new_id) + stack.deserialize(archive.open(extruder_stack_file).read().decode("utf-8")) # Ensure a unique ID and name stack._id = new_id - # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the - # bound machine also needs to change. - if stack.getMetaDataEntry("machine", None): - stack.setMetaDataEntry("machine", self.getNewId(stack.getMetaDataEntry("machine"))) - - if stack.getMetaDataEntry("type") != "extruder_train": - # Only machines need a new name, stacks may be non-unique - stack.setName(self._container_registry.uniqueName(stack.getName())) - container_stacks_added.append(stack) self._container_registry.addContainer(stack) - else: - Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"]) + extruder_stacks_added.append(stack) + containers_added.append(stack) else: - stack = ContainerStack(container_id) - # Deserialize stack by converting read data from bytes to string - stack.deserialize(archive.open(container_stack_file).read().decode("utf-8")) - container_stacks_added.append(stack) - self._container_registry.addContainer(stack) + # No extruder stack with the same ID can be found + if self._resolve_strategies["machine"] == "override": + # deserialize new extruder stack over the current ones + stack = self._overrideExtruderStack(global_stack, index, extruder_file_content) - if stack.getMetaDataEntry("type") == "extruder_train": - extruder_stacks.append(stack) - else: - global_stack = stack - Job.yieldThread() + elif self._resolve_strategies["machine"] == "new": + # container not found, create a new one + stack = ExtruderStack(container_id) + stack.deserialize(archive.open(extruder_stack_file).read().decode("utf-8")) + self._container_registry.addContainer(stack) + extruder_stacks_added.append(stack) + containers_added.append(stack) + else: + Logger.log("w", "Unknown resolve strategy: %s" % str(self._resolve_strategies["machine"])) + + if global_stack_need_rename: + if stack.getMetaDataEntry("machine"): + stack.setMetaDataEntry("machine", global_stack_id_new) + + extruder_stacks.append(stack) 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. - for container in containers_to_add: - self._container_registry.getInstance().removeContainer(container.getId()) - - for container in container_stacks_added: - self._container_registry.getInstance().removeContainer(container.getId()) - - return None + for container in containers_added: + self._container_registry.removeContainer(container.getId()) + return + # + # 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. + # if self._resolve_strategies["machine"] == "new": # A new machine was made, but it was serialized with the wrong user container. Fix that now. for container in user_instance_containers: + # replacing the container ID for user instance containers for the extruders extruder_id = container.getMetaDataEntry("extruder", None) if extruder_id: for extruder in extruder_stacks: if extruder.getId() == extruder_id: - extruder.replaceContainer(0, container) + extruder.userChanges = container continue + + # replacing the container ID for user instance containers for the machine machine_id = container.getMetaDataEntry("machine", None) if machine_id: if global_stack.getId() == machine_id: - global_stack.replaceContainer(0, container) + global_stack.userChanges = container continue - if self._resolve_strategies["quality_changes"] == "new": - # Quality changes needs to get a new ID, added to registry and to the right stacks - for container in quality_changes_instance_containers: - old_id = container.getId() - container.setName(self._container_registry.uniqueName(container.getName())) - # We're not really supposed to change the ID in normal cases, but this is an exception. - container._id = self.getNewId(container.getId()) + for changes_container_type in ("quality_changes", "definition_changes"): + if self._resolve_strategies[changes_container_type] == "new": + # Quality changes needs to get a new ID, added to registry and to the right stacks + for each_changes_container in quality_and_definition_changes_instance_containers: + old_id = each_changes_container.getId() + each_changes_container.setName(self._container_registry.uniqueName(each_changes_container.getName())) + # We're not really supposed to change the ID in normal cases, but this is an exception. + each_changes_container._id = self.getNewId(each_changes_container.getId()) - # The container was not added yet, as it didn't have an unique ID. It does now, so add it. - self._container_registry.addContainer(container) + # The container was not added yet, as it didn't have an unique ID. It does now, so add it. + self._container_registry.addContainer(each_changes_container) - # Replace the quality changes container - old_container = global_stack.findContainer({"type": "quality_changes"}) - if old_container.getId() == old_id: - quality_changes_index = global_stack.getContainerIndex(old_container) - global_stack.replaceContainer(quality_changes_index, container) - continue + # Find the old (current) changes container in the global stack + if changes_container_type == "quality_changes": + old_container = global_stack.qualityChanges + elif changes_container_type == "definition_changes": + old_container = global_stack.definitionChanges - for stack in extruder_stacks: - old_container = stack.findContainer({"type": "quality_changes"}) + # sanity checks + # NOTE: The following cases SHOULD NOT happen!!!! + if not old_container: + Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!", + changes_container_type, global_stack.getId()) + + # Replace the quality/definition changes container if it's in the GlobalStack + # NOTE: we can get an empty container here, but the IDs will not match, + # so this comparison is fine. if old_container.getId() == old_id: - quality_changes_index = stack.getContainerIndex(old_container) - stack.replaceContainer(quality_changes_index, container) + if changes_container_type == "quality_changes": + global_stack.qualityChanges = each_changes_container + elif changes_container_type == "definition_changes": + global_stack.definitionChanges = each_changes_container + continue + + # Replace the quality/definition changes container if it's in one of the ExtruderStacks + for each_extruder_stack in extruder_stacks: + changes_container = None + if changes_container_type == "quality_changes": + changes_container = each_extruder_stack.qualityChanges + elif changes_container_type == "definition_changes": + changes_container = each_extruder_stack.definitionChanges + + # sanity checks + # NOTE: The following cases SHOULD NOT happen!!!! + if not changes_container: + Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!", + changes_container_type, each_extruder_stack.getId()) + + # NOTE: we can get an empty container here, but the IDs will not match, + # so this comparison is fine. + if changes_container.getId() == old_id: + if changes_container_type == "quality_changes": + each_extruder_stack.qualityChanges = each_changes_container + elif changes_container_type == "definition_changes": + each_extruder_stack.definitionChanges = each_changes_container if self._resolve_strategies["material"] == "new": - for material in material_containers: - old_material = global_stack.findContainer({"type": "material"}) - if old_material.getId() in self._id_mapping: - material_index = global_stack.getContainerIndex(old_material) - global_stack.replaceContainer(material_index, material) + for each_material in material_containers: + old_material = global_stack.material + + # check if the old material container has been renamed to this material container ID + # if the container hasn't been renamed, we do nothing. + new_id = self._id_mapping.get(old_material.getId()) + if new_id is None or new_id != each_material.getId(): continue - for stack in extruder_stacks: - old_material = stack.findContainer({"type": "material"}) - if old_material.getId() in self._id_mapping: - material_index = stack.getContainerIndex(old_material) - stack.replaceContainer(material_index, material) + if old_material.getId() in self._id_mapping: + global_stack.material = each_material + + for each_extruder_stack in extruder_stacks: + old_material = each_extruder_stack.material + + # check if the old material container has been renamed to this material container ID + # if the container hasn't been renamed, we do nothing. + new_id = self._id_mapping.get(old_material.getId()) + if new_id is None or new_id != each_material.getId(): continue - for stack in extruder_stacks: - ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId()) + if old_material.getId() in self._id_mapping: + each_extruder_stack.material = each_material + + if extruder_stacks: + for stack in extruder_stacks: + ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId()) else: # Machine has no extruders, but it needs to be registered with the extruder manager. ExtruderManager.getInstance().registerExtruder(None, global_stack.getId()) Logger.log("d", "Workspace loading is notifying rest of the code of changes...") - # Notify everything/one that is to notify about changes. - global_stack.containersChanged.emit(global_stack.getTop()) - - for stack in extruder_stacks: - stack.setNextStack(global_stack) - stack.containersChanged.emit(stack.getTop()) + if self._resolve_strategies["machine"] == "new": + for stack in extruder_stacks: + stack.setNextStack(global_stack) + stack.containersChanged.emit(stack.getTop()) # Actually change the active machine. Application.getInstance().setGlobalContainerStack(global_stack) + # Notify everything/one that is to notify about changes. + global_stack.containersChanged.emit(global_stack.getTop()) + # Load all the nodes / meshdata of the workspace nodes = self._3mf_mesh_reader.read(file_name) if nodes is None: diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 1bae9575f2..9d6c70cf8b 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -1,7 +1,7 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from PyQt5.QtCore import Qt, QUrl, pyqtSignal, QObject, pyqtProperty, QCoreApplication +from PyQt5.QtCore import QUrl, pyqtSignal, QObject, pyqtProperty, QCoreApplication from UM.FlameProfiler import pyqtSlot from PyQt5.QtQml import QQmlComponent, QQmlContext from UM.PluginRegistry import PluginRegistry @@ -29,11 +29,13 @@ class WorkspaceDialog(QObject): self._default_strategy = "override" self._result = {"machine": self._default_strategy, "quality_changes": self._default_strategy, + "definition_changes": self._default_strategy, "material": self._default_strategy} self._visible = False self.showDialogSignal.connect(self.__show) self._has_quality_changes_conflict = False + self._has_definition_changes_conflict = False self._has_machine_conflict = False self._has_material_conflict = False self._num_visible_settings = 0 @@ -51,6 +53,7 @@ class WorkspaceDialog(QObject): machineConflictChanged = pyqtSignal() qualityChangesConflictChanged = pyqtSignal() + definitionChangesConflictChanged = pyqtSignal() materialConflictChanged = pyqtSignal() numVisibleSettingsChanged = pyqtSignal() activeModeChanged = pyqtSignal() @@ -185,6 +188,10 @@ class WorkspaceDialog(QObject): def qualityChangesConflict(self): return self._has_quality_changes_conflict + @pyqtProperty(bool, notify=definitionChangesConflictChanged) + def definitionChangesConflict(self): + return self._has_definition_changes_conflict + @pyqtProperty(bool, notify=materialConflictChanged) def materialConflict(self): return self._has_material_conflict @@ -214,11 +221,18 @@ class WorkspaceDialog(QObject): self._has_quality_changes_conflict = quality_changes_conflict self.qualityChangesConflictChanged.emit() + def setDefinitionChangesConflict(self, definition_changes_conflict): + if self._has_definition_changes_conflict != definition_changes_conflict: + self._has_definition_changes_conflict = definition_changes_conflict + self.definitionChangesConflictChanged.emit() + def getResult(self): if "machine" in self._result and not self._has_machine_conflict: self._result["machine"] = None if "quality_changes" in self._result and not self._has_quality_changes_conflict: self._result["quality_changes"] = None + if "definition_changes" in self._result and not self._has_definition_changes_conflict: + self._result["definition_changes"] = None if "material" in self._result and not self._has_material_conflict: self._result["material"] = None return self._result @@ -240,6 +254,7 @@ class WorkspaceDialog(QObject): # Reset the result self._result = {"machine": self._default_strategy, "quality_changes": self._default_strategy, + "definition_changes": self._default_strategy, "material": self._default_strategy} self._visible = True self.showDialogSignal.emit() diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py index 0960d89076..326cd87845 100644 --- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py +++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py @@ -7,6 +7,7 @@ from cura.Settings.ExtruderManager import ExtruderManager import zipfile from io import StringIO import copy +import configparser class ThreeMFWorkspaceWriter(WorkspaceWriter): @@ -48,6 +49,16 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): Preferences.getInstance().writeToFile(preferences_string) archive.writestr(preferences_file, preferences_string.getvalue()) + # Save Cura version + version_file = zipfile.ZipInfo("Cura/version.ini") + version_config_parser = configparser.ConfigParser() + version_config_parser.add_section("versions") + version_config_parser.set("versions", "cura_version", Application.getStaticVersion()) + + version_file_string = StringIO() + version_config_parser.write(version_file_string) + archive.writestr(version_file, version_file_string.getvalue()) + # Close the archive & reset states. archive.close() mesh_writer.setStoreArchive(False) diff --git a/plugins/ChangeLogPlugin/ChangeLog.txt b/plugins/ChangeLogPlugin/ChangeLog.txt index c7b63be889..508d5ecbe2 100755 --- a/plugins/ChangeLogPlugin/ChangeLog.txt +++ b/plugins/ChangeLogPlugin/ChangeLog.txt @@ -79,7 +79,7 @@ The initial and final printing temperatures reduce the amount of oozing during P Initial and final printing temperature settings have been tuned for higher quality results. For all materials the initial print temperature is 5 degrees above the default value. *Printing temperature of the materials -The printing temperature of the materials in the material profiles is now the same as the printing temperature for the Normal Quality profile. +The printing temperature of the materials in the material profiles is now the same as the printing temperature for the Fine profile. *Improved PLA-PVA layer adhesion The PVA jerk and acceleration have been optimized to improve the layer adhesion between PVA and PLA. diff --git a/plugins/CuraEngineBackend/Cura.proto b/plugins/CuraEngineBackend/Cura.proto index 4eab500f0a..c2e4e5bb5f 100644 --- a/plugins/CuraEngineBackend/Cura.proto +++ b/plugins/CuraEngineBackend/Cura.proto @@ -90,9 +90,21 @@ message GCodeLayer { } -message PrintTimeMaterialEstimates { // The print time for the whole print and material estimates for the extruder - float time = 1; // Total time estimate - repeated MaterialEstimates materialEstimates = 2; // materialEstimates data +message PrintTimeMaterialEstimates { // The print time for each feature and material estimates for the extruder + // Time estimate in each feature + float time_none = 1; + float time_inset_0 = 2; + float time_inset_x = 3; + float time_skin = 4; + float time_support = 5; + float time_skirt = 6; + float time_infill = 7; + float time_support_infill = 8; + float time_travel = 9; + float time_retract = 10; + float time_support_interface = 11; + + repeated MaterialEstimates materialEstimates = 12; // materialEstimates data } message MaterialEstimates { @@ -121,4 +133,4 @@ message GCodePrefix { } message SlicingFinished { -} \ No newline at end of file +} diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 981145bebd..9c9c9a1b90 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -13,9 +13,9 @@ from UM.Resources import Resources from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then. from UM.Platform import Platform from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.Qt.Duration import DurationFormat from PyQt5.QtCore import QObject, pyqtSlot - from cura.Settings.ExtruderManager import ExtruderManager from . import ProcessSlicedLayersJob from . import StartSliceJob @@ -187,7 +187,19 @@ class CuraEngineBackend(QObject, Backend): Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.") return - self.printDurationMessage.emit(0, [0]) + self.printDurationMessage.emit({ + "none": 0, + "inset_0": 0, + "inset_x": 0, + "skin": 0, + "support": 0, + "skirt": 0, + "infill": 0, + "support_infill": 0, + "travel": 0, + "retract": 0, + "support_interface": 0 + }, [0]) self._stored_layer_data = [] self._stored_optimized_layer_data = [] @@ -273,9 +285,15 @@ class CuraEngineBackend(QObject, Backend): if not extruders: error_keys = self._global_container_stack.getErrorKeys() error_labels = set() - definition_container = self._global_container_stack.getBottom() for key in error_keys: - error_labels.add(definition_container.findDefinitions(key = key)[0].label) + for stack in [self._global_container_stack] + extruders: #Search all container stacks for the definition of this setting. Some are only in an extruder stack. + definitions = stack.getBottom().findDefinitions(key = key) + if definitions: + break #Found it! No need to continue search. + else: #No stack has a definition for this setting. + Logger.log("w", "When checking settings for errors, unable to find definition for key: {key}".format(key = key)) + continue + error_labels.add(definitions[0].label) error_labels = ", ".join(error_labels) self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}".format(error_labels))) @@ -442,6 +460,15 @@ class CuraEngineBackend(QObject, Backend): self.backendStateChange.emit(BackendState.Done) self.processingProgress.emit(1.0) + for line in self._scene.gcode_list: + replaced = line.replace("{print_time}", str(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601))) + replaced = replaced.replace("{filament_amount}", str(Application.getInstance().getPrintInformation().materialLengths)) + replaced = replaced.replace("{filament_weight}", str(Application.getInstance().getPrintInformation().materialWeights)) + replaced = replaced.replace("{filament_cost}", str(Application.getInstance().getPrintInformation().materialCosts)) + replaced = replaced.replace("{jobname}", str(Application.getInstance().getPrintInformation().jobName)) + + self._scene.gcode_list[self._scene.gcode_list.index(line)] = replaced + self._slicing = False self._need_slicing = False Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time ) @@ -466,13 +493,26 @@ class CuraEngineBackend(QObject, Backend): ## Called when a print time message is received from the engine. # - # \param message The protobuff message containing the print time and + # \param message The protobuf message containing the print time per feature and # material amount per extruder def _onPrintTimeMaterialEstimates(self, message): material_amounts = [] for index in range(message.repeatedMessageCount("materialEstimates")): material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount) - self.printDurationMessage.emit(message.time, material_amounts) + feature_times = { + "none": message.time_none, + "inset_0": message.time_inset_0, + "inset_x": message.time_inset_x, + "skin": message.time_skin, + "support": message.time_support, + "skirt": message.time_skirt, + "infill": message.time_infill, + "support_infill": message.time_support_infill, + "travel": message.time_travel, + "retract": message.time_retract, + "support_interface": message.time_support_interface + } + self.printDurationMessage.emit(feature_times, material_amounts) ## Creates a new socket connection. def _createSocket(self): diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index 0d706f59b8..f7be2edc04 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py @@ -179,9 +179,10 @@ class ProcessSlicedLayersJob(Job): # Single extruder via global stack. material_color_map = numpy.zeros((1, 4), dtype=numpy.float32) material = global_container_stack.findContainer({"type": "material"}) - color_code = material.getMetaDataEntry("color_code") - if color_code is None: # not all stacks have a material color - color_code = "#e0e000" + color_code = "#e0e000" + if material: + if material.getMetaDataEntry("color_code") is not None: + color_code = material.getMetaDataEntry("color_code") color = colorCodeToRGBA(color_code) material_color_map[0, :] = color diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index c8cbbe8040..8abb72fa92 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -4,6 +4,7 @@ import numpy from string import Formatter from enum import IntEnum +import time from UM.Job import Job from UM.Application import Application @@ -230,25 +231,22 @@ class StartSliceJob(Job): keys = stack.getAllKeys() settings = {} for key in keys: - # Use resolvement value if available, or take the value - resolved_value = stack.getProperty(key, "resolve") - if resolved_value is not None: - # There is a resolvement value. Check if we need to use it. - user_container = stack.findContainer({"type": "user"}) - quality_changes_container = stack.findContainer({"type": "quality_changes"}) - if user_container.hasProperty(key,"value") or quality_changes_container.hasProperty(key,"value"): - # Normal case - settings[key] = stack.getProperty(key, "value") - else: - settings[key] = resolved_value - else: - # Normal case - settings[key] = stack.getProperty(key, "value") + settings[key] = stack.getProperty(key, "value") Job.yieldThread() start_gcode = settings["machine_start_gcode"] - settings["material_bed_temp_prepend"] = "{material_bed_temperature}" not in start_gcode #Pre-compute material material_bed_temp_prepend and material_print_temp_prepend - settings["material_print_temp_prepend"] = "{material_print_temperature}" not in start_gcode + #Pre-compute material material_bed_temp_prepend and material_print_temp_prepend + bed_temperature_settings = {"material_bed_temperature", "material_bed_temperature_layer_0"} + settings["material_bed_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in bed_temperature_settings)) + print_temperature_settings = {"material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"} + settings["material_print_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in print_temperature_settings)) + + settings["print_bed_temperature"] = settings["material_bed_temperature"] + settings["print_temperature"] = settings["material_print_temperature"] + + settings["time"] = time.strftime('%H:%M:%S') + settings["date"] = time.strftime('%d-%m-%Y') + settings["day"] = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][int(time.strftime('%w'))] for key, value in settings.items(): #Add all submessages for each individual setting. setting_message = self._slice_message.getMessage("global_settings").addRepeatedMessage("settings") diff --git a/plugins/GCodeProfileReader/GCodeProfileReader.py b/plugins/GCodeProfileReader/GCodeProfileReader.py index abfef6e296..0abbcfc833 100644 --- a/plugins/GCodeProfileReader/GCodeProfileReader.py +++ b/plugins/GCodeProfileReader/GCodeProfileReader.py @@ -56,7 +56,7 @@ class GCodeProfileReader(ProfileReader): # TODO: Consider moving settings to the start? serialized = "" # Will be filled with the serialized profile. try: - with open(file_name) as f: + with open(file_name, "r") as f: for line in f: if line.startswith(prefix): # Remove the prefix and the newline from the line and add it to the rest. @@ -66,9 +66,13 @@ class GCodeProfileReader(ProfileReader): return None serialized = unescapeGcodeComment(serialized) - Logger.log("i", "Serialized the following from %s: %s" %(file_name, repr(serialized))) - json_data = json.loads(serialized) + # serialized data can be invalid JSON + try: + json_data = json.loads(serialized) + except Exception as e: + Logger.log("e", "Could not parse serialized JSON data from GCode %s, error: %s", file_name, e) + return None profiles = [] global_profile = readQualityProfileFromString(json_data["global_quality"]) diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py index 1edce8a753..c59e6dce72 100755 --- a/plugins/GCodeReader/GCodeReader.py +++ b/plugins/GCodeReader/GCodeReader.py @@ -179,6 +179,7 @@ class GCodeReader(MeshReader): def _processGCode(self, G, line, position, path): func = getattr(self, "_gCode%s" % G, None) + 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 diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index 162738f073..ad730fbe2a 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -100,7 +100,7 @@ class GCodeWriter(MeshWriter): prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line. prefix_length = len(prefix) - container_with_profile = stack.findContainer({"type": "quality_changes"}) + container_with_profile = stack.qualityChanges if not container_with_profile: Logger.log("e", "No valid quality profile found, not writing settings to GCode!") return "" @@ -115,7 +115,7 @@ class GCodeWriter(MeshWriter): data = {"global_quality": serialized} for extruder in sorted(ExtruderManager.getInstance().getMachineExtruders(stack.getId()), key = lambda k: k.getMetaDataEntry("position")): - extruder_quality = extruder.findContainer({"type": "quality_changes"}) + extruder_quality = extruder.qualityChanges if not extruder_quality: Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId()) continue diff --git a/plugins/LayerView/LayerView.qml b/plugins/LayerView/LayerView.qml old mode 100644 new mode 100755 index 6ea855e20b..9dc038fe70 --- a/plugins/LayerView/LayerView.qml +++ b/plugins/LayerView/LayerView.qml @@ -7,6 +7,7 @@ import QtQuick.Layouts 1.1 import QtQuick.Controls.Styles 1.1 import UM 1.0 as UM +import Cura 1.0 as Cura Item { @@ -42,7 +43,8 @@ Item property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers") property bool show_skin: UM.Preferences.getValue("layerview/show_skin") property bool show_infill: UM.Preferences.getValue("layerview/show_infill") - property bool show_legend: UM.LayerView.compatibilityMode || UM.Preferences.getValue("layerview/layer_view_type") == 1 + // 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 only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers") property int top_layer_count: UM.Preferences.getValue("view/top_layer_count") @@ -58,6 +60,7 @@ Item anchors.left: parent.left text: catalog.i18nc("@label","View Mode: Layers") font.bold: true + color: UM.Theme.getColor("text") } Label @@ -75,6 +78,7 @@ Item text: catalog.i18nc("@label","Color scheme") visible: !UM.LayerView.compatibilityMode Layout.fillWidth: true + color: UM.Theme.getColor("text") } ListModel // matches LayerView.py @@ -102,28 +106,25 @@ Item Layout.preferredWidth: UM.Theme.getSize("layerview_row").width model: layerViewTypes visible: !UM.LayerView.compatibilityMode + style: UM.Theme.styles.combobox - property int layer_view_type: UM.Preferences.getValue("layerview/layer_view_type") - currentIndex: layer_view_type // index matches type_id - onActivated: { - // Combobox selection - var type_id = index; - UM.Preferences.setValue("layerview/layer_view_type", type_id); - updateLegend(type_id); - } - onModelChanged: { - updateLegend(UM.Preferences.getValue("layerview/layer_view_type")); + onActivated: + { + UM.Preferences.setValue("layerview/layer_view_type", index); } - // Update visibility of legend. - function updateLegend(type_id) { - if (UM.LayerView.compatibilityMode || (type_id == 1)) { - // Line type - view_settings.show_legend = true; - } else { - view_settings.show_legend = false; - } + Component.onCompleted: + { + currentIndex = UM.LayerView.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); + } + } Label @@ -149,7 +150,8 @@ Item target: UM.Preferences onPreferenceChanged: { - layerTypeCombobox.layer_view_type = UM.Preferences.getValue("layerview/layer_view_type"); + layerTypeCombobox.currentIndex = UM.LayerView.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"); view_settings.show_helpers = UM.Preferences.getValue("layerview/show_helpers"); @@ -161,106 +163,88 @@ Item } Repeater { - model: UM.LayerView.extruderCount + model: Cura.ExtrudersModel{} CheckBox { checked: view_settings.extruder_opacities[index] > 0.5 || view_settings.extruder_opacities[index] == undefined || view_settings.extruder_opacities[index] == "" onClicked: { view_settings.extruder_opacities[index] = checked ? 1.0 : 0.0 UM.Preferences.setValue("layerview/extruder_opacities", view_settings.extruder_opacities.join("|")); } - text: catalog.i18nc("@label", "Extruder %1").arg(index + 1) + text: model.name visible: !UM.LayerView.compatibilityMode enabled: index + 1 <= 4 + Rectangle { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + width: UM.Theme.getSize("layerview_legend_size").width + height: UM.Theme.getSize("layerview_legend_size").height + color: model.color + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("lining") + visible: !view_settings.show_legend + } Layout.fillWidth: true - Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height Layout.preferredWidth: UM.Theme.getSize("layerview_row").width + style: UM.Theme.styles.checkbox } } - CheckBox { - checked: view_settings.show_travel_moves - onClicked: { - UM.Preferences.setValue("layerview/show_travel_moves", checked); + Repeater { + model: ListModel { + id: typesLegenModel + Component.onCompleted: + { + typesLegenModel.append({ + label: catalog.i18nc("@label", "Show Travels"), + initialValue: view_settings.show_travel_moves, + preference: "layerview/show_travel_moves", + colorId: "layerview_move_combing" + }); + typesLegenModel.append({ + label: catalog.i18nc("@label", "Show Helpers"), + initialValue: view_settings.show_helpers, + preference: "layerview/show_helpers", + colorId: "layerview_support" + }); + typesLegenModel.append({ + label: catalog.i18nc("@label", "Show Shell"), + initialValue: view_settings.show_skin, + preference: "layerview/show_skin", + colorId: "layerview_inset_0" + }); + typesLegenModel.append({ + label: catalog.i18nc("@label", "Show Infill"), + initialValue: view_settings.show_infill, + preference: "layerview/show_infill", + colorId: "layerview_infill" + }); + } } - text: catalog.i18nc("@label", "Show Travels") - Rectangle { - anchors.top: parent.top - anchors.topMargin: 2 - anchors.right: parent.right - width: UM.Theme.getSize("layerview_legend_size").width - height: UM.Theme.getSize("layerview_legend_size").height - color: UM.Theme.getColor("layerview_move_combing") - border.width: UM.Theme.getSize("default_lining").width - border.color: UM.Theme.getColor("lining") - visible: view_settings.show_legend + + CheckBox { + checked: model.initialValue + onClicked: { + UM.Preferences.setValue(model.preference, checked); + } + text: label + Rectangle { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + width: UM.Theme.getSize("layerview_legend_size").width + height: UM.Theme.getSize("layerview_legend_size").height + color: UM.Theme.getColor(model.colorId) + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("lining") + visible: view_settings.show_legend + } + Layout.fillWidth: true + Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height + Layout.preferredWidth: UM.Theme.getSize("layerview_row").width + style: UM.Theme.styles.checkbox } - Layout.fillWidth: true - Layout.preferredHeight: UM.Theme.getSize("layerview_row").height - Layout.preferredWidth: UM.Theme.getSize("layerview_row").width - } - CheckBox { - checked: view_settings.show_helpers - onClicked: { - UM.Preferences.setValue("layerview/show_helpers", checked); - } - text: catalog.i18nc("@label", "Show Helpers") - Rectangle { - anchors.top: parent.top - anchors.topMargin: 2 - anchors.right: parent.right - width: UM.Theme.getSize("layerview_legend_size").width - height: UM.Theme.getSize("layerview_legend_size").height - color: UM.Theme.getColor("layerview_support") - border.width: UM.Theme.getSize("default_lining").width - border.color: UM.Theme.getColor("lining") - visible: view_settings.show_legend - } - Layout.fillWidth: true - Layout.preferredHeight: UM.Theme.getSize("layerview_row").height - Layout.preferredWidth: UM.Theme.getSize("layerview_row").width - } - CheckBox { - checked: view_settings.show_skin - onClicked: { - UM.Preferences.setValue("layerview/show_skin", checked); - } - text: catalog.i18nc("@label", "Show Shell") - Rectangle { - anchors.top: parent.top - anchors.topMargin: 2 - anchors.right: parent.right - width: UM.Theme.getSize("layerview_legend_size").width - height: UM.Theme.getSize("layerview_legend_size").height - color: UM.Theme.getColor("layerview_inset_0") - border.width: UM.Theme.getSize("default_lining").width - border.color: UM.Theme.getColor("lining") - visible: view_settings.show_legend - } - Layout.fillWidth: true - Layout.preferredHeight: UM.Theme.getSize("layerview_row").height - Layout.preferredWidth: UM.Theme.getSize("layerview_row").width - } - CheckBox { - checked: view_settings.show_infill - onClicked: { - UM.Preferences.setValue("layerview/show_infill", checked); - } - text: catalog.i18nc("@label", "Show Infill") - Rectangle { - anchors.top: parent.top - anchors.topMargin: 2 - anchors.right: parent.right - width: UM.Theme.getSize("layerview_legend_size").width - height: UM.Theme.getSize("layerview_legend_size").height - color: UM.Theme.getColor("layerview_infill") - border.width: UM.Theme.getSize("default_lining").width - border.color: UM.Theme.getColor("lining") - visible: view_settings.show_legend - } - Layout.fillWidth: true - Layout.preferredHeight: UM.Theme.getSize("layerview_row").height - Layout.preferredWidth: UM.Theme.getSize("layerview_row").width } + CheckBox { checked: view_settings.only_show_top_layers onClicked: { @@ -268,6 +252,7 @@ Item } text: catalog.i18nc("@label", "Only Show Top Layers") visible: UM.LayerView.compatibilityMode + style: UM.Theme.styles.checkbox } CheckBox { checked: view_settings.top_layer_count == 5 @@ -276,51 +261,44 @@ Item } text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top") visible: UM.LayerView.compatibilityMode + style: UM.Theme.styles.checkbox } - Label - { - id: topBottomLabel - anchors.left: parent.left - text: catalog.i18nc("@label","Top / Bottom") - Rectangle { - anchors.top: parent.top - anchors.topMargin: 2 - anchors.right: parent.right - width: UM.Theme.getSize("layerview_legend_size").width - height: UM.Theme.getSize("layerview_legend_size").height - color: UM.Theme.getColor("layerview_skin") - border.width: UM.Theme.getSize("default_lining").width - border.color: UM.Theme.getColor("lining") + Repeater { + model: ListModel { + id: typesLegenModelNoCheck + Component.onCompleted: + { + typesLegenModelNoCheck.append({ + label: catalog.i18nc("@label", "Top / Bottom"), + colorId: "layerview_skin", + }); + typesLegenModelNoCheck.append({ + label: catalog.i18nc("@label", "Inner Wall"), + colorId: "layerview_inset_x", + }); + } } - Layout.fillWidth: true - Layout.preferredHeight: UM.Theme.getSize("layerview_row").height - Layout.preferredWidth: UM.Theme.getSize("layerview_row").width - visible: view_settings.show_legend - } - Label - { - id: innerWallLabel - anchors.left: parent.left - text: catalog.i18nc("@label","Inner Wall") - Rectangle { - anchors.top: parent.top - anchors.topMargin: 2 - anchors.right: parent.right - width: UM.Theme.getSize("layerview_legend_size").width - height: UM.Theme.getSize("layerview_legend_size").height - color: UM.Theme.getColor("layerview_inset_x") - border.width: UM.Theme.getSize("default_lining").width - border.color: UM.Theme.getColor("lining") + Label { + text: label visible: view_settings.show_legend + Rectangle { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + width: UM.Theme.getSize("layerview_legend_size").width + height: UM.Theme.getSize("layerview_legend_size").height + color: UM.Theme.getColor(model.colorId) + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("lining") + visible: view_settings.show_legend + } + Layout.fillWidth: true + Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height + Layout.preferredWidth: UM.Theme.getSize("layerview_row").width + color: UM.Theme.getColor("text") } - Layout.fillWidth: true - Layout.preferredHeight: UM.Theme.getSize("layerview_row").height - Layout.preferredWidth: UM.Theme.getSize("layerview_row").width - visible: view_settings.show_legend } - } Item diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py old mode 100644 new mode 100755 index c27f8db4a6..983b70d174 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. from PyQt5.QtCore import pyqtProperty, pyqtSignal @@ -7,12 +7,15 @@ from UM.FlameProfiler import pyqtSlot from cura.MachineAction import MachineAction from UM.Application import Application +from UM.Preferences import Preferences from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Logger import Logger -from cura.Settings.CuraContainerRegistry import CuraContainerRegistry +from cura.CuraApplication import CuraApplication +from cura.Settings.ExtruderManager import ExtruderManager import UM.i18n catalog = UM.i18n.i18nCatalog("cura") @@ -25,36 +28,81 @@ class MachineSettingsAction(MachineAction): super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings")) self._qml_url = "MachineSettingsAction.qml" + self._global_container_stack = None self._container_index = 0 + self._extruder_container_index = 0 self._container_registry = ContainerRegistry.getInstance() self._container_registry.containerAdded.connect(self._onContainerAdded) + self._container_registry.containerRemoved.connect(self._onContainerRemoved) + Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) + ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) + + self._backend = Application.getInstance().getBackend() + + def _onContainerAdded(self, container): + # Add this action as a supported action to all machine definitions + if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine": + Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) + + def _onContainerRemoved(self, container): + # Remove definition_changes containers when a stack is removed + if container.getMetaDataEntry("type") in ["machine", "extruder_train"]: + definition_changes_container = container.findContainer({"type": "definition_changes"}) + if not definition_changes_container: + return + + self._container_registry.removeContainer(definition_changes_container.getId()) def _reset(self): - global_container_stack = Application.getInstance().getGlobalContainerStack() - if not global_container_stack: + if not self._global_container_stack: return # Make sure there is a definition_changes container to store the machine settings - definition_changes_container = global_container_stack.findContainer({"type": "definition_changes"}) + definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"}) if not definition_changes_container: - definition_changes_container = self._createDefinitionChangesContainer(global_container_stack) + definition_changes_container = self._createDefinitionChangesContainer(self._global_container_stack, self._global_container_stack.getName() + "_settings") # Notify the UI in which container to store the machine settings data - container_index = global_container_stack.getContainerIndex(definition_changes_container) + container_index = self._global_container_stack.getContainerIndex(definition_changes_container) if container_index != self._container_index: self._container_index = container_index self.containerIndexChanged.emit() - def _createDefinitionChangesContainer(self, global_container_stack, container_index = None): - definition_changes_container = InstanceContainer(global_container_stack.getName() + "_settings") - definition = global_container_stack.getBottom() + # Disable autoslicing while the machineaction is showing + self._backend.disableTimer() + + @pyqtSlot() + def onFinishAction(self): + # Restore autoslicing when the machineaction is dismissed + if self._backend.determineAutoSlicing(): + self._backend.tickle() + + def _onActiveExtruderStackChanged(self): + extruder_container_stack = ExtruderManager.getInstance().getActiveExtruderStack() + if not self._global_container_stack or not extruder_container_stack: + return + + # Make sure there is a definition_changes container to store the machine settings + definition_changes_container = extruder_container_stack.findContainer({"type": "definition_changes"}) + if not definition_changes_container: + definition_changes_container = self._createDefinitionChangesContainer(extruder_container_stack, extruder_container_stack.getId() + "_settings") + + # Notify the UI in which container to store the machine settings data + container_index = extruder_container_stack.getContainerIndex(definition_changes_container) + if container_index != self._extruder_container_index: + self._extruder_container_index = container_index + self.extruderContainerIndexChanged.emit() + + def _createDefinitionChangesContainer(self, container_stack, container_name, container_index = None): + definition_changes_container = InstanceContainer(container_name) + definition = container_stack.getBottom() definition_changes_container.setDefinition(definition) definition_changes_container.addMetaDataEntry("type", "definition_changes") + definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) self._container_registry.addContainer(definition_changes_container) - # Insert definition_changes between the definition and the variant - global_container_stack.insertContainer(-1, definition_changes_container) + container_stack.definitionChanges = definition_changes_container return definition_changes_container @@ -64,15 +112,129 @@ class MachineSettingsAction(MachineAction): def containerIndex(self): return self._container_index - def _onContainerAdded(self, container): - # Add this action as a supported action to all machine definitions - if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine": - if container.getProperty("machine_extruder_count", "value") > 1: - # Multiextruder printers are not currently supported - Logger.log("d", "Not attaching MachineSettingsAction to %s; Multi-extrusion printers are not supported", container.getId()) - return + extruderContainerIndexChanged = pyqtSignal() + + @pyqtProperty(int, notify = extruderContainerIndexChanged) + def extruderContainerIndex(self): + return self._extruder_container_index + + + def _onGlobalContainerChanged(self): + self._global_container_stack = Application.getInstance().getGlobalContainerStack() + + # This additional emit is needed because we cannot connect a UM.Signal directly to a pyqtSignal + self.globalContainerChanged.emit() + + globalContainerChanged = pyqtSignal() + + @pyqtProperty(int, notify = globalContainerChanged) + def definedExtruderCount(self): + if not self._global_container_stack: + return 0 + + return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains")) + + @pyqtSlot(int) + def setMachineExtruderCount(self, extruder_count): + machine_manager = Application.getInstance().getMachineManager() + extruder_manager = ExtruderManager.getInstance() + + definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"}) + if not self._global_container_stack or not definition_changes_container: + return + + previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value") + 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) + + # Check to see if any features are set to print with an extruder that will no longer exist + for setting_key in ["adhesion_extruder_nr", "support_extruder_nr", "support_extruder_nr_layer_0", "support_infill_extruder_nr", "support_interface_extruder_nr"]: + if int(self._global_container_stack.getProperty(setting_key, "value")) > extruder_count - 1: + Logger.log("i", "Lowering %s setting to match number of extruders", setting_key) + self._global_container_stack.getTop().setProperty(setting_key, "value", extruder_count - 1) + + # Check to see if any objects are set to print with an extruder that will no longer exist + root_node = Application.getInstance().getController().getScene().getRoot() + for node in DepthFirstIterator(root_node): + if node.getMeshData(): + extruder_nr = node.callDecoration("getActiveExtruderPosition") + + if extruder_nr is not None and int(extruder_nr) > extruder_count - 1: + node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId()) + + definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count) + self.forceUpdate() + + 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 + 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 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) - Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) @pyqtSlot() def forceUpdate(self): @@ -83,34 +245,34 @@ class MachineSettingsAction(MachineAction): @pyqtSlot() def updateHasMaterialsMetadata(self): # Updates the has_materials metadata flag after switching gcode flavor - global_container_stack = Application.getInstance().getGlobalContainerStack() - if global_container_stack: - definition = global_container_stack.getBottom() - if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False): - has_materials = global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode" + if not self._global_container_stack: + return - material_container = global_container_stack.findContainer({"type": "material"}) - material_index = global_container_stack.getContainerIndex(material_container) + definition = self._global_container_stack.getBottom() + if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False): + has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode" - if has_materials: - if "has_materials" in global_container_stack.getMetaData(): - global_container_stack.setMetaDataEntry("has_materials", True) - else: - global_container_stack.addMetaDataEntry("has_materials", True) + material_container = self._global_container_stack.material + material_index = self._global_container_stack.getContainerIndex(material_container) - # Set the material container to a sane default - if material_container.getId() == "empty_material": - search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*" } - containers = self._container_registry.findInstanceContainers(**search_criteria) - if containers: - global_container_stack.replaceContainer(material_index, containers[0]) + if has_materials: + if "has_materials" in self._global_container_stack.getMetaData(): + self._global_container_stack.setMetaDataEntry("has_materials", True) 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. - if "has_materials" in global_container_stack.getMetaData(): - global_container_stack.removeMetaDataEntry("has_materials") + self._global_container_stack.addMetaDataEntry("has_materials", True) - empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0] - global_container_stack.replaceContainer(material_index, empty_material) + # Set the material container to a sane default + if material_container.getId() == "empty_material": + search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*"} + containers = self._container_registry.findInstanceContainers(**search_criteria) + if containers: + self._global_container_stack.replaceContainer(material_index, 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. + if "has_materials" in self._global_container_stack.getMetaData(): + self._global_container_stack.removeMetaDataEntry("has_materials") - Application.getInstance().globalContainerStackChanged.emit() + self._global_container_stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer() + + Application.getInstance().globalContainerStackChanged.emit() diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.qml b/plugins/MachineSettingsAction/MachineSettingsAction.qml index 26bbccd44a..654030b013 100644 --- a/plugins/MachineSettingsAction/MachineSettingsAction.qml +++ b/plugins/MachineSettingsAction/MachineSettingsAction.qml @@ -12,6 +12,32 @@ import Cura 1.0 as Cura Cura.MachineAction { + id: base + property var extrudersModel: Cura.ExtrudersModel{} + property int extruderTabsCount: 0 + + Connections + { + target: base.extrudersModel + onModelChanged: + { + var extruderCount = base.extrudersModel.rowCount(); + base.extruderTabsCount = extruderCount > 1 ? extruderCount : 0; + } + } + + Connections + { + target: dialog ? dialog : null + ignoreUnknownSignals: true + // Any which way this action dialog is dismissed, make sure it is properly finished + onNextClicked: manager.onFinishAction() + onBackClicked: manager.onFinishAction() + onAccepted: manager.onFinishAction() + onRejected: manager.onFinishAction() + onClosing: manager.onFinishAction() + } + anchors.fill: parent; Item { @@ -28,428 +54,638 @@ Cura.MachineAction wrapMode: Text.WordWrap font.pointSize: 18; } - Label + + TabView { - id: pageDescription + id: settingsTabs + height: parent.height - y + width: parent.width + anchors.left: parent.left anchors.top: pageTitle.bottom anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "Please enter the correct settings for your printer below:") - } - Column - { - height: parent.height - y - width: parent.width - UM.Theme.getSize("default_margin").width - spacing: UM.Theme.getSize("default_margin").height + property real columnWidth: Math.floor((width - 3 * UM.Theme.getSize("default_margin").width) / 2) - anchors.left: parent.left - anchors.top: pageDescription.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - - Row + Tab { - width: parent.width - spacing: UM.Theme.getSize("default_margin").height + title: catalog.i18nc("@title:tab", "Printer"); + anchors.margins: UM.Theme.getSize("default_margin").width Column { - width: parent.width / 2 spacing: UM.Theme.getSize("default_margin").height - Label + Row { - text: catalog.i18nc("@label", "Printer Settings") - font.bold: true - } + width: parent.width + spacing: UM.Theme.getSize("default_margin").height - Grid - { - columns: 3 - columnSpacing: UM.Theme.getSize("default_margin").width - - Label + Column { - text: catalog.i18nc("@label", "X (Width)") - } - TextField - { - id: buildAreaWidthField - text: machineWidthProvider.properties.value - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: { machineWidthProvider.setPropertyValue("value", text); manager.forceUpdate() } - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Label - { - text: catalog.i18nc("@label", "Y (Depth)") - } - TextField - { - id: buildAreaDepthField - text: machineDepthProvider.properties.value - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: { machineDepthProvider.setPropertyValue("value", text); manager.forceUpdate() } - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Label - { - text: catalog.i18nc("@label", "Z (Height)") - } - TextField - { - id: buildAreaHeightField - text: machineHeightProvider.properties.value - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: { machineHeightProvider.setPropertyValue("value", text); manager.forceUpdate() } - } - Label - { - text: catalog.i18nc("@label", "mm") - } - } - - Column - { - Row - { - spacing: UM.Theme.getSize("default_margin").width + width: settingsTabs.columnWidth + spacing: UM.Theme.getSize("default_margin").height Label { - text: catalog.i18nc("@label", "Build Plate Shape") + text: catalog.i18nc("@label", "Printer Settings") + font.bold: true } - ComboBox + Grid { - id: shapeComboBox - model: ListModel + columns: 2 + columnSpacing: UM.Theme.getSize("default_margin").width + rowSpacing: UM.Theme.getSize("default_lining").width + + Label { - id: shapesModel - Component.onCompleted: + text: catalog.i18nc("@label", "X (Width)") + } + Loader + { + id: buildAreaWidthField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: machineWidthProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: true + } + + Label + { + text: catalog.i18nc("@label", "Y (Depth)") + } + Loader + { + id: buildAreaDepthField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: machineDepthProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: true + } + + Label + { + text: catalog.i18nc("@label", "Z (Height)") + } + Loader + { + id: buildAreaHeightField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: machineHeightProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: true + } + } + + Column + { + Row + { + spacing: UM.Theme.getSize("default_margin").width + + Label { - // Options come in as a string-representation of an OrderedDict - var options = machineShapeProvider.properties.options.match(/^OrderedDict\(\[\((.*)\)\]\)$/); - if(options) + text: catalog.i18nc("@label", "Build Plate Shape") + } + + ComboBox + { + id: shapeComboBox + model: ListModel { - options = options[1].split("), (") - for(var i = 0; i < options.length; i++) + id: shapesModel + Component.onCompleted: { - var option = options[i].substring(1, options[i].length - 1).split("', '") - shapesModel.append({text: option[1], value: option[0]}); + // Options come in as a string-representation of an OrderedDict + var options = machineShapeProvider.properties.options.match(/^OrderedDict\(\[\((.*)\)\]\)$/); + if(options) + { + options = options[1].split("), (") + for(var i = 0; i < options.length; i++) + { + var option = options[i].substring(1, options[i].length - 1).split("', '") + shapesModel.append({text: option[1], value: option[0]}); + } + } + } + } + currentIndex: + { + var currentValue = machineShapeProvider.properties.value; + var index = 0; + for(var i = 0; i < shapesModel.count; i++) + { + if(shapesModel.get(i).value == currentValue) { + index = i; + break; + } + } + return index + } + onActivated: + { + if(machineShapeProvider.properties.value != shapesModel.get(index).value) + { + machineShapeProvider.setPropertyValue("value", shapesModel.get(index).value); + manager.forceUpdate(); } } } } - currentIndex: + CheckBox { - var currentValue = machineShapeProvider.properties.value; - var index = 0; - for(var i = 0; i < shapesModel.count; i++) + id: centerIsZeroCheckBox + text: catalog.i18nc("@option:check", "Machine Center is Zero") + checked: String(machineCenterIsZeroProvider.properties.value).toLowerCase() != 'false' + onClicked: { - if(shapesModel.get(i).value == currentValue) { - index = i; - break; + machineCenterIsZeroProvider.setPropertyValue("value", checked); + manager.forceUpdate(); + } + } + CheckBox + { + id: heatedBedCheckBox + text: catalog.i18nc("@option:check", "Heated Bed") + checked: String(machineHeatedBedProvider.properties.value).toLowerCase() != 'false' + onClicked: machineHeatedBedProvider.setPropertyValue("value", checked) + } + } + + Row + { + spacing: UM.Theme.getSize("default_margin").width + + Label + { + text: catalog.i18nc("@label", "GCode Flavor") + } + + ComboBox + { + model: ListModel + { + id: flavorModel + Component.onCompleted: + { + // Options come in as a string-representation of an OrderedDict + var options = machineGCodeFlavorProvider.properties.options.match(/^OrderedDict\(\[\((.*)\)\]\)$/); + if(options) + { + options = options[1].split("), (") + for(var i = 0; i < options.length; i++) + { + var option = options[i].substring(1, options[i].length - 1).split("', '") + flavorModel.append({text: option[1], value: option[0]}); + } + } } } - return index - } - onActivated: - { - machineShapeProvider.setPropertyValue("value", shapesModel.get(index).value); - manager.forceUpdate(); + currentIndex: + { + var currentValue = machineGCodeFlavorProvider.properties.value; + var index = 0; + for(var i = 0; i < flavorModel.count; i++) + { + if(flavorModel.get(i).value == currentValue) { + index = i; + break; + } + } + return index + } + onActivated: + { + machineGCodeFlavorProvider.setPropertyValue("value", flavorModel.get(index).value); + manager.updateHasMaterialsMetadata(); + } } } } - CheckBox + + Column { - id: centerIsZeroCheckBox - text: catalog.i18nc("@option:check", "Machine Center is Zero") - checked: String(machineCenterIsZeroProvider.properties.value).toLowerCase() != 'false' - onClicked: + width: settingsTabs.columnWidth + spacing: UM.Theme.getSize("default_margin").height + + Label { - machineCenterIsZeroProvider.setPropertyValue("value", checked); - manager.forceUpdate(); + text: catalog.i18nc("@label", "Printhead Settings") + font.bold: true + } + + Grid + { + columns: 2 + columnSpacing: UM.Theme.getSize("default_margin").width + rowSpacing: UM.Theme.getSize("default_lining").width + + Label + { + text: catalog.i18nc("@label", "X min") + } + TextField + { + id: printheadXMinField + text: getHeadPolygonCoord("x", "min") + validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } + onEditingFinished: setHeadPolygon() + } + + Label + { + text: catalog.i18nc("@label", "Y min") + } + TextField + { + id: printheadYMinField + text: getHeadPolygonCoord("y", "min") + validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } + onEditingFinished: setHeadPolygon() + } + + Label + { + text: catalog.i18nc("@label", "X max") + } + TextField + { + id: printheadXMaxField + text: getHeadPolygonCoord("x", "max") + validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } + onEditingFinished: setHeadPolygon() + } + + Label + { + text: catalog.i18nc("@label", "Y max") + } + TextField + { + id: printheadYMaxField + text: getHeadPolygonCoord("y", "max") + validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } + onEditingFinished: setHeadPolygon() + } + + Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } + Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } + + Label + { + text: catalog.i18nc("@label", "Gantry height") + } + Loader + { + id: gantryHeightField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: gantryHeightProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: false + } + + Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } + Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } + + Label + { + text: catalog.i18nc("@label", "Number of Extruders") + visible: extruderCountComboBox.visible + } + + ComboBox + { + id: extruderCountComboBox + visible: manager.definedExtruderCount > 1 + model: ListModel + { + id: extruderCountModel + Component.onCompleted: + { + for(var i = 0; i < manager.definedExtruderCount; i++) + { + extruderCountModel.append({text: String(i + 1), value: i}); + } + } + } + currentIndex: machineExtruderCountProvider.properties.value - 1 + onActivated: + { + manager.setMachineExtruderCount(index + 1); + } + } + + Label + { + text: catalog.i18nc("@label", "Material Diameter") + } + Loader + { + id: materialDiameterField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: materialDiameterProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: false + } + Label + { + text: catalog.i18nc("@label", "Nozzle size") + visible: nozzleSizeField.visible + } + Loader + { + id: nozzleSizeField + visible: !Cura.MachineManager.hasVariants && machineExtruderCountProvider.properties.value == 1 + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: machineNozzleSizeProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: false + } } - } - CheckBox - { - id: heatedBedCheckBox - text: catalog.i18nc("@option:check", "Heated Bed") - checked: String(machineHeatedBedProvider.properties.value).toLowerCase() != 'false' - onClicked: machineHeatedBedProvider.setPropertyValue("value", checked) } } Row { spacing: UM.Theme.getSize("default_margin").width - - Label + anchors.left: parent.left + anchors.right: parent.right + height: parent.height - y + Column { - text: catalog.i18nc("@label", "GCode Flavor") - } - - ComboBox - { - model: ListModel + height: parent.height + width: settingsTabs.columnWidth + Label { - id: flavorModel + text: catalog.i18nc("@label", "Start Gcode") + font.bold: true + } + TextArea + { + id: machineStartGcodeField + width: parent.width + height: parent.height - y + font: UM.Theme.getFont("fixed") + text: machineStartGcodeProvider.properties.value + onActiveFocusChanged: + { + if(!activeFocus) + { + machineStartGcodeProvider.setPropertyValue("value", machineStartGcodeField.text) + } + } Component.onCompleted: { - // Options come in as a string-representation of an OrderedDict - var options = machineGCodeFlavorProvider.properties.options.match(/^OrderedDict\(\[\((.*)\)\]\)$/); - if(options) + wrapMode = TextEdit.NoWrap; + } + } + } + + Column { + height: parent.height + width: settingsTabs.columnWidth + Label + { + text: catalog.i18nc("@label", "End Gcode") + font.bold: true + } + TextArea + { + id: machineEndGcodeField + width: parent.width + height: parent.height - y + font: UM.Theme.getFont("fixed") + text: machineEndGcodeProvider.properties.value + onActiveFocusChanged: + { + if(!activeFocus) { - options = options[1].split("), (") - for(var i = 0; i < options.length; i++) + machineEndGcodeProvider.setPropertyValue("value", machineEndGcodeField.text) + } + } + Component.onCompleted: + { + wrapMode = TextEdit.NoWrap; + } + } + } + } + + function getHeadPolygonCoord(axis, minMax) + { + var polygon = JSON.parse(machineHeadPolygonProvider.properties.value); + var item = (axis == "x") ? 0 : 1 + var result = polygon[0][item]; + for(var i = 1; i < polygon.length; i++) { + if (minMax == "min") { + result = Math.min(result, polygon[i][item]); + } else { + result = Math.max(result, polygon[i][item]); + } + } + return Math.abs(result); + } + + function setHeadPolygon() + { + var polygon = []; + polygon.push([-parseFloat(printheadXMinField.text), parseFloat(printheadYMaxField.text)]); + polygon.push([-parseFloat(printheadXMinField.text),-parseFloat(printheadYMinField.text)]); + polygon.push([ parseFloat(printheadXMaxField.text), parseFloat(printheadYMaxField.text)]); + polygon.push([ parseFloat(printheadXMaxField.text),-parseFloat(printheadYMinField.text)]); + var polygon_string = JSON.stringify(polygon); + if(polygon != machineHeadPolygonProvider.properties.value) + { + machineHeadPolygonProvider.setPropertyValue("value", polygon_string); + manager.forceUpdate(); + } + } + } + } + + onCurrentIndexChanged: + { + if(currentIndex > 0) + { + contentItem.forceActiveFocus(); + ExtruderManager.setActiveExtruderIndex(currentIndex - 1); + } + } + + Repeater + { + id: extruderTabsRepeater + model: base.extruderTabsCount + + Tab + { + title: base.extrudersModel.getItem(index).name + anchors.margins: UM.Theme.getSize("default_margin").width + + Column + { + spacing: UM.Theme.getSize("default_margin").width + + Label + { + text: catalog.i18nc("@label", "Nozzle Settings") + font.bold: true + } + + Grid + { + columns: 2 + columnSpacing: UM.Theme.getSize("default_margin").width + rowSpacing: UM.Theme.getSize("default_lining").width + + Label + { + text: catalog.i18nc("@label", "Nozzle size") + visible: extruderNozzleSizeField.visible + } + Loader + { + id: extruderNozzleSizeField + visible: !Cura.MachineManager.hasVariants + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: extruderNozzleSizeProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: false + } + + Label + { + text: catalog.i18nc("@label", "Nozzle offset X") + } + Loader + { + id: extruderOffsetXField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: extruderOffsetXProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: true + } + Label + { + text: catalog.i18nc("@label", "Nozzle offset Y") + } + Loader + { + id: extruderOffsetYField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: extruderOffsetYProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: true + } + } + + Row + { + spacing: UM.Theme.getSize("default_margin").width + anchors.left: parent.left + anchors.right: parent.right + height: parent.height - y + Column + { + height: parent.height + width: settingsTabs.columnWidth + Label + { + text: catalog.i18nc("@label", "Extruder Start Gcode") + font.bold: true + } + TextArea + { + id: extruderStartGcodeField + width: parent.width + height: parent.height - y + font: UM.Theme.getFont("fixed") + text: (extruderStartGcodeProvider.properties.value) ? extruderStartGcodeProvider.properties.value : "" + onActiveFocusChanged: + { + if(!activeFocus) { - var option = options[i].substring(1, options[i].length - 1).split("', '") - flavorModel.append({text: option[1], value: option[0]}); + extruderStartGcodeProvider.setPropertyValue("value", extruderStartGcodeField.text) } } - } - } - currentIndex: - { - var currentValue = machineGCodeFlavorProvider.properties.value; - var index = 0; - for(var i = 0; i < flavorModel.count; i++) - { - if(flavorModel.get(i).value == currentValue) { - index = i; - break; + Component.onCompleted: + { + wrapMode = TextEdit.NoWrap; } } - return index } - onActivated: - { - machineGCodeFlavorProvider.setPropertyValue("value", flavorModel.get(index).value); - manager.updateHasMaterialsMetadata(); + Column { + height: parent.height + width: settingsTabs.columnWidth + Label + { + text: catalog.i18nc("@label", "Extruder End Gcode") + font.bold: true + } + TextArea + { + id: extruderEndGcodeField + width: parent.width + height: parent.height - y + font: UM.Theme.getFont("fixed") + text: (extruderEndGcodeProvider.properties.value) ? extruderEndGcodeProvider.properties.value : "" + onActiveFocusChanged: + { + if(!activeFocus) + { + extruderEndGcodeProvider.setPropertyValue("value", extruderEndGcodeField.text) + } + } + Component.onCompleted: + { + wrapMode = TextEdit.NoWrap; + } + } } } } } - - Column - { - width: parent.width / 2 - spacing: UM.Theme.getSize("default_margin").height - - Label - { - text: catalog.i18nc("@label", "Printhead Settings") - font.bold: true - } - - Grid - { - columns: 3 - columnSpacing: UM.Theme.getSize("default_margin").width - - Label - { - text: catalog.i18nc("@label", "X min") - } - TextField - { - id: printheadXMinField - text: getHeadPolygonCoord("x", "min") - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: setHeadPolygon() - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Label - { - text: catalog.i18nc("@label", "Y min") - } - TextField - { - id: printheadYMinField - text: getHeadPolygonCoord("y", "min") - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: setHeadPolygon() - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Label - { - text: catalog.i18nc("@label", "X max") - } - TextField - { - id: printheadXMaxField - text: getHeadPolygonCoord("x", "max") - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: setHeadPolygon() - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Label - { - text: catalog.i18nc("@label", "Y max") - } - TextField - { - id: printheadYMaxField - text: getHeadPolygonCoord("y", "max") - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: setHeadPolygon() - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } - Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } - Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } - - Label - { - text: catalog.i18nc("@label", "Gantry height") - } - TextField - { - id: gantryHeightField - text: gantryHeightProvider.properties.value - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: { gantryHeightProvider.setPropertyValue("value", text) } - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } - Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } - Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } - - Label - { - text: catalog.i18nc("@label", "Nozzle size") - visible: !Cura.MachineManager.hasVariants - } - TextField - { - id: nozzleSizeField - text: machineNozzleSizeProvider.properties.value - visible: !Cura.MachineManager.hasVariants - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: { machineNozzleSizeProvider.setPropertyValue("value", text) } - } - Label - { - text: catalog.i18nc("@label", "mm") - visible: !Cura.MachineManager.hasVariants - } - } - } } + } + } - Row + Component + { + id: numericTextFieldWithUnit + Item { + height: textField.height + width: textField.width + TextField { - spacing: UM.Theme.getSize("default_margin").width - anchors.left: parent.left - anchors.right: parent.right - height: parent.height - y - Column + id: textField + text: (propertyProvider.properties.value) ? propertyProvider.properties.value : "" + validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } + onEditingFinished: { - height: parent.height - width: parent.width / 2 - Label + if (propertyProvider && text != propertyProvider.properties.value) { - text: catalog.i18nc("@label", "Start Gcode") - } - TextArea - { - id: machineStartGcodeField - width: parent.width - height: parent.height - y - font: UM.Theme.getFont("fixed") - wrapMode: TextEdit.NoWrap - text: machineStartGcodeProvider.properties.value - onActiveFocusChanged: + propertyProvider.setPropertyValue("value", text); + if(forceUpdateOnChange) { - if(!activeFocus) + var extruderIndex = ExtruderManager.activeExtruderIndex; + manager.forceUpdate(); + if(ExtruderManager.activeExtruderIndex != extruderIndex) { - machineStartGcodeProvider.setPropertyValue("value", machineStartGcodeField.text) - } - } - } - } - Column { - height: parent.height - width: parent.width / 2 - Label - { - text: catalog.i18nc("@label", "End Gcode") - } - TextArea - { - id: machineEndGcodeField - width: parent.width - height: parent.height - y - font: UM.Theme.getFont("fixed") - wrapMode: TextEdit.NoWrap - text: machineEndGcodeProvider.properties.value - onActiveFocusChanged: - { - if(!activeFocus) - { - machineEndGcodeProvider.setPropertyValue("value", machineEndGcodeField.text) + ExtruderManager.setActiveExtruderIndex(extruderIndex) } } } } } - } - } - function getHeadPolygonCoord(axis, minMax) - { - var polygon = JSON.parse(machineHeadPolygonProvider.properties.value); - var item = (axis == "x") ? 0 : 1 - var result = polygon[0][item]; - for(var i = 1; i < polygon.length; i++) { - if (minMax == "min") { - result = Math.min(result, polygon[i][item]); - } else { - result = Math.max(result, polygon[i][item]); + Label + { + text: unit + anchors.right: textField.right + anchors.rightMargin: y - textField.y + anchors.verticalCenter: textField.verticalCenter } } - return Math.abs(result); - } - - function setHeadPolygon() - { - var polygon = []; - polygon.push([-parseFloat(printheadXMinField.text), parseFloat(printheadYMaxField.text)]); - polygon.push([-parseFloat(printheadXMinField.text),-parseFloat(printheadYMinField.text)]); - polygon.push([ parseFloat(printheadXMaxField.text), parseFloat(printheadYMaxField.text)]); - polygon.push([ parseFloat(printheadXMaxField.text),-parseFloat(printheadYMinField.text)]); - machineHeadPolygonProvider.setPropertyValue("value", JSON.stringify(polygon)); - manager.forceUpdate(); } UM.SettingPropertyProvider @@ -522,6 +758,16 @@ Cura.MachineAction storeIndex: manager.containerIndex } + UM.SettingPropertyProvider + { + id: materialDiameterProvider + + containerStackId: Cura.MachineManager.activeMachineId + key: "material_diameter" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } + UM.SettingPropertyProvider { id: machineNozzleSizeProvider @@ -532,6 +778,16 @@ Cura.MachineAction storeIndex: manager.containerIndex } + UM.SettingPropertyProvider + { + id: machineExtruderCountProvider + + containerStackId: Cura.MachineManager.activeMachineId + key: "machine_extruder_count" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } + UM.SettingPropertyProvider { id: gantryHeightProvider @@ -573,4 +829,53 @@ Cura.MachineAction storeIndex: manager.containerIndex } + UM.SettingPropertyProvider + { + id: extruderNozzleSizeProvider + + containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : "" + key: "machine_nozzle_size" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } + + UM.SettingPropertyProvider + { + id: extruderOffsetXProvider + + containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : "" + key: "machine_nozzle_offset_x" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } + + UM.SettingPropertyProvider + { + id: extruderOffsetYProvider + + containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : "" + key: "machine_nozzle_offset_y" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } + + UM.SettingPropertyProvider + { + id: extruderStartGcodeProvider + + containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : "" + key: "machine_extruder_start_code" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } + + UM.SettingPropertyProvider + { + id: extruderEndGcodeProvider + + containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : "" + key: "machine_extruder_end_code" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } } \ No newline at end of file diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py b/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py index b283608cb0..65159888b0 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py @@ -63,17 +63,20 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand stack_nr = -1 stack = None # Check from what stack we should copy the raw property of the setting from. - if definition.limit_to_extruder != "-1" and self._stack.getProperty("machine_extruder_count", "value") > 1: - # A limit to extruder function was set and it's a multi extrusion machine. Check what stack we do need to use. - stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder"))))) + if self._stack.getProperty("machine_extruder_count", "value") > 1: + if definition.limit_to_extruder != "-1": + # A limit to extruder function was set and it's a multi extrusion machine. Check what stack we do need to use. + stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder"))))) - # Check if the found stack_number is in the extruder list of extruders. - if stack_nr not in ExtruderManager.getInstance().extruderIds and self._stack.getProperty("extruder_nr", "value") is not None: - stack_nr = -1 + # Check if the found stack_number is in the extruder list of extruders. + if stack_nr not in ExtruderManager.getInstance().extruderIds and self._stack.getProperty("extruder_nr", "value") is not None: + stack_nr = -1 - # Use the found stack number to get the right stack to copy the value from. - if stack_nr in ExtruderManager.getInstance().extruderIds: - stack = ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0] + # Use the found stack number to get the right stack to copy the value from. + if stack_nr in ExtruderManager.getInstance().extruderIds: + stack = ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0] + else: + stack = self._stack # Use the raw property to set the value (so the inheritance doesn't break) if stack is not None: diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml index cb65da635b..3e78670e02 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml @@ -26,129 +26,6 @@ Item { spacing: UM.Theme.getSize("default_margin").height - Row - { - spacing: UM.Theme.getSize("default_margin").width - Label - { - text: catalog.i18nc("@label Followed by extruder selection drop-down.", "Print model with") - anchors.verticalCenter: extruderSelector.verticalCenter - - color: UM.Theme.getColor("setting_control_text") - font: UM.Theme.getFont("default") - visible: extruderSelector.visible - } - ComboBox - { - id: extruderSelector - - model: Cura.ExtrudersModel - { - id: extrudersModel - onModelChanged: extruderSelector.color = extrudersModel.getItem(extruderSelector.currentIndex).color - } - property string color: extrudersModel.getItem(extruderSelector.currentIndex).color - visible: machineExtruderCount.properties.value > 1 - textRole: "name" - width: UM.Theme.getSize("setting_control").width - height: UM.Theme.getSize("section").height - MouseArea - { - anchors.fill: parent - acceptedButtons: Qt.NoButton - onWheel: wheel.accepted = true; - } - - style: ComboBoxStyle - { - background: Rectangle - { - color: - { - if(extruderSelector.hovered || base.activeFocus) - { - return UM.Theme.getColor("setting_control_highlight"); - } - else - { - return UM.Theme.getColor("setting_control"); - } - } - border.width: UM.Theme.getSize("default_lining").width - border.color: UM.Theme.getColor("setting_control_border") - } - label: Item - { - Rectangle - { - id: swatch - height: UM.Theme.getSize("setting_control").height / 2 - width: height - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_lining").width - anchors.verticalCenter: parent.verticalCenter - - color: extruderSelector.color - border.width: UM.Theme.getSize("default_lining").width - border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : UM.Theme.getColor("setting_control_border") - } - Label - { - anchors.left: swatch.right - anchors.leftMargin: UM.Theme.getSize("default_lining").width - anchors.right: downArrow.left - anchors.rightMargin: UM.Theme.getSize("default_lining").width - anchors.verticalCenter: parent.verticalCenter - - text: extruderSelector.currentText - font: UM.Theme.getFont("default") - color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") - - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - - UM.RecolorImage - { - id: downArrow - anchors.right: parent.right - anchors.rightMargin: UM.Theme.getSize("default_lining").width * 2 - anchors.verticalCenter: parent.verticalCenter - - source: UM.Theme.getIcon("arrow_bottom") - width: UM.Theme.getSize("standard_arrow").width - height: UM.Theme.getSize("standard_arrow").height - sourceSize.width: width + 5 - sourceSize.height: width + 5 - - color: UM.Theme.getColor("setting_control_text") - } - } - } - - onActivated: - { - UM.ActiveTool.setProperty("SelectedActiveExtruder", extrudersModel.getItem(index).id); - extruderSelector.color = extrudersModel.getItem(index).color; - } - onModelChanged: updateCurrentIndex(); - - function updateCurrentIndex() - { - for(var i = 0; i < extrudersModel.rowCount(); ++i) - { - if(extrudersModel.getItem(i).id == UM.ActiveTool.properties.getValue("SelectedActiveExtruder")) - { - extruderSelector.currentIndex = i; - extruderSelector.color = extrudersModel.getItem(i).color; - return; - } - } - extruderSelector.currentIndex = -1; - } - } - } - Column { // This is to ensure that the panel is first increasing in size up to 200 and then shows a scrollbar. @@ -264,14 +141,6 @@ Item { storeIndex: 0 removeUnusedValue: false } - - // If the extruder by which the object needs to be printed is changed, ensure that the - // display is also notified of the fact. - Connections - { - target: extruderSelector - onActivated: provider.forcePropertiesChanged() - } } } } diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py index 865401804c..b2d14942ba 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py @@ -112,4 +112,4 @@ class PerObjectSettingsTool(Tool): self._single_model_selected = False # Group is selected, so tool needs to be disabled else: self._single_model_selected = True - Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, (self._advanced_mode or self._multi_extrusion) and self._single_model_selected) \ No newline at end of file + Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._advanced_mode and self._single_model_selected) diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml index a80ed1d179..27271f0d15 100644 --- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml +++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml @@ -342,6 +342,8 @@ Cura.MachineAction { regExp: /[a-zA-Z0-9\.\-\_]*/ } + + onAccepted: btnOk.clicked() } } @@ -355,6 +357,7 @@ Cura.MachineAction } }, Button { + id: btnOk text: catalog.i18nc("@action:button", "Ok") onClicked: { diff --git a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py index 7c58f2bb66..9b0eee7096 100755 --- a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py @@ -200,7 +200,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def _onAuthenticationRequired(self, reply, authenticator): if self._authentication_id is not None and self._authentication_key is not None: - Logger.log("d", "Authentication was required. Setting up authenticator with ID %s and key", self._authentication_id, self._getSafeAuthKey()) + Logger.log("d", "Authentication was required. Setting up authenticator with ID %s and key %s", self._authentication_id, self._getSafeAuthKey()) authenticator.setUser(self._authentication_id) authenticator.setPassword(self._authentication_key) else: @@ -283,10 +283,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # # /param temperature The new target temperature of the bed. def _setTargetBedTemperature(self, temperature): - if self._target_bed_temperature == temperature: + if not self._updateTargetBedTemperature(temperature): return - self._target_bed_temperature = temperature - self.targetBedTemperatureChanged.emit() url = QUrl("http://" + self._address + self._api_prefix + "printer/bed/temperature/target") data = str(temperature) @@ -294,6 +292,17 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") self._manager.put(put_request, data.encode()) + ## Updates the target bed temperature from the printer, and emit a signal if it was changed. + # + # /param temperature The new target temperature of the bed. + # /return boolean, True if the temperature was changed, false if the new temperature has the same value as the already stored temperature + def _updateTargetBedTemperature(self, temperature): + if self._target_bed_temperature == temperature: + return False + self._target_bed_temperature = temperature + self.targetBedTemperatureChanged.emit() + return True + def _stopCamera(self): self._camera_timer.stop() if self._image_reply: @@ -528,7 +537,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): bed_temperature = self._json_printer_state["bed"]["temperature"]["current"] self._setBedTemperature(bed_temperature) target_bed_temperature = self._json_printer_state["bed"]["temperature"]["target"] - self._setTargetBedTemperature(target_bed_temperature) + self._updateTargetBedTemperature(target_bed_temperature) head_x = self._json_printer_state["heads"][0]["position"]["x"] head_y = self._json_printer_state["heads"][0]["position"]["y"] @@ -629,7 +638,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "": Logger.log("e", "No cartridge loaded in slot %s, unable to start print", index + 1) self._error_message = Message( - i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No PrinterCore loaded in slot {0}".format(index + 1))) + i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No Printcore loaded in slot {0}".format(index + 1))) self._error_message.show() return if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] == "": @@ -1089,7 +1098,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_key = data["key"] self._authentication_id = data["id"] - Logger.log("i", "Got a new authentication ID (%s) and KEY (%S). Waiting for authorization.", self._authentication_id, self._getSafeAuthKey()) + Logger.log("i", "Got a new authentication ID (%s) and KEY (%s). Waiting for authorization.", self._authentication_id, self._getSafeAuthKey()) # Check if the authentication is accepted. self._checkAuthentication() diff --git a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py index f39d921fff..9f450f21ab 100644 --- a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py @@ -164,8 +164,8 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): else: if self._printers[key].isConnected(): Logger.log("d", "Closing connection [%s]..." % key) - self._printers[key].connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged) self._printers[key].close() + self._printers[key].connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged) ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addPrinter(self, name, address, properties): @@ -183,9 +183,9 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): printer = self._printers.pop(name, None) if printer: if printer.isConnected(): + printer.disconnect() printer.connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged) Logger.log("d", "removePrinter, disconnecting [%s]..." % name) - printer.disconnect() self.printerListChanged.emit() ## Handler for when the connection state of one of the detected printers changes diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 580bbf06df..0100874eab 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -148,6 +148,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): ## Start a print based on a g-code. # \param gcode_list List with gcode (strings). def printGCode(self, gcode_list): + Logger.log("d", "Started printing g-code") if self._progress or self._connection_state != ConnectionState.connected: self._error_message = Message(catalog.i18nc("@info:status", "Unable to start a new job because the printer is busy or not connected.")) self._error_message.show() @@ -183,6 +184,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): ## Private function (threaded) that actually uploads the firmware. def _updateFirmware(self): + Logger.log("d", "Attempting to update firmware") self._error_code = 0 self.setProgress(0, 100) self._firmware_update_finished = False @@ -536,6 +538,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._sendNextGcodeLine() elif b"resend" in line.lower() or b"rs" in line: # Because a resend can be asked with "resend" and "rs" try: + Logger.log("d", "Got a resend response") self._gcode_position = int(line.replace(b"N:",b" ").replace(b"N",b" ").replace(b":",b" ").split()[-1]) except: if b"rs" in line: @@ -563,19 +566,19 @@ class USBPrinterOutputDevice(PrinterOutputDevice): line = line[:line.find(";")] line = line.strip() - # Don't send empty lines. But we do have to send something, so send m105 instead. - if line == "": + # Don't send empty lines. But we do have to send something, so send + # m105 instead. + # Don't send the M0 or M1 to the machine, as M0 and M1 are handled as + # an LCD menu pause. + if line == "" or line == "M0" or line == "M1": line = "M105" - try: - if line == "M0" or line == "M1": - line = "M105" # Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause. if ("G0" in line or "G1" in line) and "Z" in line: z = float(re.search("Z([0-9\.]*)", line).group(1)) if self._current_z != z: self._current_z = z except Exception as e: - Logger.log("e", "Unexpected error with printer connection: %s" % e) + Logger.log("e", "Unexpected error with printer connection, could not parse current Z: %s: %s" % (e, line)) self._setErrorState("Unexpected error: %s" %e) checksum = functools.reduce(lambda x,y: x^y, map(ord, "N%d%s" % (self._gcode_position, line))) @@ -674,4 +677,4 @@ class USBPrinterOutputDevice(PrinterOutputDevice): def cancelPreheatBed(self): Logger.log("i", "Cancelling pre-heating of the bed.") self._setTargetBedTemperature(0) - self.preheatBedRemainingTimeChanged.emit() \ No newline at end of file + self.preheatBedRemainingTimeChanged.emit() diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py index 5b1d0b8dac..7a62a59a45 100644 --- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py +++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py @@ -259,7 +259,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension): i = 0 while True: values = winreg.EnumValue(key, i) - if not only_list_usb or "USBSER" in values[0]: + if not only_list_usb or "USBSER" or "VCP" in values[0]: base_list += [values[1]] i += 1 except Exception as e: diff --git a/plugins/UltimakerMachineActions/UM2UpgradeSelection.py b/plugins/UltimakerMachineActions/UM2UpgradeSelection.py new file mode 100644 index 0000000000..6c972efbf0 --- /dev/null +++ b/plugins/UltimakerMachineActions/UM2UpgradeSelection.py @@ -0,0 +1,65 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Uranium is released under the terms of the AGPLv3 or higher. + +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.InstanceContainer import InstanceContainer +from cura.MachineAction import MachineAction +from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty + +from UM.i18n import i18nCatalog +from UM.Application import Application +from UM.Util import parseBool +catalog = i18nCatalog("cura") + +import UM.Settings.InstanceContainer + + +## The Ultimaker 2 can have a few revisions & upgrades. +class UM2UpgradeSelection(MachineAction): + def __init__(self): + super().__init__("UM2UpgradeSelection", catalog.i18nc("@action", "Select upgrades")) + self._qml_url = "UM2UpgradeSelectionMachineAction.qml" + + self._container_registry = ContainerRegistry.getInstance() + + def _reset(self): + self.hasVariantsChanged.emit() + + hasVariantsChanged = pyqtSignal() + + @pyqtProperty(bool, notify = hasVariantsChanged) + def hasVariants(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + return parseBool(global_container_stack.getMetaDataEntry("has_variants", "false")) + + @pyqtSlot(bool) + def setHasVariants(self, has_variants = True): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + variant_container = global_container_stack.variant + variant_index = global_container_stack.getContainerIndex(variant_container) + + if has_variants: + if "has_variants" in global_container_stack.getMetaData(): + global_container_stack.setMetaDataEntry("has_variants", True) + else: + global_container_stack.addMetaDataEntry("has_variants", True) + + # Set the variant container to a sane default + empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + if type(variant_container) == type(empty_container): + search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" } + containers = self._container_registry.findInstanceContainers(**search_criteria) + if containers: + global_container_stack.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. + if "has_variants" in global_container_stack.getMetaData(): + global_container_stack.removeMetaDataEntry("has_variants") + + # Set the variant container to an empty variant + global_container_stack.variant = ContainerRegistry.getInstance().getEmptyInstanceContainer() + + Application.getInstance().globalContainerStackChanged.emit() diff --git a/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml b/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml new file mode 100644 index 0000000000..5e0096c6b0 --- /dev/null +++ b/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml @@ -0,0 +1,52 @@ +// Copyright (c) 2016 Ultimaker B.V. +// Cura is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +import UM 1.2 as UM +import Cura 1.0 as Cura + + +Cura.MachineAction +{ + anchors.fill: parent; + Item + { + id: upgradeSelectionMachineAction + anchors.fill: parent + + Label + { + id: pageTitle + width: parent.width + text: catalog.i18nc("@title", "Select Printer Upgrades") + wrapMode: Text.WordWrap + font.pointSize: 18; + } + + Label + { + id: pageDescription + anchors.top: pageTitle.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + width: parent.width + wrapMode: Text.WordWrap + text: catalog.i18nc("@label","Please select any upgrades made to this Ultimaker 2."); + } + + CheckBox + { + anchors.top: pageDescription.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + + text: catalog.i18nc("@label", "Olsson Block") + checked: manager.hasVariants + onClicked: manager.setHasVariants(checked) + } + + UM.I18nCatalog { id: catalog; name: "cura"; } + } +} \ No newline at end of file diff --git a/plugins/UltimakerMachineActions/UMOUpgradeSelection.py b/plugins/UltimakerMachineActions/UMOUpgradeSelection.py index 0428c0f5c2..02b1482719 100644 --- a/plugins/UltimakerMachineActions/UMOUpgradeSelection.py +++ b/plugins/UltimakerMachineActions/UMOUpgradeSelection.py @@ -11,7 +11,7 @@ from UM.Application import Application catalog = i18nCatalog("cura") import UM.Settings.InstanceContainer - +from cura.CuraApplication import CuraApplication ## The Ultimaker Original can have a few revisions & upgrades. This action helps with selecting them, so they are added # as a variant. @@ -49,6 +49,7 @@ class UMOUpgradeSelection(MachineAction): definition = global_container_stack.getBottom() definition_changes_container.setDefinition(definition) definition_changes_container.addMetaDataEntry("type", "definition_changes") + definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().addContainer(definition_changes_container) # Insert definition_changes between the definition and the variant diff --git a/plugins/UltimakerMachineActions/__init__.py b/plugins/UltimakerMachineActions/__init__.py index fb0b2b1f64..996026ace2 100644 --- a/plugins/UltimakerMachineActions/__init__.py +++ b/plugins/UltimakerMachineActions/__init__.py @@ -5,6 +5,7 @@ from . import BedLevelMachineAction from . import UpgradeFirmwareMachineAction from . import UMOCheckupMachineAction from . import UMOUpgradeSelection +from . import UM2UpgradeSelection from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -21,4 +22,10 @@ def getMetaData(): } def register(app): - return { "machine_action": [BedLevelMachineAction.BedLevelMachineAction(), UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(), UMOCheckupMachineAction.UMOCheckupMachineAction(), UMOUpgradeSelection.UMOUpgradeSelection()]} + return { "machine_action": [ + BedLevelMachineAction.BedLevelMachineAction(), + UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(), + UMOCheckupMachineAction.UMOCheckupMachineAction(), + UMOUpgradeSelection.UMOUpgradeSelection(), + UM2UpgradeSelection.UM2UpgradeSelection() + ]} diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py b/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py index 7a9b102758..d5bb08bb5c 100644 --- a/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py +++ b/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. import configparser #To get version numbers from config files. @@ -70,8 +70,8 @@ _printer_translations_profiles = { # as a set for which profiles were built-in. _profile_translations = { "Low Quality": "low", - "Normal Quality": "normal", - "High Quality": "high", + "Fine": "normal", + "Extra Fine": "high", "Ulti Quality": "high", #This one doesn't have an equivalent. Map it to high. "abs_0.25_normal": "um2p_abs_0.25_normal", "abs_0.4_fast": "um2p_abs_0.4_fast", @@ -249,7 +249,9 @@ class VersionUpgrade21to22(VersionUpgrade): def getCfgVersion(self, serialised): parser = configparser.ConfigParser(interpolation = None) parser.read_string(serialised) - return int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised. + format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised. + setting_version = int(parser.get("metadata", "setting_version", fallback = 0)) + return format_version * 1000000 + setting_version ## Gets the fallback quality to use for a specific machine-variant-material # combination. diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py b/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py index f2803ca62f..74d74e61ae 100644 --- a/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py @@ -18,10 +18,10 @@ def getMetaData(): "api": 3 }, "version_upgrade": { - # From To Upgrade function - ("profile", 1): ("quality", 2, upgrade.upgradeProfile), - ("machine_instance", 1): ("machine_stack", 2, upgrade.upgradeMachineInstance), - ("preferences", 2): ("preferences", 3, upgrade.upgradePreferences) + # From To Upgrade function + ("profile", 1000000): ("quality", 2000000, upgrade.upgradeProfile), + ("machine_instance", 1000000): ("machine_stack", 2000000, upgrade.upgradeMachineInstance), + ("preferences", 2000000): ("preferences", 3000000, upgrade.upgradePreferences) }, "sources": { "profile": { diff --git a/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py b/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py index b28124f16b..9d508e553b 100644 --- a/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py +++ b/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. import configparser #To get version numbers from config files. @@ -77,6 +77,7 @@ class VersionUpgrade22to24(VersionUpgrade): with open(variant_path, "r") as fhandle: variant_config.read_file(fhandle) + config_name = "Unknown Variant" if variant_config.has_section("general") and variant_config.has_option("general", "name"): config_name = variant_config.get("general", "name") if config_name.endswith("_variant"): @@ -141,7 +142,19 @@ class VersionUpgrade22to24(VersionUpgrade): config.write(output) return [filename], [output.getvalue()] + def upgradeQuality(self, serialised, filename): + config = configparser.ConfigParser(interpolation = None) + config.read_string(serialised) # Read the input string as config file. + config.set("metadata", "type", "quality_changes") # Update metadata/type to quality_changes + config.set("general", "version", "2") # Just bump the version number. That is all we need for now. + + output = io.StringIO() + config.write(output) + return [filename], [output.getvalue()] + def getCfgVersion(self, serialised): parser = configparser.ConfigParser(interpolation = None) parser.read_string(serialised) - return int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised. + format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised. + setting_version = int(parser.get("metadata", "setting_version", fallback = 0)) + return format_version * 1000000 + setting_version diff --git a/plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py b/plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py index e1114922d6..0ff121f35e 100644 --- a/plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py @@ -18,12 +18,12 @@ def getMetaData(): "api": 3 }, "version_upgrade": { - # From To Upgrade function - ("machine_instance", 2): ("machine_stack", 3, upgrade.upgradeMachineInstance), - ("extruder_train", 2): ("extruder_train", 3, upgrade.upgradeExtruderTrain), - ("preferences", 3): ("preferences", 4, upgrade.upgradePreferences) - - }, + # From To Upgrade function + ("machine_instance", 2000000): ("machine_stack", 3000000, upgrade.upgradeMachineInstance), + ("extruder_train", 2000000): ("extruder_train", 3000000, upgrade.upgradeExtruderTrain), + ("preferences", 3000000): ("preferences", 4000000, upgrade.upgradePreferences), + ("quality", 2000000): ("quality_changes", 2000000, upgrade.upgradeQuality), + }, "sources": { "machine_stack": { "get_version": upgrade.getCfgVersion, diff --git a/plugins/VersionUpgrade/VersionUpgrade24to25/__init__.py b/plugins/VersionUpgrade/VersionUpgrade24to25/__init__.py deleted file mode 100644 index 701224787c..0000000000 --- a/plugins/VersionUpgrade/VersionUpgrade24to25/__init__.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2017 Ultimaker B.V. -# Cura is released under the terms of the AGPLv3 or higher. - -from . import VersionUpgrade24to25 - -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") - -upgrade = VersionUpgrade24to25.VersionUpgrade24to25() - -def getMetaData(): - return { - "plugin": { - "name": catalog.i18nc("@label", "Version Upgrade 2.4 to 2.5"), - "author": "Ultimaker", - "version": "1.0", - "description": catalog.i18nc("@info:whatsthis", "Upgrades configurations from Cura 2.4 to Cura 2.5."), - "api": 3 - }, - "version_upgrade": { - # From To Upgrade function - ("preferences", 4): ("preferences", 5, upgrade.upgradePreferences), - ("quality", 2): ("quality", 3, upgrade.upgradeInstanceContainer), - ("variant", 2): ("variant", 3, upgrade.upgradeInstanceContainer), #We can re-use upgradeContainerStack since there is nothing specific to quality, variant or user profiles being changed. - ("user", 2): ("user", 3, upgrade.upgradeInstanceContainer) - }, - "sources": { - "quality": { - "get_version": upgrade.getCfgVersion, - "location": {"./quality"} - }, - "preferences": { - "get_version": upgrade.getCfgVersion, - "location": {"."} - }, - "user": { - "get_version": upgrade.getCfgVersion, - "location": {"./user"} - } - } - } - -def register(app): - return {} - return { "version_upgrade": upgrade } diff --git a/plugins/VersionUpgrade/VersionUpgrade24to25/VersionUpgrade24to25.py b/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py similarity index 53% rename from plugins/VersionUpgrade/VersionUpgrade24to25/VersionUpgrade24to25.py rename to plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py index 99a0f95a77..85f54dd654 100644 --- a/plugins/VersionUpgrade/VersionUpgrade24to25/VersionUpgrade24to25.py +++ b/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py @@ -5,19 +5,25 @@ import configparser #To parse the files we need to upgrade and write the new fil import io #To serialise configparser output to a string. from UM.VersionUpgrade import VersionUpgrade +from cura.CuraApplication import CuraApplication _removed_settings = { #Settings that were removed in 2.5. - "start_layers_at_same_position" + "start_layers_at_same_position", + "sub_div_rad_mult" +} + +_split_settings = { #These settings should be copied to all settings it was split into. + "support_interface_line_distance": {"support_roof_line_distance", "support_bottom_line_distance"} } ## A collection of functions that convert the configuration of the user in Cura -# 2.4 to a configuration for Cura 2.5. +# 2.5 to a configuration for Cura 2.6. # # All of these methods are essentially stateless. -class VersionUpgrade24to25(VersionUpgrade): - ## Gets the version number from a CFG file in Uranium's 2.4 format. +class VersionUpgrade25to26(VersionUpgrade): + ## Gets the version number from a CFG file in Uranium's 2.5 format. # - # Since the format may change, this is implemented for the 2.4 format only + # Since the format may change, this is implemented for the 2.5 format only # and needs to be included in the version upgrade system rather than # globally in Uranium. # @@ -29,9 +35,11 @@ class VersionUpgrade24to25(VersionUpgrade): def getCfgVersion(self, serialised): parser = configparser.ConfigParser(interpolation = None) parser.read_string(serialised) - return int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised. + format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised. + setting_version = int(parser.get("metadata", "setting_version", fallback = 0)) + return format_version * 1000000 + setting_version - ## Upgrades the preferences file from version 2.4 to 2.5. + ## Upgrades the preferences file from version 2.5 to 2.6. # # \param serialised The serialised form of a preferences file. # \param filename The name of the file to upgrade. @@ -42,8 +50,16 @@ class VersionUpgrade24to25(VersionUpgrade): #Remove settings from the visible_settings. if parser.has_section("general") and "visible_settings" in parser["general"]: visible_settings = parser["general"]["visible_settings"].split(";") - visible_settings = filter(lambda setting: setting not in _removed_settings, visible_settings) - parser["general"]["visible_settings"] = ";".join(visible_settings) + new_visible_settings = [] + for setting in visible_settings: + if setting in _removed_settings: + continue #Skip. + if setting in _split_settings: + for replaced_setting in _split_settings[setting]: + new_visible_settings.append(replaced_setting) + continue #Don't add the original. + new_visible_settings.append(setting) #No special handling, so just add the original visible setting back. + parser["general"]["visible_settings"] = ";".join(new_visible_settings) #Change the version number in the file. if parser.has_section("general"): #It better have! @@ -54,7 +70,7 @@ class VersionUpgrade24to25(VersionUpgrade): parser.write(output) return [filename], [output.getvalue()] - ## Upgrades an instance container from version 2.4 to 2.5. + ## Upgrades an instance container from version 2.5 to 2.6. # # \param serialised The serialised form of a quality profile. # \param filename The name of the file to upgrade. @@ -66,12 +82,22 @@ class VersionUpgrade24to25(VersionUpgrade): if parser.has_section("values"): for removed_setting in (_removed_settings & parser["values"].keys()): #Both in keys that need to be removed and in keys present in the file. del parser["values"][removed_setting] + for replaced_setting in (_split_settings.keys() & parser["values"].keys()): + for replacement in _split_settings[replaced_setting]: + parser["values"][replacement] = parser["values"][replaced_setting] #Copy to replacement before removing the original! + del replaced_setting - #Change the version number in the file. - if parser.has_section("general"): - parser["general"]["version"] = "3" + for each_section in ("general", "metadata"): + if not parser.has_section(each_section): + parser.add_section(each_section) + + # Change the version number in the file. + parser["metadata"]["setting_version"] = str(CuraApplication.SettingVersion) + + # Update version + parser["general"]["version"] = "2" #Re-serialise the file. output = io.StringIO() parser.write(output) - return [filename], [output.getvalue()] \ No newline at end of file + return [filename], [output.getvalue()] diff --git a/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py b/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py new file mode 100644 index 0000000000..a24473f65d --- /dev/null +++ b/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py @@ -0,0 +1,46 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from . import VersionUpgrade25to26 + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + +upgrade = VersionUpgrade25to26.VersionUpgrade25to26() + +def getMetaData(): + return { + "plugin": { + "name": catalog.i18nc("@label", "Version Upgrade 2.5 to 2.6"), + "author": "Ultimaker", + "version": "1.0", + "description": catalog.i18nc("@info:whatsthis", "Upgrades configurations from Cura 2.5 to Cura 2.6."), + "api": 3 + }, + "version_upgrade": { + # From To Upgrade function + ("preferences", 4000000): ("preferences", 4000001, upgrade.upgradePreferences), + # NOTE: All the instance containers share the same general/version, so we have to update all of them + # if any is updated. + ("quality_changes", 2000000): ("quality_changes", 2000001, upgrade.upgradeInstanceContainer), + ("user", 2000000): ("user", 2000001, upgrade.upgradeInstanceContainer), + ("quality", 2000000): ("quality", 2000001, upgrade.upgradeInstanceContainer), + }, + "sources": { + "quality_changes": { + "get_version": upgrade.getCfgVersion, + "location": {"./quality"} + }, + "preferences": { + "get_version": upgrade.getCfgVersion, + "location": {"."} + }, + "user": { + "get_version": upgrade.getCfgVersion, + "location": {"./user"} + }, + } + } + +def register(app): + return { "version_upgrade": upgrade } diff --git a/plugins/VersionUpgrade/VersionUpgrade24to25/tests/TestVersionUpgrade24to25.py b/plugins/VersionUpgrade/VersionUpgrade25to26/tests/TestVersionUpgrade25to26.py similarity index 85% rename from plugins/VersionUpgrade/VersionUpgrade24to25/tests/TestVersionUpgrade24to25.py rename to plugins/VersionUpgrade/VersionUpgrade25to26/tests/TestVersionUpgrade25to26.py index cb7300ad87..78cd8a03c5 100644 --- a/plugins/VersionUpgrade/VersionUpgrade24to25/tests/TestVersionUpgrade24to25.py +++ b/plugins/VersionUpgrade/VersionUpgrade25to26/tests/TestVersionUpgrade25to26.py @@ -4,12 +4,12 @@ import configparser #To check whether the appropriate exceptions are raised. import pytest #To register tests with. -import VersionUpgrade24to25 #The module we're testing. +import VersionUpgrade25to26 #The module we're testing. ## Creates an instance of the upgrader to test with. @pytest.fixture def upgrader(): - return VersionUpgrade24to25.VersionUpgrade24to25() + return VersionUpgrade25to26.VersionUpgrade25to26() test_cfg_version_good_data = [ { @@ -17,7 +17,7 @@ test_cfg_version_good_data = [ "file_data": """[general] version = 1 """, - "version": 1 + "version": 1000000 }, { "test_name": "Other Data Around", @@ -31,14 +31,32 @@ version = 3 layer_height = 0.12 infill_sparse_density = 42 """, - "version": 3 + "version": 3000000 }, { "test_name": "Negative Version", #Why not? "file_data": """[general] version = -20 """, - "version": -20 + "version": -20000000 + }, + { + "test_name": "Setting Version", + "file_data": """[general] +version = 1 +[metadata] +setting_version = 1 +""", + "version": 1000001 + }, + { + "test_name": "Negative Setting Version", + "file_data": """[general] +version = 1 +[metadata] +setting_version = -3 +""", + "version": 999997 } ] @@ -77,6 +95,22 @@ true = false "test_name": "Not a Number", "file_data": """[general] version = not-a-text-version-number +""", + "exception": ValueError + }, + { + "test_name": "Setting Value NaN", + "file_data": """[general] +version = 4 +[metadata] +setting_version = latest_or_something +""", + "exception": ValueError + }, + { + "test_name": "Major-Minor", + "file_data": """[general] +version = 1.2 """, "exception": ValueError } @@ -121,7 +155,7 @@ foo = bar } ] -## Tests whether the settings that should be removed are removed for the 2.5 +## Tests whether the settings that should be removed are removed for the 2.6 # version of preferences. @pytest.mark.parametrize("data", test_upgrade_preferences_removed_settings_data) def test_upgradePreferencesRemovedSettings(data, upgrader): @@ -137,7 +171,7 @@ def test_upgradePreferencesRemovedSettings(data, upgrader): upgraded_preferences = upgraded_preferences[0] #Find whether the removed setting is removed from the file now. - settings -= VersionUpgrade24to25._removed_settings + settings -= VersionUpgrade25to26._removed_settings parser = configparser.ConfigParser(interpolation = None) parser.read_string(upgraded_preferences) assert (parser.has_section("general") and "visible_settings" in parser["general"]) == (len(settings) > 0) #If there are settings, there must also be a preference. @@ -166,7 +200,7 @@ type = instance_container } ] -## Tests whether the settings that should be removed are removed for the 2.5 +## Tests whether the settings that should be removed are removed for the 2.6 # version of instance containers. @pytest.mark.parametrize("data", test_upgrade_instance_container_removed_settings_data) def test_upgradeInstanceContainerRemovedSettings(data, upgrader): @@ -182,7 +216,7 @@ def test_upgradeInstanceContainerRemovedSettings(data, upgrader): upgraded_container = upgraded_container[0] #Find whether the forbidden setting is still in the container. - settings -= VersionUpgrade24to25._removed_settings + settings -= VersionUpgrade25to26._removed_settings parser = configparser.ConfigParser(interpolation = None) parser.read_string(upgraded_container) assert parser.has_section("values") == (len(settings) > 0) #If there are settings, there must also be the values category. diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 7dc565ce26..7bdfaf404d 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -3,6 +3,7 @@ import copy import io +from typing import Optional import xml.etree.ElementTree as ET from UM.Resources import Resources @@ -11,7 +12,7 @@ from UM.Util import parseBool from cura.CuraApplication import CuraApplication import UM.Dictionary -from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.InstanceContainer import InstanceContainer, InvalidInstanceError from UM.Settings.ContainerRegistry import ContainerRegistry ## Handles serializing and deserializing material containers from an XML file @@ -20,6 +21,20 @@ class XmlMaterialProfile(InstanceContainer): super().__init__(container_id, *args, **kwargs) self._inherited_files = [] + ## Translates the version number in the XML files to the setting_version + # metadata entry. + # + # Since the two may increment independently we need a way to say which + # versions of the XML specification are compatible with our setting data + # version numbers. + # + # \param xml_version: The version number found in an XML file. + # \return The corresponding setting_version. + def xmlVersionToSettingVersion(self, xml_version: str) -> int: + if xml_version == "1.3": + return 1 + return 0 #Older than 1.3. + def getInheritedFiles(self): return self._inherited_files @@ -118,6 +133,7 @@ class XmlMaterialProfile(InstanceContainer): metadata.pop("variant", "") metadata.pop("type", "") metadata.pop("base_file", "") + metadata.pop("approximate_diameter", "") ## Begin Name Block builder.start("name") @@ -143,10 +159,10 @@ class XmlMaterialProfile(InstanceContainer): for key, value in metadata.items(): builder.start(key) - # Normally value is a string. - # Nones get handled well. - if isinstance(value, bool): - value = str(value) # parseBool in deserialize expects 'True'. + if value is not None: #Nones get handled well by the builder. + #Otherwise the builder always expects a string. + #Deserialize expects the stringified version. + value = str(value) builder.data(value) builder.end(key) @@ -369,8 +385,30 @@ class XmlMaterialProfile(InstanceContainer): self._dirty = False self._path = "" + def getConfigurationTypeFromSerialized(self, serialized: str) -> Optional[str]: + return "material" + + def getVersionFromSerialized(self, serialized: str) -> Optional[int]: + version = None + data = ET.fromstring(serialized) + metadata = data.iterfind("./um:metadata/*", self.__namespaces) + for entry in metadata: + tag_name = _tag_without_namespace(entry) + if tag_name == "version": + try: + version = int(entry.text) + except Exception as e: + raise InvalidInstanceError("Invalid version string '%s': %s" % (entry.text, e)) + break + if version is None: + raise InvalidInstanceError("Missing version in metadata") + return version + ## Overridden from InstanceContainer def deserialize(self, serialized): + # update the serialized data first + from UM.Settings.Interfaces import ContainerInterface + serialized = ContainerInterface.deserialize(self, serialized) data = ET.fromstring(serialized) # Reset previous metadata @@ -385,6 +423,10 @@ class XmlMaterialProfile(InstanceContainer): inherited = self._resolveInheritance(inherits.text) data = self._mergeXML(inherited, data) + if "version" in data.attrib: + meta_data["setting_version"] = self.xmlVersionToSettingVersion(data.attrib["version"]) + else: + meta_data["setting_version"] = self.xmlVersionToSettingVersion("1.2") #1.2 and lower didn't have that version number there yet. metadata = data.iterfind("./um:metadata/*", self.__namespaces) for entry in metadata: tag_name = _tag_without_namespace(entry) @@ -405,10 +447,10 @@ class XmlMaterialProfile(InstanceContainer): continue meta_data[tag_name] = entry.text - if not "description" in meta_data: + if "description" not in meta_data: meta_data["description"] = "" - if not "adhesion_info" in meta_data: + if "adhesion_info" not in meta_data: meta_data["adhesion_info"] = "" property_values = {} @@ -417,8 +459,7 @@ class XmlMaterialProfile(InstanceContainer): tag_name = _tag_without_namespace(entry) property_values[tag_name] = entry.text - diameter = float(property_values.get("diameter", 2.85)) # In mm - density = float(property_values.get("density", 1.3)) # In g/cm3 + meta_data["approximate_diameter"] = round(float(property_values.get("diameter", 2.85))) # In mm meta_data["properties"] = property_values self.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0]) @@ -581,7 +622,8 @@ class XmlMaterialProfile(InstanceContainer): "Ultimaker 2 Extended": "ultimaker2_extended", "Ultimaker 2 Extended+": "ultimaker2_extended_plus", "Ultimaker Original": "ultimaker_original", - "Ultimaker Original+": "ultimaker_original_plus" + "Ultimaker Original+": "ultimaker_original_plus", + "IMADE3D JellyBOX": "imade3d_jellybox" } # Map of recognised namespaces with a proper prefix. diff --git a/resources/definitions/alya3dp.def.json b/resources/definitions/alya3dp.def.json new file mode 100644 index 0000000000..1ea16773d6 --- /dev/null +++ b/resources/definitions/alya3dp.def.json @@ -0,0 +1,55 @@ +{ + "id": "alya3dp", + "name": "ALYA", + "version": 2, + "inherits": "fdmprinter", + "metadata": { + "visible": true, + "author": "ALYA", + "manufacturer": "ALYA", + "category": "Other", + "file_formats": "text/x-gcode" + }, + + "overrides": { + "machine_width": { + "default_value": 100 + }, + "machine_height": { + "default_value": 133 + }, + "machine_depth": { + "default_value": 100 + }, + "machine_center_is_zero": { + "default_value": false + }, + "machine_nozzle_size": { + "default_value": 0.4 + }, + "machine_head_shape_min_x": { + "default_value": 75 + }, + "machine_head_shape_min_y": { + "default_value": 18 + }, + "machine_head_shape_max_x": { + "default_value": 18 + }, + "machine_head_shape_max_y": { + "default_value": 35 + }, + "machine_nozzle_gantry_distance": { + "default_value": 55 + }, + "machine_gcode_flavor": { + "default_value": "RepRap" + }, + "machine_start_gcode": { + "default_value": ";Sliced at: {day} {date} {time}\n;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}\n;Print time: {print_time}\n;Filament used: {filament_amount}m {filament_weight}g\n;Filament cost: {filament_cost}\n;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line\n;M109 S{print_temperature} ;Uncomment to add your own temperature line\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to max endstops\nG1 Z115.0 F{travel_speed} ;move th e platform up 20mm\nG28 Z0 ;move Z to max endstop\nG1 Z15.0 F{travel_speed} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{travel_speed}\nM301 H1 P26.38 I2.57 D67.78\n;Put printing message on LCD screen\nM117 Printing..." + }, + "machine_end_gcode": { + "default_value": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG28 Z0\nM84 ;steppers off\nG90 ;absolute positioning\n;{profile_string}" + } + } +} \ No newline at end of file diff --git a/resources/definitions/cartesio.def.json b/resources/definitions/cartesio.def.json index d0904b9716..6567f61fb3 100644 --- a/resources/definitions/cartesio.def.json +++ b/resources/definitions/cartesio.def.json @@ -9,12 +9,18 @@ "manufacturer": "Cartesio bv", "category": "Other", "file_formats": "text/x-gcode", + + "has_machine_quality": true, + "has_materials": true, "has_machine_materials": true, + "has_variant_materials": true, "has_variants": true, + "variants_name": "Nozzle size", - "preferred_variant": "*0.4*", + "preferred_variant": "*0.8*", "preferred_material": "*pla*", - "preferred_quality": "*draft*", + "preferred_quality": "*high*", + "machine_extruder_trains": { "0": "cartesio_extruder_0", @@ -29,7 +35,8 @@ }, "overrides": { - "machine_extruder_count": { "default_value": 4 }, + "machine_extruder_count": { "default_value": 2 }, + "material_diameter": { "default_value": 1.75 }, "machine_heated_bed": { "default_value": true }, "machine_center_is_zero": { "default_value": false }, "gantry_height": { "default_value": 35 }, @@ -39,20 +46,18 @@ "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "material_print_temp_wait": { "default_value": false }, "material_bed_temp_wait": { "default_value": false }, - "infill_pattern": { "default_value": "grid"}, "prime_tower_enable": { "default_value": true }, "prime_tower_wall_thickness": { "resolve": 0.7 }, - "prime_tower_position_x": { "default_value": 30 }, - "prime_tower_position_y": { "default_value": 71 }, + "prime_tower_position_x": { "default_value": 50 }, + "prime_tower_position_y": { "default_value": 150 }, "machine_start_gcode": { - "default_value": "\nM104 S120 T1\nM104 S120 T2\nM104 S120 T3\n\nM92 E159\n\nG21\nG90\nM42 S255 P13;chamber lights\nM42 S255 P12;fume extraction\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S600 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\n\nM104 S21 T1\nM104 S21 T2\nM104 S21 T3\n\nM117 Printing .....\n" + "default_value": "\nM104 S120 T1\nM104 S120 T2\nM104 S120 T3\n\nM92 E159\n\nG21\nG90\nM42 S255 P13;chamber lights\nM42 S255 P12;fume extraction\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S1200 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\nG1 Z10 F900\n\nM104 S21 T1\nM104 S21 T2\nM104 S21 T3\n\nM117 Printing .....\n" }, "machine_end_gcode": { - "default_value": "; -- END GCODE --\nM106 S255\nM140 S5\nM104 S5 T0\nM104 S5 T1\nM104 S5 T2\nM104 S5 T3\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\nT0\n; -- end of GCODE --" + "default_value": "; -- END GCODE --\nM106 S255\nM140 S5\nM104 S5 T0\nM104 S5 T1\nM104 S5 T2\nM104 S5 T3\n\nG91\nG1 Z1 F900\nG90\n\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\nT0\n; -- end of GCODE --" }, "layer_height": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" }, "layer_height_0": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" }, - "layer_height_0": { "resolve": "0.2 if min(extruderValues('machine_nozzle_size')) < 0.3 else 0.3 "}, "machine_nozzle_heat_up_speed": {"default_value": 20}, "machine_nozzle_cool_down_speed": {"default_value": 20}, "machine_min_cool_heat_time_window": {"default_value": 5} diff --git a/resources/definitions/custom.def.json b/resources/definitions/custom.def.json index 7ae1d1bd28..8f15f00a0f 100644 --- a/resources/definitions/custom.def.json +++ b/resources/definitions/custom.def.json @@ -10,6 +10,17 @@ "category": "Custom", "file_formats": "text/x-gcode", "has_materials": true, + "machine_extruder_trains": + { + "0": "custom_extruder_1", + "1": "custom_extruder_2", + "2": "custom_extruder_3", + "3": "custom_extruder_4", + "4": "custom_extruder_5", + "5": "custom_extruder_6", + "6": "custom_extruder_7", + "7": "custom_extruder_8" + }, "first_start_actions": ["MachineSettingsAction"] } } diff --git a/resources/definitions/delta_go.def.json b/resources/definitions/delta_go.def.json index b341a17656..ccb659f973 100644 --- a/resources/definitions/delta_go.def.json +++ b/resources/definitions/delta_go.def.json @@ -14,23 +14,29 @@ }, "overrides": { "machine_name": { "default_value": "Delta Go" }, - "material_diameter": { "default_value": 1.75 }, + "material_diameter": { "default_value": 1.75 }, + "default_material_print_temperature": { "default_value": 210 }, "speed_travel": { "default_value": 150 }, - "prime_tower_size": { "default_value": 8.66 }, - "infill_sparse_density": { "default_value": 10 }, + "prime_tower_size": { "default_value": 8.66 }, + "infill_sparse_density": { "default_value": 10 }, "speed_wall_x": { "default_value": 30 }, "speed_wall_0": { "default_value": 30 }, "speed_topbottom": { "default_value": 20 }, - "layer_height": { "default_value": 0.2 }, + "layer_height": { "default_value": 0.15 }, "speed_print": { "default_value": 30 }, - "machine_heated_bed": { "default_value": false }, - "machine_center_is_zero": { "default_value": true }, - "machine_height": { "default_value": 127 }, - "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, + "machine_heated_bed": { "default_value": false }, + "machine_center_is_zero": { "default_value": true }, + "machine_height": { "default_value": 154 }, + "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "machine_depth": { "default_value": 115 }, "machine_width": { "default_value": 115 }, - "retraction_amount": { "default_value": 4.2 }, - "retraction_speed": { "default_value": 400 }, - "machine_shape": { "default_value": "elliptic"} + "raft_airgap": { "default_value": 0.15 }, + "retraction_hop_enabled": { "value": "True" }, + "retraction_amount": { "default_value": 4.1 }, + "retraction_speed": { "default_value": 500 }, + "retraction_hop": { "value": "0.2" }, + "retraction_hop_only_when_collides": { "value": "True" }, + "brim_width": { "value": "5" }, + "machine_shape": { "default_value": "elliptic"} } } diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json index 7c594e3eda..be006dfe59 100644 --- a/resources/definitions/fdmextruder.def.json +++ b/resources/definitions/fdmextruder.def.json @@ -7,6 +7,7 @@ "type": "extruder", "author": "Ultimaker B.V.", "manufacturer": "Ultimaker", + "setting_version": 1, "visible": false }, "settings": @@ -25,10 +26,22 @@ "type": "extruder", "default_value": "0", "settable_per_mesh": true, - "settable_per_extruder": false, + "settable_per_extruder": true, "settable_per_meshgroup": false, "settable_globally": false }, + "machine_nozzle_size": + { + "label": "Nozzle Diameter", + "description": "The inner diameter of the nozzle. Change this setting when using a non-standard nozzle size.", + "unit": "mm", + "type": "float", + "default_value": 0.4, + "minimum_value": "0.001", + "maximum_value_warning": "10", + "settable_per_mesh": false, + "settable_per_extruder": true + }, "machine_nozzle_offset_x": { "label": "Nozzle X Offset", diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 7b00708bcf..ea4322d580 100755 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -8,6 +8,7 @@ "author": "Ultimaker B.V.", "category": "Ultimaker", "manufacturer": "Ultimaker", + "setting_version": 1, "file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g", "visible": false, "has_materials": true, @@ -940,7 +941,7 @@ "unit": "mm", "default_value": 0.8, "minimum_value": "0", - "minimum_value_warning": "3 * resolveOrValue('layer_height')", + "minimum_value_warning": "0.2 + resolveOrValue('layer_height')", "maximum_value": "machine_height", "type": "float", "value": "top_bottom_thickness", @@ -956,7 +957,7 @@ "minimum_value": "0", "maximum_value_warning": "100", "type": "int", - "minimum_value_warning": "4", + "minimum_value_warning": "2", "value": "0 if infill_sparse_density == 100 else math.ceil(round(top_thickness / resolveOrValue('layer_height'), 4))", "limit_to_extruder": "top_bottom_extruder_nr", "settable_per_mesh": true @@ -970,7 +971,7 @@ "unit": "mm", "default_value": 0.6, "minimum_value": "0", - "minimum_value_warning": "3 * resolveOrValue('layer_height')", + "minimum_value_warning": "0.2 + resolveOrValue('layer_height')", "type": "float", "value": "top_bottom_thickness", "maximum_value": "machine_height", @@ -983,7 +984,7 @@ "label": "Bottom Layers", "description": "The number of bottom layers. When calculated by the bottom thickness, this value is rounded to a whole number.", "minimum_value": "0", - "minimum_value_warning": "4", + "minimum_value_warning": "2", "default_value": 6, "type": "int", "value": "999999 if infill_sparse_density == 100 else math.ceil(round(bottom_thickness / resolveOrValue('layer_height'), 4))", @@ -1317,20 +1318,6 @@ "limit_to_extruder": "infill_extruder_nr", "settable_per_mesh": true }, - "sub_div_rad_mult": - { - "label": "Cubic Subdivision Radius", - "description": "A multiplier on the radius from the center of each cube to check for the boundary of the model, as to decide whether this cube should be subdivided. Larger values lead to more subdivisions, i.e. more small cubes.", - "unit": "%", - "type": "float", - "default_value": 100, - "minimum_value": "0", - "minimum_value_warning": "100", - "maximum_value_warning": "200", - "enabled": "infill_sparse_density > 0 and infill_pattern == 'cubicsubdiv'", - "limit_to_extruder": "infill_extruder_nr", - "settable_per_mesh": true - }, "sub_div_rad_add": { "label": "Cubic Subdivision Shell", @@ -1441,8 +1428,8 @@ "default_value": 0, "type": "int", "minimum_value": "0", - "maximum_value_warning": "4", - "maximum_value": "(20 - math.log(infill_line_distance) / math.log(2)) if infill_line_distance > 0 and not spaghetti_infill_enabled else 0", + "maximum_value_warning": "5", + "maximum_value": "0 if spaghetti_infill_enabled else (999999 if infill_line_distance == 0 else (20 - math.log(infill_line_distance) / math.log(2)))", "enabled": "infill_sparse_density > 0 and infill_pattern != 'cubicsubdiv' and not spaghetti_infill_enabled", "limit_to_extruder": "infill_extruder_nr", "settable_per_mesh": true @@ -1453,7 +1440,7 @@ "description": "The height of infill of a given density before switching to half the density.", "unit": "mm", "type": "float", - "default_value": 5.0, + "default_value": 1.5, "minimum_value": "0.0001", "minimum_value_warning": "3 * resolveOrValue('layer_height')", "maximum_value_warning": "100", @@ -1493,8 +1480,8 @@ { "expand_upper_skins": { - "label": "Expand Upper Skins", - "description": "Expand upper skin areas (areas with air above) so that they support infill above.", + "label": "Expand Top Skins Into Infill", + "description": "Expand the top skin areas (areas with air above) so that they support infill above.", "type": "bool", "default_value": false, "value": "expand_skins_into_infill", @@ -1503,8 +1490,8 @@ }, "expand_lower_skins": { - "label": "Expand Lower Skins", - "description": "Expand lower skin areas (areas with air below) so that they are anchored by the infill layers above and below.", + "label": "Expand Bottom Skins Into Infill", + "description": "Expand the bottom skin areas (areas with air below) so that they are anchored by the infill layers above and below.", "type": "bool", "default_value": false, "limit_to_extruder": "top_bottom_extruder_nr", @@ -2562,7 +2549,6 @@ "unit": "mm/s", "type": "float", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "default_value": 20, "enabled": "resolveOrValue('jerk_enabled')", @@ -2576,7 +2562,6 @@ "unit": "mm/s", "type": "float", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "default_value": 20, "value": "jerk_print", @@ -2591,7 +2576,6 @@ "unit": "mm/s", "type": "float", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "default_value": 20, "value": "jerk_print", @@ -2607,7 +2591,6 @@ "unit": "mm/s", "type": "float", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "default_value": 20, "value": "jerk_wall", @@ -2622,7 +2605,6 @@ "unit": "mm/s", "type": "float", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "default_value": 20, "value": "jerk_wall", @@ -2639,7 +2621,6 @@ "unit": "mm/s", "type": "float", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "default_value": 20, "value": "jerk_print", @@ -2654,7 +2635,6 @@ "unit": "mm/s", "type": "float", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "default_value": 20, "value": "jerk_print", @@ -2673,7 +2653,6 @@ "default_value": 20, "value": "jerk_support", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "enabled": "resolveOrValue('jerk_enabled') and support_enable", "limit_to_extruder": "support_infill_extruder_nr", @@ -2689,7 +2668,6 @@ "default_value": 20, "value": "jerk_support", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable", "limit_to_extruder": "support_interface_extruder_nr", @@ -2706,7 +2684,6 @@ "default_value": 20, "value": "jerk_support_interface", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_roof_extruder_nr, 'support_roof_enable') and support_enable", "limit_to_extruder": "support_roof_extruder_nr", @@ -2722,7 +2699,6 @@ "default_value": 20, "value": "jerk_support_interface", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable", "limit_to_extruder": "support_bottom_extruder_nr", @@ -2740,7 +2716,6 @@ "unit": "mm/s", "type": "float", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "default_value": 20, "value": "jerk_print", @@ -2757,7 +2732,6 @@ "type": "float", "default_value": 30, "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "value": "jerk_print if magic_spiralize else 30", "enabled": "resolveOrValue('jerk_enabled')", @@ -2772,7 +2746,6 @@ "default_value": 20, "value": "jerk_print", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "enabled": "resolveOrValue('jerk_enabled')", "settable_per_mesh": true, @@ -2787,7 +2760,6 @@ "default_value": 20, "value": "jerk_layer_0", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "enabled": "resolveOrValue('jerk_enabled')", "settable_per_mesh": true @@ -2801,7 +2773,6 @@ "default_value": 20, "value": "jerk_layer_0 * jerk_travel / jerk_print", "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "enabled": "resolveOrValue('jerk_enabled')", "settable_per_extruder": true, @@ -2817,7 +2788,6 @@ "type": "float", "default_value": 20, "minimum_value": "0.1", - "minimum_value_warning": "5", "maximum_value_warning": "50", "value": "jerk_layer_0", "enabled": "resolveOrValue('jerk_enabled')", @@ -3121,8 +3091,8 @@ { "support_enable": { - "label": "Enable Support", - "description": "Enable support structures. These structures support parts of the model with severe overhangs.", + "label": "Generate Support", + "description": "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing.", "type": "bool", "default_value": false, "settable_per_mesh": true, @@ -3380,7 +3350,7 @@ "support_bottom_stair_step_height": { "label": "Support Stair Step Height", - "description": "The height of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures.", + "description": "The height of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures. Set to zero to turn off the stair-like behaviour.", "unit": "mm", "type": "float", "default_value": 0.3, @@ -3390,6 +3360,19 @@ "enabled": "support_enable", "settable_per_mesh": true }, + "support_bottom_stair_step_width": + { + "label": "Support Stair Step Maximum Width", + "description": "The maximum width of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures.", + "unit": "mm", + "type": "float", + "default_value": 5.0, + "limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr", + "minimum_value": "0", + "maximum_value_warning": "10.0", + "enabled": "support_enable", + "settable_per_mesh": true + }, "support_join_distance": { "label": "Support Join Distance", @@ -3459,7 +3442,7 @@ "type": "float", "default_value": 1, "minimum_value": "0", - "minimum_value_warning": "3 * resolveOrValue('layer_height')", + "minimum_value_warning": "0.2 + resolveOrValue('layer_height')", "maximum_value_warning": "10", "limit_to_extruder": "support_interface_extruder_nr", "enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable", @@ -3474,7 +3457,7 @@ "type": "float", "default_value": 1, "minimum_value": "0", - "minimum_value_warning": "3 * resolveOrValue('layer_height')", + "minimum_value_warning": "0.2 + resolveOrValue('layer_height')", "maximum_value_warning": "10", "value": "extruderValue(support_roof_extruder_nr, 'support_interface_height')", "limit_to_extruder": "support_roof_extruder_nr", @@ -3490,7 +3473,7 @@ "default_value": 1, "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_height')", "minimum_value": "0", - "minimum_value_warning": "min(3 * resolveOrValue('layer_height'), extruderValue(support_bottom_extruder_nr, 'support_bottom_stair_step_height'))", + "minimum_value_warning": "min(0.2 + resolveOrValue('layer_height'), extruderValue(support_bottom_extruder_nr, 'support_bottom_stair_step_height'))", "maximum_value_warning": "10", "limit_to_extruder": "support_bottom_extruder_nr", "enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable", @@ -3718,6 +3701,17 @@ "description": "Adhesion", "children": { + "prime_blob_enable": + { + "label": "Enable Prime Blob", + "description": "Whether to prime the filament with a blob before printing. Turning this setting on will ensure that the extruder will have material ready at the nozzle before printing. Printing Brim or Skirt can act like priming too, in which case turning this setting off saves some time.", + "type": "bool", + "resolve": "any(extruderValues('prime_blob_enable'))", + "default_value": true, + "settable_per_mesh": false, + "settable_per_extruder": true, + "enabled": false + }, "extruder_prime_pos_x": { "label": "Extruder Prime X Position", @@ -3757,7 +3751,7 @@ "none": "None" }, "default_value": "brim", - "resolve": "'raft' if 'raft' in extruderValues('adhesion_type') else ('brim' if 'brim' in extruderValues('adhesion_type') else 'skirt')", + "resolve": "extruderValue(adhesion_extruder_nr, 'adhesion_type')", "settable_per_mesh": false, "settable_per_extruder": false }, @@ -3968,7 +3962,7 @@ "value": "resolveOrValue('layer_height') * 1.5", "minimum_value": "0.001", "minimum_value_warning": "0.04", - "maximum_value_warning": "0.75 * extruderValue(adhesion_extruder_nr, 'raft_interface_line_width')", + "maximum_value_warning": "0.75 * extruderValue(adhesion_extruder_nr, 'machine_nozzle_size')", "enabled": "resolveOrValue('adhesion_type') == 'raft'", "settable_per_mesh": false, "settable_per_extruder": true, @@ -4337,7 +4331,7 @@ "type": "float", "unit": "mm", "enabled": "resolveOrValue('prime_tower_enable')", - "default_value": 15, + "default_value": 20, "resolve": "max(extruderValues('prime_tower_size'))", "minimum_value": "0", "maximum_value": "min(0.5 * machine_width, 0.5 * machine_depth)", @@ -4370,7 +4364,7 @@ "value": "round(max(2 * min(extruderValues('prime_tower_line_width')), 0.5 * (resolveOrValue('prime_tower_size') - math.sqrt(max(0, resolveOrValue('prime_tower_size') ** 2 - max(extruderValues('prime_tower_min_volume')) / resolveOrValue('layer_height'))))), 3)", "resolve": "max(extruderValues('prime_tower_wall_thickness'))", "minimum_value": "0.001", - "minimum_value_warning": "2 * min(extruderValues('prime_tower_line_width'))", + "minimum_value_warning": "2 * min(extruderValues('prime_tower_line_width')) - 0.0001", "maximum_value_warning": "resolveOrValue('prime_tower_size') / 2", "enabled": "resolveOrValue('prime_tower_enable')", "settable_per_mesh": false, @@ -4595,6 +4589,31 @@ "settable_per_meshgroup": false, "settable_globally": false }, + "infill_mesh_order": + { + "label": "Infill Mesh Order", + "description": "Determines which infill mesh is inside the infill of another infill mesh. An infill mesh with a higher order will modify the infill of infill meshes with lower order and normal meshes.", + "default_value": 0, + "value": "1 if infill_mesh else 0", + "minimum_value_warning": "1", + "maximum_value_warning": "50", + "type": "int", + "settable_per_mesh": true, + "settable_per_extruder": false, + "settable_per_meshgroup": false, + "settable_globally": false + }, + "cutting_mesh": + { + "label": "Cutting Mesh", + "description": "Limit the volume of this mesh to within other meshes. You can use this to make certain areas of one mesh print with different settings and with a whole different extruder.", + "type": "bool", + "default_value": false, + "settable_per_mesh": true, + "settable_per_extruder": false, + "settable_per_meshgroup": false, + "settable_globally": false + }, "mold_enabled": { "label": "Mold", @@ -4629,20 +4648,6 @@ "settable_per_mesh": true, "enabled": "mold_enabled" }, - "infill_mesh_order": - { - "label": "Infill Mesh Order", - "description": "Determines which infill mesh is inside the infill of another infill mesh. An infill mesh with a higher order will modify the infill of infill meshes with lower order and normal meshes.", - "default_value": 0, - "value": "1 if infill_mesh else 0", - "minimum_value_warning": "1", - "maximum_value_warning": "50", - "type": "int", - "settable_per_mesh": true, - "settable_per_extruder": false, - "settable_per_meshgroup": false, - "settable_globally": false - }, "support_mesh": { "label": "Support Mesh", @@ -4654,6 +4659,18 @@ "settable_per_meshgroup": false, "settable_globally": false }, + "support_mesh_drop_down": + { + "label": "Drop Down Support Mesh", + "description": "Make support everywhere below the support mesh, so that there's no overhang in the support mesh.", + "type": "bool", + "default_value": true, + "enabled": "support_mesh", + "settable_per_mesh": true, + "settable_per_extruder": false, + "settable_per_meshgroup": false, + "settable_globally": false + }, "anti_overhang_mesh": { "label": "Anti Overhang Mesh", @@ -4682,11 +4699,21 @@ "magic_spiralize": { "label": "Spiralize Outer Contour", - "description": "Spiralize smooths out the Z move of the outer edge. This will create a steady Z increase over the whole print. This feature turns a solid model into a single walled print with a solid bottom. This feature used to be called Joris in older versions.", + "description": "Spiralize smooths out the Z move of the outer edge. This will create a steady Z increase over the whole print. This feature turns a solid model into a single walled print with a solid bottom. This feature should only be enabled when each layer only contains a single part.", "type": "bool", "default_value": false, "settable_per_mesh": false, "settable_per_extruder": false + }, + "smooth_spiralized_contours": + { + "label": "Smooth Spiralized Contours", + "description": "Smooth the spiralized contours to reduce the visibility of the Z seam (the Z-seam should be barely visible on the print but will still be visible in the layer view). Note that smoothing will tend to blur fine surface details.", + "type": "bool", + "default_value": true, + "enabled": "magic_spiralize", + "settable_per_mesh": false, + "settable_per_extruder": false } } }, diff --git a/resources/definitions/imade3d_jellybox.def.json b/resources/definitions/imade3d_jellybox.def.json index f8077f2e95..86b34bfd5c 100644 --- a/resources/definitions/imade3d_jellybox.def.json +++ b/resources/definitions/imade3d_jellybox.def.json @@ -32,7 +32,7 @@ "machine_center_is_zero": { "default_value": false }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "machine_start_gcode": { - "default_value": ";---------------------------------------\n; ; ; Jellybox Start Script Begin ; ; ;\n;_______________________________________\n; M92 E140 ;optionally adjust steps per mm for your filament\n\n; Print Settings Summary\n; (leave these alone: this is only a list of the slicing settings)\n; (overwriting these values will NOT change your printer's behavior)\n; sliced for : {machine_name}\n; nozzle diameter : {machine_nozzle_size}\n; filament diameter : {material_diameter}\n; layer height : {layer_height}\n; 1st layer height : {layer_height_0}\n; line width : {line_width}\n; outer wall wipe dist. : {wall_0_wipe_dist}\n; infill line width : {infill_line_width}\n; wall thickness : {wall_thickness}\n; top thickness : {top_thickness}\n; bottom thickness : {bottom_thickness}\n; infill density : {infill_sparse_density}\n; infill pattern : {infill_pattern}\n; print temperature : {material_print_temperature}\n; 1st layer print temp. : {material_print_temperature_layer_0}\n; heated bed temperature : {material_bed_temperature}\n; 1st layer bed temp. : {material_bed_temperature_layer_0}\n; regular fan speed : {cool_fan_speed_min}\n; max fan speed : {cool_fan_speed_max}\n; retraction amount : {retraction_amount}\n; retr. retract speed : {retraction_retract_speed}\n; retr. prime speed : {retraction_prime_speed}\n; build plate adhesion : {adhesion_type}\n; support ? {support_enable}\n; spiralized ? {magic_spiralize}\n\nM117 Preparing ;write Preparing\nM140 S{material_bed_temperature_layer_0} ;set bed temperature and move on\nM104 S{material_print_temperature_layer_0} ;set extruder temperature and move on\nM206 X10.0 Y0.0 ;set x homing offset for default bed leveling\nG21 ;metric values\nG90 ;absolute positioning\nM107 ;start with the fan off\nM82 ;set extruder to absolute mode\nG28 ;home all axes\nM203 Z4 ;slow Z speed down for greater accuracy when probing\nG29 ;auto bed leveling procedure\nM203 Z7 ;pick up z speed again for printing\nM190 S{material_bed_temperature_layer_0} ;wait for the bed to reach desired temperature\nM109 S{material_print_temperature_layer_0} ;wait for the extruder to reach desired temperature\nG92 E0 ;reset the extruder position\nG1 F1500 E15 ;extrude 15mm of feed stock\nG92 E0 ;reset the extruder position again\nM117 Print starting ;write Print starting\n;---------------------------------------------\n; ; ; Jellybox Printer Start Script End ; ; ;\n;_____________________________________________\n" + "default_value": ";---------------------------------------\n; ; ; Jellybox Start Script Begin ; ; ;\n;_______________________________________\n; M92 E140 ;optionally adjust steps per mm for your filament\n\n; Print Settings Summary\n; (leave these alone: this is only a list of the slicing settings)\n; (overwriting these values will NOT change your printer's behavior)\n; sliced for : {machine_name}\n; nozzle diameter : {machine_nozzle_size}\n; filament diameter : {material_diameter}\n; layer height : {layer_height}\n; 1st layer height : {layer_height_0}\n; line width : {line_width}\n; outer wall wipe dist. : {wall_0_wipe_dist}\n; infill line width : {infill_line_width}\n; wall thickness : {wall_thickness}\n; top thickness : {top_thickness}\n; bottom thickness : {bottom_thickness}\n; infill density : {infill_sparse_density}\n; infill pattern : {infill_pattern}\n; print temperature : {material_print_temperature}\n; 1st layer print temp. : {material_print_temperature_layer_0}\n; heated bed temperature : {material_bed_temperature}\n; 1st layer bed temp. : {material_bed_temperature_layer_0}\n; regular fan speed : {cool_fan_speed_min}\n; max fan speed : {cool_fan_speed_max}\n; retraction amount : {retraction_amount}\n; retr. retract speed : {retraction_retract_speed}\n; retr. prime speed : {retraction_prime_speed}\n; build plate adhesion : {adhesion_type}\n; support ? {support_enable}\n; spiralized ? {magic_spiralize}\n\nM117 Preparing ;write Preparing\nM140 S{material_bed_temperature_layer_0} ;set bed temperature and move on\nM109 S{material_print_temperature} ; wait for the extruder to reach desired temperature\nM206 X10.0 Y0.0 ;set x homing offset for default bed leveling\nG21 ;metric values\nG90 ;absolute positioning\nM107 ;start with the fan off\nM82 ;set extruder to absolute mode\nG28 ;home all axes\nM203 Z4 ;slow Z speed down for greater accuracy when probing\nG29 ;auto bed leveling procedure\nM203 Z7 ;pick up z speed again for printing\nM190 S{material_bed_temperature_layer_0} ;wait for the bed to reach desired temperature\nM109 S{material_print_temperature_layer_0} ;wait for the extruder to reach desired temperature\nG92 E0 ;reset the extruder position\nG1 F1500 E15 ;extrude 15mm of feed stock\nG92 E0 ;reset the extruder position again\nM117 Print starting ;write Print starting\n;---------------------------------------------\n; ; ; Jellybox Printer Start Script End ; ; ;\n;_____________________________________________\n" }, "machine_end_gcode": { "default_value": "\n;---------------------------------\n;;; Jellybox End Script Begin ;;;\n;_________________________________\nM117 Finishing Up ;write Finishing Up\n\nM104 S0 ;extruder heater off\nM140 S0 ;bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG90 ;absolute positioning\nG28 X ;home x, so the head is out of the way\nG1 Y100 ;move Y forward, so the print is more accessible\nM84 ;steppers off\n\nM117 Print finished ;write Print finished\n;---------------------------------------\n;;; Jellybox End Script End ;;;\n;_______________________________________" diff --git a/resources/definitions/innovo_inventor.def.json b/resources/definitions/innovo_inventor.def.json index 40a2849979..4b169c5e31 100644 --- a/resources/definitions/innovo_inventor.def.json +++ b/resources/definitions/innovo_inventor.def.json @@ -44,12 +44,6 @@ "gantry_height": { "default_value": 82.3 }, - "machine_nozzle_offset_x": { - "default_value": 0 - }, - "machine_nozzle_offset_y": { - "default_value": 15 - }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, diff --git a/resources/definitions/makeR_pegasus.def.json b/resources/definitions/makeR_pegasus.def.json new file mode 100644 index 0000000000..efaa3a5c3f --- /dev/null +++ b/resources/definitions/makeR_pegasus.def.json @@ -0,0 +1,70 @@ +{ + "id": "makeR_pegasus", + "version": 2, + "name": "makeR Pegasus", + "inherits": "fdmprinter", + "metadata": { + "visible": true, + "author": "makeR", + "manufacturer": "makeR", + "category": "Other", + "file_formats": "text/x-gcode", + "icon": "icon_ultimaker2", + "platform": "makeR_pegasus_platform.stl", + "platform_offset": [-200,-10,200] + }, + + "overrides": { + "machine_name": { "default_value": " makeR Pegasus" }, + "machine_heated_bed": { + "default_value": true + }, + "machine_width": { + "default_value": 400 + }, + "machine_height": { + "default_value": 400 + }, + "machine_depth": { + "default_value": 400 + }, + "machine_center_is_zero": { + "default_value": false + }, + "machine_nozzle_size": { + "default_value": 0.4 + }, + "material_diameter": { + "default_value": 2.85 + }, + "machine_nozzle_heat_up_speed": { + "default_value": 2 + }, + "machine_nozzle_cool_down_speed": { + "default_value": 2 + }, + "machine_head_polygon": { + "default_value": [ + [-75, -18], + [-75, 35], + [18, 35], + [18, -18] + ] + }, + "gantry_height": { + "default_value": -25 + }, + "machine_platform_offset":{ + "default_value":-25 + }, + "machine_gcode_flavor": { + "default_value": "RepRap (Marlin/Sprinter)" + }, + "machine_start_gcode": { + "default_value": "G1 Z15;\nG28;Home\nG29;Auto Level\nG1 Z5 F5000;Move the platform down 15mm" + }, + "machine_end_gcode": { + "default_value": "M104 S0;Turn off temperature\nG28 X0; Home X\nM84; Disable Motors" + } + } +} \ No newline at end of file diff --git a/resources/definitions/makeR_prusa_tairona_i3.def.json b/resources/definitions/makeR_prusa_tairona_i3.def.json new file mode 100644 index 0000000000..ab80fd0f5e --- /dev/null +++ b/resources/definitions/makeR_prusa_tairona_i3.def.json @@ -0,0 +1,67 @@ +{ + "id": "makeR_prusa_tairona_i3", + "version": 2, + "name": "makeR Prusa Tairona i3", + "inherits": "fdmprinter", + "metadata": { + "visible": true, + "author": "makeR", + "manufacturer": "makeR", + "category": "Other", + "file_formats": "text/x-gcode", + "icon": "icon_ultimaker2", + "platform": "makeR_prusa_tairona_i3_platform.stl", + "platform_offset": [-2,0,0] + }, + + "overrides": { + "machine_name": { "default_value": "makeR Prusa Tairona I3" }, + "machine_heated_bed": { + "default_value": true + }, + "machine_width": { + "default_value": 200 + }, + "machine_height": { + "default_value": 200 + }, + "machine_depth": { + "default_value": 200 + }, + "machine_center_is_zero": { + "default_value": false + }, + "machine_nozzle_size": { + "default_value": 0.4 + }, + "material_diameter": { + "default_value": 1.75 + }, + "machine_nozzle_heat_up_speed": { + "default_value": 2 + }, + "machine_nozzle_cool_down_speed": { + "default_value": 2 + }, + "machine_head_polygon": { + "default_value": [ + [-75, -18], + [-75, 35], + [18, 35], + [18, -18] + ] + }, + "gantry_height": { + "default_value": 55 + }, + "machine_gcode_flavor": { + "default_value": "RepRap (Marlin/Sprinter)" + }, + "machine_start_gcode": { + "default_value": "G1 Z15;\nG28;Home\nG29;Auto Level\nG1 Z5 F5000;Move the platform down 15mm" + }, + "machine_end_gcode": { + "default_value": "M104 S0;Turn off temperature\nG28 X0; Home X\nM84; Disable Motors" + } + } +} \ No newline at end of file diff --git a/resources/definitions/maker_starter.def.json b/resources/definitions/maker_starter.def.json index 74cdc694ee..305a35d0ca 100644 --- a/resources/definitions/maker_starter.def.json +++ b/resources/definitions/maker_starter.def.json @@ -4,6 +4,7 @@ "name": "3DMaker Starter", "inherits": "fdmprinter", "metadata": { + "visible": true, "author": "tvlgiao", "manufacturer": "3DMaker", "category": "Other", diff --git a/resources/definitions/peopoly_moai.def.json b/resources/definitions/peopoly_moai.def.json new file mode 100644 index 0000000000..9c01ca95e4 --- /dev/null +++ b/resources/definitions/peopoly_moai.def.json @@ -0,0 +1,162 @@ +{ + "id": "peopoly_moai", + "version": 2, + "name": "Peopoly Moai", + "inherits": "fdmprinter", + "metadata": { + "visible": true, + "author": "fieldOfView", + "manufacturer": "Peopoly", + "category": "Other", + "file_formats": "text/x-gcode", + "has_machine_quality": true, + "has_materials": false + }, + + "overrides": { + "machine_name": { + "default_value": "Moai" + }, + "machine_width": { + "default_value": 130 + }, + "machine_height": { + "default_value": 180 + }, + "machine_depth": { + "default_value": 130 + }, + "machine_nozzle_size": { + "default_value": 0.067 + }, + "machine_head_with_fans_polygon": + { + "default_value": [ + [ -20, 10 ], + [ -20, -10 ], + [ 10, 10 ], + [ 10, -10 ] + ] + }, + "machine_gcode_flavor": { + "default_value": "RepRap (Marlin/Sprinter)" + }, + "machine_start_gcode": { + "default_value": "G28 ;Home" + }, + "machine_end_gcode": { + "default_value": "M104 S0\nM140 S0\nG28 X0 Y0\nM84" + }, + + "line_width": { + "minimum_value_warning": "machine_nozzle_size" + }, + "wall_line_width": { + "minimum_value_warning": "machine_nozzle_size" + }, + "wall_line_width_x": { + "minimum_value_warning": "machine_nozzle_size" + }, + "skin_line_width": { + "minimum_value_warning": "machine_nozzle_size" + }, + "infill_line_width": { + "minimum_value_warning": "machine_nozzle_size" + }, + "skirt_brim_line_width": { + "minimum_value_warning": "machine_nozzle_size" + }, + "layer_height": { + "maximum_value_warning": "0.5", + "minimum_value_warning": "0.02" + }, + "layer_height_0": { + "maximum_value_warning": "0.5", + "minimum_value_warning": "0.02", + "value": "0.1" + }, + "top_bottom_thickness": { + "minimum_value_warning": "0.1" + }, + "infill_sparse_thickness": { + "maximum_value_warning": "0.5" + }, + "speed_print": { + "maximum_value_warning": "300" + }, + "speed_infill": { + "maximum_value_warning": "300" + }, + "speed_wall": { + "maximum_value_warning": "300", + "value": "speed_print" + }, + "speed_wall_0": { + "maximum_value_warning": "300" + }, + "speed_wall_x": { + "maximum_value_warning": "300", + "value": "speed_print" + }, + "speed_topbottom": { + "maximum_value_warning": "300", + "value": "speed_print" + }, + "speed_travel": { + "value": "300" + }, + "speed_travel_layer_0": { + "value": "300" + }, + "speed_layer_0": { + "value": "5" + }, + "speed_slowdown_layers": { + "value": "2" + }, + + "acceleration_enabled": { + "value": "False" + }, + "print_sequence": { + "enabled": false + }, + "support_enable": { + "enabled": false + }, + "machine_nozzle_temp_enabled": { + "value": "False" + }, + "material_bed_temperature": { + "enabled": false + }, + "material_diameter": { + "enabled": false, + "value": "1.75" + }, + "cool_fan_enabled": { + "enabled": false, + "value": "False" + }, + "retraction_enable": { + "enabled": false, + "value": "False" + }, + "retraction_combing": { + "enabled": false, + "value": "'off'" + }, + "retract_at_layer_change": { + "enabled": false + }, + "cool_min_layer_time_fan_speed_max": { + "enabled": false + }, + "cool_fan_full_at_height": { + "enabled": false + }, + "cool_fan_full_layer": { + "enabled": false + } + } +} diff --git a/resources/definitions/rigid3d_zero2.def.json b/resources/definitions/rigid3d_zero2.def.json new file mode 100644 index 0000000000..41cd2c18db --- /dev/null +++ b/resources/definitions/rigid3d_zero2.def.json @@ -0,0 +1,130 @@ +{ + "id": "rigid3d_zero2", + "name": "Rigid3D Zero2", + "version": 2, + "inherits": "fdmprinter", + "metadata": { + "visible": true, + "author": "Rigid3D", + "manufacturer": "Rigid3D", + "category": "Other", + "has_materials": false, + "file_formats": "text/x-gcode", + "platform": "rigid3d_zero2_platform.stl", + "platform_offset": [ 5, 0, -35] + }, + "overrides": { + "machine_name": { "default_value": "Rigid3D Zero2" }, + "machine_head_with_fans_polygon": { + "default_value": [[ 30, 30], [ 30, 70], [ 30, 70], [ 30, 30]] + }, + "z_seam_type": { + "default_value": "random" + }, + "machine_heated_bed": { + "default_value": true + }, + "layer_height": { + "default_value": 0.2 + }, + "layer_height_0": { + "default_value": 0.2 + }, + "wall_thickness": { + "default_value": 0.8 + }, + "top_bottom_thickness": { + "default_value": 0.8 + }, + "xy_offset": { + "default_value": -0.2 + }, + "material_print_temperature": { + "value": 235 + }, + "material_bed_temperature": { + "default_value": 100 + }, + "material_diameter": { + "default_value": 1.75 + }, + "speed_print": { + "default_value": 40 + }, + "speed_layer_0": { + "value": 15 + }, + "speed_travel": { + "value": 100 + }, + "support_enable": { + "default_value": false + }, + "infill_sparse_density": { + "default_value": 15 + }, + "infill_pattern": { + "default_value": "lines", + "value": "lines" + }, + "retraction_amount": { + "default_value": 1 + }, + "machine_width": { + "default_value": 200 + }, + "machine_height": { + "default_value": 200 + }, + "machine_depth": { + "default_value": 200 + }, + "machine_center_is_zero": { + "default_value": false + }, + "machine_nozzle_size": { + "default_value": 0.4 + }, + "gantry_height": { + "default_value": 25 + }, + "machine_gcode_flavor": { + "default_value": "RepRap" + }, + "cool_fan_enabled": { + "default_value": false + }, + "cool_fan_speed": { + "default_value": 50, + "value": 50 + }, + "cool_fan_speed_min": { + "default_value": 0 + }, + "cool_fan_full_at_height": { + "default_value": 1.0, + "value": 1.0 + }, + "support_z_distance": { + "default_value": 0.2 + }, + "support_interface_enable": { + "default_value": true + }, + "support_interface_height": { + "default_value": 0.8 + }, + "support_interface_density": { + "default_value": 70 + }, + "support_interface_pattern": { + "default_value": "grid" + }, + "machine_start_gcode": { + "default_value": "G21\nG28 ; Home extruder\nM107 ; Turn off fan\nG91 ; Relative positioning\nG1 Z5 F180;\nG1 X100 Y100 F3000;\nG1 Z-5 F180;\nG90 ; Absolute positioning\nM82 ; Extruder in absolute mode\nG92 E0 ; Reset extruder position\n" + }, + "machine_end_gcode": { + "default_value": "G1 X0 Y180 ; Get extruder out of way.\nM107 ; Turn off fan\nG91 ; Relative positioning\nG0 Z20 ; Lift extruder up\nT0\nG1 E-1 ; Reduce filament pressure\nM104 T0 S0 ; Turn extruder heater off\nG90 ; Absolute positioning\nG92 E0 ; Reset extruder position\nM140 S0 ; Disable heated bed\nM84 ; Turn steppers off\n" + } + } +} diff --git a/resources/definitions/ultimaker2.def.json b/resources/definitions/ultimaker2.def.json index a52075fe5e..5b2f339589 100644 --- a/resources/definitions/ultimaker2.def.json +++ b/resources/definitions/ultimaker2.def.json @@ -15,7 +15,8 @@ "platform_texture": "Ultimaker2backplate.png", "platform_offset": [9, 0, 0], "has_materials": false, - "supported_actions":["UpgradeFirmware"] + "first_start_actions": ["UM2UpgradeSelection"], + "supported_actions":["UM2UpgradeSelection", "UpgradeFirmware"] }, "overrides": { "machine_name": { "default_value": "Ultimaker 2" }, diff --git a/resources/definitions/ultimaker2_extended.def.json b/resources/definitions/ultimaker2_extended.def.json index 803e9fe896..ac9d98c5eb 100644 --- a/resources/definitions/ultimaker2_extended.def.json +++ b/resources/definitions/ultimaker2_extended.def.json @@ -11,8 +11,7 @@ "file_formats": "text/x-gcode", "icon": "icon_ultimaker2.png", "platform": "ultimaker2_platform.obj", - "platform_texture": "Ultimaker2Extendedbackplate.png", - "supported_actions": ["UpgradeFirmware"] + "platform_texture": "Ultimaker2Extendedbackplate.png" }, "overrides": { diff --git a/resources/definitions/ultimaker2_go.def.json b/resources/definitions/ultimaker2_go.def.json index 5d4c898ade..5873dfbc90 100644 --- a/resources/definitions/ultimaker2_go.def.json +++ b/resources/definitions/ultimaker2_go.def.json @@ -13,6 +13,7 @@ "platform": "ultimaker2go_platform.obj", "platform_texture": "Ultimaker2Gobackplate.png", "platform_offset": [0, 0, 0], + "first_start_actions": [], "supported_actions":["UpgradeFirmware"] }, diff --git a/resources/definitions/ultimaker2_plus.def.json b/resources/definitions/ultimaker2_plus.def.json index 5b1c7909ba..d8169b9abb 100644 --- a/resources/definitions/ultimaker2_plus.def.json +++ b/resources/definitions/ultimaker2_plus.def.json @@ -16,6 +16,7 @@ "has_materials": true, "has_machine_materials": true, "has_machine_quality": true, + "first_start_actions": [], "supported_actions":["UpgradeFirmware"] }, diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json index afac8f3226..d1f8b19e36 100644 --- a/resources/definitions/ultimaker3.def.json +++ b/resources/definitions/ultimaker3.def.json @@ -70,9 +70,11 @@ "machine_start_gcode": { "default_value": "" }, "machine_end_gcode": { "default_value": "" }, "prime_tower_position_x": { "default_value": 175 }, - "prime_tower_position_y": { "default_value": 179 }, + "prime_tower_position_y": { "default_value": 178 }, "prime_tower_wipe_enabled": { "default_value": false }, + "prime_blob_enable": { "enabled": true }, + "acceleration_enabled": { "value": "True" }, "acceleration_layer_0": { "value": "acceleration_topbottom" }, "acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" }, @@ -103,7 +105,7 @@ "layer_height_0": { "value": "round(machine_nozzle_size / 1.5, 2)" }, "layer_start_x": { "value": "sum(extruderValues('machine_extruder_start_pos_x')) / len(extruderValues('machine_extruder_start_pos_x'))" }, "layer_start_y": { "value": "sum(extruderValues('machine_extruder_start_pos_y')) / len(extruderValues('machine_extruder_start_pos_y'))" }, - "line_width": { "value": "machine_nozzle_size * 0.875" }, + "line_width": { "value": "round(machine_nozzle_size * 0.875, 3)" }, "machine_min_cool_heat_time_window": { "value": "15" }, "default_material_print_temperature": { "value": "200" }, "material_print_temperature_layer_0": { "value": "material_print_temperature + 5" }, @@ -111,7 +113,7 @@ "material_bed_temperature_layer_0": { "maximum_value": "115" }, "material_standby_temperature": { "value": "100" }, "multiple_mesh_overlap": { "value": "0" }, - "prime_tower_enable": { "value": "True" }, + "prime_tower_enable": { "default_value": true }, "raft_airgap": { "value": "0" }, "raft_base_thickness": { "value": "0.3" }, "raft_interface_line_spacing": { "value": "0.5" }, diff --git a/resources/extruders/cartesio_extruder_0.def.json b/resources/extruders/cartesio_extruder_0.def.json index 7dc3aaa8af..9b25b366f7 100644 --- a/resources/extruders/cartesio_extruder_0.def.json +++ b/resources/extruders/cartesio_extruder_0.def.json @@ -19,7 +19,7 @@ "default_value": "\n;start extruder_0\n\nM117 printing\n" }, "machine_extruder_end_code": { - "default_value": "\nM104 T0 S155\nG91\nG1 Z0.5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_0\nM117 temp is {material_print_temp}" + "default_value": "\nM104 T0 S160\nG91\nG1 Z5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_0\nM117 temp is {material_print_temp}" } } } diff --git a/resources/extruders/cartesio_extruder_1.def.json b/resources/extruders/cartesio_extruder_1.def.json index d8c2e00aed..939b1fd7c8 100644 --- a/resources/extruders/cartesio_extruder_1.def.json +++ b/resources/extruders/cartesio_extruder_1.def.json @@ -19,7 +19,7 @@ "default_value": "\n;start extruder_1\n\nM117 printing\n" }, "machine_extruder_end_code": { - "default_value": "\nM104 T1 S155\nG91\nG1 Z0.5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_1\n" + "default_value": "\nM104 T1 S160\nG91\nG1 Z5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_1\n" } } } diff --git a/resources/extruders/cartesio_extruder_2.def.json b/resources/extruders/cartesio_extruder_2.def.json index 062b80581c..ce07502943 100644 --- a/resources/extruders/cartesio_extruder_2.def.json +++ b/resources/extruders/cartesio_extruder_2.def.json @@ -19,7 +19,7 @@ "default_value": "\n;start extruder_2\n\nM117 printing\n" }, "machine_extruder_end_code": { - "default_value": "\nM104 T2 S155\nG91\nG1 Z0.5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_2\n" + "default_value": "\nM104 T2 S160\nG91\nG1 Z5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_2\n" } } } diff --git a/resources/extruders/cartesio_extruder_3.def.json b/resources/extruders/cartesio_extruder_3.def.json index 5582f0f436..dea3467daa 100644 --- a/resources/extruders/cartesio_extruder_3.def.json +++ b/resources/extruders/cartesio_extruder_3.def.json @@ -19,7 +19,7 @@ "default_value": "\n;start extruder_3\n\nM117 printing\n" }, "machine_extruder_end_code": { - "default_value": "\nM104 T3 S155\nG91\nG1 Z0.5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_3\n" + "default_value": "\nM104 T3 S160\nG91\nG1 Z5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_3\n" } } } diff --git a/resources/extruders/custom_extruder_1.def.json b/resources/extruders/custom_extruder_1.def.json new file mode 100644 index 0000000000..859c6a2f22 --- /dev/null +++ b/resources/extruders/custom_extruder_1.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_1", + "version": 2, + "name": "Extruder 1", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "0" + }, + + "overrides": { + "extruder_nr": { + "default_value": 0, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_2.def.json b/resources/extruders/custom_extruder_2.def.json new file mode 100644 index 0000000000..eecda5dfcd --- /dev/null +++ b/resources/extruders/custom_extruder_2.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_2", + "version": 2, + "name": "Extruder 2", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "1" + }, + + "overrides": { + "extruder_nr": { + "default_value": 1, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_3.def.json b/resources/extruders/custom_extruder_3.def.json new file mode 100644 index 0000000000..77909ec05d --- /dev/null +++ b/resources/extruders/custom_extruder_3.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_3", + "version": 2, + "name": "Extruder 3", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "2" + }, + + "overrides": { + "extruder_nr": { + "default_value": 2, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_4.def.json b/resources/extruders/custom_extruder_4.def.json new file mode 100644 index 0000000000..be792c3a8e --- /dev/null +++ b/resources/extruders/custom_extruder_4.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_4", + "version": 2, + "name": "Extruder 4", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "3" + }, + + "overrides": { + "extruder_nr": { + "default_value": 3, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_5.def.json b/resources/extruders/custom_extruder_5.def.json new file mode 100644 index 0000000000..ea64605041 --- /dev/null +++ b/resources/extruders/custom_extruder_5.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_5", + "version": 2, + "name": "Extruder 5", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "4" + }, + + "overrides": { + "extruder_nr": { + "default_value": 4, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_6.def.json b/resources/extruders/custom_extruder_6.def.json new file mode 100644 index 0000000000..fd27fadace --- /dev/null +++ b/resources/extruders/custom_extruder_6.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_6", + "version": 2, + "name": "Extruder 6", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "5" + }, + + "overrides": { + "extruder_nr": { + "default_value": 5, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_7.def.json b/resources/extruders/custom_extruder_7.def.json new file mode 100644 index 0000000000..cf003d1a6b --- /dev/null +++ b/resources/extruders/custom_extruder_7.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_7", + "version": 2, + "name": "Extruder 7", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "6" + }, + + "overrides": { + "extruder_nr": { + "default_value": 6, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_8.def.json b/resources/extruders/custom_extruder_8.def.json new file mode 100644 index 0000000000..f418a55186 --- /dev/null +++ b/resources/extruders/custom_extruder_8.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_8", + "version": 2, + "name": "Extruder 8", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "7" + }, + + "overrides": { + "extruder_nr": { + "default_value": 7, + "maximum_value": "7" + } + } +} diff --git a/resources/i18n/jp/fdmprinter.def.json.po b/resources/i18n/jp/fdmprinter.def.json.po new file mode 100644 index 0000000000..285c4a2e7d --- /dev/null +++ b/resources/i18n/jp/fdmprinter.def.json.po @@ -0,0 +1,4009 @@ +msgid "" +msgstr "" +"Project-Id-Version: Uranium json setting files\n" +"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n" +"POT-Creation-Date: 2017-03-27 17:27+0000\n" +"PO-Revision-Date: 2017-05-12 10:53+0900\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Poedit 2.0.1\n" + +#: fdmprinter.def.json +msgctxt "machine_settings label" +msgid "Machine" +msgstr "Machine" + +#: fdmprinter.def.json +msgctxt "machine_settings description" +msgid "Machine specific settings" +msgstr "マシーン固有設定" + +#: fdmprinter.def.json +msgctxt "machine_name label" +msgid "Machine Type" +msgstr "Machine Type" + +#: fdmprinter.def.json +msgctxt "machine_name description" +msgid "The name of your 3D printer model." +msgstr "3Dプリンタのモデル" + +#: fdmprinter.def.json +msgctxt "machine_show_variants label" +msgid "Show machine variants" +msgstr "Show machine variants" + +#: fdmprinter.def.json +msgctxt "machine_show_variants description" +msgid "Whether to show the different variants of this machine, which are described in separate json files." +msgstr "このマシーンの形式を表示するかどうかは、別のjsonファイルに記述されています。" + +#: fdmprinter.def.json +msgctxt "machine_start_gcode label" +msgid "Start GCode" +msgstr "Start GCode" + +#: fdmprinter.def.json +msgctxt "machine_start_gcode description" +msgid "" +"Gcode commands to be executed at the very start - separated by \n" +"." +msgstr "開始時に実行されるGcodeのコマンドは −で区切られています。" + +#: fdmprinter.def.json +msgctxt "machine_end_gcode label" +msgid "End GCode" +msgstr "End GCode" + +#: fdmprinter.def.json +msgctxt "machine_end_gcode description" +msgid "" +"Gcode commands to be executed at the very end - separated by \n" +"." +msgstr "" +"開始時に実行されるGcodeのコマンドは \n" +"で区切られています。" + +#: fdmprinter.def.json +msgctxt "material_guid label" +msgid "Material GUID" +msgstr "Material GUID" + +#: fdmprinter.def.json +msgctxt "material_guid description" +msgid "GUID of the material. This is set automatically. " +msgstr "マテリアルのGUID。これは自動的に設定されます。" + +#: fdmprinter.def.json +msgctxt "material_bed_temp_wait label" +msgid "Wait for build plate heatup" +msgstr "Wait for build plate heatup" + +#: fdmprinter.def.json +msgctxt "material_bed_temp_wait description" +msgid "Whether to insert a command to wait until the build plate temperature is reached at the start." +msgstr "開始時にビルドプレートが温度に達するまで待つコマンドを挿入するかどうか。" + +#: fdmprinter.def.json +msgctxt "material_print_temp_wait label" +msgid "Wait for nozzle heatup" +msgstr "Wait for nozzle heatup" + +#: fdmprinter.def.json +msgctxt "material_print_temp_wait description" +msgid "Whether to wait until the nozzle temperature is reached at the start." +msgstr "開始時にノズルの温度が達するまで待つかどうか。" + +#: fdmprinter.def.json +msgctxt "material_print_temp_prepend label" +msgid "Include material temperatures" +msgstr "Include material temperatures" + +#: fdmprinter.def.json +msgctxt "material_print_temp_prepend description" +msgid "Whether to include nozzle temperature commands at the start of the gcode. When the start_gcode already contains nozzle temperature commands Cura frontend will automatically disable this setting." +msgstr "gcodeの開始時にノズル温度設定を含めるかどうか。 既に最初のgcodeにノズル温度設定が含まれている場合、Curaフロントエンドは自動的にこの設定を無効にします。" + +#: fdmprinter.def.json +msgctxt "material_bed_temp_prepend label" +msgid "Include build plate temperature" +msgstr "Include build plate temperature" + +#: fdmprinter.def.json +msgctxt "material_bed_temp_prepend description" +msgid "Whether to include build plate temperature commands at the start of the gcode. When the start_gcode already contains build plate temperature commands Cura frontend will automatically disable this setting." +msgstr "gcodeの開始時にビルドプレート温度設定を含めるかどうか。 既に最初のgcodeにビルドプレート温度設定が含まれている場合、Curaフロントエンドは自動的にこの設定を無効にします。" + +#: fdmprinter.def.json +msgctxt "machine_width label" +msgid "Machine width" +msgstr "Machine width" + +#: fdmprinter.def.json +msgctxt "machine_width description" +msgid "The width (X-direction) of the printable area." +msgstr "造形可能領域の幅(X方向)。" + +#: fdmprinter.def.json +msgctxt "machine_depth label" +msgid "Machine depth" +msgstr "Machine depth" + +#: fdmprinter.def.json +msgctxt "machine_depth description" +msgid "The depth (Y-direction) of the printable area." +msgstr "造形可能領域の幅(X方向)。" + +#: fdmprinter.def.json +msgctxt "machine_shape label" +msgid "Build plate shape" +msgstr "Build plate shape" + +#: fdmprinter.def.json +msgctxt "machine_shape description" +msgid "The shape of the build plate without taking unprintable areas into account." +msgstr "造形不能領域を考慮しないビルドプレートの形状。" + +#: fdmprinter.def.json +msgctxt "machine_shape option rectangular" +msgid "Rectangular" +msgstr "Rectangular" + +#: fdmprinter.def.json +msgctxt "machine_shape option elliptic" +msgid "Elliptic" +msgstr "Elliptic" + +#: fdmprinter.def.json +msgctxt "machine_height label" +msgid "Machine height" +msgstr "Machine height" + +#: fdmprinter.def.json +msgctxt "machine_height description" +msgid "The height (Z-direction) of the printable area." +msgstr "造形可能領域の幅(X方向)。" + +#: fdmprinter.def.json +msgctxt "machine_heated_bed label" +msgid "Has heated build plate" +msgstr "Has heated build plate" + +#: fdmprinter.def.json +msgctxt "machine_heated_bed description" +msgid "Whether the machine has a heated build plate present." +msgstr "マシーンに加熱式ビルドプレートが付属しているかどうか。" + +#: fdmprinter.def.json +msgctxt "machine_center_is_zero label" +msgid "Is center origin" +msgstr "Is center origin" + +#: fdmprinter.def.json +msgctxt "machine_center_is_zero description" +msgid "Whether the X/Y coordinates of the zero position of the printer is at the center of the printable area." +msgstr "プリンタのゼロポジションのX / Y座標が印刷可能領域の中心にあるかどうか。" + +#: fdmprinter.def.json +msgctxt "machine_extruder_count label" +msgid "Number of Extruders" +msgstr "Number of Extruders" + +#: fdmprinter.def.json +msgctxt "machine_extruder_count description" +msgid "Number of extruder trains. An extruder train is the combination of a feeder, bowden tube, and nozzle." +msgstr "エクストルーダーの数。エクストルーダーの数は、フィーダー、ボーデンチューブ、およびノズルの組合せである。" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_tip_outer_diameter label" +msgid "Outer nozzle diameter" +msgstr "Outer nozzle diameter" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_tip_outer_diameter description" +msgid "The outer diameter of the tip of the nozzle." +msgstr "ノズルの外径。" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_head_distance label" +msgid "Nozzle length" +msgstr "Nozzle length" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_head_distance description" +msgid "The height difference between the tip of the nozzle and the lowest part of the print head." +msgstr "ノズル先端とプリントヘッドの最下部との高さの差。" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_expansion_angle label" +msgid "Nozzle angle" +msgstr "Nozzle angle" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_expansion_angle description" +msgid "The angle between the horizontal plane and the conical part right above the tip of the nozzle." +msgstr "水平面とノズルの真上の円錐部分との間の角度。" + +#: fdmprinter.def.json +msgctxt "machine_heat_zone_length label" +msgid "Heat zone length" +msgstr "Heat zone length" + +#: fdmprinter.def.json +msgctxt "machine_heat_zone_length description" +msgid "The distance from the tip of the nozzle in which heat from the nozzle is transferred to the filament." +msgstr "ノズルからの熱がフィラメントに伝達される距離。" + +#: fdmprinter.def.json +msgctxt "machine_filament_park_distance label" +msgid "Filament Park Distance" +msgstr "Filament Park Distance" + +#: fdmprinter.def.json +msgctxt "machine_filament_park_distance description" +msgid "The distance from the tip of the nozzle where to park the filament when an extruder is no longer used." +msgstr "エクストルーダーが使用していない時、フィラメントを留めている場所からノズルまでの距離。" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_temp_enabled label" +msgid "Enable Nozzle Temperature Control" +msgstr "Enable Nozzle Temperature Control" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_temp_enabled description" +msgid "Whether to control temperature from Cura. Turn this off to control nozzle temperature from outside of Cura." +msgstr "Curaから温度を制御するかどうか。これをオフにして、Cura外からノズル温度を制御することで無効化。" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_heat_up_speed label" +msgid "Heat up speed" +msgstr "Heat up speed" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_heat_up_speed description" +msgid "The speed (°C/s) by which the nozzle heats up averaged over the window of normal printing temperatures and the standby temperature." +msgstr "ノズルが加熱する速度(℃/ s)は、通常の印刷時温度とスタンバイ時温度にて平均化されています。" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_cool_down_speed label" +msgid "Cool down speed" +msgstr "Cool down speed" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_cool_down_speed description" +msgid "The speed (°C/s) by which the nozzle cools down averaged over the window of normal printing temperatures and the standby temperature." +msgstr "ノズルが冷却される速度(℃/ s)は、通常の印刷温度とスタンバイ温度のウィンドウにわたって平均化されています。" + +#: fdmprinter.def.json +msgctxt "machine_min_cool_heat_time_window label" +msgid "Minimal Time Standby Temperature" +msgstr "Minimal Time Standby Temperature" + +#: fdmprinter.def.json +msgctxt "machine_min_cool_heat_time_window description" +msgid "The minimal time an extruder has to be inactive before the nozzle is cooled. Only when an extruder is not used for longer than this time will it be allowed to cool down to the standby temperature." +msgstr "ノズルが冷却される前にエクストルーダーが静止しなければならない最短時間。この時間より長時間エクストルーダーを使用しない場合にのみ、スタンバイ温度に冷却することができます。" + +#: fdmprinter.def.json +msgctxt "machine_gcode_flavor label" +msgid "Gcode flavour" +msgstr "Gcode flavour" + +#: fdmprinter.def.json +msgctxt "machine_gcode_flavor description" +msgid "The type of gcode to be generated." +msgstr "生成するGコードの種類" + +#: fdmprinter.def.json +msgctxt "machine_gcode_flavor option RepRap (Marlin/Sprinter)" +msgid "RepRap (Marlin/Sprinter)" +msgstr "RepRap (Marlin/Sprinter)" + +#: fdmprinter.def.json +msgctxt "machine_gcode_flavor option RepRap (Volumatric)" +msgid "RepRap (Volumetric)" +msgstr "RepRap (Volumetric)" + +#: fdmprinter.def.json +msgctxt "machine_gcode_flavor option UltiGCode" +msgid "Ultimaker 2" +msgstr "Ultimaker 2" + +#: fdmprinter.def.json +msgctxt "machine_gcode_flavor option Griffin" +msgid "Griffin" +msgstr "Griffin" + +#: fdmprinter.def.json +msgctxt "machine_gcode_flavor option Makerbot" +msgid "Makerbot" +msgstr "Makerbot" + +#: fdmprinter.def.json +msgctxt "machine_gcode_flavor option BFB" +msgid "Bits from Bytes" +msgstr "Bits from Bytes" + +#: fdmprinter.def.json +msgctxt "machine_gcode_flavor option MACH3" +msgid "Mach3" +msgstr "Mach3" + +#: fdmprinter.def.json +msgctxt "machine_gcode_flavor option Repetier" +msgid "Repetier" +msgstr "Repetier" + +#: fdmprinter.def.json +msgctxt "machine_disallowed_areas label" +msgid "Disallowed areas" +msgstr "Disallowed areas" + +#: fdmprinter.def.json +msgctxt "machine_disallowed_areas description" +msgid "A list of polygons with areas the print head is not allowed to enter." +msgstr "プリントヘッドの領域を持つポリゴンのリストは入力できません。" + +#: fdmprinter.def.json +msgctxt "nozzle_disallowed_areas label" +msgid "Nozzle Disallowed Areas" +msgstr "Nozzle Disallowed Areas" + +#: fdmprinter.def.json +msgctxt "nozzle_disallowed_areas description" +msgid "A list of polygons with areas the nozzle is not allowed to enter." +msgstr "ノズルが入ることができない領域を持つポリゴンのリスト。" + +#: fdmprinter.def.json +msgctxt "machine_head_polygon label" +msgid "Machine head polygon" +msgstr "Machine head polygon" + +#: fdmprinter.def.json +msgctxt "machine_head_polygon description" +msgid "A 2D silhouette of the print head (fan caps excluded)." +msgstr "プリントヘッドの2Dシルエット(ファンキャップは除く)。" + +#: fdmprinter.def.json +msgctxt "machine_head_with_fans_polygon label" +msgid "Machine head & Fan polygon" +msgstr "Machine head & Fan polygon" + +#: fdmprinter.def.json +msgctxt "machine_head_with_fans_polygon description" +msgid "A 2D silhouette of the print head (fan caps included)." +msgstr "プリントヘッドの2Dシルエット(ファンキャップが含まれています)。" + +#: fdmprinter.def.json +msgctxt "gantry_height label" +msgid "Gantry height" +msgstr "Gantry height" + +#: fdmprinter.def.json +msgctxt "gantry_height description" +msgid "The height difference between the tip of the nozzle and the gantry system (X and Y axes)." +msgstr "ノズルの先端とガントリーシステムの高さの差(X軸とY軸)。" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_size label" +msgid "Nozzle Diameter" +msgstr "Nozzle Diameter" + +#: fdmprinter.def.json +msgctxt "machine_nozzle_size description" +msgid "The inner diameter of the nozzle. Change this setting when using a non-standard nozzle size." +msgstr "ノズルの内径。標準以外のノズルを使用する場合は、この設定を変更してください。" + +#: fdmprinter.def.json +msgctxt "machine_use_extruder_offset_to_offset_coords label" +msgid "Offset With Extruder" +msgstr "Offset With Extruder" + +#: fdmprinter.def.json +msgctxt "machine_use_extruder_offset_to_offset_coords description" +msgid "Apply the extruder offset to the coordinate system." +msgstr "エクストルーダーのオフセットを座標システムに適用します。" + +#: fdmprinter.def.json +msgctxt "extruder_prime_pos_z label" +msgid "Extruder Prime Z Position" +msgstr "Extruder Prime Z Position" + +#: fdmprinter.def.json +msgctxt "extruder_prime_pos_z description" +msgid "The Z coordinate of the position where the nozzle primes at the start of printing." +msgstr "印刷開始時にノズルがポジションを確認するZ座標。" + +#: fdmprinter.def.json +msgctxt "extruder_prime_pos_abs label" +msgid "Absolute Extruder Prime Position" +msgstr "Absolute Extruder Prime Position" + +#: fdmprinter.def.json +msgctxt "extruder_prime_pos_abs description" +msgid "Make the extruder prime position absolute rather than relative to the last-known location of the head." +msgstr "最後のヘッドの既知位置からではなく、エクストルーダー現在位置を絶対位置にします。" + +#: fdmprinter.def.json +msgctxt "machine_max_feedrate_x label" +msgid "Maximum Speed X" +msgstr "Maximum Speed X" + +#: fdmprinter.def.json +msgctxt "machine_max_feedrate_x description" +msgid "The maximum speed for the motor of the X-direction." +msgstr "X方向のモーターの最大速度。" + +#: fdmprinter.def.json +msgctxt "machine_max_feedrate_y label" +msgid "Maximum Speed Y" +msgstr "Maximum Speed Y" + +#: fdmprinter.def.json +msgctxt "machine_max_feedrate_y description" +msgid "The maximum speed for the motor of the Y-direction." +msgstr "Y方向のモーターの最大速度。" + +#: fdmprinter.def.json +msgctxt "machine_max_feedrate_z label" +msgid "Maximum Speed Z" +msgstr "Maximum Speed Z" + +#: fdmprinter.def.json +msgctxt "machine_max_feedrate_z description" +msgid "The maximum speed for the motor of the Z-direction." +msgstr "Z方向のモーターの最大速度。" + +#: fdmprinter.def.json +msgctxt "machine_max_feedrate_e label" +msgid "Maximum Feedrate" +msgstr "Maximum Feedrate" + +#: fdmprinter.def.json +msgctxt "machine_max_feedrate_e description" +msgid "The maximum speed of the filament." +msgstr "フィラメントの最大速度。" + +#: fdmprinter.def.json +msgctxt "machine_max_acceleration_x label" +msgid "Maximum Acceleration X" +msgstr "Maximum Acceleration X" + +#: fdmprinter.def.json +msgctxt "machine_max_acceleration_x description" +msgid "Maximum acceleration for the motor of the X-direction" +msgstr "X方向のモーターの最大速度。" + +#: fdmprinter.def.json +msgctxt "machine_max_acceleration_y label" +msgid "Maximum Acceleration Y" +msgstr "Maximum Acceleration Y" + +#: fdmprinter.def.json +msgctxt "machine_max_acceleration_y description" +msgid "Maximum acceleration for the motor of the Y-direction." +msgstr "Y方向のモーターの最大加速度。" + +#: fdmprinter.def.json +msgctxt "machine_max_acceleration_z label" +msgid "Maximum Acceleration Z" +msgstr "Maximum Acceleration Z" + +#: fdmprinter.def.json +msgctxt "machine_max_acceleration_z description" +msgid "Maximum acceleration for the motor of the Z-direction." +msgstr "Z方向のモーターの最大加速度。" + +#: fdmprinter.def.json +msgctxt "machine_max_acceleration_e label" +msgid "Maximum Filament Acceleration" +msgstr "Maximum Filament Acceleration" + +#: fdmprinter.def.json +msgctxt "machine_max_acceleration_e description" +msgid "Maximum acceleration for the motor of the filament." +msgstr "フィラメントのモーターの最大加速度。" + +#: fdmprinter.def.json +msgctxt "machine_acceleration label" +msgid "Default Acceleration" +msgstr "Default Acceleration" + +#: fdmprinter.def.json +msgctxt "machine_acceleration description" +msgid "The default acceleration of print head movement." +msgstr "プリントヘッド移動のデフォルトの加速度。" + +#: fdmprinter.def.json +msgctxt "machine_max_jerk_xy label" +msgid "Default X-Y Jerk" +msgstr "Default X-Y Jerk" + +#: fdmprinter.def.json +msgctxt "machine_max_jerk_xy description" +msgid "Default jerk for movement in the horizontal plane." +msgstr "水平面内での移動のデフォルトジャーク。" + +#: fdmprinter.def.json +msgctxt "machine_max_jerk_z label" +msgid "Default Z Jerk" +msgstr "Default Z Jerk" + +#: fdmprinter.def.json +msgctxt "machine_max_jerk_z description" +msgid "Default jerk for the motor of the Z-direction." +msgstr "Z方向のモーターのデフォルトジャーク。" + +#: fdmprinter.def.json +msgctxt "machine_max_jerk_e label" +msgid "Default Filament Jerk" +msgstr "Default Filament Jerk" + +#: fdmprinter.def.json +msgctxt "machine_max_jerk_e description" +msgid "Default jerk for the motor of the filament." +msgstr "フィラメントのモーターのデフォルトジャーク。" + +#: fdmprinter.def.json +msgctxt "machine_minimum_feedrate label" +msgid "Minimum Feedrate" +msgstr "Minimum Feedrate" + +#: fdmprinter.def.json +msgctxt "machine_minimum_feedrate description" +msgid "The minimal movement speed of the print head." +msgstr "プリントヘッドの最小移動速度。" + +#: fdmprinter.def.json +msgctxt "resolution label" +msgid "Quality" +msgstr "Quality" + +#: fdmprinter.def.json +msgctxt "resolution description" +msgid "All settings that influence the resolution of the print. These settings have a large impact on the quality (and print time)" +msgstr "プリントの解像度に影響を与えるすべての設定。これらの設定は、品質(および印刷時間)に大きな影響を与えます。" + +#: fdmprinter.def.json +msgctxt "layer_height label" +msgid "Layer Height" +msgstr "Layer Height" + +#: fdmprinter.def.json +msgctxt "layer_height description" +msgid "The height of each layer in mm. Higher values produce faster prints in lower resolution, lower values produce slower prints in higher resolution." +msgstr "各レイヤーの高さ(mm)。値を大きくすると早く印刷しますが荒くなり、小さくすると印刷が遅くなりますが造形が綺麗になります。" + +#: fdmprinter.def.json +msgctxt "layer_height_0 label" +msgid "Initial Layer Height" +msgstr "Initial Layer Height" + +#: fdmprinter.def.json +msgctxt "layer_height_0 description" +msgid "The height of the initial layer in mm. A thicker initial layer makes adhesion to the build plate easier." +msgstr "初期レイヤーの高さ(mm)。厚い初期層はビルドプレートへの接着を容易にする。" + +#: fdmprinter.def.json +msgctxt "line_width label" +msgid "Line Width" +msgstr "Line Width" + +#: fdmprinter.def.json +msgctxt "line_width description" +msgid "Width of a single line. Generally, the width of each line should correspond to the width of the nozzle. However, slightly reducing this value could produce better prints." +msgstr "1ラインの幅。一般に、各ラインの幅は、ノズルの幅に対応する必要があります。ただし、この値を少し小さくすると、より良い造形が得られます。" + +#: fdmprinter.def.json +msgctxt "wall_line_width label" +msgid "Wall Line Width" +msgstr "Wall Line Width" + +#: fdmprinter.def.json +msgctxt "wall_line_width description" +msgid "Width of a single wall line." +msgstr "ウォールラインの幅。" + +#: fdmprinter.def.json +msgctxt "wall_line_width_0 label" +msgid "Outer Wall Line Width" +msgstr "Outer Wall Line Width" + +#: fdmprinter.def.json +msgctxt "wall_line_width_0 description" +msgid "Width of the outermost wall line. By lowering this value, higher levels of detail can be printed." +msgstr "最も外側のウォールラインの幅。この値を下げると、より詳細な印刷できます。" + +#: fdmprinter.def.json +msgctxt "wall_line_width_x label" +msgid "Inner Wall(s) Line Width" +msgstr "Inner Wall(s) Line Width" + +#: fdmprinter.def.json +msgctxt "wall_line_width_x description" +msgid "Width of a single wall line for all wall lines except the outermost one." +msgstr "一番外側のウォールラインを除くすべてのウォールラインのラインの幅。" + +#: fdmprinter.def.json +msgctxt "skin_line_width label" +msgid "Top/Bottom Line Width" +msgstr "Top/Bottom Line Width" + +#: fdmprinter.def.json +msgctxt "skin_line_width description" +msgid "Width of a single top/bottom line." +msgstr "上辺/底辺線のライン幅。" + +#: fdmprinter.def.json +msgctxt "infill_line_width label" +msgid "Infill Line Width" +msgstr "Infill Line Width" + +#: fdmprinter.def.json +msgctxt "infill_line_width description" +msgid "Width of a single infill line." +msgstr "インフィル線の幅。" + +#: fdmprinter.def.json +msgctxt "skirt_brim_line_width label" +msgid "Skirt/Brim Line Width" +msgstr "Skirt/Brim Line Width" + +#: fdmprinter.def.json +msgctxt "skirt_brim_line_width description" +msgid "Width of a single skirt or brim line." +msgstr "単一のスカートまたはブリムラインの幅。" + +#: fdmprinter.def.json +msgctxt "support_line_width label" +msgid "Support Line Width" +msgstr "Support Line Width" + +#: fdmprinter.def.json +msgctxt "support_line_width description" +msgid "Width of a single support structure line." +msgstr "単一のサポート構造のライン幅。" + +#: fdmprinter.def.json +msgctxt "support_interface_line_width label" +msgid "Support Interface Line Width" +msgstr "Support Interface Line Width" + +#: fdmprinter.def.json +msgctxt "support_interface_line_width description" +msgid "Width of a single support interface line." +msgstr "単一のサポートインタフェースラインの幅。" + +#: fdmprinter.def.json +msgctxt "prime_tower_line_width label" +msgid "Prime Tower Line Width" +msgstr "Prime Tower Line Width" + +#: fdmprinter.def.json +msgctxt "prime_tower_line_width description" +msgid "Width of a single prime tower line." +msgstr "単一のプライムタワーラインの幅。" + +#: fdmprinter.def.json +msgctxt "shell label" +msgid "Shell" +msgstr "Shell" + +#: fdmprinter.def.json +msgctxt "shell description" +msgid "Shell" +msgstr "外郭" + +#: fdmprinter.def.json +msgctxt "wall_thickness label" +msgid "Wall Thickness" +msgstr "Wall Thickness" + +#: fdmprinter.def.json +msgctxt "wall_thickness description" +msgid "The thickness of the outside walls in the horizontal direction. This value divided by the wall line width defines the number of walls." +msgstr "水平方向の外壁厚さ この値をウォールライン幅で割ることで、ウォール数を定義します。" + +#: fdmprinter.def.json +msgctxt "wall_line_count label" +msgid "Wall Line Count" +msgstr "Wall Line Count" + +#: fdmprinter.def.json +msgctxt "wall_line_count description" +msgid "The number of walls. When calculated by the wall thickness, this value is rounded to a whole number." +msgstr "ウォールの数。厚さから計算された場合、この値は整数になります。" + +#: fdmprinter.def.json +msgctxt "wall_0_wipe_dist label" +msgid "Outer Wall Wipe Distance" +msgstr "Outer Wall Wipe Distance" + +#: fdmprinter.def.json +msgctxt "wall_0_wipe_dist description" +msgid "Distance of a travel move inserted after the outer wall, to hide the Z seam better." +msgstr "外壁の後に挿入された移動の距離はZシームをよりよく隠します。" + +#: fdmprinter.def.json +msgctxt "top_bottom_thickness label" +msgid "Top/Bottom Thickness" +msgstr "Top/Bottom Thickness" + +#: fdmprinter.def.json +msgctxt "top_bottom_thickness description" +msgid "The thickness of the top/bottom layers in the print. This value divided by the layer height defines the number of top/bottom layers." +msgstr "プリント時の最上面、最底面の厚み。これを積層ピッチで割った値で最上面、最低面のレイヤーの数を定義します。" + +#: fdmprinter.def.json +msgctxt "top_thickness label" +msgid "Top Thickness" +msgstr "Top Thickness" + +#: fdmprinter.def.json +msgctxt "top_thickness description" +msgid "The thickness of the top layers in the print. This value divided by the layer height defines the number of top layers." +msgstr "プリント時の最上面の厚み。これを積層ピッチで割った値で最上面のレイヤーの数を定義します。" + +#: fdmprinter.def.json +msgctxt "top_layers label" +msgid "Top Layers" +msgstr "Top Layers" + +#: fdmprinter.def.json +msgctxt "top_layers description" +msgid "The number of top layers. When calculated by the top thickness, this value is rounded to a whole number." +msgstr "最上面のレイヤー数。トップの厚さを計算する場合、この値は整数になります。" + +#: fdmprinter.def.json +msgctxt "bottom_thickness label" +msgid "Bottom Thickness" +msgstr "Bottom Thickness" + +#: fdmprinter.def.json +msgctxt "bottom_thickness description" +msgid "The thickness of the bottom layers in the print. This value divided by the layer height defines the number of bottom layers." +msgstr "プリント時の最底面の厚み。これを積層ピッチで割った値で最低面のレイヤーの数を定義します。" + +#: fdmprinter.def.json +msgctxt "bottom_layers label" +msgid "Bottom Layers" +msgstr "Bottom Layers" + +#: fdmprinter.def.json +msgctxt "bottom_layers description" +msgid "The number of bottom layers. When calculated by the bottom thickness, this value is rounded to a whole number." +msgstr "最底面のレイヤー数。下の厚さで計算すると、この値は整数に変換されます。" + +#: fdmprinter.def.json +msgctxt "top_bottom_pattern label" +msgid "Top/Bottom Pattern" +msgstr "Top/Bottom Pattern" + +#: fdmprinter.def.json +msgctxt "top_bottom_pattern description" +msgid "The pattern of the top/bottom layers." +msgstr "上層/底層のパターン。" + +#: fdmprinter.def.json +msgctxt "top_bottom_pattern option lines" +msgid "Lines" +msgstr "Lines" + +#: fdmprinter.def.json +msgctxt "top_bottom_pattern option concentric" +msgid "Concentric" +msgstr "Concentric" + +#: fdmprinter.def.json +msgctxt "top_bottom_pattern option zigzag" +msgid "Zig Zag" +msgstr "Zig Zag" + +#: fdmprinter.def.json +msgctxt "top_bottom_pattern_0 label" +msgid "Bottom Pattern Initial Layer" +msgstr "Bottom Pattern Initial Layer" + +#: fdmprinter.def.json +msgctxt "top_bottom_pattern_0 description" +msgid "The pattern on the bottom of the print on the first layer." +msgstr "第1層のプリントの底部のパターン。" + +#: fdmprinter.def.json +msgctxt "top_bottom_pattern_0 option lines" +msgid "Lines" +msgstr "Lines" + +#: fdmprinter.def.json +msgctxt "top_bottom_pattern_0 option concentric" +msgid "Concentric" +msgstr "Concentric" + +#: fdmprinter.def.json +msgctxt "top_bottom_pattern_0 option zigzag" +msgid "Zig Zag" +msgstr "Zig Zag" + +#: fdmprinter.def.json +msgctxt "skin_angles label" +msgid "Top/Bottom Line Directions" +msgstr "Top/Bottom Line Directions" + +#: fdmprinter.def.json +msgctxt "skin_angles description" +msgid "A list of integer line directions to use when the top/bottom layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)." +msgstr "上/下のレイヤーが線またはジグザグパターンを使う際の整数線の方向のリスト。リストの要素は、層が進行するにつれて順番に使用され、リストの終わりに達すると、最初から再び開始されます。リスト項目はコンマで区切られ、リスト全体は大括弧で囲まれています。デフォルトは、従来のデフォルト角度(45度と135度)を使用する空のリストです。" + +#: fdmprinter.def.json +msgctxt "wall_0_inset label" +msgid "Outer Wall Inset" +msgstr "Outer Wall Inset" + +#: fdmprinter.def.json +msgctxt "wall_0_inset description" +msgid "Inset applied to the path of the outer wall. If the outer wall is smaller than the nozzle, and printed after the inner walls, use this offset to get the hole in the nozzle to overlap with the inner walls instead of the outside of the model." +msgstr "外壁の経路にはめ込む。外壁がノズルよりも小さく、内壁の後に造形されている場合は、オフセットを使用して、ノズルの穴をモデルの外側ではなく内壁と重なるようにします。" + +#: fdmprinter.def.json +msgctxt "outer_inset_first label" +msgid "Outer Before Inner Walls" +msgstr "Outer Before Inner Walls" + +#: fdmprinter.def.json +msgctxt "outer_inset_first description" +msgid "Prints walls in order of outside to inside when enabled. This can help improve dimensional accuracy in X and Y when using a high viscosity plastic like ABS; however it can decrease outer surface print quality, especially on overhangs." +msgstr "有効にすると、壁は外側から内側に順番に印刷します。これは、ABSのような高粘度のプラスチックを使用する際、XとYの寸法精度を改善するのに役立ちます。しかし、特にオーバーハング時に、外面の印刷品質を低下させる可能性があります。" + +#: fdmprinter.def.json +msgctxt "alternate_extra_perimeter label" +msgid "Alternate Extra Wall" +msgstr "Alternate Extra Wall" + +#: fdmprinter.def.json +msgctxt "alternate_extra_perimeter description" +msgid "Prints an extra wall at every other layer. This way infill gets caught between these extra walls, resulting in stronger prints." +msgstr "すべてのレイヤーごとに予備の壁を印刷します。このようにして、インフィルは余分な壁の間に挟まれ、より強い印刷物が得られる。" + +#: fdmprinter.def.json +msgctxt "travel_compensate_overlapping_walls_enabled label" +msgid "Compensate Wall Overlaps" +msgstr "Compensate Wall Overlaps" + +#: fdmprinter.def.json +msgctxt "travel_compensate_overlapping_walls_enabled description" +msgid "Compensate the flow for parts of a wall being printed where there is already a wall in place." +msgstr "すでに壁が設置されている部品の壁の流れを補正します。" + +#: fdmprinter.def.json +msgctxt "travel_compensate_overlapping_walls_0_enabled label" +msgid "Compensate Outer Wall Overlaps" +msgstr "Compensate Outer Wall Overlaps" + +#: fdmprinter.def.json +msgctxt "travel_compensate_overlapping_walls_0_enabled description" +msgid "Compensate the flow for parts of an outer wall being printed where there is already a wall in place." +msgstr "すでに壁が設置されている場所にプリントされている外壁の部分の流れを補う。" + +#: fdmprinter.def.json +msgctxt "travel_compensate_overlapping_walls_x_enabled label" +msgid "Compensate Inner Wall Overlaps" +msgstr "Compensate Inner Wall Overlaps" + +#: fdmprinter.def.json +msgctxt "travel_compensate_overlapping_walls_x_enabled description" +msgid "Compensate the flow for parts of an inner wall being printed where there is already a wall in place." +msgstr "すでに壁が設置されている場所にプリントされている内壁の部分の流れを補正します。" + +#: fdmprinter.def.json +msgctxt "fill_perimeter_gaps label" +msgid "Fill Gaps Between Walls" +msgstr "Fill Gaps Between Walls" + +#: fdmprinter.def.json +msgctxt "fill_perimeter_gaps description" +msgid "Fills the gaps between walls where no walls fit." +msgstr "壁が入らない壁の隙間を埋める。" + +#: fdmprinter.def.json +msgctxt "fill_perimeter_gaps option nowhere" +msgid "Nowhere" +msgstr "Nowhere" + +#: fdmprinter.def.json +msgctxt "fill_perimeter_gaps option everywhere" +msgid "Everywhere" +msgstr "Everywhere" + +#: fdmprinter.def.json +msgctxt "xy_offset label" +msgid "Horizontal Expansion" +msgstr "Horizontal Expansion" + +#: fdmprinter.def.json +msgctxt "xy_offset description" +msgid "Amount of offset applied to all polygons in each layer. Positive values can compensate for too big holes; negative values can compensate for too small holes." +msgstr "各レイヤーのすべてのポリゴンに適用されるオフセットの量。正の値は大きすぎる穴を補うことができます。負の値は小さすぎる穴を補うことができます。" + +#: fdmprinter.def.json +msgctxt "z_seam_type label" +msgid "Z Seam Alignment" +msgstr "Z Seam Alignment" + +#: fdmprinter.def.json +msgctxt "z_seam_type description" +msgid "Starting point of each path in a layer. When paths in consecutive layers start at the same point a vertical seam may show on the print. When aligning these near a user specified location, the seam is easiest to remove. When placed randomly the inaccuracies at the paths' start will be less noticeable. When taking the shortest path the print will be quicker." +msgstr "レイヤーの経路始点。連続するレイヤー経路が同じポイントで開始すると、縦のシームが印刷に表示されることがあります。ユーザーが指定した場所の近くでこれらを整列させる場合、継ぎ目は最も簡単に取り除くことができます。無作為に配置すると、経路開始時の粗さが目立たなくなります。最短経路をとると、印刷が速くなります。" + +#: fdmprinter.def.json +msgctxt "z_seam_type option back" +msgid "User Specified" +msgstr "User Specified" + +#: fdmprinter.def.json +msgctxt "z_seam_type option shortest" +msgid "Shortest" +msgstr "Shortest" + +#: fdmprinter.def.json +msgctxt "z_seam_type option random" +msgid "Random" +msgstr "Random" + +#: fdmprinter.def.json +msgctxt "z_seam_x label" +msgid "Z Seam X" +msgstr "Z Seam X" + +#: fdmprinter.def.json +msgctxt "z_seam_x description" +msgid "The X coordinate of the position near where to start printing each part in a layer." +msgstr "" +"レイヤー内の各印刷を開始するX座\n" +"標の位置。" + +#: fdmprinter.def.json +msgctxt "z_seam_y label" +msgid "Z Seam Y" +msgstr "Z Seam Y" + +#: fdmprinter.def.json +msgctxt "z_seam_y description" +msgid "The Y coordinate of the position near where to start printing each part in a layer." +msgstr "The Y coordinate of the position near where to start printing each part in a layer." + +#: fdmprinter.def.json +msgctxt "skin_no_small_gaps_heuristic label" +msgid "Ignore Small Z Gaps" +msgstr "Ignore Small Z Gaps" + +#: fdmprinter.def.json +msgctxt "skin_no_small_gaps_heuristic description" +msgid "When the model has small vertical gaps, about 5% extra computation time can be spent on generating top and bottom skin in these narrow spaces. In such case, disable the setting." +msgstr "モデルに垂直方向のギャップが小さくある場合、これらの狭いスペースにおいて上部および下部スキンを生成するために、約5%の計算時間が追加されます。そのような場合は、設定を無効にしてください。" + +#: fdmprinter.def.json +msgctxt "infill label" +msgid "Infill" +msgstr "Infill" + +#: fdmprinter.def.json +msgctxt "infill description" +msgid "Infill" +msgstr "インフィル" + +#: fdmprinter.def.json +msgctxt "infill_sparse_density label" +msgid "Infill Density" +msgstr "Infill Density" + +#: fdmprinter.def.json +msgctxt "infill_sparse_density description" +msgid "Adjusts the density of infill of the print." +msgstr "プリントのインフィルの密度を調整します。" + +#: fdmprinter.def.json +msgctxt "infill_line_distance label" +msgid "Infill Line Distance" +msgstr "Infill Line Distance" + +#: fdmprinter.def.json +msgctxt "infill_line_distance description" +msgid "Distance between the printed infill lines. This setting is calculated by the infill density and the infill line width." +msgstr "造形されたインフィルラインの距離。この設定は、インフィル密度およびライン幅によって計算される。" + +#: fdmprinter.def.json +msgctxt "infill_pattern label" +msgid "Infill Pattern" +msgstr "Infill Pattern" + +#: fdmprinter.def.json +msgctxt "infill_pattern description" +msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, cubic, tetrahedral and concentric patterns are fully printed every layer. Cubic and tetrahedral infill change with every layer to provide a more equal distribution of strength over each direction." +msgstr "印刷物のインフィルのパターン。ラインとジグザグのインフィルは交互のレイヤー方向をずらし、材料費を削減します。グリッド、三角形、キュービック、四面体、同心円のパターンは、各レイヤーに完全に印刷されます。立方体および四面体のインフィルは各層ごとに変化し、各方向に沿ってより均等な強度分布を提供する。" + +#: fdmprinter.def.json +msgctxt "infill_pattern option grid" +msgid "Grid" +msgstr "Grid" + +#: fdmprinter.def.json +msgctxt "infill_pattern option lines" +msgid "Lines" +msgstr "Lines" + +#: fdmprinter.def.json +msgctxt "infill_pattern option triangles" +msgid "Triangles" +msgstr "Triangles" + +#: fdmprinter.def.json +msgctxt "infill_pattern option cubic" +msgid "Cubic" +msgstr "Cubic" + +#: fdmprinter.def.json +msgctxt "infill_pattern option cubicsubdiv" +msgid "Cubic Subdivision" +msgstr "Cubic Subdivision" + +#: fdmprinter.def.json +msgctxt "infill_pattern option tetrahedral" +msgid "Tetrahedral" +msgstr "Tetrahedral" + +#: fdmprinter.def.json +msgctxt "infill_pattern option concentric" +msgid "Concentric" +msgstr "Concentric" + +#: fdmprinter.def.json +msgctxt "infill_pattern option concentric_3d" +msgid "Concentric 3D" +msgstr "Concentric 3D" + +#: fdmprinter.def.json +msgctxt "infill_pattern option zigzag" +msgid "Zig Zag" +msgstr "Zig Zag" + +#: fdmprinter.def.json +msgctxt "infill_angles label" +msgid "Infill Line Directions" +msgstr "Infill Line Directions" + +#: fdmprinter.def.json +msgctxt "infill_angles description" +msgid "A list of integer line directions to use. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees for the lines and zig zag patterns and 45 degrees for all other patterns)." +msgstr "使用する整数線の方向のリスト。リストの要素は、レイヤの層に合わせて順番に使用され、リストの末尾に達すると、最初から再び開始されます。リスト項目はコンマで区切られ、リスト全体は大括弧で囲まれています。デフォルトは空のリストです。これは、従来のデフォルト角度(線とジグザグのパターンでは45と135度、他のすべてのパターンでは45度)を使用することを意味します。" + +#: fdmprinter.def.json +msgctxt "sub_div_rad_mult label" +msgid "Cubic Subdivision Radius" +msgstr "Cubic Subdivision Radius" + +#: fdmprinter.def.json +msgctxt "sub_div_rad_mult description" +msgid "A multiplier on the radius from the center of each cube to check for the boundary of the model, as to decide whether this cube should be subdivided. Larger values lead to more subdivisions, i.e. more small cubes." +msgstr "各立方体の中心からの半径上の乗数で、モデルの境界をチェックし、この立方体を細分するかどうかを決定します。値を大きくすると細分化が増えます。つまり、より小さなキューブになります。" + +#: fdmprinter.def.json +msgctxt "sub_div_rad_add label" +msgid "Cubic Subdivision Shell" +msgstr "Cubic Subdivision Shell" + +#: fdmprinter.def.json +msgctxt "sub_div_rad_add description" +msgid "An addition to the radius from the center of each cube to check for the boundary of the model, as to decide whether this cube should be subdivided. Larger values lead to a thicker shell of small cubes near the boundary of the model." +msgstr "この立方体を細分するかどうかを決定するために、各立方体の中心から半径に加えてモデルの境界を調べます。値を大きくすると、モデルの境界付近に小さな立方体のより厚いシェルができます。" + +#: fdmprinter.def.json +msgctxt "infill_overlap label" +msgid "Infill Overlap Percentage" +msgstr "Infill Overlap Percentage" + +#: fdmprinter.def.json +msgctxt "infill_overlap description" +msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill." +msgstr "インフィルと壁が交差する量、わずかな交差によって壁がインフィルにしっかりつながります。" + +#: fdmprinter.def.json +msgctxt "infill_overlap_mm label" +msgid "Infill Overlap" +msgstr "Infill Overlap" + +#: fdmprinter.def.json +msgctxt "infill_overlap_mm description" +msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill." +msgstr "インフィルと壁が交差する量、わずかな交差によって壁がインフィルにしっかりつながります。" + +#: fdmprinter.def.json +msgctxt "skin_overlap label" +msgid "Skin Overlap Percentage" +msgstr "Skin Overlap Percentage" + +#: fdmprinter.def.json +msgctxt "skin_overlap description" +msgid "The amount of overlap between the skin and the walls. A slight overlap allows the walls to connect firmly to the skin." +msgstr "スキンと壁の間のオーバーラップ量 わずかなオーバーラップによって壁がスキンにしっかりつながります。" + +#: fdmprinter.def.json +msgctxt "skin_overlap_mm label" +msgid "Skin Overlap" +msgstr "Skin Overlap" + +#: fdmprinter.def.json +msgctxt "skin_overlap_mm description" +msgid "The amount of overlap between the skin and the walls. A slight overlap allows the walls to connect firmly to the skin." +msgstr "スキンと壁の間の交差した量 わずかなオーバーラップによって壁がスキンにしっかりつながります。" + +#: fdmprinter.def.json +msgctxt "infill_wipe_dist label" +msgid "Infill Wipe Distance" +msgstr "Infill Wipe Distance" + +#: fdmprinter.def.json +msgctxt "infill_wipe_dist description" +msgid "Distance of a travel move inserted after every infill line, to make the infill stick to the walls better. This option is similar to infill overlap, but without extrusion and only on one end of the infill line." +msgstr "インフィルラインごとに挿入された移動距離は壁のインフィルへの接着をより良くします。このオプションは、インフィルオーバーラップに似ていますが、押出なく、インフィルラインの片側にのみあります。" + +#: fdmprinter.def.json +msgctxt "infill_sparse_thickness label" +msgid "Infill Layer Thickness" +msgstr "Infill Layer Thickness" + +#: fdmprinter.def.json +msgctxt "infill_sparse_thickness description" +msgid "The thickness per layer of infill material. This value should always be a multiple of the layer height and is otherwise rounded." +msgstr "インフィルマテリアルの層ごとの厚さ。この値は常にレイヤーの高さの倍数でなければなりません。" + +#: fdmprinter.def.json +msgctxt "gradual_infill_steps label" +msgid "Gradual Infill Steps" +msgstr "Gradual Infill Steps" + +#: fdmprinter.def.json +msgctxt "gradual_infill_steps description" +msgid "Number of times to reduce the infill density by half when getting further below top surfaces. Areas which are closer to top surfaces get a higher density, up to the Infill Density." +msgstr "天井面の表面に近づく際にインフィル密度が半減する回数。天井面に近い領域ほど高い密度となり、インフィル密度まで達します。" + +#: fdmprinter.def.json +msgctxt "gradual_infill_step_height label" +msgid "Gradual Infill Step Height" +msgstr "Gradual Infill Step Height" + +#: fdmprinter.def.json +msgctxt "gradual_infill_step_height description" +msgid "The height of infill of a given density before switching to half the density." +msgstr "密度が半分に切り替える前の所定のインフィルの高さ。" + +#: fdmprinter.def.json +msgctxt "infill_before_walls label" +msgid "Infill Before Walls" +msgstr "Infill Before Walls" + +#: fdmprinter.def.json +msgctxt "infill_before_walls description" +msgid "Print the infill before printing the walls. Printing the walls first may lead to more accurate walls, but overhangs print worse. Printing the infill first leads to sturdier walls, but the infill pattern might sometimes show through the surface." +msgstr "" +"壁より前にインフィルをプリントします はじめに壁をプリントするとより精密な壁になりますが、オーバーハングのプリントは悪化します\n" +"はじめにインフィルをプリントすると丈夫な壁になりますが、インフィルの模様が時折表面から透けて表れます" + +#: fdmprinter.def.json +msgctxt "min_infill_area label" +msgid "Minimum Infill Area" +msgstr "Minimum Infill Area" + +#: fdmprinter.def.json +msgctxt "min_infill_area description" +msgid "Don't generate areas of infill smaller than this (use skin instead)." +msgstr "これより小さいインフィルの領域を生成しないでください (代わりにスキンを使用してください)。" + +#: fdmprinter.def.json +msgctxt "expand_skins_into_infill label" +msgid "Expand Skins Into Infill" +msgstr "Expand Skins Into Infill" + +#: fdmprinter.def.json +msgctxt "expand_skins_into_infill description" +msgid "Expand skin areas of top and/or bottom skin of flat surfaces. By default, skins stop under the wall lines that surround infill but this can lead to holes appearing when the infill density is low. This setting extends the skins beyond the wall lines so that the infill on the next layer rests on skin." +msgstr "平らな面の上部または底部のスキン部の及びその領域を展開します。既定では、スキンインフィルの周りの壁の線で停止しますが、これはインフィル密度が低いときに現れる穴につながることがあります。この設定は、次の層の面材が皮膚にかかっているので、壁の線を超えてスキンを拡張します。" + +#: fdmprinter.def.json +msgctxt "expand_upper_skins label" +msgid "Expand Upper Skins" +msgstr "Expand Upper Skins" + +#: fdmprinter.def.json +msgctxt "expand_upper_skins description" +msgid "Expand upper skin areas (areas with air above) so that they support infill above." +msgstr "上部のインフィルをサポートするので、スキン面 (上記の空気を含んだ領域) を展開します。" + +#: fdmprinter.def.json +msgctxt "expand_lower_skins label" +msgid "Expand Lower Skins" +msgstr "Expand Lower Skins" + +#: fdmprinter.def.json +msgctxt "expand_lower_skins description" +msgid "Expand lower skin areas (areas with air below) so that they are anchored by the infill layers above and below." +msgstr "彼らは上と下の面材のレイヤーによって固定されますので、低い肌の部分 (空気を含んだ領域) を展開します。" + +#: fdmprinter.def.json +msgctxt "expand_skins_expand_distance label" +msgid "Skin Expand Distance" +msgstr "Skin Expand Distance" + +#: fdmprinter.def.json +msgctxt "expand_skins_expand_distance description" +msgid "The distance the skins are expanded into the infill. The default distance is enough to bridge the gap between the infill lines and will stop holes appearing in the skin where it meets the wall when the infill density is low. A smaller distance will often be sufficient." +msgstr "スキンがインフィルに展開される距離。デフォルトの距離は、インフィルの密度が低いときにスキンに現れる穴とインフィルの行間とギャップを埋めるのにに十分です。大抵の場合、距離は小さくても問題ありません。" + +#: fdmprinter.def.json +msgctxt "max_skin_angle_for_expansion label" +msgid "Maximum Skin Angle for Expansion" +msgstr "Maximum Skin Angle for Expansion" + +#: fdmprinter.def.json +msgctxt "max_skin_angle_for_expansion description" +msgid "Top and/or bottom surfaces of your object with an angle larger than this setting, won't have their top/bottom skin expanded. This avoids expanding the narrow skin areas that are created when the model surface has a near vertical slope. An angle of 0° is horizontal, while an angle of 90° is vertical." +msgstr "この設定より大きい角を持つオブジェクトの上部または底部の表面、その表面のスキンはを拡大しません。これは、モデルのサーフェスに近い垂直斜面がある場合に作成される狭いスキン領域の拡大を回避します。0 ° の角度は水平方向、90 ° の角度が垂直方向です。" + +#: fdmprinter.def.json +msgctxt "min_skin_width_for_expansion label" +msgid "Minimum Skin Width for Expansion" +msgstr "Minimum Skin Width for Expansion" + +#: fdmprinter.def.json +msgctxt "min_skin_width_for_expansion description" +msgid "Skin areas narrower than this are not expanded. This avoids expanding the narrow skin areas that are created when the model surface has a slope close to the vertical." +msgstr "これより狭いスキン領域は展開されません。モデル表面に、垂直に近い斜面がある場合に作成される狭いスキン領域の拡大を回避するためです。" + +#: fdmprinter.def.json +msgctxt "material label" +msgid "Material" +msgstr "Material" + +#: fdmprinter.def.json +msgctxt "material description" +msgid "Material" +msgstr "マテリアル" + +#: fdmprinter.def.json +msgctxt "material_flow_dependent_temperature label" +msgid "Auto Temperature" +msgstr "Auto Temperature" + +#: fdmprinter.def.json +msgctxt "material_flow_dependent_temperature description" +msgid "Change the temperature for each layer automatically with the average flow speed of that layer." +msgstr "その画層の平均流速で自動的にレイヤーごとに温度を変更します。" + +#: fdmprinter.def.json +msgctxt "default_material_print_temperature label" +msgid "Default Printing Temperature" +msgstr "Default Printing Temperature" + +#: fdmprinter.def.json +msgctxt "default_material_print_temperature description" +msgid "The default temperature used for printing. This should be the \"base\" temperature of a material. All other print temperatures should use offsets based on this value" +msgstr "印刷中のデフォルトの温度。これはマテリアルの基本温度となります。他のすべての造形温度はこの値に基づいてオフセットする必要があります。" + +#: fdmprinter.def.json +msgctxt "material_print_temperature label" +msgid "Printing Temperature" +msgstr "Printing Temperature" + +#: fdmprinter.def.json +msgctxt "material_print_temperature description" +msgid "The temperature used for printing." +msgstr "印刷中の温度。" + +#: fdmprinter.def.json +msgctxt "material_print_temperature_layer_0 label" +msgid "Printing Temperature Initial Layer" +msgstr "Printing Temperature Initial Layer" + +#: fdmprinter.def.json +msgctxt "material_print_temperature_layer_0 description" +msgid "The temperature used for printing the first layer. Set at 0 to disable special handling of the initial layer." +msgstr "最初のレイヤーを印刷する温度。初期レイヤーのみ特別設定が必要ない場合は 0 に設定します。" + +#: fdmprinter.def.json +msgctxt "material_initial_print_temperature label" +msgid "Initial Printing Temperature" +msgstr "Initial Printing Temperature" + +#: fdmprinter.def.json +msgctxt "material_initial_print_temperature description" +msgid "The minimal temperature while heating up to the Printing Temperature at which printing can already start." +msgstr "加熱中、印刷を開始することができる最低の温度。" + +#: fdmprinter.def.json +msgctxt "material_final_print_temperature label" +msgid "Final Printing Temperature" +msgstr "Final Printing Temperature" + +#: fdmprinter.def.json +msgctxt "material_final_print_temperature description" +msgid "The temperature to which to already start cooling down just before the end of printing." +msgstr "印刷終了直前に冷却を開始する温度。" + +#: fdmprinter.def.json +msgctxt "material_flow_temp_graph label" +msgid "Flow Temperature Graph" +msgstr "Flow Temperature Graph" + +#: fdmprinter.def.json +msgctxt "material_flow_temp_graph description" +msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)." +msgstr "マテリアルフロー(毎秒 3mm) と温度 (° c) をリンクします。" + +#: fdmprinter.def.json +msgctxt "material_extrusion_cool_down_speed label" +msgid "Extrusion Cool Down Speed Modifier" +msgstr "Extrusion Cool Down Speed Modifier" + +#: fdmprinter.def.json +msgctxt "material_extrusion_cool_down_speed description" +msgid "The extra speed by which the nozzle cools while extruding. The same value is used to signify the heat up speed lost when heating up while extruding." +msgstr "印刷中にノズルが冷える際の速度。同じ値が、加熱する際の加熱速度に割当られます。" + +#: fdmprinter.def.json +msgctxt "material_bed_temperature label" +msgid "Build Plate Temperature" +msgstr "Build Plate Temperature" + +#: fdmprinter.def.json +msgctxt "material_bed_temperature description" +msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print." +msgstr "加熱式ビルドプレート温度。これが 0 の場合、ベッドは加熱しません。" + +#: fdmprinter.def.json +msgctxt "material_bed_temperature_layer_0 label" +msgid "Build Plate Temperature Initial Layer" +msgstr "Build Plate Temperature Initial Layer" + +#: fdmprinter.def.json +msgctxt "material_bed_temperature_layer_0 description" +msgid "The temperature used for the heated build plate at the first layer." +msgstr "最初のレイヤー印刷時のビルドプレートの温度。" + +#: fdmprinter.def.json +msgctxt "material_diameter label" +msgid "Diameter" +msgstr "Diameter" + +#: fdmprinter.def.json +msgctxt "material_diameter description" +msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament." +msgstr "使用するフィラメントの太さの調整 この値を使用するフィラメントの太さと一致させてください。" + +#: fdmprinter.def.json +msgctxt "material_flow label" +msgid "Flow" +msgstr "Flow" + +#: fdmprinter.def.json +msgctxt "material_flow description" +msgid "Flow compensation: the amount of material extruded is multiplied by this value." +msgstr "流れの補修: 押出されるマテリアルの量は、この値から乗算されます。" + +#: fdmprinter.def.json +msgctxt "retraction_enable label" +msgid "Enable Retraction" +msgstr "Enable Retraction" + +#: fdmprinter.def.json +msgctxt "retraction_enable description" +msgid "Retract the filament when the nozzle is moving over a non-printed area. " +msgstr "ノズルが印刷しないで良い領域を移動する際にフィラメントを引き戻す。" + +#: fdmprinter.def.json +msgctxt "retract_at_layer_change label" +msgid "Retract at Layer Change" +msgstr "Retract at Layer Change" + +#: fdmprinter.def.json +msgctxt "retract_at_layer_change description" +msgid "Retract the filament when the nozzle is moving to the next layer." +msgstr "ノズルは次の層に移動するときフィラメントを引き戻します。" + +#: fdmprinter.def.json +msgctxt "retraction_amount label" +msgid "Retraction Distance" +msgstr "Retraction Distance" + +#: fdmprinter.def.json +msgctxt "retraction_amount description" +msgid "The length of material retracted during a retraction move." +msgstr "引き戻されるマテリアルの長さ" + +#: fdmprinter.def.json +msgctxt "retraction_speed label" +msgid "Retraction Speed" +msgstr "Retraction Speed" + +#: fdmprinter.def.json +#, fuzzy +msgctxt "retraction_speed description" +msgid "The speed at which the filament is retracted and primed during a retraction move." +msgstr "フィラメントが引き戻される時のスピード" + +#: fdmprinter.def.json +msgctxt "retraction_retract_speed label" +msgid "Retraction Retract Speed" +msgstr "Retraction Retract Speed" + +#: fdmprinter.def.json +#, fuzzy +msgctxt "retraction_retract_speed description" +msgid "The speed at which the filament is retracted during a retraction move." +msgstr "フィラメントが引き戻される時のスピード" + +#: fdmprinter.def.json +msgctxt "retraction_prime_speed label" +msgid "Retraction Prime Speed" +msgstr "Retraction Prime Speed" + +#: fdmprinter.def.json +#, fuzzy +msgctxt "retraction_prime_speed description" +msgid "The speed at which the filament is primed during a retraction move." +msgstr "フィラメントが引き戻される時のスピード" + +#: fdmprinter.def.json +msgctxt "retraction_extra_prime_amount label" +msgid "Retraction Extra Prime Amount" +msgstr "Retraction Extra Prime Amount" + +#: fdmprinter.def.json +msgctxt "retraction_extra_prime_amount description" +msgid "Some material can ooze away during a travel move, which can be compensated for here." +msgstr "マテリアルによっては、移動中に滲み出てきてしまうときがあり、ここで調整することができます。" + +#: fdmprinter.def.json +msgctxt "retraction_min_travel label" +msgid "Retraction Minimum Travel" +msgstr "Retraction Minimum Travel" + +#: fdmprinter.def.json +msgctxt "retraction_min_travel description" +msgid "The minimum distance of travel needed for a retraction to happen at all. This helps to get fewer retractions in a small area." +msgstr "フィラメントを引き戻す際に必要な最小移動距離。これは、短い距離内での引き戻しの回数を減らすために役立ちます。" + +#: fdmprinter.def.json +msgctxt "retraction_count_max label" +msgid "Maximum Retraction Count" +msgstr "Maximum Retraction Count" + +#: fdmprinter.def.json +msgctxt "retraction_count_max description" +msgid "This setting limits the number of retractions occurring within the minimum extrusion distance window. Further retractions within this window will be ignored. This avoids retracting repeatedly on the same piece of filament, as that can flatten the filament and cause grinding issues." +msgstr "この設定は、決められた距離の中で起こる引き戻しの回数を制限します。制限数以上の引き戻しは無視されます。これによりフィーダーでフィラメントを誤って削ってしまう問題を軽減させます。" + +#: fdmprinter.def.json +msgctxt "retraction_extrusion_window label" +msgid "Minimum Extrusion Distance Window" +msgstr "Minimum Extrusion Distance Window" + +#: fdmprinter.def.json +msgctxt "retraction_extrusion_window description" +msgid "The window in which the maximum retraction count is enforced. This value should be approximately the same as the retraction distance, so that effectively the number of times a retraction passes the same patch of material is limited." +msgstr "最大の引き戻し回数。この値は引き戻す距離と同じであることで、引き戻しが効果的に行われます。" + +#: fdmprinter.def.json +msgctxt "material_standby_temperature label" +msgid "Standby Temperature" +msgstr "Standby Temperature" + +#: fdmprinter.def.json +msgctxt "material_standby_temperature description" +msgid "The temperature of the nozzle when another nozzle is currently used for printing." +msgstr "印刷していないノズルの温度(もう一方のノズルが印刷中)" + +#: fdmprinter.def.json +msgctxt "switch_extruder_retraction_amount label" +msgid "Nozzle Switch Retraction Distance" +msgstr "Nozzle Switch Retraction Distance" + +#: fdmprinter.def.json +msgctxt "switch_extruder_retraction_amount description" +msgid "The amount of retraction: Set at 0 for no retraction at all. This should generally be the same as the length of the heat zone." +msgstr "引き込み量:引き込みを行わない場合は0に設定します。これは通常、ヒートゾーンの長さと同じに設定します。" + +#: fdmprinter.def.json +msgctxt "switch_extruder_retraction_speeds label" +msgid "Nozzle Switch Retraction Speed" +msgstr "Nozzle Switch Retraction Speed" + +#: fdmprinter.def.json +msgctxt "switch_extruder_retraction_speeds description" +msgid "The speed at which the filament is retracted. A higher retraction speed works better, but a very high retraction speed can lead to filament grinding." +msgstr "フィラメントを引き戻す速度。速度が早い程良いが早すぎるとフィラメントを削ってしまう可能性があります。" + +#: fdmprinter.def.json +msgctxt "switch_extruder_retraction_speed label" +msgid "Nozzle Switch Retract Speed" +msgstr "Nozzle Switch Retract Speed" + +#: fdmprinter.def.json +msgctxt "switch_extruder_retraction_speed description" +msgid "The speed at which the filament is retracted during a nozzle switch retract." +msgstr "ノズル切り替え中のフィラメントの引き込み速度。" + +#: fdmprinter.def.json +msgctxt "switch_extruder_prime_speed label" +msgid "Nozzle Switch Prime Speed" +msgstr "Nozzle Switch Prime Speed" + +#: fdmprinter.def.json +msgctxt "switch_extruder_prime_speed description" +msgid "The speed at which the filament is pushed back after a nozzle switch retraction." +msgstr "ノズル スイッチ後にフィラメントが押し戻される速度。" + +#: fdmprinter.def.json +msgctxt "speed label" +msgid "Speed" +msgstr "Speed" + +#: fdmprinter.def.json +msgctxt "speed description" +msgid "Speed" +msgstr "スピード" + +#: fdmprinter.def.json +msgctxt "speed_print label" +msgid "Print Speed" +msgstr "Print Speed" + +#: fdmprinter.def.json +msgctxt "speed_print description" +msgid "The speed at which printing happens." +msgstr "印刷スピード" + +#: fdmprinter.def.json +msgctxt "speed_infill label" +msgid "Infill Speed" +msgstr "Infill Speed" + +#: fdmprinter.def.json +msgctxt "speed_infill description" +msgid "The speed at which infill is printed." +msgstr "インフィルを印刷する速度" + +#: fdmprinter.def.json +msgctxt "speed_wall label" +msgid "Wall Speed" +msgstr "Wall Speed" + +#: fdmprinter.def.json +msgctxt "speed_wall description" +msgid "The speed at which the walls are printed." +msgstr "ウォールを印刷する速度" + +#: fdmprinter.def.json +msgctxt "speed_wall_0 label" +msgid "Outer Wall Speed" +msgstr "Outer Wall Speed" + +#: fdmprinter.def.json +msgctxt "speed_wall_0 description" +msgid "The speed at which the outermost walls are printed. Printing the outer wall at a lower speed improves the final skin quality. However, having a large difference between the inner wall speed and the outer wall speed will affect quality in a negative way." +msgstr "最も外側のウォールをプリントする速度。外側の壁を低速でプリントすると表面の質が改善しますが、内壁と外壁のプリント速度の差が大きすぎると、印刷の質が悪化します。" + +#: fdmprinter.def.json +msgctxt "speed_wall_x label" +msgid "Inner Wall Speed" +msgstr "Inner Wall Speed" + +#: fdmprinter.def.json +msgctxt "speed_wall_x description" +msgid "The speed at which all inner walls are printed. Printing the inner wall faster than the outer wall will reduce printing time. It works well to set this in between the outer wall speed and the infill speed." +msgstr "内側のウォールをプリントする速度 外壁より内壁を高速でプリントすると、印刷時間の短縮になります。外壁のプリント速度とインフィルのプリント速度の中間に設定するのが適切です。" + +#: fdmprinter.def.json +msgctxt "speed_topbottom label" +msgid "Top/Bottom Speed" +msgstr "Top/Bottom Speed" + +#: fdmprinter.def.json +msgctxt "speed_topbottom description" +msgid "The speed at which top/bottom layers are printed." +msgstr "トップ/ボトムのレイヤーのプリント速度" + +#: fdmprinter.def.json +msgctxt "speed_support label" +msgid "Support Speed" +msgstr "Support Speed" + +#: fdmprinter.def.json +msgctxt "speed_support description" +msgid "The speed at which the support structure is printed. Printing support at higher speeds can greatly reduce printing time. The surface quality of the support structure is not important since it is removed after printing." +msgstr "サポート材をプリントする速度です。高速でサポートをプリントすると、印刷時間を大幅に短縮できます。サポート材は印刷後に削除されますので、サポート構造の品質はそれほど重要ではありません。" + +#: fdmprinter.def.json +msgctxt "speed_support_infill label" +msgid "Support Infill Speed" +msgstr "Support Infill Speed" + +#: fdmprinter.def.json +msgctxt "speed_support_infill description" +msgid "The speed at which the infill of support is printed. Printing the infill at lower speeds improves stability." +msgstr "サポート材のインフィルをプリントする速度 低速でプリントすると安定性が向上します。" + +#: fdmprinter.def.json +msgctxt "speed_support_interface label" +msgid "Support Interface Speed" +msgstr "Support Interface Speed" + +#: fdmprinter.def.json +msgctxt "speed_support_interface description" +msgid "The speed at which the roofs and bottoms of support are printed. Printing the them at lower speeds can improve overhang quality." +msgstr "天井と底面のサポート材をプリントする速度 これらを低速でプリントするとオーバーハング部分の品質を向上できます。" + +#: fdmprinter.def.json +msgctxt "speed_prime_tower label" +msgid "Prime Tower Speed" +msgstr "Prime Tower Speed" + +#: fdmprinter.def.json +msgctxt "speed_prime_tower description" +msgid "The speed at which the prime tower is printed. Printing the prime tower slower can make it more stable when the adhesion between the different filaments is suboptimal." +msgstr "プライムタワーをプリントする速度です。異なるフィラメントの印刷で密着性が最適ではない場合、低速にてプライム タワーをプリントすることでより安定させることができます。" + +#: fdmprinter.def.json +msgctxt "speed_travel label" +msgid "Travel Speed" +msgstr "Travel Speed" + +#: fdmprinter.def.json +msgctxt "speed_travel description" +msgid "The speed at which travel moves are made." +msgstr "移動中のスピード" + +#: fdmprinter.def.json +msgctxt "speed_layer_0 label" +msgid "Initial Layer Speed" +msgstr "Initial Layer Speed" + +#: fdmprinter.def.json +msgctxt "speed_layer_0 description" +msgid "The speed for the initial layer. A lower value is advised to improve adhesion to the build plate." +msgstr "一層目での速度。ビルトプレートへの接着を向上するため低速を推奨します" + +#: fdmprinter.def.json +msgctxt "speed_print_layer_0 label" +msgid "Initial Layer Print Speed" +msgstr "Initial Layer Print Speed" + +#: fdmprinter.def.json +msgctxt "speed_print_layer_0 description" +msgid "The speed of printing for the initial layer. A lower value is advised to improve adhesion to the build plate." +msgstr "一層目をプリントする速度 ビルトプレートへの接着を向上するため低速を推奨します" + +#: fdmprinter.def.json +msgctxt "speed_travel_layer_0 label" +msgid "Initial Layer Travel Speed" +msgstr "Initial Layer Travel Speed" + +#: fdmprinter.def.json +msgctxt "speed_travel_layer_0 description" +msgid "The speed of travel moves in the initial layer. A lower value is advised to prevent pulling previously printed parts away from the build plate. The value of this setting can automatically be calculated from the ratio between the Travel Speed and the Print Speed." +msgstr "最初のレイヤーを印刷する際のトラベルスピード。低速の方が、ビルドプレート剥がれるリスクを軽減することができます。この設定の値は、移動速度と印刷速度の比から自動的に計算されます。" + +#: fdmprinter.def.json +msgctxt "skirt_brim_speed label" +msgid "Skirt/Brim Speed" +msgstr "Skirt/Brim Speed" + +#: fdmprinter.def.json +msgctxt "skirt_brim_speed description" +msgid "The speed at which the skirt and brim are printed. Normally this is done at the initial layer speed, but sometimes you might want to print the skirt or brim at a different speed." +msgstr "スカートとブリムのプリント速度 通常は一層目のスピードと同じですが、異なる速度でスカートやブリムをプリントしたい場合に設定してください。" + +#: fdmprinter.def.json +msgctxt "max_feedrate_z_override label" +msgid "Maximum Z Speed" +msgstr "Maximum Z Speed" + +#: fdmprinter.def.json +msgctxt "max_feedrate_z_override description" +msgid "The maximum speed with which the build plate is moved. Setting this to zero causes the print to use the firmware defaults for the maximum z speed." +msgstr "ビルトプレートが移動する最高速度 この値を0に設定すると、ファームウェアのデフォルト値のZの最高速度が適用されます。" + +#: fdmprinter.def.json +msgctxt "speed_slowdown_layers label" +msgid "Number of Slower Layers" +msgstr "Number of Slower Layers" + +#: fdmprinter.def.json +msgctxt "speed_slowdown_layers description" +msgid "The first few layers are printed slower than the rest of the model, to get better adhesion to the build plate and improve the overall success rate of prints. The speed is gradually increased over these layers." +msgstr "最初の数層は印刷失敗の可能性を軽減させるために、設定した印刷スピードよりも遅く印刷されます。" + +#: fdmprinter.def.json +msgctxt "speed_equalize_flow_enabled label" +msgid "Equalize Filament Flow" +msgstr "Equalize Filament Flow" + +#: fdmprinter.def.json +msgctxt "speed_equalize_flow_enabled description" +msgid "Print thinner than normal lines faster so that the amount of material extruded per second remains the same. Thin pieces in your model might require lines printed with smaller line width than provided in the settings. This setting controls the speed changes for such lines." +msgstr "通常より細いラインを高速プリントするので、時間当たりのフィラメント押出量は同じです。薄いモデル部品は、設定よりも細い線幅でプリントすることが要求される場合があり、本設定はそのような線の速度を変更します。" + +#: fdmprinter.def.json +msgctxt "speed_equalize_flow_max label" +msgid "Maximum Speed for Flow Equalization" +msgstr "Maximum Speed for Flow Equalization" + +#: fdmprinter.def.json +msgctxt "speed_equalize_flow_max description" +msgid "Maximum print speed when adjusting the print speed in order to equalize flow." +msgstr "吐出を均一にするための調整時の最高スピード" + +#: fdmprinter.def.json +msgctxt "acceleration_enabled label" +msgid "Enable Acceleration Control" +msgstr "Enable Acceleration Control" + +#: fdmprinter.def.json +msgctxt "acceleration_enabled description" +msgid "Enables adjusting the print head acceleration. Increasing the accelerations can reduce printing time at the cost of print quality." +msgstr "プリントヘッドのスピード調整の有効化 加速度の増加は印刷時間を短縮しますが印刷の質を損ねます。" + +#: fdmprinter.def.json +msgctxt "acceleration_print label" +msgid "Print Acceleration" +msgstr "Print Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_print description" +msgid "The acceleration with which printing happens." +msgstr "印刷の加速スピードです。" + +#: fdmprinter.def.json +msgctxt "acceleration_infill label" +msgid "Infill Acceleration" +msgstr "Infill Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_infill description" +msgid "The acceleration with which infill is printed." +msgstr "インフィルの印刷の加速スピード。" + +#: fdmprinter.def.json +msgctxt "acceleration_wall label" +msgid "Wall Acceleration" +msgstr "Wall Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_wall description" +msgid "The acceleration with which the walls are printed." +msgstr "ウォールをプリントする際の加速度。" + +#: fdmprinter.def.json +msgctxt "acceleration_wall_0 label" +msgid "Outer Wall Acceleration" +msgstr "Outer Wall Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_wall_0 description" +msgid "The acceleration with which the outermost walls are printed." +msgstr "最も外側の壁をプリントする際の加速度" + +#: fdmprinter.def.json +msgctxt "acceleration_wall_x label" +msgid "Inner Wall Acceleration" +msgstr "Inner Wall Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_wall_x description" +msgid "The acceleration with which all inner walls are printed." +msgstr "内側のウォールがが出力される際のスピード。" + +#: fdmprinter.def.json +msgctxt "acceleration_topbottom label" +msgid "Top/Bottom Acceleration" +msgstr "Top/Bottom Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_topbottom description" +msgid "The acceleration with which top/bottom layers are printed." +msgstr "トップとボトムのレイヤーの印刷加速度。" + +#: fdmprinter.def.json +msgctxt "acceleration_support label" +msgid "Support Acceleration" +msgstr "Support Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_support description" +msgid "The acceleration with which the support structure is printed." +msgstr "サポート材プリント時の加速スピード" + +#: fdmprinter.def.json +msgctxt "acceleration_support_infill label" +msgid "Support Infill Acceleration" +msgstr "Support Infill Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_support_infill description" +msgid "The acceleration with which the infill of support is printed." +msgstr "インフィルのサポート材のプリント時の加速度。" + +#: fdmprinter.def.json +msgctxt "acceleration_support_interface label" +msgid "Support Interface Acceleration" +msgstr "Support Interface Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_support_interface description" +msgid "The acceleration with which the roofs and bottoms of support are printed. Printing them at lower accelerations can improve overhang quality." +msgstr "サポート材の上面と底面が印刷されるスピード 低速度で印刷するとオーバーハングの品質が向上します。" + +#: fdmprinter.def.json +msgctxt "acceleration_prime_tower label" +msgid "Prime Tower Acceleration" +msgstr "Prime Tower Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_prime_tower description" +msgid "The acceleration with which the prime tower is printed." +msgstr "プライムタワーの印刷時のスピード。" + +#: fdmprinter.def.json +msgctxt "acceleration_travel label" +msgid "Travel Acceleration" +msgstr "Travel Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_travel description" +msgid "The acceleration with which travel moves are made." +msgstr "移動中の加速度。" + +#: fdmprinter.def.json +msgctxt "acceleration_layer_0 label" +msgid "Initial Layer Acceleration" +msgstr "Initial Layer Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_layer_0 description" +msgid "The acceleration for the initial layer." +msgstr "初期レイヤーの加速度。" + +#: fdmprinter.def.json +msgctxt "acceleration_print_layer_0 label" +msgid "Initial Layer Print Acceleration" +msgstr "Initial Layer Print Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_print_layer_0 description" +msgid "The acceleration during the printing of the initial layer." +msgstr "初期レイヤーの印刷中の加速度。" + +#: fdmprinter.def.json +msgctxt "acceleration_travel_layer_0 label" +msgid "Initial Layer Travel Acceleration" +msgstr "Initial Layer Travel Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_travel_layer_0 description" +msgid "The acceleration for travel moves in the initial layer." +msgstr "最初のレイヤー時の加速度" + +#: fdmprinter.def.json +msgctxt "acceleration_skirt_brim label" +msgid "Skirt/Brim Acceleration" +msgstr "Skirt/Brim Acceleration" + +#: fdmprinter.def.json +msgctxt "acceleration_skirt_brim description" +msgid "The acceleration with which the skirt and brim are printed. Normally this is done with the initial layer acceleration, but sometimes you might want to print the skirt or brim at a different acceleration." +msgstr "スカートとブリム印刷時の加速度。通常、初期レイヤーの印刷スピードにて適用されるが、異なる速度でスカートやブリムを印刷したい場合使用できる。" + +#: fdmprinter.def.json +msgctxt "jerk_enabled label" +msgid "Enable Jerk Control" +msgstr "Enable Jerk Control" + +#: fdmprinter.def.json +msgctxt "jerk_enabled description" +msgid "Enables adjusting the jerk of print head when the velocity in the X or Y axis changes. Increasing the jerk can reduce printing time at the cost of print quality." +msgstr "X または Y 軸の速度が変更する際、プリントヘッドのジャークを調整することができます。ジャークを増やすことは、印刷時間を短縮できますがプリントの質を損ねます。" + +#: fdmprinter.def.json +msgctxt "jerk_print label" +msgid "Print Jerk" +msgstr "Print Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_print description" +msgid "The maximum instantaneous velocity change of the print head." +msgstr "プリントヘッドの最大瞬間速度の変更。" + +#: fdmprinter.def.json +msgctxt "jerk_infill label" +msgid "Infill Jerk" +msgstr "Infill Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_infill description" +msgid "The maximum instantaneous velocity change with which infill is printed." +msgstr "インフィルの印刷時の瞬間速度の変更。" + +#: fdmprinter.def.json +msgctxt "jerk_wall label" +msgid "Wall Jerk" +msgstr "Wall Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_wall description" +msgid "The maximum instantaneous velocity change with which the walls are printed." +msgstr "ウォールのプリント時の最大瞬間速度を変更。" + +#: fdmprinter.def.json +msgctxt "jerk_wall_0 label" +msgid "Outer Wall Jerk" +msgstr "Outer Wall Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_wall_0 description" +msgid "The maximum instantaneous velocity change with which the outermost walls are printed." +msgstr "外側のウォールが出力される際の最大瞬間速度の変更。" + +#: fdmprinter.def.json +msgctxt "jerk_wall_x label" +msgid "Inner Wall Jerk" +msgstr "Inner Wall Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_wall_x description" +msgid "The maximum instantaneous velocity change with which all inner walls are printed." +msgstr "内側のウォールがプリントされれう際の最大瞬間速度の変更。" + +#: fdmprinter.def.json +msgctxt "jerk_topbottom label" +msgid "Top/Bottom Jerk" +msgstr "Top/Bottom Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_topbottom description" +msgid "The maximum instantaneous velocity change with which top/bottom layers are printed." +msgstr "トップとボトムのレイヤーを印刷する際の最大瞬間速度の変更。" + +#: fdmprinter.def.json +msgctxt "jerk_support label" +msgid "Support Jerk" +msgstr "Support Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_support description" +msgid "The maximum instantaneous velocity change with which the support structure is printed." +msgstr "サポート材の印刷時の最大瞬間速度の変更。" + +#: fdmprinter.def.json +msgctxt "jerk_support_infill label" +msgid "Support Infill Jerk" +msgstr "Support Infill Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_support_infill description" +msgid "The maximum instantaneous velocity change with which the infill of support is printed." +msgstr "サポート材の印刷時、最大瞬間速度の変更。" + +#: fdmprinter.def.json +msgctxt "jerk_support_interface label" +msgid "Support Interface Jerk" +msgstr "Support Interface Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_support_interface description" +msgid "The maximum instantaneous velocity change with which the roofs and bottoms of support are printed." +msgstr "サポート材の屋根とボトムのプリント時、最大瞬間速度の変更。" + +#: fdmprinter.def.json +msgctxt "jerk_prime_tower label" +msgid "Prime Tower Jerk" +msgstr "Prime Tower Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_prime_tower description" +msgid "The maximum instantaneous velocity change with which the prime tower is printed." +msgstr "プライムタワーがプリントされる際の最大瞬間速度を変更します。" + +#: fdmprinter.def.json +msgctxt "jerk_travel label" +msgid "Travel Jerk" +msgstr "Travel Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_travel description" +msgid "The maximum instantaneous velocity change with which travel moves are made." +msgstr "移動する際の最大瞬時速度の変更。" + +#: fdmprinter.def.json +msgctxt "jerk_layer_0 label" +msgid "Initial Layer Jerk" +msgstr "Initial Layer Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_layer_0 description" +msgid "The print maximum instantaneous velocity change for the initial layer." +msgstr "初期レイヤーの最大瞬時速度の変更。" + +#: fdmprinter.def.json +msgctxt "jerk_print_layer_0 label" +msgid "Initial Layer Print Jerk" +msgstr "Initial Layer Print Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_print_layer_0 description" +msgid "The maximum instantaneous velocity change during the printing of the initial layer." +msgstr "初期レイヤー印刷中の最大瞬時速度の変化。" + +#: fdmprinter.def.json +msgctxt "jerk_travel_layer_0 label" +msgid "Initial Layer Travel Jerk" +msgstr "Initial Layer Travel Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_travel_layer_0 description" +msgid "The acceleration for travel moves in the initial layer." +msgstr "移動加速度は最初のレイヤーに適用されます。" + +#: fdmprinter.def.json +msgctxt "jerk_skirt_brim label" +msgid "Skirt/Brim Jerk" +msgstr "Skirt/Brim Jerk" + +#: fdmprinter.def.json +msgctxt "jerk_skirt_brim description" +msgid "The maximum instantaneous velocity change with which the skirt and brim are printed." +msgstr "スカートとブリムがプリントされる最大瞬時速度の変更。" + +#: fdmprinter.def.json +msgctxt "travel label" +msgid "Travel" +msgstr "Travel" + +#: fdmprinter.def.json +msgctxt "travel description" +msgid "travel" +msgstr "移動" + +#: fdmprinter.def.json +msgctxt "retraction_combing label" +msgid "Combing Mode" +msgstr "Combing Mode" + +#: fdmprinter.def.json +msgctxt "retraction_combing description" +msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only." +msgstr "コーミングは、走行時にすでに印刷された領域内にノズルを保ちます。その結果、移動距離はわずかに長くなりますが、引き込みの必要性は減ります。コーミングがオフの場合、フィラメントの引き戻しを行い、ノズルは次のポイントまで直線移動します。また、インフィルのみにてコーミングすることにより、トップとボトムのスキン領域上での櫛通りを回避します。" + +#: fdmprinter.def.json +msgctxt "retraction_combing option off" +msgid "Off" +msgstr "Off" + +#: fdmprinter.def.json +msgctxt "retraction_combing option all" +msgid "All" +msgstr "All" + +#: fdmprinter.def.json +msgctxt "retraction_combing option noskin" +msgid "No Skin" +msgstr "No Skin" + +#: fdmprinter.def.json +msgctxt "travel_retract_before_outer_wall label" +msgid "Retract Before Outer Wall" +msgstr "Retract Before Outer Wall" + +#: fdmprinter.def.json +msgctxt "travel_retract_before_outer_wall description" +msgid "Always retract when moving to start an outer wall." +msgstr "移動して外側のウォールをプリントする際、毎回引き戻しをします。" + +#: fdmprinter.def.json +msgctxt "travel_avoid_other_parts label" +msgid "Avoid Printed Parts When Traveling" +msgstr "Avoid Printed Parts When Traveling" + +#: fdmprinter.def.json +msgctxt "travel_avoid_other_parts description" +msgid "The nozzle avoids already printed parts when traveling. This option is only available when combing is enabled." +msgstr "ノズルは、移動時に既に印刷されたパーツを避けます。このオプションは、コーミングが有効な場合にのみ使用できます。" + +#: fdmprinter.def.json +msgctxt "travel_avoid_distance label" +msgid "Travel Avoid Distance" +msgstr "Travel Avoid Distance" + +#: fdmprinter.def.json +msgctxt "travel_avoid_distance description" +msgid "The distance between the nozzle and already printed parts when avoiding during travel moves." +msgstr "ノズルが既に印刷された部分を移動する際の間隔" + +#: fdmprinter.def.json +msgctxt "start_layers_at_same_position label" +msgid "Start Layers with the Same Part" +msgstr "Start Layers with the Same Part" + +#: fdmprinter.def.json +#, fuzzy +msgctxt "start_layers_at_same_position description" +msgid "In each layer start with printing the object near the same point, so that we don't start a new layer with printing the piece which the previous layer ended with. This makes for better overhangs and small parts, but increases printing time." +msgstr "各レイヤーの印刷は決まった場所近い距離のポイントにて印刷を始めます。そのため、前のレイヤーが終わった部分から新しいレイヤーのプリントを開始しません。これによりオーバーハングや小さなパーツの印刷改善されますが、その代わり印刷時間が長くなります。" + +#: fdmprinter.def.json +msgctxt "layer_start_x label" +msgid "Layer Start X" +msgstr "Layer Start X" + +#: fdmprinter.def.json +msgctxt "layer_start_x description" +msgid "The X coordinate of the position near where to find the part to start printing each layer." +msgstr "各レイヤーのプリントを開始する部分をしめすX座標。" + +#: fdmprinter.def.json +msgctxt "layer_start_y label" +msgid "Layer Start Y" +msgstr "Layer Start Y" + +#: fdmprinter.def.json +msgctxt "layer_start_y description" +msgid "The Y coordinate of the position near where to find the part to start printing each layer." +msgstr "各レイヤーのプリントを開始する部分をしめすY座標。" + +#: fdmprinter.def.json +msgctxt "retraction_hop_enabled label" +msgid "Z Hop When Retracted" +msgstr "Z Hop When Retracted" + +#: fdmprinter.def.json +msgctxt "retraction_hop_enabled description" +msgid "Whenever a retraction is done, the build plate is lowered to create clearance between the nozzle and the print. It prevents the nozzle from hitting the print during travel moves, reducing the chance to knock the print from the build plate." +msgstr "引き戻しが完了すると、ビルドプレートが下降してノズルとプリントの間に隙間ができます。ノズルの走行中に造形物に当たるのを防ぎ、造形物をビルドプレートから剥がしてしまう現象を減らします。" + +#: fdmprinter.def.json +msgctxt "retraction_hop_only_when_collides label" +msgid "Z Hop Only Over Printed Parts" +msgstr "Z Hop Only Over Printed Parts" + +#: fdmprinter.def.json +msgctxt "retraction_hop_only_when_collides description" +msgid "Only perform a Z Hop when moving over printed parts which cannot be avoided by horizontal motion by Avoid Printed Parts when Traveling." +msgstr "走行時に印刷部品への衝突を避けるため、水平移動で回避できない造形物上を移動するときは、Zホップを実行します。" + +#: fdmprinter.def.json +msgctxt "retraction_hop label" +msgid "Z Hop Height" +msgstr "Z Hop Height" + +#: fdmprinter.def.json +msgctxt "retraction_hop description" +msgid "The height difference when performing a Z Hop." +msgstr "Zホップを実行するときの高さ。" + +#: fdmprinter.def.json +msgctxt "retraction_hop_after_extruder_switch label" +msgid "Z Hop After Extruder Switch" +msgstr "Z Hop After Extruder Switch" + +#: fdmprinter.def.json +msgctxt "retraction_hop_after_extruder_switch description" +msgid "After the machine switched from one extruder to the other, the build plate is lowered to create clearance between the nozzle and the print. This prevents the nozzle from leaving oozed material on the outside of a print." +msgstr "マシーンが1つのエクストルーダーからもう一つのエクストルーダーに切り替えられた際、ビルドプレートが下降して、ノズルと印刷物との間に隙間が形成される。これによりノズルが造形物の外側にはみ出たマテリアルを残さないためである。" + +#: fdmprinter.def.json +msgctxt "cooling label" +msgid "Cooling" +msgstr "Cooling" + +#: fdmprinter.def.json +msgctxt "cooling description" +msgid "Cooling" +msgstr "冷却" + +#: fdmprinter.def.json +msgctxt "cool_fan_enabled label" +msgid "Enable Print Cooling" +msgstr "Enable Print Cooling" + +#: fdmprinter.def.json +msgctxt "cool_fan_enabled description" +msgid "Enables the print cooling fans while printing. The fans improve print quality on layers with short layer times and bridging / overhangs." +msgstr "印刷中の冷却ファンを有効にします。ファンは、短いレイヤープリントやブリッジ/オーバーハングのレイヤーがある印刷物の品質を向上させます。" + +#: fdmprinter.def.json +msgctxt "cool_fan_speed label" +msgid "Fan Speed" +msgstr "Fan Speed" + +#: fdmprinter.def.json +msgctxt "cool_fan_speed description" +msgid "The speed at which the print cooling fans spin." +msgstr "冷却ファンが回転する速度。" + +#: fdmprinter.def.json +msgctxt "cool_fan_speed_min label" +msgid "Regular Fan Speed" +msgstr "Regular Fan Speed" + +#: fdmprinter.def.json +#, fuzzy +msgctxt "cool_fan_speed_min description" +msgid "The speed at which the fans spin before hitting the threshold. When a layer prints faster than the threshold, the fan speed gradually inclines towards the maximum fan speed." +msgstr "しきい値に達する前のファンの回転スピード。プリント速度がしきい値より速くなると、ファンの速度は上がっていきます。" + +#: fdmprinter.def.json +msgctxt "cool_fan_speed_max label" +msgid "Maximum Fan Speed" +msgstr "Maximum Fan Speed" + +#: fdmprinter.def.json +msgctxt "cool_fan_speed_max description" +msgid "The speed at which the fans spin on the minimum layer time. The fan speed gradually increases between the regular fan speed and maximum fan speed when the threshold is hit." +msgstr "最小積層時間でファンが回転する速度。しきい値に達すると、通常のファンの速度と最速の間でファン速度が徐々に加速しはじめます。" + +#: fdmprinter.def.json +msgctxt "cool_min_layer_time_fan_speed_max label" +msgid "Regular/Maximum Fan Speed Threshold" +msgstr "Regular/Maximum Fan Speed Threshold" + +#: fdmprinter.def.json +msgctxt "cool_min_layer_time_fan_speed_max description" +msgid "The layer time which sets the threshold between regular fan speed and maximum fan speed. Layers that print slower than this time use regular fan speed. For faster layers the fan speed gradually increases towards the maximum fan speed." +msgstr "通常速度と最速の間でしきい値を設定する積層時間。この時間よりも遅く印刷する積層は、通常速度を使用します。より速い層の場合、ファンは最高速度に向かって徐々に加速します。" + +#: fdmprinter.def.json +msgctxt "cool_fan_speed_0 label" +msgid "Initial Fan Speed" +msgstr "Initial Fan Speed" + +#: fdmprinter.def.json +#, fuzzy +msgctxt "cool_fan_speed_0 description" +msgid "The speed at which the fans spin at the start of the print. In subsequent layers the fan speed is gradually increased up to the layer corresponding to Regular Fan Speed at Height." +msgstr "プリント開始時にファンが回転する速度。後続のレイヤーでは、ファン速度は、高さに応じて早くなります。" + +#: fdmprinter.def.json +msgctxt "cool_fan_full_at_height label" +msgid "Regular Fan Speed at Height" +msgstr "Regular Fan Speed at Height" + +#: fdmprinter.def.json +msgctxt "cool_fan_full_at_height description" +msgid "The height at which the fans spin on regular fan speed. At the layers below the fan speed gradually increases from Initial Fan Speed to Regular Fan Speed." +msgstr "通常速度でファンが回転するときの高さ。ここより下層レイヤーでは初期ファンのスピードから通常の速度まで徐々に増加します。" + +#: fdmprinter.def.json +msgctxt "cool_fan_full_layer label" +msgid "Regular Fan Speed at Layer" +msgstr "Regular Fan Speed at Layer" + +#: fdmprinter.def.json +msgctxt "cool_fan_full_layer description" +msgid "The layer at which the fans spin on regular fan speed. If regular fan speed at height is set, this value is calculated and rounded to a whole number." +msgstr "ファンが通常の速度で回転する時のレイヤー。通常速度のファンの高さが設定されている場合、この値が計算され、整数に変換されます。" + +#: fdmprinter.def.json +msgctxt "cool_min_layer_time label" +msgid "Minimum Layer Time" +msgstr "Minimum Layer Time" + +#: fdmprinter.def.json +msgctxt "cool_min_layer_time description" +msgid "The minimum time spent in a layer. This forces the printer to slow down, to at least spend the time set here in one layer. This allows the printed material to cool down properly before printing the next layer. Layers may still take shorter than the minimal layer time if Lift Head is disabled and if the Minimum Speed would otherwise be violated." +msgstr "一つのレイヤーに最低限費やす時間。1つの層に必ず設定された時間を費やすため、場合によってはプリントに遅れが生じます。しかしこれにより、次の層をプリントする前に造形物を適切に冷却することができます。 Lift Headが無効になっていて、最小速度を下回った場合、最小レイヤー時間よりも短くなる場合があります。" + +#: fdmprinter.def.json +msgctxt "cool_min_speed label" +msgid "Minimum Speed" +msgstr "Minimum Speed" + +#: fdmprinter.def.json +msgctxt "cool_min_speed description" +msgid "The minimum print speed, despite slowing down due to the minimum layer time. When the printer would slow down too much, the pressure in the nozzle would be too low and result in bad print quality." +msgstr "最遅印刷速度。印刷の速度が遅すぎると、ノズル内の圧力が低すぎて印刷品質が低下します。" + +#: fdmprinter.def.json +msgctxt "cool_lift_head label" +msgid "Lift Head" +msgstr "Lift Head" + +#: fdmprinter.def.json +msgctxt "cool_lift_head description" +msgid "When the minimum speed is hit because of minimum layer time, lift the head away from the print and wait the extra time until the minimum layer time is reached." +msgstr "レイヤーの最小プリント時間より早く印刷が終わった場合、ヘッド部分を持ち上げてレイヤーの最小プリント時間に到達するまで待機します" + +#: fdmprinter.def.json +msgctxt "support label" +msgid "Support" +msgstr "Support" + +#: fdmprinter.def.json +msgctxt "support description" +msgid "Support" +msgstr "サポート" + +#: fdmprinter.def.json +msgctxt "support_enable label" +msgid "Enable Support" +msgstr "Enable Support" + +#: fdmprinter.def.json +msgctxt "support_enable description" +msgid "Enable support structures. These structures support parts of the model with severe overhangs." +msgstr "サポート材を印刷可能にします。これは、モデル上のオーバーハング部分にサポート材を構築します。" + +#: fdmprinter.def.json +msgctxt "support_extruder_nr label" +msgid "Support Extruder" +msgstr "Support Extruder" + +#: fdmprinter.def.json +msgctxt "support_extruder_nr description" +msgid "The extruder train to use for printing the support. This is used in multi-extrusion." +msgstr "サポート材を印刷するためのエクストルーダー。複数のエクストルーダーがある場合に使用されます。" + +#: fdmprinter.def.json +msgctxt "support_infill_extruder_nr label" +msgid "Support Infill Extruder" +msgstr "Support Infill Extruder" + +#: fdmprinter.def.json +msgctxt "support_infill_extruder_nr description" +msgid "The extruder train to use for printing the infill of the support. This is used in multi-extrusion." +msgstr "サポート材のインフィルを印刷に使用するためのエクストルーダー。複数のエクストルーダーがある場合に使用されます。" + +#: fdmprinter.def.json +msgctxt "support_extruder_nr_layer_0 label" +msgid "First Layer Support Extruder" +msgstr "First Layer Support Extruder" + +#: fdmprinter.def.json +msgctxt "support_extruder_nr_layer_0 description" +msgid "The extruder train to use for printing the first layer of support infill. This is used in multi-extrusion." +msgstr "サポートのインフィルの最初の層を印刷に使用するエクストルーダー。複数のエクストルーダーがある場合に使用されます。" + +#: fdmprinter.def.json +msgctxt "support_interface_extruder_nr label" +msgid "Support Interface Extruder" +msgstr "Support Interface Extruder" + +#: fdmprinter.def.json +msgctxt "support_interface_extruder_nr description" +msgid "The extruder train to use for printing the roofs and bottoms of the support. This is used in multi-extrusion." +msgstr "サポートの天井とボトム部分を印刷する際のエクストルーダー。複数のエクストルーダーがある場合に使用される。" + +#: fdmprinter.def.json +msgctxt "support_type label" +msgid "Support Placement" +msgstr "Support Placement" + +#: fdmprinter.def.json +msgctxt "support_type description" +msgid "Adjusts the placement of the support structures. The placement can be set to touching build plate or everywhere. When set to everywhere the support structures will also be printed on the model." +msgstr "サポート材の配置を調整します。配置はTouching BuildplateまたはEveryWhereに設定することができます。EveryWhereに設定した場合、サポート材がモデルの上にもプリントされます。" + +#: fdmprinter.def.json +msgctxt "support_type option buildplate" +msgid "Touching Buildplate" +msgstr "Touching Buildplate" + +#: fdmprinter.def.json +msgctxt "support_type option everywhere" +msgid "Everywhere" +msgstr "Everywhere" + +#: fdmprinter.def.json +msgctxt "support_angle label" +msgid "Support Overhang Angle" +msgstr "Support Overhang Angle" + +#: fdmprinter.def.json +msgctxt "support_angle description" +msgid "The minimum angle of overhangs for which support is added. At a value of 0° all overhangs are supported, 90° will not provide any support." +msgstr "サポート材がつくオーバーハングの最小角度。0° のときはすべてのオーバーハングにサポートが生成され、90° ではサポートが生成されません。" + +#: fdmprinter.def.json +msgctxt "support_pattern label" +msgid "Support Pattern" +msgstr "Support Pattern" + +#: fdmprinter.def.json +msgctxt "support_pattern description" +msgid "The pattern of the support structures of the print. The different options available result in sturdy or easy to remove support." +msgstr "サポート材の形。サポート材の除去の方法を頑丈または容易にする設定が可能です。" + +#: fdmprinter.def.json +msgctxt "support_pattern option lines" +msgid "Lines" +msgstr "Lines" + +#: fdmprinter.def.json +msgctxt "support_pattern option grid" +msgid "Grid" +msgstr "Grid" + +#: fdmprinter.def.json +msgctxt "support_pattern option triangles" +msgid "Triangles" +msgstr "Triangles" + +#: fdmprinter.def.json +msgctxt "support_pattern option concentric" +msgid "Concentric" +msgstr "Concentric" + +#: fdmprinter.def.json +msgctxt "support_pattern option concentric_3d" +msgid "Concentric 3D" +msgstr "Concentric 3D" + +#: fdmprinter.def.json +msgctxt "support_pattern option zigzag" +msgid "Zig Zag" +msgstr "Zig Zag" + +#: fdmprinter.def.json +msgctxt "support_connect_zigzags label" +msgid "Connect Support ZigZags" +msgstr "Connect Support ZigZags" + +#: fdmprinter.def.json +msgctxt "support_connect_zigzags description" +msgid "Connect the ZigZags. This will increase the strength of the zig zag support structure." +msgstr "ジグザグを接続します。ジグザグ形のサポート材の強度が上がります。" + +#: fdmprinter.def.json +msgctxt "support_infill_rate label" +msgid "Support Density" +msgstr "Support Density" + +#: fdmprinter.def.json +msgctxt "support_infill_rate description" +msgid "Adjusts the density of the support structure. A higher value results in better overhangs, but the supports are harder to remove." +msgstr "サポート材の密度を調整します。大きな値ではオーバーハングが良くなりますが、サポート材が除去しにくくなります。" + +#: fdmprinter.def.json +msgctxt "support_line_distance label" +msgid "Support Line Distance" +msgstr "Support Line Distance" + +#: fdmprinter.def.json +msgctxt "support_line_distance description" +msgid "Distance between the printed support structure lines. This setting is calculated by the support density." +msgstr "印刷されたサポート材の間隔。この設定は、サポート材の密度によって算出されます。" + +#: fdmprinter.def.json +msgctxt "support_z_distance label" +msgid "Support Z Distance" +msgstr "Support Z Distance" + +#: fdmprinter.def.json +msgctxt "support_z_distance description" +msgid "Distance from the top/bottom of the support structure to the print. This gap provides clearance to remove the supports after the model is printed. This value is rounded up to a multiple of the layer height." +msgstr "サポート材のトップ/ボトム部分と印刷物との距離。この幅がプリント後のサポート材を除去する隙間を作ります。値は積層ピッチの倍数にて計算されます。" + +#: fdmprinter.def.json +msgctxt "support_top_distance label" +msgid "Support Top Distance" +msgstr "Support Top Distance" + +#: fdmprinter.def.json +msgctxt "support_top_distance description" +msgid "Distance from the top of the support to the print." +msgstr "サポートの上部から印刷物までの距離。" + +#: fdmprinter.def.json +msgctxt "support_bottom_distance label" +msgid "Support Bottom Distance" +msgstr "Support Bottom Distance" + +#: fdmprinter.def.json +msgctxt "support_bottom_distance description" +msgid "Distance from the print to the bottom of the support." +msgstr "印刷物とサポート材底部までの距離。" + +#: fdmprinter.def.json +msgctxt "support_xy_distance label" +msgid "Support X/Y Distance" +msgstr "Support X/Y Distance" + +#: fdmprinter.def.json +msgctxt "support_xy_distance description" +msgid "Distance of the support structure from the print in the X/Y directions." +msgstr "印刷物からX/Y方向へのサポート材との距離" + +#: fdmprinter.def.json +msgctxt "support_xy_overrides_z label" +msgid "Support Distance Priority" +msgstr "Support Distance Priority" + +#: fdmprinter.def.json +msgctxt "support_xy_overrides_z description" +msgid "Whether the Support X/Y Distance overrides the Support Z Distance or vice versa. When X/Y overrides Z the X/Y distance can push away the support from the model, influencing the actual Z distance to the overhang. We can disable this by not applying the X/Y distance around overhangs." +msgstr "X /Y方向のサポートの距離がZ方向のサポートの距離を上書きしようとする時やまたその逆も同様。X または Y がZを上書きする際、X Y 方向の距離は印刷物からオーバーハングする Z 方向の距離に影響を及ぼしながらサポートを押しのけようとします。オーバー ハング周りのX Yの距離を無効にすることで、無効にできる。" + +#: fdmprinter.def.json +msgctxt "support_xy_overrides_z option xy_overrides_z" +msgid "X/Y overrides Z" +msgstr "X/Y overrides Z" + +#: fdmprinter.def.json +msgctxt "support_xy_overrides_z option z_overrides_xy" +msgid "Z overrides X/Y" +msgstr "Z overrides X/Y" + +#: fdmprinter.def.json +msgctxt "support_xy_distance_overhang label" +msgid "Minimum Support X/Y Distance" +msgstr "Minimum Support X/Y Distance" + +#: fdmprinter.def.json +msgctxt "support_xy_distance_overhang description" +msgid "Distance of the support structure from the overhang in the X/Y directions. " +msgstr "X/Y方向におけるオーバーハングからサポートまでの距離" + +#: fdmprinter.def.json +msgctxt "support_bottom_stair_step_height label" +msgid "Support Stair Step Height" +msgstr "Support Stair Step Height" + +#: fdmprinter.def.json +msgctxt "support_bottom_stair_step_height description" +msgid "The height of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures." +msgstr "モデルにかかる階段形サポートの下部の高さです。低い値のサポートの除去は難しく、高すぎる値は不安定なサポート構造につながります。" + +#: fdmprinter.def.json +msgctxt "support_join_distance label" +msgid "Support Join Distance" +msgstr "Support Join Distance" + +#: fdmprinter.def.json +msgctxt "support_join_distance description" +msgid "The maximum distance between support structures in the X/Y directions. When seperate structures are closer together than this value, the structures merge into one." +msgstr "X/Y方向のサポート構造間の最大距離。別の構造がこの値より近づいた場合、構造は 1 つにマージします。" + +#: fdmprinter.def.json +msgctxt "support_offset label" +msgid "Support Horizontal Expansion" +msgstr "Support Horizontal Expansion" + +#: fdmprinter.def.json +msgctxt "support_offset description" +msgid "Amount of offset applied to all support polygons in each layer. Positive values can smooth out the support areas and result in more sturdy support." +msgstr "各レイヤーのサポート用ポリゴンに適用されるオフセットの量。正の値はサポート領域を円滑にし、より丈夫なサポートにつながります。" + +#: fdmprinter.def.json +msgctxt "support_interface_enable label" +msgid "Enable Support Interface" +msgstr "Enable Support Interface" + +#: fdmprinter.def.json +msgctxt "support_interface_enable description" +msgid "Generate a dense interface between the model and the support. This will create a skin at the top of the support on which the model is printed and at the bottom of the support, where it rests on the model." +msgstr "モデルとサポートの間に密なインターフェースを生成します。これにより、モデルが印刷されているサポートの上部、モデル上のサポートの下部にスキンが作成されます。" + +#: fdmprinter.def.json +msgctxt "support_interface_height label" +msgid "Support Interface Thickness" +msgstr "Support Interface Thickness" + +#: fdmprinter.def.json +msgctxt "support_interface_height description" +msgid "The thickness of the interface of the support where it touches with the model on the bottom or the top." +msgstr "底面または上部のモデルと接触するサポートのインターフェイスの厚さ。" + +#: fdmprinter.def.json +msgctxt "support_roof_height label" +msgid "Support Roof Thickness" +msgstr "Support Roof Thickness" + +#: fdmprinter.def.json +msgctxt "support_roof_height description" +msgid "The thickness of the support roofs. This controls the amount of dense layers at the top of the support on which the model rests." +msgstr "サポートの屋根の厚さ。これは、モデルの下につくサポートの上部にある密度の量を制御します。" + +#: fdmprinter.def.json +msgctxt "support_bottom_height label" +msgid "Support Bottom Thickness" +msgstr "Support Bottom Thickness" + +#: fdmprinter.def.json +msgctxt "support_bottom_height description" +msgid "The thickness of the support bottoms. This controls the number of dense layers are printed on top of places of a model on which support rests." +msgstr "サポート材の底部の厚さ。これは、サモデルの上に印刷されるサポートの積層密度を制御します。" + +#: fdmprinter.def.json +msgctxt "support_interface_skip_height label" +msgid "Support Interface Resolution" +msgstr "Support Interface Resolution" + +#: fdmprinter.def.json +msgctxt "support_interface_skip_height description" +msgid "When checking where there's model above the support, take steps of the given height. Lower values will slice slower, while higher values may cause normal support to be printed in some places where there should have been support interface." +msgstr "サポート上にモデルがあることを確認するときは、指定された高さのステップを実行します。値が小さいほどスライスが遅くなりますが、値が大きくなるとサポートインターフェイスが必要な場所で通常のサポートが印刷されることがあります。" + +#: fdmprinter.def.json +msgctxt "support_interface_density label" +msgid "Support Interface Density" +msgstr "Support Interface Density" + +#: fdmprinter.def.json +msgctxt "support_interface_density description" +msgid "Adjusts the density of the roofs and bottoms of the support structure. A higher value results in better overhangs, but the supports are harder to remove." +msgstr "サポート材の屋根と底部の密度を調整します 大きな値ではオーバーハングでの成功率があがりますが、サポート材が除去しにくくなります" + +#: fdmprinter.def.json +msgctxt "support_interface_line_distance label" +msgid "Support Interface Line Distance" +msgstr "Support Interface Line Distance" + +#: fdmprinter.def.json +msgctxt "support_interface_line_distance description" +msgid "Distance between the printed support interface lines. This setting is calculated by the Support Interface Density, but can be adjusted separately." +msgstr "印刷されたサポートインタフェースラインの間隔。この設定はSupport Interface Densityで計算されますが、個別に調整することができます。" + +#: fdmprinter.def.json +msgctxt "support_interface_pattern label" +msgid "Support Interface Pattern" +msgstr "Support Interface Pattern" + +#: fdmprinter.def.json +msgctxt "support_interface_pattern description" +msgid "The pattern with which the interface of the support with the model is printed." +msgstr "モデルとサポートのインタフェースが印刷されるパターン。" + +#: fdmprinter.def.json +msgctxt "support_interface_pattern option lines" +msgid "Lines" +msgstr "Lines" + +#: fdmprinter.def.json +msgctxt "support_interface_pattern option grid" +msgid "Grid" +msgstr "Grid" + +#: fdmprinter.def.json +msgctxt "support_interface_pattern option triangles" +msgid "Triangles" +msgstr "Triangles" + +#: fdmprinter.def.json +msgctxt "support_interface_pattern option concentric" +msgid "Concentric" +msgstr "Concentric" + +#: fdmprinter.def.json +msgctxt "support_interface_pattern option concentric_3d" +msgid "Concentric 3D" +msgstr "Concentric 3D" + +#: fdmprinter.def.json +msgctxt "support_interface_pattern option zigzag" +msgid "Zig Zag" +msgstr "Zig Zag" + +#: fdmprinter.def.json +msgctxt "support_use_towers label" +msgid "Use Towers" +msgstr "Use Towers" + +#: fdmprinter.def.json +msgctxt "support_use_towers description" +msgid "Use specialized towers to support tiny overhang areas. These towers have a larger diameter than the region they support. Near the overhang the towers' diameter decreases, forming a roof." +msgstr "特殊なタワーを使用して、小さなオーバーハングしているエリアをサポートします。これらの塔は、サポートできる領域より大きな直径を支えれます。オーバーハング付近では塔の直径が減少し、屋根を形成します。" + +#: fdmprinter.def.json +msgctxt "support_tower_diameter label" +msgid "Tower Diameter" +msgstr "Tower Diameter" + +#: fdmprinter.def.json +msgctxt "support_tower_diameter description" +msgid "The diameter of a special tower." +msgstr "特別な塔の直径。" + +#: fdmprinter.def.json +msgctxt "support_minimal_diameter label" +msgid "Minimum Diameter" +msgstr "Minimum Diameter" + +#: fdmprinter.def.json +msgctxt "support_minimal_diameter description" +msgid "Minimum diameter in the X/Y directions of a small area which is to be supported by a specialized support tower." +msgstr "特殊なサポート塔によって支持される小さな領域のX / Y方向の最小直径。" + +#: fdmprinter.def.json +msgctxt "support_tower_roof_angle label" +msgid "Tower Roof Angle" +msgstr "Tower Roof Angle" + +#: fdmprinter.def.json +msgctxt "support_tower_roof_angle description" +msgid "The angle of a rooftop of a tower. A higher value results in pointed tower roofs, a lower value results in flattened tower roofs." +msgstr "タワーの屋上の角度。値が高いほど尖った屋根が得られ、値が低いほど屋根が平らになります。" + +#: fdmprinter.def.json +msgctxt "platform_adhesion label" +msgid "Build Plate Adhesion" +msgstr "Build Plate Adhesion" + +#: fdmprinter.def.json +msgctxt "platform_adhesion description" +msgid "Adhesion" +msgstr "密着性" + +#: fdmprinter.def.json +msgctxt "extruder_prime_pos_x label" +msgid "Extruder Prime X Position" +msgstr "Extruder Prime X Position" + +#: fdmprinter.def.json +msgctxt "extruder_prime_pos_x description" +msgid "The X coordinate of the position where the nozzle primes at the start of printing." +msgstr "プリント開始時のノズルの位置を表すX座標。" + +#: fdmprinter.def.json +msgctxt "extruder_prime_pos_y label" +msgid "Extruder Prime Y Position" +msgstr "Extruder Prime Y Position" + +#: fdmprinter.def.json +msgctxt "extruder_prime_pos_y description" +msgid "The Y coordinate of the position where the nozzle primes at the start of printing." +msgstr "プリント開始時にノズル位置を表すY座標。" + +#: fdmprinter.def.json +msgctxt "adhesion_type label" +msgid "Build Plate Adhesion Type" +msgstr "Build Plate Adhesion Type" + +#: fdmprinter.def.json +msgctxt "adhesion_type description" +msgid "Different options that help to improve both priming your extrusion and adhesion to the build plate. Brim adds a single layer flat area around the base of your model to prevent warping. Raft adds a thick grid with a roof below the model. Skirt is a line printed around the model, but not connected to the model." +msgstr "エクストルーダーとビルドプレートへの接着両方を改善するのに役立つさまざまなオプション。 Brimは、モデルのベースの周りに単一レイヤーを平面的に追加して、ワーピングを防止します。 Raftは、モデルの下に太いグリッドを追加します。スカートはモデルの周りに印刷されたラインですが、モデルには接続されていません。" + +#: fdmprinter.def.json +msgctxt "adhesion_type option skirt" +msgid "Skirt" +msgstr "Skirt" + +#: fdmprinter.def.json +msgctxt "adhesion_type option brim" +msgid "Brim" +msgstr "Brim" + +#: fdmprinter.def.json +msgctxt "adhesion_type option raft" +msgid "Raft" +msgstr "Raft" + +#: fdmprinter.def.json +msgctxt "adhesion_type option none" +msgid "None" +msgstr "None" + +#: fdmprinter.def.json +msgctxt "adhesion_extruder_nr label" +msgid "Build Plate Adhesion Extruder" +msgstr "Build Plate Adhesion Extruder" + +#: fdmprinter.def.json +msgctxt "adhesion_extruder_nr description" +msgid "The extruder train to use for printing the skirt/brim/raft. This is used in multi-extrusion." +msgstr "スカート/ブリム/ラフトをプリントする際のエクストルーダー。これはマルチエクストルージョン時に使用されます。" + +#: fdmprinter.def.json +msgctxt "skirt_line_count label" +msgid "Skirt Line Count" +msgstr "Skirt Line Count" + +#: fdmprinter.def.json +msgctxt "skirt_line_count description" +msgid "Multiple skirt lines help to prime your extrusion better for small models. Setting this to 0 will disable the skirt." +msgstr "複数のスカートラインを使用すると、小さなモデル形成時の射出をより良く行うことができます。これを0に設定するとスカートが無効になります。" + +#: fdmprinter.def.json +msgctxt "skirt_gap label" +msgid "Skirt Distance" +msgstr "Skirt Distance" + +#: fdmprinter.def.json +msgctxt "skirt_gap description" +msgid "" +"The horizontal distance between the skirt and the first layer of the print.\n" +"This is the minimum distance, multiple skirt lines will extend outwards from this distance." +msgstr "スカートとプリントの最初のレイヤーの間の水平距離。これが最小距離であり、複数のスカートラインがこの距離から外側に延びている。" + +#: fdmprinter.def.json +msgctxt "skirt_brim_minimal_length label" +msgid "Skirt/Brim Minimum Length" +msgstr "Skirt/Brim Minimum Length" + +#: fdmprinter.def.json +msgctxt "skirt_brim_minimal_length description" +msgid "The minimum length of the skirt or brim. If this length is not reached by all skirt or brim lines together, more skirt or brim lines will be added until the minimum length is reached. Note: If the line count is set to 0 this is ignored." +msgstr "スカートまたはブリム最短の長さ。この長さにすべてのスカートまたはブリムが達していない場合は、最小限の長さに達するまで、スカートまたはブリムラインが追加されます。注:行数が0に設定されている場合、これは無視されます。" + +#: fdmprinter.def.json +msgctxt "brim_width label" +msgid "Brim Width" +msgstr "Brim Width" + +#: fdmprinter.def.json +msgctxt "brim_width description" +msgid "The distance from the model to the outermost brim line. A larger brim enhances adhesion to the build plate, but also reduces the effective print area." +msgstr "モデルから最外線のブリムまでの距離。大きなブリムは、ビルドプレートへの接着を高めますが、有効な印刷面積も減少させます。" + +#: fdmprinter.def.json +msgctxt "brim_line_count label" +msgid "Brim Line Count" +msgstr "Brim Line Count" + +#: fdmprinter.def.json +msgctxt "brim_line_count description" +msgid "The number of lines used for a brim. More brim lines enhance adhesion to the build plate, but also reduces the effective print area." +msgstr "ブリムに使用される線数。ブリムの線数は、ビルドプレートへの接着性を向上させるだけでなく、有効な印刷面積を減少させる。" + +#: fdmprinter.def.json +msgctxt "brim_outside_only label" +msgid "Brim Only on Outside" +msgstr "Brim Only on Outside" + +#: fdmprinter.def.json +msgctxt "brim_outside_only description" +msgid "Only print the brim on the outside of the model. This reduces the amount of brim you need to remove afterwards, while it doesn't reduce the bed adhesion that much." +msgstr "モデルの外側のみにブリムを印刷します。これにより、後で取り除くブリムの量が減少します。またプレートへの接着力はそれほど低下しません。" + +#: fdmprinter.def.json +msgctxt "raft_margin label" +msgid "Raft Extra Margin" +msgstr "Raft Extra Margin" + +#: fdmprinter.def.json +msgctxt "raft_margin description" +msgid "If the raft is enabled, this is the extra raft area around the model which is also given a raft. Increasing this margin will create a stronger raft while using more material and leaving less area for your print." +msgstr "ラフトが有効になっている場合、モデルの周りに余分なラフト領域ができます。値を大きくするとより強力なラフトができますが、多くの材料を使用し、造形範囲は少なくなります。" + +#: fdmprinter.def.json +msgctxt "raft_airgap label" +msgid "Raft Air Gap" +msgstr "Raft Air Gap" + +#: fdmprinter.def.json +msgctxt "raft_airgap description" +msgid "The gap between the final raft layer and the first layer of the model. Only the first layer is raised by this amount to lower the bonding between the raft layer and the model. Makes it easier to peel off the raft." +msgstr "モデルの第一層のラフトと最終ラフト層の隙間。この値で第1層のみを上げることで、ラフトとモデルとの間の結合を低下させる。結果ラフトを剥がしやすくします。" + +#: fdmprinter.def.json +msgctxt "layer_0_z_overlap label" +msgid "Initial Layer Z Overlap" +msgstr "Initial Layer Z Overlap" + +#: fdmprinter.def.json +msgctxt "layer_0_z_overlap description" +msgid "Make the first and second layer of the model overlap in the Z direction to compensate for the filament lost in the airgap. All models above the first model layer will be shifted down by this amount." +msgstr "エアギャップ内で失われたフィラメントを補うために、モデルの第1層と第2層をZ方向にオーバーラップさせます。この値によって、最初のモデルレイヤーがシフトダウンされます。" + +#: fdmprinter.def.json +msgctxt "raft_surface_layers label" +msgid "Raft Top Layers" +msgstr "Raft Top Layers" + +#: fdmprinter.def.json +msgctxt "raft_surface_layers description" +msgid "The number of top layers on top of the 2nd raft layer. These are fully filled layers that the model sits on. 2 layers result in a smoother top surface than 1." +msgstr "第2ラフト層の上の最上層の数。これらは、モデルが置かれる完全に塗りつぶされた積層です。 2つの層は、1よりも滑らかな上面をもたらす。" + +#: fdmprinter.def.json +msgctxt "raft_surface_thickness label" +msgid "Raft Top Layer Thickness" +msgstr "Raft Top Layer Thickness" + +#: fdmprinter.def.json +msgctxt "raft_surface_thickness description" +msgid "Layer thickness of the top raft layers." +msgstr "トップラフト層の層厚。" + +#: fdmprinter.def.json +msgctxt "raft_surface_line_width label" +msgid "Raft Top Line Width" +msgstr "Raft Top Line Width" + +#: fdmprinter.def.json +msgctxt "raft_surface_line_width description" +msgid "Width of the lines in the top surface of the raft. These can be thin lines so that the top of the raft becomes smooth." +msgstr "ラフトの上面の線の幅。これらは細い線で、ラフトの頂部が滑らかになります。" + +#: fdmprinter.def.json +msgctxt "raft_surface_line_spacing label" +msgid "Raft Top Spacing" +msgstr "Raft Top Spacing" + +#: fdmprinter.def.json +msgctxt "raft_surface_line_spacing description" +msgid "The distance between the raft lines for the top raft layers. The spacing should be equal to the line width, so that the surface is solid." +msgstr "上のラフト層とラフト線の間の距離。間隔は線の幅と同じにして、サーフェスがソリッドになるようにします。" + +#: fdmprinter.def.json +msgctxt "raft_interface_thickness label" +msgid "Raft Middle Thickness" +msgstr "Raft Middle Thickness" + +#: fdmprinter.def.json +msgctxt "raft_interface_thickness description" +msgid "Layer thickness of the middle raft layer." +msgstr "中間のラフト層の層の厚さ。" + +#: fdmprinter.def.json +msgctxt "raft_interface_line_width label" +msgid "Raft Middle Line Width" +msgstr "Raft Middle Line Width" + +#: fdmprinter.def.json +msgctxt "raft_interface_line_width description" +msgid "Width of the lines in the middle raft layer. Making the second layer extrude more causes the lines to stick to the build plate." +msgstr "中間ラフト層の線の幅。第2層をより押し出すと、ラインがビルドプレートに固着します。" + +#: fdmprinter.def.json +msgctxt "raft_interface_line_spacing label" +msgid "Raft Middle Spacing" +msgstr "Raft Middle Spacing" + +#: fdmprinter.def.json +msgctxt "raft_interface_line_spacing description" +msgid "The distance between the raft lines for the middle raft layer. The spacing of the middle should be quite wide, while being dense enough to support the top raft layers." +msgstr "中間ラフト層とラフト線の間の距離。中央の間隔はかなり広くなければならず、トップラフト層を支えるために十分な密度でなければならない。" + +#: fdmprinter.def.json +msgctxt "raft_base_thickness label" +msgid "Raft Base Thickness" +msgstr "Raft Base Thickness" + +#: fdmprinter.def.json +msgctxt "raft_base_thickness description" +msgid "Layer thickness of the base raft layer. This should be a thick layer which sticks firmly to the printer build plate." +msgstr "ベースラフト層の層厚さ。プリンタのビルドプレートにしっかりと固着する厚い層でなければなりません。" + +#: fdmprinter.def.json +msgctxt "raft_base_line_width label" +msgid "Raft Base Line Width" +msgstr "Raft Base Line Width" + +#: fdmprinter.def.json +msgctxt "raft_base_line_width description" +msgid "Width of the lines in the base raft layer. These should be thick lines to assist in build plate adhesion." +msgstr "ベースラフト層の線幅。ビルドプレートの接着のため太い線でなければなりません。" + +#: fdmprinter.def.json +msgctxt "raft_base_line_spacing label" +msgid "Raft Line Spacing" +msgstr "Raft Line Spacing" + +#: fdmprinter.def.json +msgctxt "raft_base_line_spacing description" +msgid "The distance between the raft lines for the base raft layer. Wide spacing makes for easy removal of the raft from the build plate." +msgstr "ベースラフト層のラフトライン間の距離。広い間隔は、ブルドプレートからのラフトの除去を容易にする。" + +#: fdmprinter.def.json +msgctxt "raft_speed label" +msgid "Raft Print Speed" +msgstr "Raft Print Speed" + +#: fdmprinter.def.json +msgctxt "raft_speed description" +msgid "The speed at which the raft is printed." +msgstr "ラフトが印刷される速度。" + +#: fdmprinter.def.json +msgctxt "raft_surface_speed label" +msgid "Raft Top Print Speed" +msgstr "Raft Top Print Speed" + +#: fdmprinter.def.json +msgctxt "raft_surface_speed description" +msgid "The speed at which the top raft layers are printed. These should be printed a bit slower, so that the nozzle can slowly smooth out adjacent surface lines." +msgstr "トップラフト層が印刷される速度。この値はノズルが隣接するサーフェスラインをゆっくりと滑らかにするために、少し遅く印刷する必要があります。" + +#: fdmprinter.def.json +msgctxt "raft_interface_speed label" +msgid "Raft Middle Print Speed" +msgstr "Raft Middle Print Speed" + +#: fdmprinter.def.json +msgctxt "raft_interface_speed description" +msgid "The speed at which the middle raft layer is printed. This should be printed quite slowly, as the volume of material coming out of the nozzle is quite high." +msgstr "ミドルラフト層が印刷される速度。ノズルから出てくるマテリアルの量がかなり多いので、ゆっくりと印刷されるべきである。" + +#: fdmprinter.def.json +msgctxt "raft_base_speed label" +msgid "Raft Base Print Speed" +msgstr "Raft Base Print Speed" + +#: fdmprinter.def.json +msgctxt "raft_base_speed description" +msgid "The speed at which the base raft layer is printed. This should be printed quite slowly, as the volume of material coming out of the nozzle is quite high." +msgstr "ベースラフト層が印刷される速度。これは、ノズルから出てくるマテリアルの量がかなり多いので、ゆっくりと印刷されるべきである。" + +#: fdmprinter.def.json +msgctxt "raft_acceleration label" +msgid "Raft Print Acceleration" +msgstr "Raft Print Acceleration" + +#: fdmprinter.def.json +msgctxt "raft_acceleration description" +msgid "The acceleration with which the raft is printed." +msgstr "ラフト印刷時の加速度。" + +#: fdmprinter.def.json +msgctxt "raft_surface_acceleration label" +msgid "Raft Top Print Acceleration" +msgstr "Raft Top Print Acceleration" + +#: fdmprinter.def.json +msgctxt "raft_surface_acceleration description" +msgid "The acceleration with which the top raft layers are printed." +msgstr "ラフトのトップ印刷時の加速度" + +#: fdmprinter.def.json +msgctxt "raft_interface_acceleration label" +msgid "Raft Middle Print Acceleration" +msgstr "Raft Middle Print Acceleration" + +#: fdmprinter.def.json +msgctxt "raft_interface_acceleration description" +msgid "The acceleration with which the middle raft layer is printed." +msgstr "ラフトの中間層印刷時の加速度" + +#: fdmprinter.def.json +msgctxt "raft_base_acceleration label" +msgid "Raft Base Print Acceleration" +msgstr "Raft Base Print Acceleration" + +#: fdmprinter.def.json +msgctxt "raft_base_acceleration description" +msgid "The acceleration with which the base raft layer is printed." +msgstr "ラフトの底面印刷時の加速度" + +#: fdmprinter.def.json +msgctxt "raft_jerk label" +msgid "Raft Print Jerk" +msgstr "Raft Print Jerk" + +#: fdmprinter.def.json +msgctxt "raft_jerk description" +msgid "The jerk with which the raft is printed." +msgstr "ラフトが印刷時のジャーク。" + +#: fdmprinter.def.json +msgctxt "raft_surface_jerk label" +msgid "Raft Top Print Jerk" +msgstr "Raft Top Print Jerk" + +#: fdmprinter.def.json +msgctxt "raft_surface_jerk description" +msgid "The jerk with which the top raft layers are printed." +msgstr "トップラフト層印刷時のジャーク" + +#: fdmprinter.def.json +msgctxt "raft_interface_jerk label" +msgid "Raft Middle Print Jerk" +msgstr "Raft Middle Print Jerk" + +#: fdmprinter.def.json +msgctxt "raft_interface_jerk description" +msgid "The jerk with which the middle raft layer is printed." +msgstr "ミドルラフト層印刷時のジャーク" + +#: fdmprinter.def.json +msgctxt "raft_base_jerk label" +msgid "Raft Base Print Jerk" +msgstr "Raft Base Print Jerk" + +#: fdmprinter.def.json +msgctxt "raft_base_jerk description" +msgid "The jerk with which the base raft layer is printed." +msgstr "ベースラフト層印刷時のジャーク" + +#: fdmprinter.def.json +msgctxt "raft_fan_speed label" +msgid "Raft Fan Speed" +msgstr "Raft Fan Speed" + +#: fdmprinter.def.json +msgctxt "raft_fan_speed description" +msgid "The fan speed for the raft." +msgstr "ラフト印刷時のファンの速度。" + +#: fdmprinter.def.json +msgctxt "raft_surface_fan_speed label" +msgid "Raft Top Fan Speed" +msgstr "Raft Top Fan Speed" + +#: fdmprinter.def.json +msgctxt "raft_surface_fan_speed description" +msgid "The fan speed for the top raft layers." +msgstr "トップラフト印刷時のファンの速度。" + +#: fdmprinter.def.json +msgctxt "raft_interface_fan_speed label" +msgid "Raft Middle Fan Speed" +msgstr "Raft Middle Fan Speed" + +#: fdmprinter.def.json +msgctxt "raft_interface_fan_speed description" +msgid "The fan speed for the middle raft layer." +msgstr "ミドルラフト印刷時のファンの速度。" + +#: fdmprinter.def.json +msgctxt "raft_base_fan_speed label" +msgid "Raft Base Fan Speed" +msgstr "Raft Base Fan Speed" + +#: fdmprinter.def.json +msgctxt "raft_base_fan_speed description" +msgid "The fan speed for the base raft layer." +msgstr "ベースラフト層印刷時のファン速度" + +#: fdmprinter.def.json +msgctxt "dual label" +msgid "Dual Extrusion" +msgstr "Dual Extrusion" + +#: fdmprinter.def.json +msgctxt "dual description" +msgid "Settings used for printing with multiple extruders." +msgstr "デュアルエクストルーダーで印刷するための設定" + +#: fdmprinter.def.json +msgctxt "prime_tower_enable label" +msgid "Enable Prime Tower" +msgstr "Enable Prime Tower" + +#: fdmprinter.def.json +msgctxt "prime_tower_enable description" +msgid "Print a tower next to the print which serves to prime the material after each nozzle switch." +msgstr "印刷物の横にタワーを造形して、ノズル交換後にフィラメントの調整をします" + +#: fdmprinter.def.json +msgctxt "prime_tower_size label" +msgid "Prime Tower Size" +msgstr "Prime Tower Size" + +#: fdmprinter.def.json +msgctxt "prime_tower_size description" +msgid "The width of the prime tower." +msgstr "プライムタワーの幅。" + +#: fdmprinter.def.json +msgctxt "prime_tower_min_volume label" +msgid "Prime Tower Minimum Volume" +msgstr "Prime Tower Minimum Volume" + +#: fdmprinter.def.json +msgctxt "prime_tower_min_volume description" +msgid "The minimum volume for each layer of the prime tower in order to purge enough material." +msgstr "プライムタワーの各層の最小容積" + +#: fdmprinter.def.json +msgctxt "prime_tower_wall_thickness label" +msgid "Prime Tower Thickness" +msgstr "Prime Tower Thickness" + +#: fdmprinter.def.json +msgctxt "prime_tower_wall_thickness description" +msgid "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower." +msgstr "中空プライムタワーの厚さ。プライムタワーの半分を超える厚さは、密集したプライムタワーになります。" + +#: fdmprinter.def.json +msgctxt "prime_tower_position_x label" +msgid "Prime Tower X Position" +msgstr "Prime Tower X Position" + +#: fdmprinter.def.json +msgctxt "prime_tower_position_x description" +msgid "The x coordinate of the position of the prime tower." +msgstr "プライムタワーの位置のx座標。" + +#: fdmprinter.def.json +msgctxt "prime_tower_position_y label" +msgid "Prime Tower Y Position" +msgstr "Prime Tower Y Position" + +#: fdmprinter.def.json +msgctxt "prime_tower_position_y description" +msgid "The y coordinate of the position of the prime tower." +msgstr "プライムタワーの位置のy座標。" + +#: fdmprinter.def.json +msgctxt "prime_tower_flow label" +msgid "Prime Tower Flow" +msgstr "Prime Tower Flow" + +#: fdmprinter.def.json +msgctxt "prime_tower_flow description" +msgid "Flow compensation: the amount of material extruded is multiplied by this value." +msgstr "吐出量: マテリアルの吐出量はこの値の乗算で計算されます" + +#: fdmprinter.def.json +msgctxt "prime_tower_wipe_enabled label" +msgid "Wipe Inactive Nozzle on Prime Tower" +msgstr "Wipe Inactive Nozzle on Prime Tower" + +#: fdmprinter.def.json +msgctxt "prime_tower_wipe_enabled description" +msgid "After printing the prime tower with one nozzle, wipe the oozed material from the other nozzle off on the prime tower." +msgstr "1本のノズルでプライムタワーを印刷した後、もう片方のノズルから滲み出した材料をプライムタワーが拭き取ります。" + +#: fdmprinter.def.json +msgctxt "dual_pre_wipe label" +msgid "Wipe Nozzle After Switch" +msgstr "Wipe Nozzle After Switch" + +#: fdmprinter.def.json +msgctxt "dual_pre_wipe description" +msgid "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print." +msgstr "エクストルーダーを切り替えた後、最初に印刷したものの上にあるノズルから滲み出したマテリアルを拭き取ってください。余分に出たマテリアルがプリントの表面品質に与える影響が最も少ない場所で、ゆっくりと払拭を行います。" + +#: fdmprinter.def.json +msgctxt "ooze_shield_enabled label" +msgid "Enable Ooze Shield" +msgstr "Enable Ooze Shield" + +#: fdmprinter.def.json +msgctxt "ooze_shield_enabled description" +msgid "Enable exterior ooze shield. This will create a shell around the model which is likely to wipe a second nozzle if it's at the same height as the first nozzle." +msgstr "モデルの周りに壁(ooze shield)を作る。これを生成することで、一つ目のノズルの高さと2つ目のノズルが同じ高さであったとき、2つ目のノズルを綺麗にします。" + +#: fdmprinter.def.json +msgctxt "ooze_shield_angle label" +msgid "Ooze Shield Angle" +msgstr "Ooze Shield Angle" + +#: fdmprinter.def.json +msgctxt "ooze_shield_angle description" +msgid "The maximum angle a part in the ooze shield will have. With 0 degrees being vertical, and 90 degrees being horizontal. A smaller angle leads to less failed ooze shields, but more material." +msgstr "壁(ooze shield)作成時の最大の角度。 0度は垂直であり、90度は水平である。角度を小さくすると、壁が少なくなりますが、より多くの材料が使用されます。" + +#: fdmprinter.def.json +msgctxt "ooze_shield_dist label" +msgid "Ooze Shield Distance" +msgstr "Ooze Shield Distance" + +#: fdmprinter.def.json +msgctxt "ooze_shield_dist description" +msgid "Distance of the ooze shield from the print, in the X/Y directions." +msgstr "壁(ooze shield)の造形物からの距離" + +#: fdmprinter.def.json +msgctxt "meshfix label" +msgid "Mesh Fixes" +msgstr "Mesh Fixes" + +#: fdmprinter.def.json +msgctxt "meshfix description" +msgid "category_fixes" +msgstr "category_fixes" + +#: fdmprinter.def.json +msgctxt "meshfix_union_all label" +msgid "Union Overlapping Volumes" +msgstr "Union Overlapping Volumes" + +#: fdmprinter.def.json +msgctxt "meshfix_union_all description" +msgid "Ignore the internal geometry arising from overlapping volumes within a mesh and print the volumes as one. This may cause unintended internal cavities to disappear." +msgstr "メッシュ内の重なり合うボリュームから生じる内部ジオメトリを無視し、ボリュームを1つとして印刷します。これにより、意図しない内部空洞が消えることがあります。" + +#: fdmprinter.def.json +msgctxt "meshfix_union_all_remove_holes label" +msgid "Remove All Holes" +msgstr "Remove All Holes" + +#: fdmprinter.def.json +msgctxt "meshfix_union_all_remove_holes description" +msgid "Remove the holes in each layer and keep only the outside shape. This will ignore any invisible internal geometry. However, it also ignores layer holes which can be viewed from above or below." +msgstr "各レイヤーの穴を消し、外形のみを保持します。これにより、見えない部分の不要な部分が無視されますが、表面上にある穴も全て造形されなくなります。" + +#: fdmprinter.def.json +msgctxt "meshfix_extensive_stitching label" +msgid "Extensive Stitching" +msgstr "Extensive Stitching" + +#: fdmprinter.def.json +msgctxt "meshfix_extensive_stitching description" +msgid "Extensive stitching tries to stitch up open holes in the mesh by closing the hole with touching polygons. This option can introduce a lot of processing time." +msgstr "強めのスティッチングは、穴をメッシュで塞いでデータを作成します。このオプションは、長い処理時間が必要となります。" + +#: fdmprinter.def.json +msgctxt "meshfix_keep_open_polygons label" +msgid "Keep Disconnected Faces" +msgstr "Keep Disconnected Faces" + +#: fdmprinter.def.json +msgctxt "meshfix_keep_open_polygons description" +msgid "Normally Cura tries to stitch up small holes in the mesh and remove parts of a layer with big holes. Enabling this option keeps those parts which cannot be stitched. This option should be used as a last resort option when everything else fails to produce proper GCode." +msgstr "通常、Curaはメッシュ内の小さな穴をスティッチし、大きな穴のあるレイヤーの部分を削除しようとします。このオプションを有効にすると、スティッチできない部分が保持されます。このオプションは、他のすべてが適切なGCodeを生成できない場合の最後の手段として使用する必要があります。" + +#: fdmprinter.def.json +msgctxt "multiple_mesh_overlap label" +msgid "Merged Meshes Overlap" +msgstr "Merged Meshes Overlap" + +#: fdmprinter.def.json +msgctxt "multiple_mesh_overlap description" +msgid "Make meshes which are touching each other overlap a bit. This makes them bond together better." +msgstr "触れているメッシュを少し重ねてください。これによって、より良い接着をします。" + +#: fdmprinter.def.json +msgctxt "carve_multiple_volumes label" +msgid "Remove Mesh Intersection" +msgstr "Remove Mesh Intersection" + +#: fdmprinter.def.json +msgctxt "carve_multiple_volumes description" +msgid "Remove areas where multiple meshes are overlapping with each other. This may be used if merged dual material objects overlap with each other." +msgstr "複数のメッシュが重なっている領域を削除します。これは、結合された2つのマテリアルのオブジェクトが互いに重なっている場合に使用されます。" + +#: fdmprinter.def.json +msgctxt "alternate_carve_order label" +msgid "Alternate Mesh Removal" +msgstr "Alternate Mesh Removal" + +#: fdmprinter.def.json +msgctxt "alternate_carve_order description" +msgid "Switch to which mesh intersecting volumes will belong with every layer, so that the overlapping meshes become interwoven. Turning this setting off will cause one of the meshes to obtain all of the volume in the overlap, while it is removed from the other meshes." +msgstr "交差するメッシュがどのレイヤーに属しているかを切り替えることで、オーバーラップしているメッシュを絡み合うようにします。この設定をオフにすると、一方のメッシュはオーバーラップ内のすべてのボリュームを取得し、他方のメッシュは他から削除されます。" + +#: fdmprinter.def.json +msgctxt "blackmagic label" +msgid "Special Modes" +msgstr "Special Modes" + +#: fdmprinter.def.json +msgctxt "blackmagic description" +msgid "category_blackmagic" +msgstr "category_blackmagic" + +#: fdmprinter.def.json +msgctxt "print_sequence label" +msgid "Print Sequence" +msgstr "Print Sequence" + +#: fdmprinter.def.json +msgctxt "print_sequence description" +msgid "Whether to print all models one layer at a time or to wait for one model to finish, before moving on to the next. One at a time mode is only possible if all models are separated in such a way that the whole print head can move in between and all models are lower than the distance between the nozzle and the X/Y axes." +msgstr "すべてのモデルをレイヤーごとに印刷するか、1つのモデルがプリント完了するのを待ち次のモデルに移動するかどうか。造形物の間にヘッドが通るだけのスペースがある場合のみ、一つずつ印刷する事が出来ます。" + +#: fdmprinter.def.json +msgctxt "print_sequence option all_at_once" +msgid "All at Once" +msgstr "All at Once" + +#: fdmprinter.def.json +msgctxt "print_sequence option one_at_a_time" +msgid "One at a Time" +msgstr "One at a Time" + +#: fdmprinter.def.json +msgctxt "infill_mesh label" +msgid "Infill Mesh" +msgstr "Infill Mesh" + +#: fdmprinter.def.json +msgctxt "infill_mesh description" +msgid "Use this mesh to modify the infill of other meshes with which it overlaps. Replaces infill regions of other meshes with regions for this mesh. It's suggested to only print one Wall and no Top/Bottom Skin for this mesh." +msgstr "このメッシュを使用して、重なる他のメッシュのインフィルを変更します。他のメッシュのインフィル領域を改なメッシュに置き換えます。これを利用する場合、1つのWallだけを印刷しTop / Bottom Skinは使用しないことをお勧めします。" + +#: fdmprinter.def.json +msgctxt "infill_mesh_order label" +msgid "Infill Mesh Order" +msgstr "Infill Mesh Order" + +#: fdmprinter.def.json +msgctxt "infill_mesh_order description" +msgid "Determines which infill mesh is inside the infill of another infill mesh. An infill mesh with a higher order will modify the infill of infill meshes with lower order and normal meshes." +msgstr "他のインフィルメッシュのインフィル内にあるインフィルメッシュを決定します。優先度の高いのインフィルメッシュは、低いメッシュと通常のメッシュのインフィルを変更します" + +#: fdmprinter.def.json +msgctxt "support_mesh label" +msgid "Support Mesh" +msgstr "Support Mesh" + +#: fdmprinter.def.json +msgctxt "support_mesh description" +msgid "Use this mesh to specify support areas. This can be used to generate support structure." +msgstr "このメッシュを使用してサポート領域を指定します。これは、サポート構造を生成するために使用できます。" + +#: fdmprinter.def.json +msgctxt "anti_overhang_mesh label" +msgid "Anti Overhang Mesh" +msgstr "Anti Overhang Mesh" + +#: fdmprinter.def.json +msgctxt "anti_overhang_mesh description" +msgid "Use this mesh to specify where no part of the model should be detected as overhang. This can be used to remove unwanted support structure." +msgstr "このメッシュを使用して、モデルのどの部分をオーバーハングとして検出する必要がないかを指定します。これは、不要なサポート構造を削除するために使用できます。" + +#: fdmprinter.def.json +msgctxt "magic_mesh_surface_mode label" +msgid "Surface Mode" +msgstr "Surface Mode" + +#: fdmprinter.def.json +msgctxt "magic_mesh_surface_mode description" +msgid "Treat the model as a surface only, a volume, or volumes with loose surfaces. The normal print mode only prints enclosed volumes. \"Surface\" prints a single wall tracing the mesh surface with no infill and no top/bottom skin. \"Both\" prints enclosed volumes like normal and any remaining polygons as surfaces." +msgstr "モデルを表面のみ、ボリューム、または緩い表面のボリュームとして扱います。通常の印刷モードでは、囲まれた内部が印刷されます。 「Surface」は表面のみ印刷をして、インフィルもトップもボトムも印刷しません。 \"Both\"は通常と同様に囲まれた内部を印刷し残りのポリゴンをサーフェスとして印刷します。" + +#: fdmprinter.def.json +msgctxt "magic_mesh_surface_mode option normal" +msgid "Normal" +msgstr "Normal" + +#: fdmprinter.def.json +msgctxt "magic_mesh_surface_mode option surface" +msgid "Surface" +msgstr "Surface" + +#: fdmprinter.def.json +msgctxt "magic_mesh_surface_mode option both" +msgid "Both" +msgstr "Both" + +#: fdmprinter.def.json +msgctxt "magic_spiralize label" +msgid "Spiralize Outer Contour" +msgstr "Spiralize Outer Contour" + +#: fdmprinter.def.json +msgctxt "magic_spiralize description" +msgid "Spiralize smooths out the Z move of the outer edge. This will create a steady Z increase over the whole print. This feature turns a solid model into a single walled print with a solid bottom. This feature used to be called Joris in older versions." +msgstr "Spiralizeは外縁のZ移動を平滑化します。これにより、プリント全体にわたって安定したZ値が得られます。この機能は、ソリッドモデルを単一のウォールプリントに変換し、底面と側面のみ印刷します。この機能は以前のバージョンではJorisと呼ばれていました。" + +#: fdmprinter.def.json +msgctxt "experimental label" +msgid "Experimental" +msgstr "Experimental" + +#: fdmprinter.def.json +msgctxt "experimental description" +msgid "experimental!" +msgstr "実験的" + +#: fdmprinter.def.json +msgctxt "draft_shield_enabled label" +msgid "Enable Draft Shield" +msgstr "Enable Draft Shield" + +#: fdmprinter.def.json +msgctxt "draft_shield_enabled description" +msgid "This will create a wall around the model, which traps (hot) air and shields against exterior airflow. Especially useful for materials which warp easily." +msgstr "これにより、モデルの周囲に壁ができ、熱を閉じ込め、外気の流れを遮蔽します。特に反りやすい材料に有効です。" + +#: fdmprinter.def.json +msgctxt "draft_shield_dist label" +msgid "Draft Shield X/Y Distance" +msgstr "Draft Shield X/Y Distance" + +#: fdmprinter.def.json +msgctxt "draft_shield_dist description" +msgid "Distance of the draft shield from the print, in the X/Y directions." +msgstr "ドラフトシールドと造形物のX / Y方向の距離" + +#: fdmprinter.def.json +msgctxt "draft_shield_height_limitation label" +msgid "Draft Shield Limitation" +msgstr "Draft Shield Limitation" + +#: fdmprinter.def.json +msgctxt "draft_shield_height_limitation description" +msgid "Set the height of the draft shield. Choose to print the draft shield at the full height of the model or at a limited height." +msgstr "ドラフトシールドの高さを設定します。ドラフトシールドは、モデルの全高、または限られた高さで印刷するように選択します。" + +#: fdmprinter.def.json +msgctxt "draft_shield_height_limitation option full" +msgid "Full" +msgstr "Full" + +#: fdmprinter.def.json +msgctxt "draft_shield_height_limitation option limited" +msgid "Limited" +msgstr "Limited" + +#: fdmprinter.def.json +msgctxt "draft_shield_height label" +msgid "Draft Shield Height" +msgstr "Draft Shield Height" + +#: fdmprinter.def.json +msgctxt "draft_shield_height description" +msgid "Height limitation of the draft shield. Above this height no draft shield will be printed." +msgstr "ドラフトシールドの高さ制限。この高さを超えるとドラフトシールドが印刷されません。" + +#: fdmprinter.def.json +msgctxt "conical_overhang_enabled label" +msgid "Make Overhang Printable" +msgstr "Make Overhang Printable" + +#: fdmprinter.def.json +msgctxt "conical_overhang_enabled description" +msgid "Change the geometry of the printed model such that minimal support is required. Steep overhangs will become shallow overhangs. Overhanging areas will drop down to become more vertical." +msgstr "最小限のサポートが必要となるように印刷モデルのジオメトリを変更します。急なオーバーハングは浅いオーバーハングになります。オーバーハングした領域は、より垂直になるように下がります。" + +#: fdmprinter.def.json +msgctxt "conical_overhang_angle label" +msgid "Maximum Model Angle" +msgstr "Maximum Model Angle" + +#: fdmprinter.def.json +msgctxt "conical_overhang_angle description" +msgid "The maximum angle of overhangs after the they have been made printable. At a value of 0° all overhangs are replaced by a piece of model connected to the build plate, 90° will not change the model in any way." +msgstr "印刷可能になったオーバーハングの最大角度。 0°の値では、すべてのオーバーハングがビルドプレートに接続されたモデルの一部に置き換えられます。90°では、モデルは決して変更されません。" + +#: fdmprinter.def.json +msgctxt "coasting_enable label" +msgid "Enable Coasting" +msgstr "Enable Coasting" + +#: fdmprinter.def.json +msgctxt "coasting_enable description" +msgid "Coasting replaces the last part of an extrusion path with a travel path. The oozed material is used to print the last piece of the extrusion path in order to reduce stringing." +msgstr "コースティングは、それぞれの造形ラインの最後の部分をトラベルパスで置き換えます。はみ出た材料は、糸引きを減らすために造形ライン最後の部分を印刷するために使用される。" + +#: fdmprinter.def.json +msgctxt "coasting_volume label" +msgid "Coasting Volume" +msgstr "Coasting Volume" + +#: fdmprinter.def.json +msgctxt "coasting_volume description" +msgid "The volume otherwise oozed. This value should generally be close to the nozzle diameter cubed." +msgstr "はみ出るフィラメントのボリューム。この値は、一般に、ノズル直径の3乗に近い値でなければならない。" + +#: fdmprinter.def.json +msgctxt "coasting_min_volume label" +msgid "Minimum Volume Before Coasting" +msgstr "Minimum Volume Before Coasting" + +#: fdmprinter.def.json +msgctxt "coasting_min_volume description" +msgid "The smallest volume an extrusion path should have before allowing coasting. For smaller extrusion paths, less pressure has been built up in the bowden tube and so the coasted volume is scaled linearly. This value should always be larger than the Coasting Volume." +msgstr "コースティングに必要な最小の容積。より小さい押出経路の場合、ボーデンチューブにはより少ない圧力しか蓄積されないので、コースティングの容積は比例する。この値は、常に、コースティングのボリュームよりも大きな必要があります。" + +#: fdmprinter.def.json +msgctxt "coasting_speed label" +msgid "Coasting Speed" +msgstr "Coasting Speed" + +#: fdmprinter.def.json +msgctxt "coasting_speed description" +msgid "The speed by which to move during coasting, relative to the speed of the extrusion path. A value slightly under 100% is advised, since during the coasting move the pressure in the bowden tube drops." +msgstr "コースティング中の移動速度。印刷時の経路の速度設定に比例します。ボーデンチューブの圧力が低下するので、100%よりわずかに低い値が推奨される。" + +#: fdmprinter.def.json +msgctxt "skin_outline_count label" +msgid "Extra Skin Wall Count" +msgstr "Extra Skin Wall Count" + +#: fdmprinter.def.json +msgctxt "skin_outline_count description" +msgid "Replaces the outermost part of the top/bottom pattern with a number of concentric lines. Using one or two lines improves roofs that start on infill material." +msgstr "上部/下部パターンの最も外側の部分を同心円の線で置き換えます。 1つまたは2つの線を使用すると、トップ部分の造形が改善されます。" + +#: fdmprinter.def.json +msgctxt "skin_alternate_rotation label" +msgid "Alternate Skin Rotation" +msgstr "Alternate Skin Rotation" + +#: fdmprinter.def.json +msgctxt "skin_alternate_rotation description" +msgid "Alternate the direction in which the top/bottom layers are printed. Normally they are printed diagonally only. This setting adds the X-only and Y-only directions." +msgstr "トップ/ボトムのレイヤーが印刷される方向を変更します。通常、それらは斜めに印刷されます。この設定では、X方向のみとY方向のみが追加されます。" + +#: fdmprinter.def.json +msgctxt "support_conical_enabled label" +msgid "Enable Conical Support" +msgstr "Enable Conical Support" + +#: fdmprinter.def.json +msgctxt "support_conical_enabled description" +msgid "Experimental feature: Make support areas smaller at the bottom than at the overhang." +msgstr "実験的機能:オーバーハング部分よりも底面のサポート領域を小さくする。" + +#: fdmprinter.def.json +msgctxt "support_conical_angle label" +msgid "Conical Support Angle" +msgstr "Conical Support Angle" + +#: fdmprinter.def.json +msgctxt "support_conical_angle description" +msgid "The angle of the tilt of conical support. With 0 degrees being vertical, and 90 degrees being horizontal. Smaller angles cause the support to be more sturdy, but consist of more material. Negative angles cause the base of the support to be wider than the top." +msgstr "円錐形のサポートの傾きの角度。 0度は垂直であり、90度は水平である。角度が小さいと、サポートはより頑丈になりますが、より多くのマテリアルが必要になります。負の角度は、サポートのベースがトップよりも広くなります。" + +#: fdmprinter.def.json +msgctxt "support_conical_min_width label" +msgid "Conical Support Minimum Width" +msgstr "Conical Support Minimum Width" + +#: fdmprinter.def.json +msgctxt "support_conical_min_width description" +msgid "Minimum width to which the base of the conical support area is reduced. Small widths can lead to unstable support structures." +msgstr "円錐形のサポート領域のベースが縮小される最小幅。幅が狭いと、サポートが不安定になる可能性があります。" + +#: fdmprinter.def.json +msgctxt "infill_hollow label" +msgid "Hollow Out Objects" +msgstr "Hollow Out Objects" + +#: fdmprinter.def.json +msgctxt "infill_hollow description" +msgid "Remove all infill and make the inside of the object eligible for support." +msgstr "すべてのインフィルを取り除き、オブジェクトの内部をサポート可能にします。" + +#: fdmprinter.def.json +msgctxt "magic_fuzzy_skin_enabled label" +msgid "Fuzzy Skin" +msgstr "Fuzzy Skin" + +#: fdmprinter.def.json +msgctxt "magic_fuzzy_skin_enabled description" +msgid "Randomly jitter while printing the outer wall, so that the surface has a rough and fuzzy look." +msgstr "外壁を印刷する際に振動が起こり、表面が粗くてぼやける。" + +#: fdmprinter.def.json +msgctxt "magic_fuzzy_skin_thickness label" +msgid "Fuzzy Skin Thickness" +msgstr "Fuzzy Skin Thickness" + +#: fdmprinter.def.json +msgctxt "magic_fuzzy_skin_thickness description" +msgid "The width within which to jitter. It's advised to keep this below the outer wall width, since the inner walls are unaltered." +msgstr "振動が起こる幅。内壁は変更されていないので、これを外壁の幅より小さく設定することをお勧めします。" + +#: fdmprinter.def.json +msgctxt "magic_fuzzy_skin_point_density label" +msgid "Fuzzy Skin Density" +msgstr "Fuzzy Skin Density" + +#: fdmprinter.def.json +msgctxt "magic_fuzzy_skin_point_density description" +msgid "The average density of points introduced on each polygon in a layer. Note that the original points of the polygon are discarded, so a low density results in a reduction of the resolution." +msgstr "レイヤー内の各ポリゴンに導入されたポイントの平均密度。ポリゴンの元の点は破棄されるため、密度が低いと解像度が低下します。" + +#: fdmprinter.def.json +msgctxt "magic_fuzzy_skin_point_dist label" +msgid "Fuzzy Skin Point Distance" +msgstr "Fuzzy Skin Point Distance" + +#: fdmprinter.def.json +msgctxt "magic_fuzzy_skin_point_dist description" +msgid "The average distance between the random points introduced on each line segment. Note that the original points of the polygon are discarded, so a high smoothness results in a reduction of the resolution. This value must be higher than half the Fuzzy Skin Thickness." +msgstr "各線分に導入されたランダム点間の平均距離。ポリゴンの元の点は破棄されるので、積層の値を低くすることで、なめらかな仕上がりになります。この値は、ファジースキンの厚さの半分よりも大きくなければなりません。" + +#: fdmprinter.def.json +msgctxt "wireframe_enabled label" +msgid "Wire Printing" +msgstr "Wire Printing" + +#: fdmprinter.def.json +msgctxt "wireframe_enabled description" +msgid "Print only the outside surface with a sparse webbed structure, printing 'in thin air'. This is realized by horizontally printing the contours of the model at given Z intervals which are connected via upward and diagonally downward lines." +msgstr "薄い空気中に印刷し、疎なウエブ構造で外面のみを印刷します。これは、上向きおよび斜め下向きの線を介して接続された所定のZ間隔でモデルの輪郭を水平に印刷することによって実現される。" + +#: fdmprinter.def.json +msgctxt "wireframe_height label" +msgid "WP Connection Height" +msgstr "WP Connection Height" + +#: fdmprinter.def.json +msgctxt "wireframe_height description" +msgid "The height of the upward and diagonally downward lines between two horizontal parts. This determines the overall density of the net structure. Only applies to Wire Printing." +msgstr "2つの水平なパーツ間の、上向きおよび斜め下向きの線の高さ。これは、ネット構造の全体密度を決定します。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_roof_inset label" +msgid "WP Roof Inset Distance" +msgstr "WP Roof Inset Distance" + +#: fdmprinter.def.json +msgctxt "wireframe_roof_inset description" +msgid "The distance covered when making a connection from a roof outline inward. Only applies to Wire Printing." +msgstr "屋根から内側に輪郭を描くときの距離。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_printspeed label" +msgid "WP Speed" +msgstr "WP Speed" + +#: fdmprinter.def.json +msgctxt "wireframe_printspeed description" +msgid "Speed at which the nozzle moves when extruding material. Only applies to Wire Printing." +msgstr "マテリアルを押し出すときにノズルが動く速度。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_printspeed_bottom label" +msgid "WP Bottom Printing Speed" +msgstr "WP Bottom Printing Speed" + +#: fdmprinter.def.json +msgctxt "wireframe_printspeed_bottom description" +msgid "Speed of printing the first layer, which is the only layer touching the build platform. Only applies to Wire Printing." +msgstr "ブルドプラットフォームに接触する第1層の印刷速度。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_printspeed_up label" +msgid "WP Upward Printing Speed" +msgstr "WP Upward Printing Speed" + +#: fdmprinter.def.json +msgctxt "wireframe_printspeed_up description" +msgid "Speed of printing a line upward 'in thin air'. Only applies to Wire Printing." +msgstr "薄い空気の中で上向きに線を印刷する速度。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_printspeed_down label" +msgid "WP Downward Printing Speed" +msgstr "WP Downward Printing Speed" + +#: fdmprinter.def.json +msgctxt "wireframe_printspeed_down description" +msgid "Speed of printing a line diagonally downward. Only applies to Wire Printing." +msgstr "斜め下方に線を印刷する速度。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_printspeed_flat label" +msgid "WP Horizontal Printing Speed" +msgstr "WP Horizontal Printing Speed" + +#: fdmprinter.def.json +msgctxt "wireframe_printspeed_flat description" +msgid "Speed of printing the horizontal contours of the model. Only applies to Wire Printing." +msgstr "モデルの水平輪郭を印刷する速度。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_flow label" +msgid "WP Flow" +msgstr "WP Flow" + +#: fdmprinter.def.json +msgctxt "wireframe_flow description" +msgid "Flow compensation: the amount of material extruded is multiplied by this value. Only applies to Wire Printing." +msgstr "流れ補正:押出されたマテリアルの量はこの値の乗算になります。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_flow_connection label" +msgid "WP Connection Flow" +msgstr "WP Connection Flow" + +#: fdmprinter.def.json +msgctxt "wireframe_flow_connection description" +msgid "Flow compensation when going up or down. Only applies to Wire Printing." +msgstr "上下に動くときの吐出補正。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_flow_flat label" +msgid "WP Flat Flow" +msgstr "WP Flat Flow" + +#: fdmprinter.def.json +msgctxt "wireframe_flow_flat description" +msgid "Flow compensation when printing flat lines. Only applies to Wire Printing." +msgstr "フラットラインを印刷する際の吐出補正。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_top_delay label" +msgid "WP Top Delay" +msgstr "WP Top Delay" + +#: fdmprinter.def.json +msgctxt "wireframe_top_delay description" +msgid "Delay time after an upward move, so that the upward line can harden. Only applies to Wire Printing." +msgstr "上向きの線が硬くなるように、上向きの動きの後の時間を遅らせる。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_bottom_delay label" +msgid "WP Bottom Delay" +msgstr "WP Bottom Delay" + +#: fdmprinter.def.json +msgctxt "wireframe_bottom_delay description" +msgid "Delay time after a downward move. Only applies to Wire Printing." +msgstr "下降後の遅延時間。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_flat_delay label" +msgid "WP Flat Delay" +msgstr "WP Flat Delay" + +#: fdmprinter.def.json +msgctxt "wireframe_flat_delay description" +msgid "Delay time between two horizontal segments. Introducing such a delay can cause better adhesion to previous layers at the connection points, while too long delays cause sagging. Only applies to Wire Printing." +msgstr "2つの水平セグメント間の遅延時間。このような遅延を挿入すると、前のレイヤーとの接着性が向上することがありますが、遅延が長すぎると垂れ下がりが発生します。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_up_half_speed label" +msgid "WP Ease Upward" +msgstr "WP Ease Upward" + +#: fdmprinter.def.json +msgctxt "wireframe_up_half_speed description" +msgid "" +"Distance of an upward move which is extruded with half speed.\n" +"This can cause better adhesion to previous layers, while not heating the material in those layers too much. Only applies to Wire Printing." +msgstr "半分の速度で押出される上方への移動距離。過度にマテリアルを加熱することなく、前の層とのより良い接着を作ります。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_top_jump label" +msgid "WP Knot Size" +msgstr "WP Knot Size" + +#: fdmprinter.def.json +msgctxt "wireframe_top_jump description" +msgid "Creates a small knot at the top of an upward line, so that the consecutive horizontal layer has a better chance to connect to it. Only applies to Wire Printing." +msgstr "上向きの線の上端に小さな結び目を作成し、連続する水平レイヤーを接着力を高めます。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_fall_down label" +msgid "WP Fall Down" +msgstr "WP Fall Down" + +#: fdmprinter.def.json +msgctxt "wireframe_fall_down description" +msgid "Distance with which the material falls down after an upward extrusion. This distance is compensated for. Only applies to Wire Printing." +msgstr "上向き押出後にマテリアルが落下する距離。この距離は補正される。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_drag_along label" +msgid "WP Drag Along" +msgstr "WP Drag Along" + +#: fdmprinter.def.json +msgctxt "wireframe_drag_along description" +msgid "Distance with which the material of an upward extrusion is dragged along with the diagonally downward extrusion. This distance is compensated for. Only applies to Wire Printing." +msgstr "斜め下方への押出に伴い上向き押出しているマテリアルが引きずられる距離。この距離は補正される。ワイヤ印刷のみに適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_strategy label" +msgid "WP Strategy" +msgstr "WP Strategy" + +#: fdmprinter.def.json +msgctxt "wireframe_strategy description" +msgid "Strategy for making sure two consecutive layers connect at each connection point. Retraction lets the upward lines harden in the right position, but may cause filament grinding. A knot can be made at the end of an upward line to heighten the chance of connecting to it and to let the line cool; however, it may require slow printing speeds. Another strategy is to compensate for the sagging of the top of an upward line; however, the lines won't always fall down as predicted." +msgstr "各接続ポイントで2つの連続したレイヤーが密着していることを確認するためのストラテジー。収縮すると上向きの線が正しい位置で硬化しますが、フィラメントの研削が行われる可能性があります。上向きの線の終わりに結び目をつけて接続する機会を増やし、線を冷やすことができます。ただし、印刷速度が遅くなることがあります。別の方法は、上向きの線の上端のたるみを補償することである。しかし、予測どおりにラインが必ずしも落ちるとは限りません。" + +#: fdmprinter.def.json +msgctxt "wireframe_strategy option compensate" +msgid "Compensate" +msgstr "Compensate" + +#: fdmprinter.def.json +msgctxt "wireframe_strategy option knot" +msgid "Knot" +msgstr "Knot" + +#: fdmprinter.def.json +msgctxt "wireframe_strategy option retract" +msgid "Retract" +msgstr "Retract" + +#: fdmprinter.def.json +msgctxt "wireframe_straight_before_down label" +msgid "WP Straighten Downward Lines" +msgstr "WP Straighten Downward Lines" + +#: fdmprinter.def.json +msgctxt "wireframe_straight_before_down description" +msgid "Percentage of a diagonally downward line which is covered by a horizontal line piece. This can prevent sagging of the top most point of upward lines. Only applies to Wire Printing." +msgstr "水平方向の直線部分で覆われた斜めに下降線の割合です。これは上向きラインのほとんどのポイント、上部のたるみを防ぐことができます。ワイヤ印刷にのみ適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_roof_fall_down label" +msgid "WP Roof Fall Down" +msgstr "WP Roof Fall Down" + +#: fdmprinter.def.json +msgctxt "wireframe_roof_fall_down description" +msgid "The distance which horizontal roof lines printed 'in thin air' fall down when being printed. This distance is compensated for. Only applies to Wire Printing." +msgstr "水平屋根が ”薄い空気”に印刷され落ちる距離。この距離は補正されています。ワイヤ印刷に適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_roof_drag_along label" +msgid "WP Roof Drag Along" +msgstr "WP Roof Drag Along" + +#: fdmprinter.def.json +msgctxt "wireframe_roof_drag_along description" +msgid "The distance of the end piece of an inward line which gets dragged along when going back to the outer outline of the roof. This distance is compensated for. Only applies to Wire Printing." +msgstr "屋根の外側の輪郭に戻る際に引きずる内側ラインの終わり部分の距離。この距離は補正されていてワイヤ印刷のみ適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_roof_outer_delay label" +msgid "WP Roof Outer Delay" +msgstr "WP Roof Outer Delay" + +#: fdmprinter.def.json +msgctxt "wireframe_roof_outer_delay description" +msgid "Time spent at the outer perimeters of hole which is to become a roof. Longer times can ensure a better connection. Only applies to Wire Printing." +msgstr "トップレイヤーにある穴の外側に掛ける時間。長い時間の方はより良い密着を得られます。ワイヤ印刷にのみ適用されます。" + +#: fdmprinter.def.json +msgctxt "wireframe_nozzle_clearance label" +msgid "WP Nozzle Clearance" +msgstr "WP Nozzle Clearance" + +#: fdmprinter.def.json +msgctxt "wireframe_nozzle_clearance description" +msgid "Distance between the nozzle and horizontally downward lines. Larger clearance results in diagonally downward lines with a less steep angle, which in turn results in less upward connections with the next layer. Only applies to Wire Printing." +msgstr "ノズルと水平方向に下向きの線間の距離。大きな隙間がある場合、急な角度で斜め下方線となり、次の層が上方接続しずらくなる。ワイヤ印刷にのみ適用されます。" + +#: fdmprinter.def.json +msgctxt "command_line_settings label" +msgid "Command Line Settings" +msgstr "Command Line Settings" + +#: fdmprinter.def.json +msgctxt "command_line_settings description" +msgid "Settings which are only used if CuraEngine isn't called from the Cura frontend." +msgstr "CuraエンジンがCuraフロントエンドから呼び出されない場合のみ使用される設定。" + +#: fdmprinter.def.json +msgctxt "center_object label" +msgid "Center object" +msgstr "Center object" + +#: fdmprinter.def.json +msgctxt "center_object description" +msgid "Whether to center the object on the middle of the build platform (0,0), instead of using the coordinate system in which the object was saved." +msgstr "オブジェクトが保存された座標系を使用する代わりにビルドプラットフォームの中間(0,0)にオブジェクトを配置するかどうか。" + +#: fdmprinter.def.json +msgctxt "mesh_position_x label" +msgid "Mesh position x" +msgstr "Mesh position x" + +#: fdmprinter.def.json +msgctxt "mesh_position_x description" +msgid "Offset applied to the object in the x direction." +msgstr "オブジェクトの x 方向に適用されたオフセット。" + +#: fdmprinter.def.json +msgctxt "mesh_position_y label" +msgid "Mesh position y" +msgstr "Mesh position y" + +#: fdmprinter.def.json +msgctxt "mesh_position_y description" +msgid "Offset applied to the object in the y direction." +msgstr "オブジェクトのY 方向適用されたオフセット。" + +#: fdmprinter.def.json +msgctxt "mesh_position_z label" +msgid "Mesh position z" +msgstr "Mesh position z" + +#: fdmprinter.def.json +msgctxt "mesh_position_z description" +msgid "Offset applied to the object in the z direction. With this you can perform what was used to be called 'Object Sink'." +msgstr "オブジェクトの z 方向に適用されたオフセット。この 'オブジェクト シンク' と呼ばれていたものを再現できます。" + +#: fdmprinter.def.json +msgctxt "mesh_rotation_matrix label" +msgid "Mesh Rotation Matrix" +msgstr "Mesh Rotation Matrix" + +#: fdmprinter.def.json +msgctxt "mesh_rotation_matrix description" +msgid "Transformation matrix to be applied to the model when loading it from file." +msgstr "ファイルから読み込むときに、モデルに適用するトランスフォーメーションマトリックス。" diff --git a/resources/i18n/ptbr/cura.po b/resources/i18n/ptbr/cura.po index b21b36cc9c..681249d0c4 100644 --- a/resources/i18n/ptbr/cura.po +++ b/resources/i18n/ptbr/cura.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-03-27 17:27+0200\n" -"PO-Revision-Date: 2017-01-21 09:40+0200\n" +"PO-Revision-Date: 2017-04-09 18:00-0300\n" "Last-Translator: Cláudio Sampaio