diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 5c147d2006..603cda14d1 100644 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -27,7 +27,7 @@ import UM.Settings.ContainerRegistry # Setting for clearance around the prime -PRIME_CLEARANCE = 1.5 +PRIME_CLEARANCE = 6.5 ## Build volume is a special kind of node that is responsible for rendering the printable area & disallowed areas. @@ -701,6 +701,8 @@ class BuildVolume(SceneNode): bed_adhesion_size += value elif adhesion_type == "raft": bed_adhesion_size = self._getSettingFromAdhesionExtruder("raft_margin") + elif adhesion_type == "none": + bed_adhesion_size = 0 else: raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?") diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py index a7d0775e21..c4b2fe0337 100644 --- a/cura/ConvexHullDecorator.py +++ b/cura/ConvexHullDecorator.py @@ -236,6 +236,8 @@ class ConvexHullDecorator(SceneNodeDecorator): extra_margin = max(0, self._getSettingProperty("raft_margin", "value")) elif adhesion_type == "brim": extra_margin = max(0, self._getSettingProperty("brim_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value")) + elif adhesion_type == "none": + extra_margin = 0 elif adhesion_type == "skirt": extra_margin = max( 0, self._getSettingProperty("skirt_gap", "value") + diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 0216bacce4..b89df31dd9 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -581,6 +581,8 @@ class CuraApplication(QtApplication): else: if self._previous_active_tool: self.getController().setActiveTool(self._previous_active_tool) + if not self.getController().getActiveTool().getEnabled(): + self.getController().setActiveTool("TranslateTool") self._previous_active_tool = None else: # Default @@ -777,7 +779,11 @@ class CuraApplication(QtApplication): for node in nodes: # Ensure that the object is above the build platform node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) - op.addOperation(SetTransformOperation(node, Vector(0, node.getWorldPosition().y - node.getBoundingBox().bottom, 0))) + if node.getBoundingBox(): + center_y = node.getWorldPosition().y - node.getBoundingBox().bottom + else: + center_y = 0 + op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0))) op.push() ## Reset all transformations on nodes with mesh data. @@ -799,11 +805,10 @@ class CuraApplication(QtApplication): for node in nodes: # Ensure that the object is above the build platform node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) - center_y = 0 - if node.callDecoration("isGroup"): + if node.getBoundingBox(): center_y = node.getWorldPosition().y - node.getBoundingBox().bottom else: - center_y = node.getMeshData().getCenterPosition().y + center_y = 0 op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1))) op.push() diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 98c37da617..3982418070 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -210,7 +210,7 @@ class CuraContainerRegistry(ContainerRegistry): return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())} # 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.", file_name)} + return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)} def _configureProfile(self, profile, id_seed, new_name): profile.setReadOnly(False) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index d414386563..4d4f66652e 100644 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -348,8 +348,8 @@ class ExtruderManager(QObject): if support_interface_enabled: used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_interface_extruder_nr", "value"))]) - #The platform adhesion extruder. Not used if using brim and brim width is 0. - if global_stack.getProperty("adhesion_type", "value") != "brim" or global_stack.getProperty("brim_line_count", "value") > 0: + #The platform adhesion extruder. Not used if using none. + if global_stack.getProperty("adhesion_type", "value") != "none": used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("adhesion_extruder_nr", "value"))]) return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids] diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 89b3ed7a82..9d448e11bb 100644 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -220,6 +220,9 @@ class CuraEngineBackend(Backend): # # \param job The start slice job that was just finished. def _onStartSliceCompleted(self, job): + if self._error_message: + self._error_message.hide() + # Note that cancelled slice jobs can still call this method. if self._start_slice_job is job: self._start_slice_job = None @@ -243,9 +246,8 @@ class CuraEngineBackend(Backend): error_keys = [] for extruder in extruders: error_keys.extend(extruder.getErrorKeys()) - else: + 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: @@ -259,6 +261,14 @@ class CuraEngineBackend(Backend): self.backendStateChange.emit(BackendState.NotStarted) return + if job.getResult() == StartSliceJob.StartJobResult.BuildPlateError: + if Application.getInstance().getPlatformActivity: + self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid.")) + self._error_message.show() + self.backendStateChange.emit(BackendState.Error) + else: + self.backendStateChange.emit(BackendState.NotStarted) + if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice: if Application.getInstance().getPlatformActivity: self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit.")) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index b343c307af..0319186518 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -25,6 +25,7 @@ class StartJobResult(IntEnum): SettingError = 3 NothingToSlice = 4 MaterialIncompatible = 5 + BuildPlateError = 6 ## Formatter class that handles token expansion in start/end gcod @@ -80,7 +81,7 @@ class StartSliceJob(Job): return if Application.getInstance().getBuildVolume().hasErrors(): - self.setResult(StartJobResult.SettingError) + self.setResult(StartJobResult.BuildPlateError) return for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getId()): diff --git a/plugins/CuraProfileReader/CuraProfileReader.py b/plugins/CuraProfileReader/CuraProfileReader.py index d1be5b59a8..2198d73b22 100644 --- a/plugins/CuraProfileReader/CuraProfileReader.py +++ b/plugins/CuraProfileReader/CuraProfileReader.py @@ -99,4 +99,6 @@ class CuraProfileReader(ProfileReader): return [] filenames, outputs = profile_convert_funcs[0](serialized, profile_id) + if filenames is None and outputs is None: + return [] return list(zip(outputs, filenames)) diff --git a/plugins/GCodeProfileReader/GCodeProfileReader.py b/plugins/GCodeProfileReader/GCodeProfileReader.py index 24c92d08e9..abfef6e296 100644 --- a/plugins/GCodeProfileReader/GCodeProfileReader.py +++ b/plugins/GCodeProfileReader/GCodeProfileReader.py @@ -70,10 +70,19 @@ class GCodeProfileReader(ProfileReader): json_data = json.loads(serialized) - profile_strings = [json_data["global_quality"]] - profile_strings.extend(json_data.get("extruder_quality", [])) + profiles = [] + global_profile = readQualityProfileFromString(json_data["global_quality"]) - return [readQualityProfileFromString(profile_string) for profile_string in profile_strings] + # This is a fix for profiles created with 2.3.0 For some reason it added the "extruder" property to the + # global profile. + # The fix is simple and safe, as a global profile should never have the extruder entry. + if global_profile.getMetaDataEntry("extruder", None) is not None: + global_profile.setMetaDataEntry("extruder", None) + profiles.append(global_profile) + + for profile_string in json_data.get("extruder_quality", []): + profiles.append(readQualityProfileFromString(profile_string)) + return profiles ## Unescape a string which has been escaped for use in a gcode comment. # diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py b/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py index d7d20db071..8be5a63151 100644 --- a/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py +++ b/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py @@ -5,6 +5,7 @@ import configparser #To read config files. import io #To write config files to strings as if they were files. import UM.VersionUpgrade +from UM.Logger import Logger ## Creates a new profile instance by parsing a serialised profile in version 1 # of the file format. diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 94f7368ab0..995669d256 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -165,7 +165,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): machine_container_map = {} machine_nozzle_map = {} - all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID")) + all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self._id) for container in all_containers: definition_id = container.getDefinition().id if definition_id == "fdmprinter": @@ -209,7 +209,17 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): if not variant_containers: continue - builder.start("hotend", { "id": variant_containers[0].getName() }) + builder.start("hotend", {"id": variant_containers[0].getName()}) + + # Compatible is a special case, as it's added as a meta data entry (instead of an instance). + compatible = hotend.getMetaDataEntry("compatible") + if compatible is not None: + builder.start("setting", {"key": "hardware compatible"}) + if compatible: + builder.data("yes") + else: + builder.data("no") + builder.end("setting") for instance in hotend.findInstances(): if container.getInstance(instance.definition.key) and container.getProperty(instance.definition.key, "value") == instance.value: diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 1962476424..6591a9f3ec 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -169,7 +169,7 @@ }, "machine_extruder_count": { - "label": "Number extruders", + "label": "Number of Extruders", "description": "Number of extruder trains. An extruder train is the combination of a feeder, bowden tube, and nozzle.", "default_value": 1, "minimum_value": "1", @@ -921,6 +921,17 @@ } } }, + "fill_perimeter_gaps": { + "label": "Fill Gaps Between Walls", + "description": "Fills the gaps between walls where no walls fit.", + "type": "enum", + "options": { + "nowhere": "Nowhere", + "everywhere": "Everywhere" + }, + "default_value": "everywhere", + "settable_per_mesh": true + }, "xy_offset": { "label": "Horizontal Expansion", @@ -2393,6 +2404,20 @@ "settable_per_extruder": true, "children": { + "cool_fan_speed_0": + { + "label": "Initial Fan Speed", + "description": "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.", + "unit": "%", + "type": "float", + "minimum_value": "0", + "maximum_value": "100", + "value": "cool_fan_speed", + "default_value": 100, + "enabled": "cool_fan_enabled", + "settable_per_mesh": false, + "settable_per_extruder": true + }, "cool_fan_speed_min": { "label": "Regular Fan Speed", @@ -2438,7 +2463,7 @@ "cool_fan_full_at_height": { "label": "Regular Fan Speed at Height", - "description": "The height at which the fans spin on regular fan speed. At the layers below the fan speed gradually increases from zero to regular fan speed.", + "description": "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.", "unit": "mm", "type": "float", "default_value": 0.5, @@ -2871,7 +2896,7 @@ "type": "float", "default_value": 0.4, "minimum_value": "0", - "minimum_value_warning": "support_interface_line_width", + "minimum_value_warning": "support_interface_line_width - 0.0001", "value": "0 if support_interface_density == 0 else (support_interface_line_width * 100) / support_interface_density * (2 if support_interface_pattern == 'grid' else (3 if support_interface_pattern == 'triangles' else 1))", "limit_to_extruder": "support_interface_extruder_nr", "enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable", @@ -2997,7 +3022,8 @@ { "skirt": "Skirt", "brim": "Brim", - "raft": "Raft" + "raft": "Raft", + "none": "None" }, "default_value": "brim", "resolve": "'raft' if 'raft' in extruderValues('adhesion_type') else ('brim' if 'brim' in extruderValues('adhesion_type') else 'skirt')", @@ -3010,7 +3036,7 @@ "description": "The extruder train to use for printing the skirt/brim/raft. This is used in multi-extrusion.", "type": "extruder", "default_value": "0", - "enabled": "machine_extruder_count > 1", + "enabled": "machine_extruder_count > 1 and resolveOrValue('adhesion_type') != 'none'", "settable_per_mesh": false, "settable_per_extruder": false }, @@ -3747,6 +3773,17 @@ "settable_per_mesh": false, "settable_per_extruder": false, "settable_per_meshgroup": true + }, + "alternate_carve_order": + { + "label": "Alternate Mesh Removal", + "description": "With every layer switch from which model intersecting volumes are removed, so that the overlapping volumes become interwoven.", + "type": "bool", + "default_value": true, + "enabled": "carve_multiple_volumes", + "settable_per_mesh": false, + "settable_per_extruder": false, + "settable_per_meshgroup": true } } }, @@ -3769,6 +3806,7 @@ "one_at_a_time": "One at a Time" }, "default_value": "all_at_once", + "enabled": "machine_extruder_count == 1", "settable_per_mesh": false, "settable_per_extruder": false, "settable_per_meshgroup": false diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json index 16feda7d64..1356efff91 100644 --- a/resources/definitions/ultimaker3.def.json +++ b/resources/definitions/ultimaker3.def.json @@ -71,8 +71,6 @@ "prime_tower_position_x": { "default_value": 175 }, "prime_tower_position_y": { "default_value": 179 }, - "print_sequence": {"enabled": false}, - "acceleration_enabled": { "value": "True" }, "acceleration_layer_0": { "value": "acceleration_topbottom" }, "acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" }, @@ -101,8 +99,8 @@ "jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" }, "jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" }, "layer_height_0": { "value": "round(machine_nozzle_size / 1.5, 2)" }, - "layer_start_x": { "value": "sum(extruderValues('extruder_prime_pos_x')) / len(extruderValues('extruder_prime_pos_x'))" }, - "layer_start_y": { "value": "sum(extruderValues('extruder_prime_pos_y')) / len(extruderValues('extruder_prime_pos_y'))" }, + "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" }, "machine_min_cool_heat_time_window": { "value": "15" }, "material_print_temperature": { "value": "200" }, diff --git a/resources/definitions/ultimaker_original_dual.def.json b/resources/definitions/ultimaker_original_dual.def.json index 48d2436b92..ab2f3ddf46 100644 --- a/resources/definitions/ultimaker_original_dual.def.json +++ b/resources/definitions/ultimaker_original_dual.def.json @@ -72,9 +72,6 @@ "machine_extruder_count": { "default_value": 2 }, - "print_sequence": { - "enabled": false - }, "prime_tower_position_x": { "default_value": 185 }, diff --git a/resources/qml/MultiplyObjectOptions.qml b/resources/qml/MultiplyObjectOptions.qml index 7756ab074b..4b22a96644 100644 --- a/resources/qml/MultiplyObjectOptions.qml +++ b/resources/qml/MultiplyObjectOptions.qml @@ -19,7 +19,7 @@ UM.Dialog width: minimumWidth height: minimumHeight - property int objectId: 0; + property var objectId: 0; onAccepted: Printer.multiplyObject(base.objectId, parseInt(copiesField.text)) property variant catalog: UM.I18nCatalog { name: "cura" } diff --git a/resources/qml/Settings/SettingCheckBox.qml b/resources/qml/Settings/SettingCheckBox.qml index 1fcd24ccf6..97a72a026e 100644 --- a/resources/qml/Settings/SettingCheckBox.qml +++ b/resources/qml/Settings/SettingCheckBox.qml @@ -29,11 +29,11 @@ SettingItem // 4: variant // 5: machine var value; - if ((propertyProvider.properties.resolve != "None") && (stackLevel != 0) && (stackLevel != 1)) { + if ((base.resolve != "None") && (stackLevel != 0) && (stackLevel != 1)) { // We have a resolve function. Indicates that the setting is not settable per extruder and that // we have to choose between the resolved value (default) and the global value // (if user has explicitly set this). - value = propertyProvider.properties.resolve; + value = base.resolve; } else { value = propertyProvider.properties.value; } diff --git a/resources/qml/Settings/SettingComboBox.qml b/resources/qml/Settings/SettingComboBox.qml index c4ca637506..dfa070667a 100644 --- a/resources/qml/Settings/SettingComboBox.qml +++ b/resources/qml/Settings/SettingComboBox.qml @@ -96,11 +96,11 @@ SettingItem { // FIXME this needs to go away once 'resolve' is combined with 'value' in our data model. var value; - if ((propertyProvider.properties.resolve != "None") && (base.stackLevel != 0) && (base.stackLevel != 1)) { + if ((base.resolve != "None") && (base.stackLevel != 0) && (base.stackLevel != 1)) { // We have a resolve function. Indicates that the setting is not settable per extruder and that // we have to choose between the resolved value (default) and the global value // (if user has explicitly set this). - value = propertyProvider.properties.resolve; + value = base.resolve; } else { value = propertyProvider.properties.value; } diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml index 7fa2856e27..2aa15e9244 100644 --- a/resources/qml/Settings/SettingItem.qml +++ b/resources/qml/Settings/SettingItem.qml @@ -27,7 +27,8 @@ Item { // Create properties to put property provider stuff in (bindings break in qt 5.5.1 otherwise) property var state: propertyProvider.properties.state - property var resolve: propertyProvider.properties.resolve + // There is no resolve property if there is only one stack. + property var resolve: Cura.MachineManager.activeStackId != Cura.MachineManager.activeMachineId ? propertyProvider.properties.resolve : "None" property var stackLevels: propertyProvider.stackLevels property var stackLevel: stackLevels[0] diff --git a/resources/qml/Settings/SettingTextField.qml b/resources/qml/Settings/SettingTextField.qml index d02c8854b4..d89f540aa3 100644 --- a/resources/qml/Settings/SettingTextField.qml +++ b/resources/qml/Settings/SettingTextField.qml @@ -114,11 +114,11 @@ SettingItem // 3: material -> user changed material in materialspage // 4: variant // 5: machine - if ((propertyProvider.properties.resolve != "None" && propertyProvider.properties.resolve) && (stackLevel != 0) && (stackLevel != 1)) { + if ((base.resolve != "None" && base.resolve) && (stackLevel != 0) && (stackLevel != 1)) { // We have a resolve function. Indicates that the setting is not settable per extruder and that // we have to choose between the resolved value (default) and the global value // (if user has explicitly set this). - return propertyProvider.properties.resolve; + return base.resolve; } else { return propertyProvider.properties.value; }