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_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(cura/CuraVersion.py.in CuraVersion.py @ONLY)

View file

@ -3,6 +3,7 @@
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger
from UM.Math.Polygon import Polygon
from UM.Math.Vector import Vector
from cura.Arranging.ShapeArray import ShapeArray
from cura.Scene import ZOffsetDecorator
@ -45,7 +46,7 @@ class Arrange:
# \param scene_root Root for finding all scene nodes
# \param fixed_nodes Scene nodes to be placed
@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.centerFirst()
@ -58,9 +59,10 @@ class Arrange:
# Place all objects 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:
continue
vertices = vertices.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
points = copy.deepcopy(vertices._points)
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
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)
# return the nodes that should be placed
# \param node
# \param offset_shape_arr ShapeArray with offset, used to find location
# \param hull_shape_arr ShapeArray without offset, for placing the shape
# \param offset_shape_arr ShapeArray with 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):
new_node = copy.deepcopy(node)
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
# Save the last priority.
@ -102,7 +104,7 @@ class Arrange:
if x is not None: # We could find a place
new_node.setPosition(Vector(x, center_y, y))
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:
Logger.log("d", "Could not find spot!"),
found_spot = False

View file

@ -110,7 +110,7 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
arrange_array.add()
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
node.removeDecorator(ZOffsetDecorator)
if node.getBoundingBox():
@ -118,7 +118,7 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
else:
center_y = 0
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)
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_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
nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
for node in self._nodes:
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))
# Sort the nodes with the biggest area first.
@ -63,7 +66,7 @@ class ArrangeObjectsJob(Job):
start_priority = last_priority
else:
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
node.removeDecorator(ZOffsetDecorator)
if node.getBoundingBox():
@ -74,7 +77,7 @@ class ArrangeObjectsJob(Job):
last_size = size
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))
else:
Logger.log("d", "Arrange all: could not find spot!")

View file

