Merge branch '3.4' into fix_retraction_amount

This commit is contained in:
Diego Prado Gesto 2018-06-04 14:42:39 +02:00
commit 36e72ae744
41 changed files with 258 additions and 134 deletions

View file

@ -19,7 +19,10 @@ endif()
set(CURA_VERSION "master" CACHE STRING "Version name of Cura") set(CURA_VERSION "master" CACHE STRING "Version name of Cura")
set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'") set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'")
set(CURA_PACKAGES_VERSION "" CACHE STRING "Packages version of Cura") set(CURA_SDK_VERSION "" CACHE STRING "SDK version of Cura")
set(CURA_CLOUD_API_ROOT "" CACHE STRING "Alternative Cura cloud API root")
set(CURA_CLOUD_API_VERSION "" CACHE STRING "Alternative Cura cloud API version")
configure_file(${CMAKE_SOURCE_DIR}/cura.desktop.in ${CMAKE_BINARY_DIR}/cura.desktop @ONLY) configure_file(${CMAKE_SOURCE_DIR}/cura.desktop.in ${CMAKE_BINARY_DIR}/cura.desktop @ONLY)
configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY) configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY)

View file

@ -3,6 +3,7 @@
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger from UM.Logger import Logger
from UM.Math.Polygon import Polygon
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from cura.Arranging.ShapeArray import ShapeArray from cura.Arranging.ShapeArray import ShapeArray
from cura.Scene import ZOffsetDecorator from cura.Scene import ZOffsetDecorator
@ -45,7 +46,7 @@ class Arrange:
# \param scene_root Root for finding all scene nodes # \param scene_root Root for finding all scene nodes
# \param fixed_nodes Scene nodes to be placed # \param fixed_nodes Scene nodes to be placed
@classmethod @classmethod
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250): def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250, min_offset = 8):
arranger = Arrange(x, y, x // 2, y // 2, scale = scale) arranger = Arrange(x, y, x // 2, y // 2, scale = scale)
arranger.centerFirst() arranger.centerFirst()
@ -58,9 +59,10 @@ class Arrange:
# Place all objects fixed nodes # Place all objects fixed nodes
for fixed_node in fixed_nodes: for fixed_node in fixed_nodes:
vertices = fixed_node.callDecoration("getConvexHull") vertices = fixed_node.callDecoration("getConvexHullHead") or fixed_node.callDecoration("getConvexHull")
if not vertices: if not vertices:
continue continue
vertices = vertices.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
points = copy.deepcopy(vertices._points) points = copy.deepcopy(vertices._points)
shape_arr = ShapeArray.fromPolygon(points, scale = scale) shape_arr = ShapeArray.fromPolygon(points, scale = scale)
arranger.place(0, 0, shape_arr) arranger.place(0, 0, shape_arr)
@ -81,12 +83,12 @@ class Arrange:
## Find placement for a node (using offset shape) and place it (using hull shape) ## Find placement for a node (using offset shape) and place it (using hull shape)
# return the nodes that should be placed # return the nodes that should be placed
# \param node # \param node
# \param offset_shape_arr ShapeArray with offset, used to find location # \param offset_shape_arr ShapeArray with offset, for placing the shape
# \param hull_shape_arr ShapeArray without offset, for placing the shape # \param hull_shape_arr ShapeArray without offset, used to find location
def findNodePlacement(self, node, offset_shape_arr, hull_shape_arr, step = 1): def findNodePlacement(self, node, offset_shape_arr, hull_shape_arr, step = 1):
new_node = copy.deepcopy(node) new_node = copy.deepcopy(node)
best_spot = self.bestSpot( best_spot = self.bestSpot(
offset_shape_arr, start_prio = self._last_priority, step = step) hull_shape_arr, start_prio = self._last_priority, step = step)
x, y = best_spot.x, best_spot.y x, y = best_spot.x, best_spot.y
# Save the last priority. # Save the last priority.
@ -102,7 +104,7 @@ class Arrange:
if x is not None: # We could find a place if x is not None: # We could find a place
new_node.setPosition(Vector(x, center_y, y)) new_node.setPosition(Vector(x, center_y, y))
found_spot = True found_spot = True
self.place(x, y, hull_shape_arr) # place the object in arranger self.place(x, y, offset_shape_arr) # place the object in arranger
else: else:
Logger.log("d", "Could not find spot!"), Logger.log("d", "Could not find spot!"),
found_spot = False found_spot = False

View file

@ -110,7 +110,7 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
arrange_array.add() arrange_array.add()
arranger = arrange_array.get(current_build_plate_number) arranger = arrange_array.get(current_build_plate_number)
best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority) best_spot = arranger.bestSpot(hull_shape_arr, start_prio=start_priority)
x, y = best_spot.x, best_spot.y x, y = best_spot.x, best_spot.y
node.removeDecorator(ZOffsetDecorator) node.removeDecorator(ZOffsetDecorator)
if node.getBoundingBox(): if node.getBoundingBox():
@ -118,7 +118,7 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
else: else:
center_y = 0 center_y = 0
if x is not None: # We could find a place if x is not None: # We could find a place
arranger.place(x, y, hull_shape_arr) # place the object in the arranger arranger.place(x, y, offset_shape_arr) # place the object in the arranger
node.callDecoration("setBuildPlateNumber", current_build_plate_number) node.callDecoration("setBuildPlateNumber", current_build_plate_number)
grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True)) grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))

View file

@ -37,12 +37,15 @@ class ArrangeObjectsJob(Job):
machine_width = global_container_stack.getProperty("machine_width", "value") machine_width = global_container_stack.getProperty("machine_width", "value")
machine_depth = global_container_stack.getProperty("machine_depth", "value") machine_depth = global_container_stack.getProperty("machine_depth", "value")
arranger = Arrange.create(x = machine_width, y = machine_depth, fixed_nodes = self._fixed_nodes) arranger = Arrange.create(x = machine_width, y = machine_depth, fixed_nodes = self._fixed_nodes, min_offset = self._min_offset)
# Collect nodes to be placed # Collect nodes to be placed
nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr) nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
for node in self._nodes: for node in self._nodes:
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset) offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset)
if offset_shape_arr is None:
Logger.log("w", "Node [%s] could not be converted to an array for arranging...", str(node))
continue
nodes_arr.append((offset_shape_arr.arr.shape[0] * offset_shape_arr.arr.shape[1], node, offset_shape_arr, hull_shape_arr)) nodes_arr.append((offset_shape_arr.arr.shape[0] * offset_shape_arr.arr.shape[1], node, offset_shape_arr, hull_shape_arr))
# Sort the nodes with the biggest area first. # Sort the nodes with the biggest area first.
@ -63,7 +66,7 @@ class ArrangeObjectsJob(Job):
start_priority = last_priority start_priority = last_priority
else: else:
start_priority = 0 start_priority = 0
best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority) best_spot = arranger.bestSpot(hull_shape_arr, start_prio = start_priority)
x, y = best_spot.x, best_spot.y x, y = best_spot.x, best_spot.y
node.removeDecorator(ZOffsetDecorator) node.removeDecorator(ZOffsetDecorator)
if node.getBoundingBox(): if node.getBoundingBox():
@ -74,7 +77,7 @@ class ArrangeObjectsJob(Job):
last_size = size last_size = size
last_priority = best_spot.priority last_priority = best_spot.priority
arranger.place(x, y, hull_shape_arr) # take place before the next one arranger.place(x, y, offset_shape_arr) # take place before the next one
grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True)) grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))
else: else:
Logger.log("d", "Arrange all: could not find spot!") Logger.log("d", "Arrange all: could not find spot!")

View file