@ -452,7 +452,7 @@ class CuraApplication(QtApplication):
## 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 = []):
self._message_box_callback = callback
@ -1500,11 +1500,15 @@ class CuraApplication(QtApplication):
def _reloadMeshFinished(self, job):
# 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()
if mesh_data:
job._node.setMeshData(mesh_data)
else:
job_result = job.getResult()
if len(job_result) == 0:
Logger.log("e", "Reloading the mesh failed.")
return
mesh_data = job_result[0].getMeshData()
if not mesh_data:
Logger.log("w", "Could not find a mesh in reloaded node.")
return
job._node.setMeshData(mesh_data)
def _openFile(self, filename):
self.readLocalFile(QUrl.fromLocalFile(filename))
@ -1568,10 +1572,11 @@ class CuraApplication(QtApplication):
f = file.toLocalFile()
extension = os.path.splitext(f)[1]
extension = extension.lower()
filename = os.path.basename(f)
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 extension.lower() in self._non_sliceable_extensions:
if extension in self._non_sliceable_extensions:
message = Message(
self._i18n_catalog.i18nc("@info:status",
"Only one G-code file can be loaded at a time. Skipped importing {0}",
@ -1580,7 +1585,8 @@ class CuraApplication(QtApplication):
return
# 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]
if extension.lower() in self._non_sliceable_extensions:
extension = extension.lower()
if extension in self._non_sliceable_extensions:
message = Message(
self._i18n_catalog.i18nc("@info:status",
"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
if package_id in self._bundled_package_dict:
package_info = self._bundled_package_dict[package_id]["package_info"]
package_info["is_installed"] = True
# Add installed plugins
if package_id in self._installed_package_dict:
package_info = self._installed_package_dict[package_id]["package_info"]
package_info["is_installed"] = True
# Add to install plugins
if package_id in self._to_install_package_dict:
package_info = self._to_install_package_dict[package_id]["package_info"]
package_info["is_installed"] = False
if package_info is None:
continue

View file

@ -4,4 +4,6 @@
CuraVersion = "@CURA_VERSION@"
CuraBuildType = "@CURA_BUILDTYPE@"
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()
for node in nodes_to_check:
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():
fallback_id = self.getFallbackMaterialIdByMaterialType(node.metadata["material"])
if fallback_id in machine_exclude_materials:
if material_id in machine_exclude_materials:
Logger.log("d", "Exclude material [%s] for machine [%s]",
material_id, machine_definition.getId())
continue

View file

@ -39,6 +39,8 @@ class BaseMaterialsModel(ListModel):
self._extruder_position = 0
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):
global_stack = self._machine_manager.activeMachine
@ -50,9 +52,11 @@ class BaseMaterialsModel(ListModel):
self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
if self._extruder_stack is not None:
self._extruder_stack.pyqtContainersChanged.connect(self._update)
# Force update the model when the extruder stack changes
self._update()
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._updateExtruderStack()
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.
import copy
from UM.Job import Job
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Message import Message
@ -36,7 +38,7 @@ class MultiplyObjectsJob(Job):
root = scene.getRoot()
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 = []
nodes = []
@ -64,6 +66,8 @@ class MultiplyObjectsJob(Job):
# We do place the nodes one by one, as we want to yield in between.
if not node_too_big:
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:
found_solution_for_all = False
new_location = new_node.getPosition()

View file

@ -329,6 +329,8 @@ class PrintInformation(QObject):
baseNameChanged = pyqtSignal()
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
name = os.path.basename(base_name)
@ -355,11 +357,12 @@ class PrintInformation(QObject):
data = mime_type.stripExtension(name)
except:
Logger.log("w", "Unsupported Mime Type Database file extension")
data = 'unnamed'
if data is not None and check_name is not None:
self._base_name = data
else:
self._base_name = ''
self._base_name = 'unnamed'
self._updateJobName()

View file

@ -475,7 +475,7 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_definition = extruder_definitions[0]
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.setDefinition(extruder_definition)
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
# will raise an exception.
class CuraContainerStack(ContainerStack):
def __init__(self, container_id: str, *args, **kwargs):
super().__init__(container_id, *args, **kwargs)
def __init__(self, container_id: str):
super().__init__(container_id)
self._container_registry = ContainerRegistry.getInstance()

View file

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

View file

@ -25,8 +25,8 @@ if TYPE_CHECKING:
#
#
class ExtruderStack(CuraContainerStack):
def __init__(self, container_id: str, *args, **kwargs):
super().__init__(container_id, *args, **kwargs)
def __init__(self, container_id: str):
super().__init__(container_id)
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.
#
class GlobalStack(CuraContainerStack):
def __init__(self, container_id: str, *args, **kwargs):
super().__init__(container_id, *args, **kwargs)
def __init__(self, container_id: str):
super().__init__(container_id)
self.addMetaDataEntry("type", "machine") # For backward compatibility

View file

@ -1041,6 +1041,10 @@ class MachineManager(QObject):
self.activeQualityChangesGroupChanged.emit()
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:
return
for node in quality_group.nodes_for_extruders.values():
@ -1051,10 +1055,6 @@ class MachineManager(QObject):
if empty_quality_changes:
self._current_quality_changes_group = None
if quality_group is None:
self._setEmptyQuality()
return
# Set quality and quality_changes for the GlobalStack
self._global_container_stack.quality = quality_group.node_for_global.getContainer()
if empty_quality_changes:
@ -1289,6 +1289,10 @@ class MachineManager(QObject):
self._global_container_stack.variant = self._empty_variant_container
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'
def replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None:
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
@ -1340,6 +1344,10 @@ class MachineManager(QObject):
self._setMaterial(position, container_node)
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)
def setVariantByName(self, position: str, variant_name: str) -> None:
machine_definition_id = self._global_container_stack.definition.id
@ -1355,6 +1363,10 @@ class MachineManager(QObject):
self._updateMaterialWithVariant(position)
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)
def setQualityGroupByQualityType(self, quality_type: str) -> 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.MeshReader import MeshReader
from UM.Scene.GroupDecorator import GroupDecorator
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Scene.CuraSceneNode import CuraSceneNode
@ -26,15 +25,6 @@ from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
MYPY = False
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-cura-project-file",
comment = "Cura Project File",
suffixes = ["curaproject.3mf"]
)
)
try:
if not MYPY:
import xml.etree.cElementTree as ET

View file

@ -13,8 +13,24 @@ from . import ThreeMFWorkspaceReader
from UM.i18n import i18nCatalog
from UM.Platform import Platform
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
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:
# Workarround for osx not supporting double file extensions correctly.
if Platform.isOSX():

View file

@ -64,20 +64,25 @@ class FirmwareUpdateCheckerJob(Job):
if (checked_version != "") and (checked_version != current_version):
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(
"@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(
machine_name=machine_name),
title=i18n_catalog.i18nc(
"@info:title The %s gets replaced with the printer name.",
"New %s firmware available") % machine_name,
footer = footer_message)
"New %s firmware available") % machine_name)
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.show()

View file

@ -46,10 +46,11 @@ class SliceInfo(QObject, Extension):
dismissable = False,
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,
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.show()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ Item
{
color: UM.Theme.getColor("lining")
width: parent.width
height: UM.Theme.getSize("default_lining").height
height: Math.floor(UM.Theme.getSize("default_lining").height)
anchors.bottom: parent.bottom
}
Row
@ -40,14 +40,14 @@ Item
Column
{
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")
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
{
text: model.name
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
wrapMode: Text.WordWrap
font: UM.Theme.getFont("default_bold")
color: pluginInfo.color
@ -81,7 +81,7 @@ Item
}
}
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft

View file