@ -452,7 +452,7 @@ class CuraApplication(QtApplication):
## A reusable dialogbox ## A reusable dialogbox
# #
showMessageBox = pyqtSignal(str, str, str, str, str, int, int, arguments = ["title", "footer", "text", "informativeText", "detailedText", "buttons", "icon"]) showMessageBox = pyqtSignal(str, str, str, str, int, int, arguments = ["title", "text", "informativeText", "detailedText", "buttons", "icon"])
def messageBox(self, title, text, informativeText = "", detailedText = "", buttons = QMessageBox.Ok, icon = QMessageBox.NoIcon, callback = None, callback_arguments = []): def messageBox(self, title, text, informativeText = "", detailedText = "", buttons = QMessageBox.Ok, icon = QMessageBox.NoIcon, callback = None, callback_arguments = []):
self._message_box_callback = callback self._message_box_callback = callback
@ -1500,11 +1500,15 @@ class CuraApplication(QtApplication):
def _reloadMeshFinished(self, job): def _reloadMeshFinished(self, job):
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh! # TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
mesh_data = job.getResult()[0].getMeshData() job_result = job.getResult()
if mesh_data: if len(job_result) == 0:
job._node.setMeshData(mesh_data) Logger.log("e", "Reloading the mesh failed.")
else: return
mesh_data = job_result[0].getMeshData()
if not mesh_data:
Logger.log("w", "Could not find a mesh in reloaded node.") Logger.log("w", "Could not find a mesh in reloaded node.")
return
job._node.setMeshData(mesh_data)
def _openFile(self, filename): def _openFile(self, filename):
self.readLocalFile(QUrl.fromLocalFile(filename)) self.readLocalFile(QUrl.fromLocalFile(filename))
@ -1568,10 +1572,11 @@ class CuraApplication(QtApplication):
f = file.toLocalFile() f = file.toLocalFile()
extension = os.path.splitext(f)[1] extension = os.path.splitext(f)[1]
extension = extension.lower()
filename = os.path.basename(f) filename = os.path.basename(f)
if len(self._currently_loading_files) > 0: if len(self._currently_loading_files) > 0:
# If a non-slicable file is already being loaded, we prevent loading of any further non-slicable files # If a non-slicable file is already being loaded, we prevent loading of any further non-slicable files
if extension.lower() in self._non_sliceable_extensions: if extension in self._non_sliceable_extensions:
message = Message( message = Message(
self._i18n_catalog.i18nc("@info:status", self._i18n_catalog.i18nc("@info:status",
"Only one G-code file can be loaded at a time. Skipped importing {0}", "Only one G-code file can be loaded at a time. Skipped importing {0}",
@ -1580,7 +1585,8 @@ class CuraApplication(QtApplication):
return return
# If file being loaded is non-slicable file, then prevent loading of any other files # If file being loaded is non-slicable file, then prevent loading of any other files
extension = os.path.splitext(self._currently_loading_files[0])[1] extension = os.path.splitext(self._currently_loading_files[0])[1]
if extension.lower() in self._non_sliceable_extensions: extension = extension.lower()
if extension in self._non_sliceable_extensions:
message = Message( message = Message(
self._i18n_catalog.i18nc("@info:status", self._i18n_catalog.i18nc("@info:status",
"Can't open any other file if G-code is loading. Skipped importing {0}", "Can't open any other file if G-code is loading. Skipped importing {0}",

View file

@ -158,14 +158,17 @@ class CuraPackageManager(QObject):
# Add bundled plugins # Add bundled plugins
if package_id in self._bundled_package_dict: if package_id in self._bundled_package_dict:
package_info = self._bundled_package_dict[package_id]["package_info"] package_info = self._bundled_package_dict[package_id]["package_info"]
package_info["is_installed"] = True
# Add installed plugins # Add installed plugins
if package_id in self._installed_package_dict: if package_id in self._installed_package_dict:
package_info = self._installed_package_dict[package_id]["package_info"] package_info = self._installed_package_dict[package_id]["package_info"]
package_info["is_installed"] = True
# Add to install plugins # Add to install plugins
if package_id in self._to_install_package_dict: if package_id in self._to_install_package_dict:
package_info = self._to_install_package_dict[package_id]["package_info"] package_info = self._to_install_package_dict[package_id]["package_info"]
package_info["is_installed"] = False
if package_info is None: if package_info is None:
continue continue

View file

@ -4,4 +4,6 @@
CuraVersion = "@CURA_VERSION@" CuraVersion = "@CURA_VERSION@"
CuraBuildType = "@CURA_BUILDTYPE@" CuraBuildType = "@CURA_BUILDTYPE@"
CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False
CuraPackagesVersion = "@CURA_PACKAGES_VERSION@" CuraSDKVersion = "@CURA_SDK_VERSION@"
CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@"
CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@"

View file

@ -291,9 +291,10 @@ class MaterialManager(QObject):
material_id_metadata_dict = dict() material_id_metadata_dict = dict()
for node in nodes_to_check: for node in nodes_to_check:
if node is not None: if node is not None:
# Only exclude the materials that are explicitly specified in the "exclude_materials" field.
# Do not exclude other materials that are of the same type.
for material_id, node in node.material_map.items(): for material_id, node in node.material_map.items():
fallback_id = self.getFallbackMaterialIdByMaterialType(node.metadata["material"]) if material_id in machine_exclude_materials:
if fallback_id in machine_exclude_materials:
Logger.log("d", "Exclude material [%s] for machine [%s]", Logger.log("d", "Exclude material [%s] for machine [%s]",
material_id, machine_definition.getId()) material_id, machine_definition.getId())
continue continue

View file

@ -39,6 +39,8 @@ class BaseMaterialsModel(ListModel):
self._extruder_position = 0 self._extruder_position = 0
self._extruder_stack = None self._extruder_stack = None
# Update the stack and the model data when the machine changes
self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
def _updateExtruderStack(self): def _updateExtruderStack(self):
global_stack = self._machine_manager.activeMachine global_stack = self._machine_manager.activeMachine
@ -50,9 +52,11 @@ class BaseMaterialsModel(ListModel):
self._extruder_stack = global_stack.extruders.get(str(self._extruder_position)) self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
if self._extruder_stack is not None: if self._extruder_stack is not None:
self._extruder_stack.pyqtContainersChanged.connect(self._update) self._extruder_stack.pyqtContainersChanged.connect(self._update)
# Force update the model when the extruder stack changes
self._update()
def setExtruderPosition(self, position: int): def setExtruderPosition(self, position: int):
if self._extruder_position != position: if self._extruder_stack is None or self._extruder_position != position:
self._extruder_position = position self._extruder_position = position
self._updateExtruderStack() self._updateExtruderStack()
self.extruderPositionChanged.emit() self.extruderPositionChanged.emit()

View file

@ -1,6 +1,8 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import copy
from UM.Job import Job from UM.Job import Job
from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.GroupedOperation import GroupedOperation
from UM.Message import Message from UM.Message import Message
@ -36,7 +38,7 @@ class MultiplyObjectsJob(Job):
root = scene.getRoot() root = scene.getRoot()
scale = 0.5 scale = 0.5
arranger = Arrange.create(x = machine_width, y = machine_depth, scene_root = root, scale = scale) arranger = Arrange.create(x = machine_width, y = machine_depth, scene_root = root, scale = scale, min_offset = self._min_offset)
processed_nodes = [] processed_nodes = []
nodes = [] nodes = []
@ -64,6 +66,8 @@ class MultiplyObjectsJob(Job):
# We do place the nodes one by one, as we want to yield in between. # We do place the nodes one by one, as we want to yield in between.
if not node_too_big: if not node_too_big:
new_node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr) new_node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
else:
new_node = copy.deepcopy(node)
if node_too_big or not solution_found: if node_too_big or not solution_found:
found_solution_for_all = False found_solution_for_all = False
new_location = new_node.getPosition() new_location = new_node.getPosition()

View file

@ -329,6 +329,8 @@ class PrintInformation(QObject):
baseNameChanged = pyqtSignal() baseNameChanged = pyqtSignal()
def setBaseName(self, base_name: str, is_project_file: bool = False): def setBaseName(self, base_name: str, is_project_file: bool = False):
self._is_user_specified_job_name = False
# Ensure that we don't use entire path but only filename # Ensure that we don't use entire path but only filename
name = os.path.basename(base_name) name = os.path.basename(base_name)
@ -355,11 +357,12 @@ class PrintInformation(QObject):
data = mime_type.stripExtension(name) data = mime_type.stripExtension(name)
except: except:
Logger.log("w", "Unsupported Mime Type Database file extension") Logger.log("w", "Unsupported Mime Type Database file extension")
data = 'unnamed'
if data is not None and check_name is not None: if data is not None and check_name is not None:
self._base_name = data self._base_name = data
else: else:
self._base_name = '' self._base_name = 'unnamed'
self._updateJobName() self._updateJobName()

View file

@ -475,7 +475,7 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_definition = extruder_definitions[0] extruder_definition = extruder_definitions[0]
unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) if create_new_ids else machine.getName() + " " + new_extruder_id unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) if create_new_ids else machine.getName() + " " + new_extruder_id
extruder_stack = ExtruderStack.ExtruderStack(unique_name, parent = machine) extruder_stack = ExtruderStack.ExtruderStack(unique_name)
extruder_stack.setName(extruder_definition.getName()) extruder_stack.setName(extruder_definition.getName())
extruder_stack.setDefinition(extruder_definition) extruder_stack.setDefinition(extruder_definition)
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))

View file

@ -39,8 +39,8 @@ from . import Exceptions
# This also means that operations on the stack that modifies the container ordering is prohibited and # This also means that operations on the stack that modifies the container ordering is prohibited and
# will raise an exception. # will raise an exception.
class CuraContainerStack(ContainerStack): class CuraContainerStack(ContainerStack):
def __init__(self, container_id: str, *args, **kwargs): def __init__(self, container_id: str):
super().__init__(container_id, *args, **kwargs) super().__init__(container_id)
self._container_registry = ContainerRegistry.getInstance() self._container_registry = ContainerRegistry.getInstance()

View file

@ -99,8 +99,7 @@ class CuraStackBuilder:
position = position, position = position,
variant_container = extruder_variant_container, variant_container = extruder_variant_container,
material_container = material_container, material_container = material_container,
quality_container = application.empty_quality_container, quality_container = application.empty_quality_container
global_stack = new_global_stack,
) )
new_extruder.setNextStack(new_global_stack) new_extruder.setNextStack(new_global_stack)
new_global_stack.addExtruder(new_extruder) new_global_stack.addExtruder(new_extruder)
@ -139,11 +138,11 @@ class CuraStackBuilder:
@classmethod @classmethod
def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, machine_definition_id: str, def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, machine_definition_id: str,
position: int, position: int,
variant_container, material_container, quality_container, global_stack) -> ExtruderStack: variant_container, material_container, quality_container) -> ExtruderStack:
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance() application = CuraApplication.getInstance()
stack = ExtruderStack(new_stack_id, parent = global_stack) stack = ExtruderStack(new_stack_id)
stack.setName(extruder_definition.getName()) stack.setName(extruder_definition.getName())
stack.setDefinition(extruder_definition) stack.setDefinition(extruder_definition)

View file

@ -25,8 +25,8 @@ if TYPE_CHECKING:
# #
# #
class ExtruderStack(CuraContainerStack): class ExtruderStack(CuraContainerStack):
def __init__(self, container_id: str, *args, **kwargs): def __init__(self, container_id: str):
super().__init__(container_id, *args, **kwargs) super().__init__(container_id)
self.addMetaDataEntry("type", "extruder_train") # For backward compatibility self.addMetaDataEntry("type", "extruder_train") # For backward compatibility

View file

@ -23,8 +23,8 @@ from .CuraContainerStack import CuraContainerStack
## Represents the Global or Machine stack and its related containers. ## Represents the Global or Machine stack and its related containers.
# #
class GlobalStack(CuraContainerStack): class GlobalStack(CuraContainerStack):
def __init__(self, container_id: str, *args, **kwargs): def __init__(self, container_id: str):
super().__init__(container_id, *args, **kwargs) super().__init__(container_id)
self.addMetaDataEntry("type", "machine") # For backward compatibility self.addMetaDataEntry("type", "machine") # For backward compatibility

View file

@ -1041,6 +1041,10 @@ class MachineManager(QObject):
self.activeQualityChangesGroupChanged.emit() self.activeQualityChangesGroupChanged.emit()
def _setQualityGroup(self, quality_group, empty_quality_changes: bool = True) -> None: def _setQualityGroup(self, quality_group, empty_quality_changes: bool = True) -> None:
if quality_group is None:
self._setEmptyQuality()
return
if quality_group.node_for_global.getContainer() is None: if quality_group.node_for_global.getContainer() is None:
return return
for node in quality_group.nodes_for_extruders.values(): for node in quality_group.nodes_for_extruders.values():
@ -1051,10 +1055,6 @@ class MachineManager(QObject):
if empty_quality_changes: if empty_quality_changes:
self._current_quality_changes_group = None self._current_quality_changes_group = None
if quality_group is None:
self._setEmptyQuality()
return
# Set quality and quality_changes for the GlobalStack # Set quality and quality_changes for the GlobalStack
self._global_container_stack.quality = quality_group.node_for_global.getContainer() self._global_container_stack.quality = quality_group.node_for_global.getContainer()
if empty_quality_changes: if empty_quality_changes:
@ -1289,6 +1289,10 @@ class MachineManager(QObject):
self._global_container_stack.variant = self._empty_variant_container self._global_container_stack.variant = self._empty_variant_container
self._updateQualityWithMaterial() self._updateQualityWithMaterial()
# See if we need to show the Discard or Keep changes screen
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
self._application.discardOrKeepProfileChanges()
## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value' ## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value'
def replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None: def replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None:
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine") machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
@ -1340,6 +1344,10 @@ class MachineManager(QObject):
self._setMaterial(position, container_node) self._setMaterial(position, container_node)
self._updateQualityWithMaterial() self._updateQualityWithMaterial()
# See if we need to show the Discard or Keep changes screen
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
self._application.discardOrKeepProfileChanges()
@pyqtSlot(str, str) @pyqtSlot(str, str)
def setVariantByName(self, position: str, variant_name: str) -> None: def setVariantByName(self, position: str, variant_name: str) -> None:
machine_definition_id = self._global_container_stack.definition.id machine_definition_id = self._global_container_stack.definition.id
@ -1355,6 +1363,10 @@ class MachineManager(QObject):
self._updateMaterialWithVariant(position) self._updateMaterialWithVariant(position)
self._updateQualityWithMaterial() self._updateQualityWithMaterial()
# See if we need to show the Discard or Keep changes screen
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
self._application.discardOrKeepProfileChanges()
@pyqtSlot(str) @pyqtSlot(str)
def setQualityGroupByQualityType(self, quality_type: str) -> None: def setQualityGroupByQualityType(self, quality_type: str) -> None:
if self._global_container_stack is None: if self._global_container_stack is None:

View file

@ -15,7 +15,6 @@ from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Mesh.MeshReader import MeshReader from UM.Mesh.MeshReader import MeshReader
from UM.Scene.GroupDecorator import GroupDecorator from UM.Scene.GroupDecorator import GroupDecorator
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Scene.CuraSceneNode import CuraSceneNode
@ -26,15 +25,6 @@ from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
MYPY = False MYPY = False
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-cura-project-file",
comment = "Cura Project File",
suffixes = ["curaproject.3mf"]
)
)
try: try:
if not MYPY: if not MYPY:
import xml.etree.cElementTree as ET import xml.etree.cElementTree as ET

View file

@ -13,8 +13,24 @@ from . import ThreeMFWorkspaceReader
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Platform import Platform from UM.Platform import Platform
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-cura-project-file",
comment = "Cura Project File",
suffixes = ["curaproject.3mf"]
)
)
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-cura-project-file",
comment = "Cura Project File",
suffixes = ["3mf"]
)
)
def getMetaData() -> Dict: def getMetaData() -> Dict:
# Workarround for osx not supporting double file extensions correctly. # Workarround for osx not supporting double file extensions correctly.
if Platform.isOSX(): if Platform.isOSX():

View file