@ -13,6 +13,16 @@ Column
width: UM.Theme.getSize("toolbox_action_button").width
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
{
id: updateButton
@ -39,7 +49,7 @@ Column
{
id: removeButton
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
style: ButtonStyle
{

View file

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

View file

@ -29,8 +29,9 @@ class PackagesModel(ListModel):
self.addRoleName(Qt.UserRole + 12, "last_updated")
self.addRoleName(Qt.UserRole + 13, "is_bundled")
self.addRoleName(Qt.UserRole + 14, "is_enabled")
self.addRoleName(Qt.UserRole + 15, "has_configs")
self.addRoleName(Qt.UserRole + 16, "supported_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, "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.
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,
"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_installed": package["is_installed"] if "is_installed" in package else False,
"has_configs": has_configs,
"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
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:
super().__init__(parent)
@ -37,16 +38,10 @@ class Toolbox(QObject, Extension):
self._package_manager = None
self._plugin_registry = Application.getInstance().getPluginRegistry()
self._sdk_version = self._getPackagesVersion()
self._cloud_api_version = 1
self._cloud_api_root = self._getPackagesApiRoot()
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._sdk_version = None
self._cloud_api_version = None
self._cloud_api_root = None
self._api_url = None
# Network:
self._get_packages_request = None
@ -67,12 +62,7 @@ class Toolbox(QObject, Extension):
)
)
]
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._request_urls = {}
self._to_update = [] # Package_ids that are waiting to be updated
# Data:
@ -164,22 +154,50 @@ class Toolbox(QObject, Extension):
# this is initialized. Therefore, we wait until the application is ready.
def _onAppInitialized(self) -> None:
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.
def _getPackagesApiRoot(self) -> str:
def _getCloudAPIRoot(self) -> str:
if not hasattr(cura, "CuraVersion"):
return self.DEFAULT_PACKAGES_API_ROOT
if not hasattr(cura.CuraVersion, "CuraPackagesApiRoot"):
return self.DEFAULT_PACKAGES_API_ROOT
return cura.CuraVersion.CuraPackagesApiRoot
return self.DEFAULT_CLOUD_API_ROOT
if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"):
return self.DEFAULT_CLOUD_API_ROOT
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.
def _getPackagesVersion(self) -> int:
def _getSDKVersion(self) -> int:
if not hasattr(cura, "CuraVersion"):
return self._plugin_registry.APIVersion
if not hasattr(cura.CuraVersion, "CuraPackagesVersion"):
if not hasattr(cura.CuraVersion, "CuraSDKVersion"):
return self._plugin_registry.APIVersion
return cura.CuraVersion.CuraPackagesVersion
if not cura.CuraVersion.CuraSDKVersion:
return self._plugin_registry.APIVersion
return cura.CuraVersion.CuraSDKVersion
@pyqtSlot()
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.
_renamed_settings = {
"infill_hollow": "infill_support_enabled"
}
## Upgrades configurations from the state they were in at version 3.3 to the
# state they should be in at version 3.4.
@ -38,6 +41,17 @@ class VersionUpgrade33to34(VersionUpgrade):
# Update version number.
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()
parser.write(result)
return [filename], [result.getvalue()]

View file

@ -1807,6 +1807,30 @@
"limit_to_extruder": "infill_extruder_nr",
"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":
{
"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.",
"type": "str",
"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",
"settable_per_mesh": false,
"settable_per_extruder": true
@ -5918,14 +5942,6 @@
"limit_to_extruder": "support_infill_extruder_nr",
"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":
{
"label": "Fuzzy Skin",
@ -6662,14 +6678,6 @@
"type": "float",
"enabled": "bridge_settings_enabled and bridge_enable_more_layers",
"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)
{
// 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"))
{
// Try to install plugin & close.
@ -334,11 +335,13 @@ UM.MainWindow
packageInstallDialog.text = catalog.i18nc("@label", "This package will be installed after restarting.");
packageInstallDialog.icon = StandardIcon.Information;
packageInstallDialog.open();
return;
}
else
{
nonPackages.push(filename);
}
}
openDialog.handleOpenFileUrls(drop.urls);
openDialog.handleOpenFileUrls(nonPackages);
}
}
}

View file

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

View file

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

View file

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

View file

@ -13,7 +13,7 @@ Button
id: base
property var outputDevice: null
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
height: parent.height

View file

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

View file

@ -100,8 +100,8 @@ Item {
if (saveToButton.enabled) {
saveToButton.clicked();
}
// prepare button
if (prepareButton.enabled) {
// slice button
if (sliceButton.enabled) {
sliceOrStopSlicing();
}
}
@ -131,7 +131,7 @@ Item {
Row {
id: additionalComponentsRow
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
spacing: UM.Theme.getSize("default_margin").width
@ -159,14 +159,14 @@ Item {
onPreferenceChanged:
{
var autoSlice = UM.Preferences.getValue("general/auto_slice");
prepareButton.autoSlice = autoSlice;
sliceButton.autoSlice = autoSlice;
saveToButton.autoSlice = autoSlice;
}
}
// Prepare button, only shows if auto_slice is off
// Slice button, only shows if auto_slice is off
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")
// 1 = not started, 2 = Processing
@ -180,7 +180,7 @@ Item {
anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
// 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:
{
sliceOrStopSlicing();

View file

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