@ -64,20 +64,25 @@ class FirmwareUpdateCheckerJob(Job):
if (checked_version != "") and (checked_version != current_version): if (checked_version != "") and (checked_version != current_version):
Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE") Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE")
footer_text = i18n_catalog.i18nc("@action:info", "Read more on how to update printer firmware")
footer_link = "?url=https://ultimaker.com/en/resources/23129-updating-the-firmware?utm_source=cura&utm_medium=software&utm_campaign=hw-update"
footer_message = footer_text + " " + footer_link
message = Message(i18n_catalog.i18nc( message = Message(i18n_catalog.i18nc(
"@info Don't translate {machine_name}, since it gets replaced by a printer name!", "@info Don't translate {machine_name}, since it gets replaced by a printer name!",
"New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format( "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format(
machine_name=machine_name), machine_name=machine_name),
title=i18n_catalog.i18nc( title=i18n_catalog.i18nc(
"@info:title The %s gets replaced with the printer name.", "@info:title The %s gets replaced with the printer name.",
"New %s firmware available") % machine_name, "New %s firmware available") % machine_name)
footer = footer_message)
message.addAction("download",
i18n_catalog.i18nc("@action:button", "How to update"),
"[no_icon]",
"[no_description]",
button_style=Message.ActionButtonStyle.LINK,
button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT)
# If we do this in a cool way, the download url should be available in the JSON file
if self._set_download_url_callback:
self._set_download_url_callback("https://ultimaker.com/en/resources/20500-upgrade-firmware")
message.actionTriggered.connect(self._callback) message.actionTriggered.connect(self._callback)
message.show() message.show()

View file

@ -46,10 +46,11 @@ class SliceInfo(QObject, Extension):
dismissable = False, dismissable = False,
title = catalog.i18nc("@info:title", "Collecting Data")) title = catalog.i18nc("@info:title", "Collecting Data"))
self.send_slice_info_message.addAction("Dismiss", name = catalog.i18nc("@action:button", "Allow"), icon = None,
description = catalog.i18nc("@action:tooltip", "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."))
self.send_slice_info_message.addAction("MoreInfo", name = catalog.i18nc("@action:button", "More info"), icon = None, self.send_slice_info_message.addAction("MoreInfo", name = catalog.i18nc("@action:button", "More info"), icon = None,
description = catalog.i18nc("@action:tooltip", "See more information on what data Cura sends."), button_style = Message.ActionButtonStyle.LINK) description = catalog.i18nc("@action:tooltip", "See more information on what data Cura sends."), button_style = Message.ActionButtonStyle.LINK)
self.send_slice_info_message.addAction("Dismiss", name = catalog.i18nc("@action:button", "Allow"), icon = None,
description = catalog.i18nc("@action:tooltip", "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."))
self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered) self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
self.send_slice_info_message.show() self.send_slice_info_message.show()

View file

@ -31,8 +31,9 @@ Item
frameVisible: false frameVisible: false
selectionMode: 0 selectionMode: 0
model: packageData.supported_configs model: packageData.supported_configs
headerDelegate: Item headerDelegate: Rectangle
{ {
color: UM.Theme.getColor("sidebar")
height: UM.Theme.getSize("toolbox_chart_row").height height: UM.Theme.getSize("toolbox_chart_row").height
Label Label
{ {

View file

@ -12,6 +12,7 @@ Column
height: childrenRect.height height: childrenRect.height
width: parent.width width: parent.width
spacing: UM.Theme.getSize("default_margin").height spacing: UM.Theme.getSize("default_margin").height
/* Hidden for 3.4
Label Label
{ {
id: heading id: heading
@ -20,6 +21,7 @@ Column
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
} }
*/
GridLayout GridLayout
{ {
id: grid id: grid

View file

@ -18,6 +18,8 @@ ScrollView
spacing: UM.Theme.getSize("default_margin").height spacing: UM.Theme.getSize("default_margin").height
padding: UM.Theme.getSize("wide_margin").height padding: UM.Theme.getSize("wide_margin").height
height: childrenRect.height + 2 * padding height: childrenRect.height + 2 * padding
/* Hide for 3.4
ToolboxDownloadsShowcase ToolboxDownloadsShowcase
{ {
id: showcase id: showcase
@ -29,6 +31,8 @@ ScrollView
width: parent.width width: parent.width
height: UM.Theme.getSize("default_lining").height height: UM.Theme.getSize("default_lining").height
} }
*/
ToolboxDownloadsGrid ToolboxDownloadsGrid
{ {
id: allPlugins id: allPlugins

View file

@ -33,6 +33,8 @@ Item
toolbox.viewPage = "overview" toolbox.viewPage = "overview"
} }
} }
/* Hide for 3.4
ToolboxTabButton ToolboxTabButton
{ {
text: catalog.i18nc("@title:tab", "Materials") text: catalog.i18nc("@title:tab", "Materials")
@ -45,6 +47,7 @@ Item
toolbox.viewPage = "overview" toolbox.viewPage = "overview"
} }
} }
*/
} }
ToolboxTabButton ToolboxTabButton
{ {

View file

@ -65,6 +65,7 @@ ScrollView
} }
} }
} }
/* Hidden in 3.4
Label Label
{ {
visible: toolbox.materialsInstalledModel.items.length > 0 visible: toolbox.materialsInstalledModel.items.length > 0
@ -102,5 +103,6 @@ ScrollView
} }
} }
} }
*/
} }
} }

View file

@ -16,7 +16,7 @@ Item
{ {
color: UM.Theme.getColor("lining") color: UM.Theme.getColor("lining")
width: parent.width width: parent.width
height: UM.Theme.getSize("default_lining").height height: Math.floor(UM.Theme.getSize("default_lining").height)
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
} }
Row Row
@ -40,14 +40,14 @@ Item
Column Column
{ {
id: pluginInfo id: pluginInfo
topPadding: UM.Theme.getSize("default_margin").height / 2 topPadding: Math.floor(UM.Theme.getSize("default_margin").height / 2)
property var color: model.type === "plugin" && !isEnabled ? UM.Theme.getColor("lining") : UM.Theme.getColor("text") property var color: model.type === "plugin" && !isEnabled ? UM.Theme.getColor("lining") : UM.Theme.getColor("text")
width: tileRow.width - (authorInfo.width + pluginActions.width + 2 * tileRow.spacing + ((disableButton.visible) ? disableButton.width + tileRow.spacing : 0)) width: Math.floor(tileRow.width - (authorInfo.width + pluginActions.width + 2 * tileRow.spacing + ((disableButton.visible) ? disableButton.width + tileRow.spacing : 0)))
Label Label
{ {
text: model.name text: model.name
width: parent.width width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("default_bold")
color: pluginInfo.color color: pluginInfo.color
@ -81,7 +81,7 @@ Item
} }
} }
width: parent.width width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft

View file

@ -13,6 +13,16 @@ Column
width: UM.Theme.getSize("toolbox_action_button").width width: UM.Theme.getSize("toolbox_action_button").width
spacing: UM.Theme.getSize("narrow_margin").height spacing: UM.Theme.getSize("narrow_margin").height
Label
{
visible: !model.is_installed
text: catalog.i18nc("@label", "Will install upon restarting")
color: UM.Theme.getColor("lining")
font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap
width: parent.width
}
ToolboxProgressButton ToolboxProgressButton
{ {
id: updateButton id: updateButton
@ -39,7 +49,7 @@ Column
{ {
id: removeButton id: removeButton
text: canDowngrade ? catalog.i18nc("@action:button", "Downgrade") : catalog.i18nc("@action:button", "Uninstall") text: canDowngrade ? catalog.i18nc("@action:button", "Downgrade") : catalog.i18nc("@action:button", "Uninstall")
visible: !model.is_bundled visible: !model.is_bundled && model.is_installed
enabled: !toolbox.isDownloading enabled: !toolbox.isDownloading
style: ButtonStyle style: ButtonStyle
{ {

View file

@ -150,7 +150,7 @@ Item
{ {
id: loader id: loader
visible: active visible: active
source: "../images/loading.gif" source: visible ? "../images/loading.gif" : ""
width: UM.Theme.getSize("toolbox_loader").width width: UM.Theme.getSize("toolbox_loader").width
height: UM.Theme.getSize("toolbox_loader").height height: UM.Theme.getSize("toolbox_loader").height
anchors.right: button.left anchors.right: button.left

View file

@ -29,8 +29,9 @@ class PackagesModel(ListModel):
self.addRoleName(Qt.UserRole + 12, "last_updated") self.addRoleName(Qt.UserRole + 12, "last_updated")
self.addRoleName(Qt.UserRole + 13, "is_bundled") self.addRoleName(Qt.UserRole + 13, "is_bundled")
self.addRoleName(Qt.UserRole + 14, "is_enabled") self.addRoleName(Qt.UserRole + 14, "is_enabled")
self.addRoleName(Qt.UserRole + 15, "has_configs") self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
self.addRoleName(Qt.UserRole + 16, "supported_configs") self.addRoleName(Qt.UserRole + 16, "has_configs")
self.addRoleName(Qt.UserRole + 17, "supported_configs")
# List of filters for queries. The result is the union of the each list of results. # List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str, str] self._filter = {} # type: Dict[str, str]
@ -73,6 +74,7 @@ class PackagesModel(ListModel):
"last_updated": package["last_updated"] if "last_updated" in package else None, "last_updated": package["last_updated"] if "last_updated" in package else None,
"is_bundled": package["is_bundled"] if "is_bundled" in package else False, "is_bundled": package["is_bundled"] if "is_bundled" in package else False,
"is_enabled": package["is_enabled"] if "is_enabled" in package else False, "is_enabled": package["is_enabled"] if "is_enabled" in package else False,
"is_installed": package["is_installed"] if "is_installed" in package else False,
"has_configs": has_configs, "has_configs": has_configs,
"supported_configs": configs_model "supported_configs": configs_model
}) })

View file

@ -28,7 +28,8 @@ i18n_catalog = i18nCatalog("cura")
## The Toolbox class is responsible of communicating with the server through the API ## The Toolbox class is responsible of communicating with the server through the API
class Toolbox(QObject, Extension): class Toolbox(QObject, Extension):
DEFAULT_PACKAGES_API_ROOT = "https://api.ultimaker.com" DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com"
DEFAULT_CLOUD_API_VERSION = 1
def __init__(self, parent=None) -> None: def __init__(self, parent=None) -> None:
super().__init__(parent) super().__init__(parent)
@ -37,16 +38,10 @@ class Toolbox(QObject, Extension):
self._package_manager = None self._package_manager = None
self._plugin_registry = Application.getInstance().getPluginRegistry() self._plugin_registry = Application.getInstance().getPluginRegistry()
self._sdk_version = self._getPackagesVersion() self._sdk_version = None
self._cloud_api_version = None
self._cloud_api_version = 1 self._cloud_api_root = None
self._cloud_api_root = self._getPackagesApiRoot() self._api_url = None
self._api_url = "{cloud_api_root}/cura-packages/v{cloud_api_version}/cura/v{sdk_version}".format(
cloud_api_root = self._cloud_api_root,
cloud_api_version = self._cloud_api_version,
sdk_version = self._sdk_version
)
# Network: # Network:
self._get_packages_request = None self._get_packages_request = None
@ -67,12 +62,7 @@ class Toolbox(QObject, Extension):
) )
) )
] ]
self._request_urls = { self._request_urls = {}
"authors": QUrl("{base_url}/authors".format(base_url = self._api_url)),
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url)),
"plugins_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)),
"materials_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url))
}
self._to_update = [] # Package_ids that are waiting to be updated self._to_update = [] # Package_ids that are waiting to be updated
# Data: # Data:
@ -164,22 +154,50 @@ class Toolbox(QObject, Extension):
# this is initialized. Therefore, we wait until the application is ready. # this is initialized. Therefore, we wait until the application is ready.
def _onAppInitialized(self) -> None: def _onAppInitialized(self) -> None:
self._package_manager = Application.getInstance().getCuraPackageManager() self._package_manager = Application.getInstance().getCuraPackageManager()
self._sdk_version = self._getSDKVersion()
self._cloud_api_version = self._getCloudAPIVersion()
self._cloud_api_root = self._getCloudAPIRoot()
self._api_url = "{cloud_api_root}/cura-packages/v{cloud_api_version}/cura/v{sdk_version}".format(
cloud_api_root=self._cloud_api_root,
cloud_api_version=self._cloud_api_version,
sdk_version=self._sdk_version
)
self._request_urls = {
"authors": QUrl("{base_url}/authors".format(base_url=self._api_url)),
"packages": QUrl("{base_url}/packages".format(base_url=self._api_url)),
"plugins_showcase": QUrl("{base_url}/showcase".format(base_url=self._api_url)),
"materials_showcase": QUrl("{base_url}/showcase".format(base_url=self._api_url))
}
# Get the API root for the packages API depending on Cura version settings. # Get the API root for the packages API depending on Cura version settings.
def _getPackagesApiRoot(self) -> str: def _getCloudAPIRoot(self) -> str:
if not hasattr(cura, "CuraVersion"): if not hasattr(cura, "CuraVersion"):
return self.DEFAULT_PACKAGES_API_ROOT return self.DEFAULT_CLOUD_API_ROOT
if not hasattr(cura.CuraVersion, "CuraPackagesApiRoot"): if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"):
return self.DEFAULT_PACKAGES_API_ROOT return self.DEFAULT_CLOUD_API_ROOT
return cura.CuraVersion.CuraPackagesApiRoot if not cura.CuraVersion.CuraCloudAPIRoot:
return self.DEFAULT_CLOUD_API_ROOT
return cura.CuraVersion.CuraCloudAPIRoot
# Get the cloud API version from CuraVersion
def _getCloudAPIVersion(self) -> int:
if not hasattr(cura, "CuraVersion"):
return self.DEFAULT_CLOUD_API_VERSION
if not hasattr(cura.CuraVersion, "CuraCloudAPIVersion"):
return self.DEFAULT_CLOUD_API_VERSION
if not cura.CuraVersion.CuraCloudAPIVersion:
return self.DEFAULT_CLOUD_API_VERSION
return cura.CuraVersion.CuraCloudAPIVersion
# Get the packages version depending on Cura version settings. # Get the packages version depending on Cura version settings.
def _getPackagesVersion(self) -> int: def _getSDKVersion(self) -> int:
if not hasattr(cura, "CuraVersion"): if not hasattr(cura, "CuraVersion"):
return self._plugin_registry.APIVersion return self._plugin_registry.APIVersion
if not hasattr(cura.CuraVersion, "CuraPackagesVersion"): if not hasattr(cura.CuraVersion, "CuraSDKVersion"):
return self._plugin_registry.APIVersion return self._plugin_registry.APIVersion
return cura.CuraVersion.CuraPackagesVersion if not cura.CuraVersion.CuraSDKVersion:
return self._plugin_registry.APIVersion
return cura.CuraVersion.CuraSDKVersion
@pyqtSlot() @pyqtSlot()
def browsePackages(self) -> None: def browsePackages(self) -> None:

View file

@ -6,6 +6,9 @@ import io #To serialise the preference files afterwards.
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this. from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
_renamed_settings = {
"infill_hollow": "infill_support_enabled"
}
## Upgrades configurations from the state they were in at version 3.3 to the ## Upgrades configurations from the state they were in at version 3.3 to the
# state they should be in at version 3.4. # state they should be in at version 3.4.
@ -38,6 +41,17 @@ class VersionUpgrade33to34(VersionUpgrade):
# Update version number. # Update version number.
parser["general"]["version"] = "4" parser["general"]["version"] = "4"
if "values" in parser:
#If infill_hollow was enabled and the overhang angle was adjusted, copy that overhang angle to the new infill support angle.
if "infill_hollow" in parser["values"] and parser["values"]["infill_hollow"] and "support_angle" in parser["values"]:
parser["values"]["infill_support_angle"] = parser["values"]["support_angle"]
#Renamed settings.
for original, replacement in _renamed_settings.items():
if original in parser["values"]:
parser["values"][replacement] = parser["values"][original]
del parser["values"][original]
result = io.StringIO() result = io.StringIO()
parser.write(result) parser.write(result)
return [filename], [result.getvalue()] return [filename], [result.getvalue()]

View file

@ -1807,6 +1807,30 @@
"limit_to_extruder": "infill_extruder_nr", "limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"infill_support_enabled":
{
"label": "Infill Support",
"description": "Print infill structures only where tops of the model should be supported. Enabling this reduces print time and material usage, but leads to ununiform object strength.",
"type": "bool",
"default_value": false,
"enabled": "infill_sparse_density > 0",
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"infill_support_angle":
{
"label": "Infill Overhang Angle",
"description": "The minimum angle of internal overhangs for which infill is added. At a value of 0° objects are totally filled with infill, 90° will not provide any infill.",
"unit": "°",
"type": "float",
"minimum_value": "0",
"minimum_value_warning": "2",
"maximum_value": "90",
"default_value": 40,
"enabled": "infill_sparse_density > 0 and infill_support_enabled",
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"skin_preshrink": "skin_preshrink":
{ {
"label": "Skin Removal Width", "label": "Skin Removal Width",
@ -5788,7 +5812,7 @@
"description": "The file location of an image of which the brightness values determine the minimal density at the corresponding location in the support.", "description": "The file location of an image of which the brightness values determine the minimal density at the corresponding location in the support.",
"type": "str", "type": "str",
"default_value": "", "default_value": "",
"enabled": "infill_pattern == 'cross' or infill_pattern == 'cross_3d'", "enabled": "support_pattern == 'cross' or support_pattern == 'cross_3d'",
"limit_to_extruder": "support_infill_extruder_nr", "limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
@ -5918,14 +5942,6 @@
"limit_to_extruder": "support_infill_extruder_nr", "limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"infill_hollow":
{
"label": "Hollow Out Objects",
"description": "Remove all infill and make the inside of the object eligible for support.",
"type": "bool",
"default_value": false,
"settable_per_mesh": true
},
"magic_fuzzy_skin_enabled": "magic_fuzzy_skin_enabled":
{ {
"label": "Fuzzy Skin", "label": "Fuzzy Skin",
@ -6662,14 +6678,6 @@
"type": "float", "type": "float",
"enabled": "bridge_settings_enabled and bridge_enable_more_layers", "enabled": "bridge_settings_enabled and bridge_enable_more_layers",
"settable_per_mesh": true "settable_per_mesh": true
},
"wall_try_line_thickness":
{
"label": "Try Multiple Line Thicknesses",
"description": "When creating inner walls, try various line thicknesses to fit the wall lines better in narrow spaces. This reduces or increases the inner wall line width by up to 0.01mm.",
"default_value": false,
"type": "bool",
"settable_per_mesh": true
} }
} }
}, },

View file

@ -323,10 +323,11 @@ UM.MainWindow
{ {
if (drop.urls.length > 0) if (drop.urls.length > 0)
{ {
// As the drop area also supports plugins, first check if it's a plugin that was dropped.
if (drop.urls.length == 1) var nonPackages = [];
for (var i = 0; i < drop.urls.length; i++)
{ {
var filename = drop.urls[0]; var filename = drop.urls[i];
if (filename.endsWith(".curapackage")) if (filename.endsWith(".curapackage"))
{ {
// Try to install plugin & close. // Try to install plugin & close.
@ -334,11 +335,13 @@ UM.MainWindow
packageInstallDialog.text = catalog.i18nc("@label", "This package will be installed after restarting."); packageInstallDialog.text = catalog.i18nc("@label", "This package will be installed after restarting.");
packageInstallDialog.icon = StandardIcon.Information; packageInstallDialog.icon = StandardIcon.Information;
packageInstallDialog.open(); packageInstallDialog.open();
return; }
else
{
nonPackages.push(filename);
} }
} }
openDialog.handleOpenFileUrls(nonPackages);
openDialog.handleOpenFileUrls(drop.urls);
} }
} }
} }

View file

@ -81,10 +81,9 @@ Item {
text: PrintInformation.jobName text: PrintInformation.jobName
horizontalAlignment: TextInput.AlignRight horizontalAlignment: TextInput.AlignRight
onEditingFinished: { onEditingFinished: {
PrintInformation.setJobName(text, true); text = text == "" ? "unnamed" : text;
if (printJobTextfield.text != ''){ PrintInformation.setJobName(printJobTextfield.text, true);
printJobTextfield.focus = false; printJobTextfield.focus = false;
}
} }
validator: RegExpValidator { validator: RegExpValidator {
regExp: /^[^\\ \/ \*\?\|\[\]]*$/ regExp: /^[^\\ \/ \*\?\|\[\]]*$/

View file

@ -92,6 +92,7 @@ Rectangle
anchors.verticalCenter: buildplateIcon.verticalCenter anchors.verticalCenter: buildplateIcon.verticalCenter
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").height / 2) anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").height / 2)
text: configuration.buildplateConfiguration text: configuration.buildplateConfiguration
renderType: Text.NativeRendering
color: textColor color: textColor
} }
} }

View file

@ -26,6 +26,7 @@ Column
{ {
id: extruderLabel id: extruderLabel
text: catalog.i18nc("@label:extruder label", "Extruder") text: catalog.i18nc("@label:extruder label", "Extruder")
renderType: Text.NativeRendering
elide: Text.ElideRight elide: Text.ElideRight
anchors.left: parent.left anchors.left: parent.left
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
@ -59,6 +60,7 @@ Column
id: extruderNumberText id: extruderNumberText
anchors.centerIn: parent anchors.centerIn: parent
text: printCoreConfiguration.position + 1 text: printCoreConfiguration.position + 1
renderType: Text.NativeRendering
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: mainColor color: mainColor
} }
@ -69,6 +71,7 @@ Column
{ {
id: materialLabel id: materialLabel
text: printCoreConfiguration.material.name text: printCoreConfiguration.material.name
renderType: Text.NativeRendering
elide: Text.ElideRight elide: Text.ElideRight
width: parent.width width: parent.width
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("default_bold")
@ -79,6 +82,7 @@ Column
{ {
id: printCoreTypeLabel id: printCoreTypeLabel
text: printCoreConfiguration.hotendID text: printCoreConfiguration.hotendID
renderType: Text.NativeRendering
elide: Text.ElideRight elide: Text.ElideRight
width: parent.width width: parent.width
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")

View file

@ -13,7 +13,7 @@ Button
id: base id: base
property var outputDevice: null property var outputDevice: null
property var matched: updateOnSync() property var matched: updateOnSync()
text: matched == true ? "Yes" : "No" text: matched == true ? catalog.i18nc("@label:extruder label", "Yes") : catalog.i18nc("@label:extruder label", "No")
width: parent.width width: parent.width
height: parent.height height: parent.height

View file

@ -63,8 +63,7 @@ Menu
exclusiveGroup: group exclusiveGroup: group
onTriggered: onTriggered:
{ {
var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex; Cura.MachineManager.setMaterial(extruderIndex, model.container_node);
Cura.MachineManager.setMaterial(activeExtruderIndex, model.container_node);
} }
} }
onObjectAdded: brandMaterialsMenu.insertItem(index, object) onObjectAdded: brandMaterialsMenu.insertItem(index, object)

View file

@ -100,8 +100,8 @@ Item {
if (saveToButton.enabled) { if (saveToButton.enabled) {
saveToButton.clicked(); saveToButton.clicked();
} }
// prepare button // slice button
if (prepareButton.enabled) { if (sliceButton.enabled) {
sliceOrStopSlicing(); sliceOrStopSlicing();
} }
} }
@ -131,7 +131,7 @@ Item {
Row { Row {
id: additionalComponentsRow id: additionalComponentsRow
anchors.top: parent.top anchors.top: parent.top
anchors.right: saveToButton.visible ? saveToButton.left : (prepareButton.visible ? prepareButton.left : parent.right) anchors.right: saveToButton.visible ? saveToButton.left : (sliceButton.visible ? sliceButton.left : parent.right)
anchors.rightMargin: UM.Theme.getSize("default_margin").width anchors.rightMargin: UM.Theme.getSize("default_margin").width
spacing: UM.Theme.getSize("default_margin").width spacing: UM.Theme.getSize("default_margin").width
@ -159,14 +159,14 @@ Item {
onPreferenceChanged: onPreferenceChanged:
{ {
var autoSlice = UM.Preferences.getValue("general/auto_slice"); var autoSlice = UM.Preferences.getValue("general/auto_slice");
prepareButton.autoSlice = autoSlice; sliceButton.autoSlice = autoSlice;
saveToButton.autoSlice = autoSlice; saveToButton.autoSlice = autoSlice;
} }
} }
// Prepare button, only shows if auto_slice is off // Slice button, only shows if auto_slice is off
Button { Button {
id: prepareButton id: sliceButton
tooltip: [1, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@info:tooltip","Slice current printjob") : catalog.i18nc("@info:tooltip","Cancel slicing process") tooltip: [1, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@info:tooltip","Slice current printjob") : catalog.i18nc("@info:tooltip","Cancel slicing process")
// 1 = not started, 2 = Processing // 1 = not started, 2 = Processing
@ -180,7 +180,7 @@ Item {
anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
// 1 = not started, 4 = error, 5 = disabled // 1 = not started, 4 = error, 5 = disabled
text: [1, 4, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@label:Printjob", "Prepare") : catalog.i18nc("@label:Printjob", "Cancel") text: [1, 4, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@label:Printjob", "Slice") : catalog.i18nc("@label:Printjob", "Cancel")
onClicked: onClicked:
{ {
sliceOrStopSlicing(); sliceOrStopSlicing();

View file

@ -87,6 +87,7 @@ gradual_infill_steps
gradual_infill_step_height gradual_infill_step_height
infill_before_walls infill_before_walls
min_infill_area min_infill_area
infill_support_enabled
skin_preshrink skin_preshrink
top_skin_preshrink top_skin_preshrink
bottom_skin_preshrink bottom_skin_preshrink
@ -369,7 +370,6 @@ spaghetti_infill_extra_volume
support_conical_enabled support_conical_enabled
support_conical_angle support_conical_angle
support_conical_min_width support_conical_min_width
infill_hollow
magic_fuzzy_skin_enabled magic_fuzzy_skin_enabled
magic_fuzzy_skin_thickness magic_fuzzy_skin_thickness
magic_fuzzy_skin_point_density magic_fuzzy_skin_point_density