Merge branch 'Ultimaker:master' into master

This commit is contained in:
goofoo3d 2022-03-30 14:05:05 +08:00 committed by GitHub
commit 247269fbee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
906 changed files with 49141 additions and 43655 deletions

View file

@ -34,7 +34,7 @@ Build scripts
-------------
Please check out [cura-build](https://github.com/Ultimaker/cura-build) for detailed building instructions.
If you want to build the entire environment from scratch before building Cura as well, [cura-build-environment](https://github.com/Ultimaker/cura-build) might be a starting point before cura-build. (Again, see cura-build for more details.)
If you want to build the entire environment from scratch before building Cura as well, [cura-build-environment](https://github.com/Ultimaker/cura-build-environment) might be a starting point before cura-build. (Again, see cura-build for more details.)
Running from Source
-------------

5
SECURITY.md Normal file
View file

@ -0,0 +1,5 @@
# Reporting vulnerabilities
If you discover a vulnerability, please let us know as soon as possible via`security@ultimaker.com`.
Please do not take advantage of the vulnerability and do not reveal the problem to others.
To allow us to resolve the issue, please do provide us with sufficient information to reproduce the problem.

View file

@ -1,258 +0,0 @@
# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from UM.Decorators import deprecated
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 UM.Scene.SceneNode import SceneNode
from cura.Arranging.ShapeArray import ShapeArray
from cura.BuildVolume import BuildVolume
from cura.Scene import ZOffsetDecorator
from collections import namedtuple
import numpy
import copy
LocationSuggestion = namedtuple("LocationSuggestion", ["x", "y", "penalty_points", "priority"])
"""Return object for bestSpot"""
class Arrange:
"""
The Arrange classed is used together with :py:class:`cura.Arranging.ShapeArray.ShapeArray`. Use it to find good locations for objects that you try to put
on a build place. Different priority schemes can be defined so it alters the behavior while using the same logic.
.. note::
Make sure the scale is the same between :py:class:`cura.Arranging.ShapeArray.ShapeArray` objects and the :py:class:`cura.Arranging.Arrange.Arrange` instance.
"""
build_volume = None # type: Optional[BuildVolume]
@deprecated("Use the functions in Nest2dArrange instead", "4.8")
def __init__(self, x, y, offset_x, offset_y, scale = 0.5):
self._scale = scale # convert input coordinates to arrange coordinates
world_x, world_y = int(x * self._scale), int(y * self._scale)
self._shape = (world_y, world_x)
self._priority = numpy.zeros((world_y, world_x), dtype=numpy.int32) # beware: these are indexed (y, x)
self._priority_unique_values = []
self._occupied = numpy.zeros((world_y, world_x), dtype=numpy.int32) # beware: these are indexed (y, x)
self._offset_x = int(offset_x * self._scale)
self._offset_y = int(offset_y * self._scale)
self._last_priority = 0
self._is_empty = True
@classmethod
@deprecated("Use the functions in Nest2dArrange instead", "4.8")
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250, min_offset = 8) -> "Arrange":
"""Helper to create an :py:class:`cura.Arranging.Arrange.Arrange` instance
Either fill in scene_root and create will find all sliceable nodes by itself, or use fixed_nodes to provide the
nodes yourself.
:param scene_root: Root for finding all scene nodes default = None
:param fixed_nodes: Scene nodes to be placed default = None
:param scale: default = 0.5
:param x: default = 350
:param y: default = 250
:param min_offset: default = 8
"""
arranger = Arrange(x, y, x // 2, y // 2, scale = scale)
arranger.centerFirst()
if fixed_nodes is None:
fixed_nodes = []
for node_ in DepthFirstIterator(scene_root):
# Only count sliceable objects
if node_.callDecoration("isSliceable"):
fixed_nodes.append(node_)
# Place all objects fixed nodes
for fixed_node in fixed_nodes:
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)
# After scaling (like up to 0.1 mm) the node might not have points
if not points.size:
continue
try:
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
except ValueError:
Logger.logException("w", "Unable to create polygon")
continue
arranger.place(0, 0, shape_arr)
# If a build volume was set, add the disallowed areas
if Arrange.build_volume:
disallowed_areas = Arrange.build_volume.getDisallowedAreasNoBrim()
for area in disallowed_areas:
points = copy.deepcopy(area._points)
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
arranger.place(0, 0, shape_arr, update_empty = False)
return arranger
def resetLastPriority(self):
"""This resets the optimization for finding location based on size"""
self._last_priority = 0
@deprecated("Use the functions in Nest2dArrange instead", "4.8")
def findNodePlacement(self, node: SceneNode, offset_shape_arr: ShapeArray, hull_shape_arr: ShapeArray, step = 1) -> bool:
"""Find placement for a node (using offset shape) and place it (using hull shape)
:param node: The node to be placed
:param offset_shape_arr: shape array with offset, for placing the shape
:param hull_shape_arr: shape array without offset, used to find location
:param step: default = 1
:return: the nodes that should be placed
"""
best_spot = self.bestSpot(
hull_shape_arr, start_prio = self._last_priority, step = step)
x, y = best_spot.x, best_spot.y
# Save the last priority.
self._last_priority = best_spot.priority
# Ensure that the object is above the build platform
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
bbox = node.getBoundingBox()
if bbox:
center_y = node.getWorldPosition().y - bbox.bottom
else:
center_y = 0
if x is not None: # We could find a place
node.setPosition(Vector(x, center_y, y))
found_spot = True
self.place(x, y, offset_shape_arr) # place the object in arranger
else:
Logger.log("d", "Could not find spot!")
found_spot = False
node.setPosition(Vector(200, center_y, 100))
return found_spot
def centerFirst(self):
"""Fill priority, center is best. Lower value is better. """
# Square distance: creates a more round shape
self._priority = numpy.fromfunction(
lambda j, i: (self._offset_x - i) ** 2 + (self._offset_y - j) ** 2, self._shape, dtype=numpy.int32)
self._priority_unique_values = numpy.unique(self._priority)
self._priority_unique_values.sort()
def backFirst(self):
"""Fill priority, back is best. Lower value is better """
self._priority = numpy.fromfunction(
lambda j, i: 10 * j + abs(self._offset_x - i), self._shape, dtype=numpy.int32)
self._priority_unique_values = numpy.unique(self._priority)
self._priority_unique_values.sort()
def checkShape(self, x, y, shape_arr) -> Optional[numpy.ndarray]:
"""Return the amount of "penalty points" for polygon, which is the sum of priority
:param x: x-coordinate to check shape
:param y: y-coordinate to check shape
:param shape_arr: the shape array object to place
:return: None if occupied
"""
x = int(self._scale * x)
y = int(self._scale * y)
offset_x = x + self._offset_x + shape_arr.offset_x
offset_y = y + self._offset_y + shape_arr.offset_y
if offset_x < 0 or offset_y < 0:
return None # out of bounds in self._occupied
occupied_x_max = offset_x + shape_arr.arr.shape[1]
occupied_y_max = offset_y + shape_arr.arr.shape[0]
if occupied_x_max > self._occupied.shape[1] + 1 or occupied_y_max > self._occupied.shape[0] + 1:
return None # out of bounds in self._occupied
occupied_slice = self._occupied[
offset_y:occupied_y_max,
offset_x:occupied_x_max]
try:
if numpy.any(occupied_slice[numpy.where(shape_arr.arr == 1)]):
return None
except IndexError: # out of bounds if you try to place an object outside
return None
prio_slice = self._priority[
offset_y:offset_y + shape_arr.arr.shape[0],
offset_x:offset_x + shape_arr.arr.shape[1]]
return numpy.sum(prio_slice[numpy.where(shape_arr.arr == 1)])
def bestSpot(self, shape_arr, start_prio = 0, step = 1) -> LocationSuggestion:
"""Find "best" spot for ShapeArray
:param shape_arr: shape array
:param start_prio: Start with this priority value (and skip the ones before)
:param step: Slicing value, higher = more skips = faster but less accurate
:return: namedtuple with properties x, y, penalty_points, priority.
"""
start_idx_list = numpy.where(self._priority_unique_values == start_prio)
if start_idx_list:
try:
start_idx = start_idx_list[0][0]
except IndexError:
start_idx = 0
else:
start_idx = 0
priority = 0
for priority in self._priority_unique_values[start_idx::step]:
tryout_idx = numpy.where(self._priority == priority)
for idx in range(len(tryout_idx[0])):
x = tryout_idx[1][idx]
y = tryout_idx[0][idx]
projected_x = int((x - self._offset_x) / self._scale)
projected_y = int((y - self._offset_y) / self._scale)
penalty_points = self.checkShape(projected_x, projected_y, shape_arr)
if penalty_points is not None:
return LocationSuggestion(x = projected_x, y = projected_y, penalty_points = penalty_points, priority = priority)
return LocationSuggestion(x = None, y = None, penalty_points = None, priority = priority) # No suitable location found :-(
def place(self, x, y, shape_arr, update_empty = True):
"""Place the object.
Marks the locations in self._occupied and self._priority
:param x:
:param y:
:param shape_arr:
:param update_empty: updates the _is_empty, used when adding disallowed areas
"""
x = int(self._scale * x)
y = int(self._scale * y)
offset_x = x + self._offset_x + shape_arr.offset_x
offset_y = y + self._offset_y + shape_arr.offset_y
shape_y, shape_x = self._occupied.shape
min_x = min(max(offset_x, 0), shape_x - 1)
min_y = min(max(offset_y, 0), shape_y - 1)
max_x = min(max(offset_x + shape_arr.arr.shape[1], 0), shape_x - 1)
max_y = min(max(offset_y + shape_arr.arr.shape[0], 0), shape_y - 1)
occupied_slice = self._occupied[min_y:max_y, min_x:max_x]
# we use a slice of shape because it can be out of bounds
new_occupied = numpy.where(shape_arr.arr[
min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)
if update_empty and new_occupied:
self._is_empty = False
occupied_slice[new_occupied] = 1
# Set priority to low (= high number), so it won't get picked at trying out.
prio_slice = self._priority[min_y:max_y, min_x:max_x]
prio_slice[new_occupied] = 999
@property
def isEmpty(self):
return self._is_empty

View file

@ -1,154 +0,0 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Job import Job
from UM.Scene.SceneNode import SceneNode
from UM.Math.Vector import Vector
from UM.Operations.TranslateOperation import TranslateOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Message import Message
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
from cura.Arranging.Arrange import Arrange
from cura.Arranging.ShapeArray import ShapeArray
from typing import List
class ArrangeArray:
"""Do arrangements on multiple build plates (aka builtiplexer)"""
def __init__(self, x: int, y: int, fixed_nodes: List[SceneNode]) -> None:
self._x = x
self._y = y
self._fixed_nodes = fixed_nodes
self._count = 0
self._first_empty = None
self._has_empty = False
self._arrange = [] # type: List[Arrange]
def _updateFirstEmpty(self):
for i, a in enumerate(self._arrange):
if a.isEmpty:
self._first_empty = i
self._has_empty = True
return
self._first_empty = None
self._has_empty = False
def add(self):
new_arrange = Arrange.create(x = self._x, y = self._y, fixed_nodes = self._fixed_nodes)
self._arrange.append(new_arrange)
self._count += 1
self._updateFirstEmpty()
def count(self):
return self._count
def get(self, index):
return self._arrange[index]
def getFirstEmpty(self):
if not self._has_empty:
self.add()
return self._arrange[self._first_empty]
class ArrangeObjectsAllBuildPlatesJob(Job):
def __init__(self, nodes: List[SceneNode], min_offset = 8) -> None:
super().__init__()
self._nodes = nodes
self._min_offset = min_offset
def run(self):
status_message = Message(i18n_catalog.i18nc("@info:status", "Finding new location for objects"),
lifetime = 0,
dismissable=False,
progress = 0,
title = i18n_catalog.i18nc("@info:title", "Finding Location"))
status_message.show()
# 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)
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.
nodes_arr.sort(key=lambda item: item[0])
nodes_arr.reverse()
global_container_stack = Application.getInstance().getGlobalContainerStack()
machine_width = global_container_stack.getProperty("machine_width", "value")
machine_depth = global_container_stack.getProperty("machine_depth", "value")
x, y = machine_width, machine_depth
arrange_array = ArrangeArray(x = x, y = y, fixed_nodes = [])
arrange_array.add()
# Place nodes one at a time
start_priority = 0
grouped_operation = GroupedOperation()
found_solution_for_all = True
left_over_nodes = [] # nodes that do not fit on an empty build plate
for idx, (size, node, offset_shape_arr, hull_shape_arr) in enumerate(nodes_arr):
# For performance reasons, we assume that when a location does not fit,
# it will also not fit for the next object (while what can be untrue).
try_placement = True
current_build_plate_number = 0 # always start with the first one
while try_placement:
# make sure that current_build_plate_number is not going crazy or you'll have a lot of arrange objects
while current_build_plate_number >= arrange_array.count():
arrange_array.add()
arranger = arrange_array.get(current_build_plate_number)
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():
center_y = node.getWorldPosition().y - node.getBoundingBox().bottom
else:
center_y = 0
if x is not None: # We could find a place
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))
try_placement = False
else:
# very naive, because we skip to the next build plate if one model doesn't fit.
if arranger.isEmpty:
# apparently we can never place this object
left_over_nodes.append(node)
try_placement = False
else:
# try next build plate
current_build_plate_number += 1
try_placement = True
status_message.setProgress((idx + 1) / len(nodes_arr) * 100)
Job.yieldThread()
for node in left_over_nodes:
node.callDecoration("setBuildPlateNumber", -1) # these are not on any build plate
found_solution_for_all = False
grouped_operation.push()
status_message.hide()
if not found_solution_for_all:
no_full_solution_message = Message(i18n_catalog.i18nc("@info:status",
"Unable to find a location within the build volume for all objects"),
title = i18n_catalog.i18nc("@info:title", "Can't Find Location"),
message_type = Message.MessageType.WARNING)
no_full_solution_message.show()

View file

@ -72,7 +72,7 @@ class BuildVolume(SceneNode):
self._origin_mesh = None # type: Optional[MeshData]
self._origin_line_length = 20
self._origin_line_width = 1.5
self._origin_line_width = 1
self._enabled = False
self._grid_mesh = None # type: Optional[MeshData]
@ -601,6 +601,7 @@ class BuildVolume(SceneNode):
if self._adhesion_type == "raft":
self._raft_thickness = (
self._global_container_stack.getProperty("raft_base_thickness", "value") +
self._global_container_stack.getProperty("raft_interface_layers", "value") *
self._global_container_stack.getProperty("raft_interface_thickness", "value") +
self._global_container_stack.getProperty("raft_surface_layers", "value") *
self._global_container_stack.getProperty("raft_surface_thickness", "value") +
@ -1112,7 +1113,8 @@ class BuildVolume(SceneNode):
# Use brim width if brim is enabled OR the prime tower has a brim.
if adhesion_type == "brim":
brim_line_count = skirt_brim_stack.getProperty("brim_line_count", "value")
bed_adhesion_size = skirt_brim_line_width * brim_line_count * initial_layer_line_width_factor / 100.0
brim_gap = skirt_brim_stack.getProperty("brim_gap", "value")
bed_adhesion_size = brim_gap + skirt_brim_line_width * brim_line_count * initial_layer_line_width_factor / 100.0
for extruder_stack in used_extruders:
bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0
@ -1213,8 +1215,8 @@ class BuildVolume(SceneNode):
return max(min(value, max_value), min_value)
_machine_settings = ["machine_width", "machine_depth", "machine_height", "machine_shape", "machine_center_is_zero"]
_skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "initial_layer_line_width_factor"]
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"]
_skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_gap", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "initial_layer_line_width_factor"]
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_layers", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"]
_extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "prime_blob_enable"]
_tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y", "prime_tower_brim_enable"]

View file

@ -43,7 +43,7 @@ from UM.Scene.Selection import Selection
from UM.Scene.ToolHandle import ToolHandle
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType
from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType, toIntConversion
from UM.Settings.SettingFunction import SettingFunction
from UM.Settings.Validator import Validator
from UM.View.SelectionPass import SelectionPass # For typing.
@ -52,8 +52,6 @@ from UM.i18n import i18nCatalog
from cura import ApplicationMetadata
from cura.API import CuraAPI
from cura.API.Account import Account
from cura.Arranging.Arrange import Arrange
from cura.Arranging.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob
from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob
from cura.Arranging.Nest2DArrange import arrange
from cura.Machines.MachineErrorChecker import MachineErrorChecker
@ -382,11 +380,12 @@ class CuraApplication(QtApplication):
SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default=None,
depends_on="value")
SettingDefinition.addSettingType("extruder", None, str, Validator)
SettingDefinition.addSettingType("optional_extruder", None, str, None)
SettingDefinition.addSettingType("extruder", None, toIntConversion, Validator)
SettingDefinition.addSettingType("optional_extruder", None, toIntConversion, None)
SettingDefinition.addSettingType("[int]", None, str, None)
def _initializeSettingFunctions(self):
"""Adds custom property types, settings types, and extra operators (functions).
@ -494,7 +493,7 @@ class CuraApplication(QtApplication):
"CuraEngineBackend", #Cura is useless without this one since you can't slice.
"FileLogger", #You want to be able to read the log if something goes wrong.
"XmlMaterialProfile", #Cura crashes without this one.
"Toolbox", #This contains the interface to enable/disable plug-ins, so if you disable it you can't enable it back.
"Marketplace", #This contains the interface to enable/disable plug-ins, so if you disable it you can't enable it back.
"PrepareStage", #Cura is useless without this one since you can't load models.
"PreviewStage", #This shows the list of the plugin views that are installed in Cura.
"MonitorStage", #Major part of Cura's functionality.
@ -573,6 +572,10 @@ class CuraApplication(QtApplication):
preferences.addPreference("general/accepted_user_agreement", False)
preferences.addPreference("cura/market_place_show_plugin_banner", True)
preferences.addPreference("cura/market_place_show_material_banner", True)
preferences.addPreference("cura/market_place_show_manage_packages_banner", True)
for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
"dialog_profile_path",
@ -675,22 +678,6 @@ class CuraApplication(QtApplication):
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing Active Machine..."))
super().setGlobalContainerStack(stack)
showMessageBox = pyqtSignal(str,str, str, str, int, int,
arguments = ["title", "text", "informativeText", "detailedText","buttons", "icon"])
"""A reusable dialogbox"""
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_arguments = callback_arguments
self.showMessageBox.emit(title, text, informativeText, detailedText, buttons, icon)
showDiscardOrKeepProfileChanges = pyqtSignal()
def discardOrKeepProfileChanges(self) -> bool:
@ -830,9 +817,6 @@ class CuraApplication(QtApplication):
root = self.getController().getScene().getRoot()
self._volume = BuildVolume.BuildVolume(self, root)
# Ensure that the old style arranger still works.
Arrange.build_volume = self._volume
# initialize info objects
self._print_information = PrintInformation.PrintInformation(self)
self._cura_actions = CuraActions.CuraActions(self)
@ -1388,33 +1372,6 @@ class CuraApplication(QtApplication):
op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1)))
op.push()
@pyqtSlot()
def arrangeObjectsToAllBuildPlates(self) -> None:
"""Arrange all objects."""
nodes_to_arrange = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if not isinstance(node, SceneNode):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesn't have a mesh and is not a group.
parent_node = node.getParent()
if parent_node and parent_node.callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is reset)
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue # i.e. node with layer data
bounding_box = node.getBoundingBox()
# Skip nodes that are too big
if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth:
nodes_to_arrange.append(node)
job = ArrangeObjectsAllBuildPlatesJob(nodes_to_arrange)
job.start()
self.getCuraSceneController().setActiveBuildPlate(0) # Select first build plate
# Single build plate
@pyqtSlot()
def arrangeAll(self) -> None:

View file

@ -1,13 +1,15 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import List, Tuple, TYPE_CHECKING, Optional
from typing import Any, cast, Dict, List, Set, Tuple, TYPE_CHECKING, Optional
from cura.CuraApplication import CuraApplication #To find some resource types.
from cura.CuraApplication import CuraApplication # To find some resource types.
from cura.Settings.GlobalStack import GlobalStack
from UM.PackageManager import PackageManager #The class we're extending.
from UM.Resources import Resources #To find storage paths for some resource types.
from UM.PackageManager import PackageManager # The class we're extending.
from UM.Resources import Resources # To find storage paths for some resource types.
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
if TYPE_CHECKING:
from UM.Qt.QtApplication import QtApplication
@ -17,6 +19,31 @@ if TYPE_CHECKING:
class CuraPackageManager(PackageManager):
def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None:
super().__init__(application, parent)
self._local_packages: Optional[List[Dict[str, Any]]] = None
self._local_packages_ids: Optional[Set[str]] = None
self.installedPackagesChanged.connect(self._updateLocalPackages)
def _updateLocalPackages(self) -> None:
self._local_packages = self.getAllLocalPackages()
self._local_packages_ids = set(pkg["package_id"] for pkg in self._local_packages)
@property
def local_packages(self) -> List[Dict[str, Any]]:
"""locally installed packages, lazy execution"""
if self._local_packages is None:
self._updateLocalPackages()
# _updateLocalPackages always results in a list of packages, not None.
# It's guaranteed to be a list now.
return cast(List[Dict[str, Any]], self._local_packages)
@property
def local_packages_ids(self) -> Set[str]:
"""locally installed packages, lazy execution"""
if self._local_packages_ids is None:
self._updateLocalPackages()
# _updateLocalPackages always results in a list of packages, not None.
# It's guaranteed to be a list now.
return cast(Set[str], self._local_packages_ids)
def initialize(self) -> None:
self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer)
@ -47,3 +74,12 @@ class CuraPackageManager(PackageManager):
machine_with_qualities.append((global_stack, str(extruder_nr), container_id))
return machine_with_materials, machine_with_qualities
def getAllLocalPackages(self) -> List[Dict[str, Any]]:
""" Returns an unordered list of all the package_info of installed, to be installed, or bundled packages"""
packages: List[Dict[str, Any]] = []
for packages_to_add in self.getAllInstalledPackagesInfo().values():
packages.extend(packages_to_add)
return packages

View file

@ -1,4 +1,4 @@
# Copyright (c) 2020 Ultimaker B.V.
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
@ -9,6 +9,7 @@ from UM import i18nCatalog
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction # To format setting functions differently.
import os
@ -173,12 +174,22 @@ class QualitySettingsModel(ListModel):
label = definition.label
if self._i18n_catalog:
label = self._i18n_catalog.i18nc(definition.key + " label", label)
if profile_value_source == "quality_changes":
label = f"<i>{label}</i>" # Make setting name italic if it's derived from the quality-changes profile.
if isinstance(profile_value, SettingFunction):
if self._i18n_catalog:
profile_value_display = self._i18n_catalog.i18nc("@info:status", "Calculated")
else:
profile_value_display = "Calculated"
else:
profile_value_display = "" if profile_value is None else str(profile_value)
items.append({
"key": definition.key,
"label": label,
"unit": definition.unit,
"profile_value": "" if profile_value is None else str(profile_value), # it is for display only
"profile_value": profile_value_display,
"profile_value_source": profile_value_source,
"user_value": "" if user_value is None else str(user_value),
"category": current_category

View file

@ -1,4 +0,0 @@
import warnings
warnings.warn("Importing cura.PrinterOutput.PrinterOutputModel has been deprecated since 4.1, use cura.PrinterOutput.Models.PrinterOutputModel instead", DeprecationWarning, stacklevel=2)
# We moved the the models to one submodule deeper
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel

View file

@ -1,4 +0,0 @@
import warnings
warnings.warn("Importing cura.PrinterOutputDevice has been deprecated since 4.1, use cura.PrinterOutput.PrinterOutputDevice instead", DeprecationWarning, stacklevel=2)
# We moved the PrinterOutput device to it's own submodule.
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionState

View file

@ -268,7 +268,8 @@ class ExtruderManager(QObject):
used_adhesion_extruders.add("skirt_brim_extruder_nr") # There's a brim or prime tower brim.
if adhesion_type == "raft":
used_adhesion_extruders.add("raft_base_extruder_nr")
used_adhesion_extruders.add("raft_interface_extruder_nr")
if global_stack.getProperty("raft_interface_layers", "value") > 0:
used_adhesion_extruders.add("raft_interface_extruder_nr")
if global_stack.getProperty("raft_surface_layers", "value") > 0:
used_adhesion_extruders.add("raft_surface_extruder_nr")
for extruder_setting in used_adhesion_extruders:

View file

@ -60,16 +60,6 @@ class GlobalStack(CuraContainerStack):
extrudersChanged = pyqtSignal()
configuredConnectionTypesChanged = pyqtSignal()
@pyqtProperty("QVariantMap", notify = extrudersChanged)
@deprecated("Please use extruderList instead.", "4.4")
def extruders(self) -> Dict[str, "ExtruderStack"]:
"""Get the list of extruders of this stack.
:return: The extruders registered with this stack.
"""
return self._extruders
@pyqtProperty("QVariantList", notify = extrudersChanged)
def extruderList(self) -> List["ExtruderStack"]:
result_tuple_list = sorted(list(self._extruders.items()), key=lambda x: int(x[0]))

View file

@ -14,7 +14,7 @@ import time
class CuraSplashScreen(QSplashScreen):
def __init__(self):
super().__init__()
self._scale = 0.7
self._scale = 1
self._version_y_offset = 0 # when extra visual elements are in the background image, move version text down
if ApplicationMetadata.IsAlternateVersion:
@ -69,23 +69,21 @@ class CuraSplashScreen(QSplashScreen):
version = Application.getInstance().getVersion().split("-")
# Draw version text
font = QFont() # Using system-default font here
font.setPixelSize(18)
font = QFont()
font.setPixelSize(24)
painter.setFont(font)
painter.drawText(60, 70 + self._version_y_offset, round(330 * self._scale), round(230 * self._scale), Qt.AlignLeft | Qt.AlignTop, version[0] if not ApplicationMetadata.IsAlternateVersion else ApplicationMetadata.CuraBuildType)
if len(version) > 1:
font.setPixelSize(16)
painter.setFont(font)
painter.setPen(QColor(200, 200, 200, 255))
painter.drawText(247, 105 + self._version_y_offset, round(330 * self._scale), round(255 * self._scale), Qt.AlignLeft | Qt.AlignTop, version[1])
painter.setPen(QColor(255, 255, 255, 255))
if len(version) == 1:
painter.drawText(40, 104 + self._version_y_offset, round(330 * self._scale), round(230 * self._scale), Qt.AlignLeft | Qt.AlignTop, version[0] if not ApplicationMetadata.IsAlternateVersion else ApplicationMetadata.CuraBuildType)
elif len(version) > 1:
painter.drawText(40, 104 + self._version_y_offset, round(330 * self._scale), round(230 * self._scale), Qt.AlignLeft | Qt.AlignTop, version[0] +" "+ version[1] if not ApplicationMetadata.IsAlternateVersion else ApplicationMetadata.CuraBuildType)
# Draw the loading image
pen = QPen()
pen.setWidthF(6 * self._scale)
pen.setColor(QColor(32, 166, 219, 255))
pen.setWidthF(2 * self._scale)
pen.setColor(QColor(255, 255, 255, 255))
painter.setPen(pen)
painter.drawArc(60, 150, round(32 * self._scale), round(32 * self._scale), round(self._loading_image_rotation_angle * 16), 300 * 16)
painter.drawArc(38, 324, round(20 * self._scale), round(20 * self._scale), round(self._loading_image_rotation_angle * 16), 300 * 16)
# Draw message text
if self._current_message:
@ -95,7 +93,7 @@ class CuraSplashScreen(QSplashScreen):
pen.setColor(QColor(255, 255, 255, 255))
painter.setPen(pen)
painter.setFont(font)
painter.drawText(100, 128, 170, 64,
painter.drawText(70, 320, 170, 24,
Qt.AlignLeft | Qt.AlignVCenter | Qt.TextWordWrap,
self._current_message)

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before After
Before After

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before After
Before After

View file

@ -6,7 +6,7 @@ import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
import UM 1.1 as UM
import UM 1.5 as UM
import Cura 1.1 as Cura
UM.Dialog
@ -17,11 +17,8 @@ UM.Dialog
minimumWidth: UM.Theme.getSize("popup_dialog").width
minimumHeight: UM.Theme.getSize("popup_dialog").height
width: minimumWidth
height: Math.max(dialogSummaryItem.height + 2 * buttonsItem.height, minimumHeight) // 2 * button height to also have some extra space around the button relative to the button size
property int comboboxHeight: 15 * screenScaleFactor
property int spacerHeight: 10 * screenScaleFactor
property int doubleSpacerHeight: 20 * screenScaleFactor
property int comboboxHeight: UM.Theme.getSize("default_margin").height
onClosing: manager.notifyClosed()
onVisibleChanged:
@ -46,10 +43,6 @@ UM.Dialog
id: catalog
name: "cura"
}
SystemPalette
{
id: palette
}
ListModel
{
@ -68,45 +61,39 @@ UM.Dialog
{
width: parent.width
height: childrenRect.height
spacing: 2 * screenScaleFactor
Label
spacing: UM.Theme.getSize("default_margin").height
Column
{
id: titleLabel
text: catalog.i18nc("@action:title", "Summary - Cura Project")
font.pointSize: 18
}
Rectangle
{
id: separator
color: palette.text
width: parent.width
height: 1
}
Item // Spacer
{
height: doubleSpacerHeight
width: height
height: childrenRect.height
UM.Label
{
id: titleLabel
text: catalog.i18nc("@action:title", "Summary - Cura Project")
font: UM.Theme.getFont("large")
}
Rectangle
{
id: separator
color: UM.Theme.getColor("text")
width: parent.width
height: UM.Theme.getSize("default_lining").height
}
}
Row
Item
{
height: childrenRect.height
width: parent.width
Label
{
text: catalog.i18nc("@action:label", "Printer settings")
font.bold: true
width: (parent.width / 3) | 0
}
Item
{
// spacer
height: spacerHeight
width: (parent.width / 3) | 0
}
height: childrenRect.height
UM.TooltipArea
{
id: machineResolveStrategyTooltip
anchors.top: parent.top
anchors.right: parent.right
width: (parent.width / 3) | 0
height: visible ? comboboxHeight : 0
visible: base.visible && machineResolveComboBox.model.count > 1
@ -157,64 +144,65 @@ UM.Dialog
}
}
}
}
Row
{
width: parent.width
height: childrenRect.height
Label
Column
{
text: catalog.i18nc("@action:label", "Type")
width: (parent.width / 3) | 0
}
Label
{
text: manager.machineType
width: (parent.width / 3) | 0
width: parent.width
height: childrenRect.height
UM.Label
{
id: printer_settings_label
text: catalog.i18nc("@action:label", "Printer settings")
font: UM.Theme.getFont("default_bold")
}
Row
{
width: parent.width
height: childrenRect.height
UM.Label
{
text: catalog.i18nc("@action:label", "Type")
width: (parent.width / 3) | 0
}
UM.Label
{
text: manager.machineType
width: (parent.width / 3) | 0
}
}
Row
{
width: parent.width
height: childrenRect.height
UM.Label
{
text: catalog.i18nc("@action:label", manager.isPrinterGroup ? "Printer Group" : "Printer Name")
width: (parent.width / 3) | 0
}
UM.Label
{
text: manager.machineName
width: (parent.width / 3) | 0
wrapMode: Text.WordWrap
}
}
}
}
Row
Item
{
width: parent.width
height: childrenRect.height
Label
{
text: catalog.i18nc("@action:label", manager.isPrinterGroup ? "Printer Group" : "Printer Name")
width: (parent.width / 3) | 0
}
Label
{
text: manager.machineName
width: (parent.width / 3) | 0
wrapMode: Text.WordWrap
}
}
Item // Spacer
{
height: doubleSpacerHeight
width: height
}
Row
{
height: childrenRect.height
width: parent.width
Label
{
text: catalog.i18nc("@action:label", "Profile settings")
font.bold: true
width: (parent.width / 3) | 0
}
Item
{
// spacer
height: spacerHeight
width: (parent.width / 3) | 0
}
UM.TooltipArea
{
id: qualityChangesResolveTooltip
anchors.right: parent.right
anchors.top: parent.top
width: (parent.width / 3) | 0
height: visible ? comboboxHeight : 0
visible: manager.qualityChangesConflict
@ -232,96 +220,105 @@ UM.Dialog
}
}
}
Column
{
width: parent.width
height: childrenRect.height
UM.Label
{
text: catalog.i18nc("@action:label", "Profile settings")
font: UM.Theme.getFont("default_bold")
}
Row
{
width: parent.width
height: childrenRect.height
UM.Label
{
text: catalog.i18nc("@action:label", "Name")
width: (parent.width / 3) | 0
}
UM.Label
{
text: manager.qualityName
width: (parent.width / 3) | 0
wrapMode: Text.WordWrap
}
}
Row
{
width: parent.width
height: childrenRect.height
UM.Label
{
text: catalog.i18nc("@action:label", "Intent")
width: (parent.width / 3) | 0
}
UM.Label
{
text: manager.intentName
width: (parent.width / 3) | 0
wrapMode: Text.WordWrap
}
}
Row
{
width: parent.width
height: childrenRect.height
UM.Label
{
text: catalog.i18nc("@action:label", "Not in profile")
visible: manager.numUserSettings != 0
width: (parent.width / 3) | 0
}
UM.Label
{
text: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.numUserSettings).arg(manager.numUserSettings)
visible: manager.numUserSettings != 0
width: (parent.width / 3) | 0
}
}
Row
{
width: parent.width
height: childrenRect.height
UM.Label
{
text: catalog.i18nc("@action:label", "Derivative from")
visible: manager.numSettingsOverridenByQualityChanges != 0
width: (parent.width / 3) | 0
}
UM.Label
{
text: catalog.i18ncp("@action:label", "%1, %2 override", "%1, %2 overrides", manager.numSettingsOverridenByQualityChanges).arg(manager.qualityType).arg(manager.numSettingsOverridenByQualityChanges)
width: (parent.width / 3) | 0
visible: manager.numSettingsOverridenByQualityChanges != 0
wrapMode: Text.WordWrap
}
}
}
}
Row
Item
{
width: parent.width
height: childrenRect.height
Label
{
text: catalog.i18nc("@action:label", "Name")
width: (parent.width / 3) | 0
}
Label
{
text: manager.qualityName
width: (parent.width / 3) | 0
wrapMode: Text.WordWrap
}
}
Row
{
width: parent.width
height: childrenRect.height
Label
{
text: catalog.i18nc("@action:label", "Intent")
width: (parent.width / 3) | 0
}
Label
{
text: manager.intentName
width: (parent.width / 3) | 0
wrapMode: Text.WordWrap
}
}
Row
{
width: parent.width
height: manager.numUserSettings != 0 ? childrenRect.height : 0
Label
{
text: catalog.i18nc("@action:label", "Not in profile")
width: (parent.width / 3) | 0
}
Label
{
text: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.numUserSettings).arg(manager.numUserSettings)
width: (parent.width / 3) | 0
}
visible: manager.numUserSettings != 0
}
Row
{
width: parent.width
height: manager.numSettingsOverridenByQualityChanges != 0 ? childrenRect.height : 0
Label
{
text: catalog.i18nc("@action:label", "Derivative from")
width: (parent.width / 3) | 0
}
Label
{
text: catalog.i18ncp("@action:label", "%1, %2 override", "%1, %2 overrides", manager.numSettingsOverridenByQualityChanges).arg(manager.qualityType).arg(manager.numSettingsOverridenByQualityChanges)
width: (parent.width / 3) | 0
wrapMode: Text.WordWrap
}
visible: manager.numSettingsOverridenByQualityChanges != 0
}
Item // Spacer
{
height: doubleSpacerHeight
width: height
}
Row
{
height: childrenRect.height
width: parent.width
Label
{
text: catalog.i18nc("@action:label", "Material settings")
font.bold: true
width: (parent.width / 3) | 0
}
Item
{
// spacer
height: spacerHeight
width: (parent.width / 3) | 0
}
UM.TooltipArea
{
id: materialResolveTooltip
anchors.right: parent.right
anchors.top: parent.top
width: (parent.width / 3) | 0
height: visible ? comboboxHeight : 0
visible: manager.materialConflict
@ -339,76 +336,91 @@ UM.Dialog
}
}
}
}
Repeater
{
model: manager.materialLabels
delegate: Row
Column
{
width: parent.width
height: childrenRect.height
Label
Row
{
text: catalog.i18nc("@action:label", "Name")
width: (parent.width / 3) | 0
height: childrenRect.height
width: parent.width
spacing: UM.Theme.getSize("narrow_margin").width
UM.Label
{
text: catalog.i18nc("@action:label", "Material settings")
font: UM.Theme.getFont("default_bold")
width: (parent.width / 3) | 0
}
}
Label
Repeater
{
text: modelData
width: (parent.width / 3) | 0
wrapMode: Text.WordWrap
model: manager.materialLabels
delegate: Row
{
width: parent.width
height: childrenRect.height
UM.Label
{
text: catalog.i18nc("@action:label", "Name")
width: (parent.width / 3) | 0
}
UM.Label
{
text: modelData
width: (parent.width / 3) | 0
wrapMode: Text.WordWrap
}
}
}
}
}
Item // Spacer
Column
{
height: doubleSpacerHeight
width: height
width: parent.width
height: childrenRect.height
UM.Label
{
text: catalog.i18nc("@action:label", "Setting visibility")
font: UM.Theme.getFont("default_bold")
}
Row
{
width: parent.width
height: childrenRect.height
UM.Label
{
text: catalog.i18nc("@action:label", "Mode")
width: (parent.width / 3) | 0
}
UM.Label
{
text: manager.activeMode
width: (parent.width / 3) | 0
}
}
Row
{
width: parent.width
height: childrenRect.height
visible: manager.hasVisibleSettingsField
UM.Label
{
text: catalog.i18nc("@action:label", "Visible settings:")
width: (parent.width / 3) | 0
}
UM.Label
{
text: catalog.i18nc("@action:label", "%1 out of %2" ).arg(manager.numVisibleSettings).arg(manager.totalNumberOfSettings)
width: (parent.width / 3) | 0
}
}
}
Label
{
text: catalog.i18nc("@action:label", "Setting visibility")
font.bold: true
}
Row
{
width: parent.width
height: childrenRect.height
Label
{
text: catalog.i18nc("@action:label", "Mode")
width: (parent.width / 3) | 0
}
Label
{
text: manager.activeMode
width: (parent.width / 3) | 0
}
}
Row
{
width: parent.width
height: childrenRect.height
visible: manager.hasVisibleSettingsField
Label
{
text: catalog.i18nc("@action:label", "Visible settings:")
width: (parent.width / 3) | 0
}
Label
{
text: catalog.i18nc("@action:label", "%1 out of %2" ).arg(manager.numVisibleSettings).arg(manager.totalNumberOfSettings)
width: (parent.width / 3) | 0
}
}
Item // Spacer
{
height: spacerHeight
width: height
}
Row
{
width: parent.width
@ -418,12 +430,10 @@ UM.Dialog
{
width: warningLabel.height
height: width
source: UM.Theme.getIcon("Information")
color: palette.text
color: UM.Theme.getColor("text")
}
Label
UM.Label
{
id: warningLabel
text: catalog.i18nc("@action:warning", "Loading a project will clear all models on the build plate.")
@ -432,44 +442,22 @@ UM.Dialog
}
}
}
Item
{
id: buttonsItem
width: parent.width
height: childrenRect.height
anchors.bottom: parent.bottom
anchors.right: parent.right
Button
buttonSpacing: UM.Theme.getSize("default_margin").width
rightButtons: [
Cura.TertiaryButton
{
id: cancel_button
text: catalog.i18nc("@action:button","Cancel");
onClicked: { manager.onCancelButtonClicked() }
enabled: true
anchors.bottom: parent.bottom
anchors.right: ok_button.left
anchors.rightMargin: 2 * screenScaleFactor
}
Button
text: catalog.i18nc("@action:button", "Cancel")
onClicked: reject()
},
Cura.PrimaryButton
{
id: ok_button
anchors.right: parent.right
anchors.bottom: parent.bottom
text: catalog.i18nc("@action:button","Open");
onClicked: { manager.closeBackend(); manager.onOkButtonClicked() }
text: catalog.i18nc("@action:button", "Open")
onClicked: accept()
}
}
]
function accept() {
manager.closeBackend();
manager.onOkButtonClicked();
base.visible = false;
base.accept();
}
function reject() {
manager.onCancelButtonClicked();
base.visible = false;
base.rejected();
}
onRejected: manager.onCancelButtonClicked()
onAccepted: manager.onOkButtonClicked()
}

View file

@ -1,39 +1,34 @@
// Copyright (c) 2018 Ultimaker B.V.
// Copyright (c) 2022 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import UM 1.1 as UM
import UM 1.5 as UM
ScrollView
ListView
{
property alias model: backupList.model
width: parent.width
clip: true
ListView
ScrollBar.vertical: UM.ScrollBar {}
delegate: Item
{
id: backupList
width: parent.width
delegate: Item
// Add a margin, otherwise the scrollbar is on top of the right most component
width: parent.width - UM.Theme.getSize("scrollbar").width
height: childrenRect.height
BackupListItem
{
// Add a margin, otherwise the scrollbar is on top of the right most component
width: parent.width - UM.Theme.getSize("default_margin").width
height: childrenRect.height
id: backupListItem
width: parent.width
}
BackupListItem
{
id: backupListItem
width: parent.width
}
Rectangle
{
id: divider
color: UM.Theme.getColor("lining")
height: UM.Theme.getSize("default_lining").height
}
Rectangle
{
id: divider
color: UM.Theme.getColor("lining")
height: UM.Theme.getSize("default_lining").height
}
}
}

View file

@ -5,7 +5,7 @@ import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import UM 1.3 as UM
import UM 1.5 as UM
import Cura 1.0 as Cura
import "../components"
@ -35,7 +35,7 @@ RowLayout
busy: CuraDrive.isCreatingBackup
}
Cura.CheckBoxWithTooltip
UM.CheckBox
{
id: autoBackupEnabled
checked: CuraDrive.autoBackupEnabled

View file

@ -1,12 +1,11 @@
// Copyright (c) 2018 Ultimaker B.V.
// Copyright (c) 2022 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.1
import UM 1.1 as UM
import UM 1.5 as UM
import Cura 1.0 as Cura
Item
@ -42,28 +41,22 @@ Item
onClicked: backupListItem.showDetails = !backupListItem.showDetails
}
Label
UM.Label
{
text: new Date(modelData.generated_time).toLocaleString(UM.Preferences.getValue("general/language"))
color: UM.Theme.getColor("text")
elide: Text.ElideRight
Layout.minimumWidth: 100 * screenScaleFactor
Layout.maximumWidth: 500 * screenScaleFactor
Layout.fillWidth: true
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
Label
UM.Label
{
text: modelData.metadata.description
color: UM.Theme.getColor("text")
elide: Text.ElideRight
Layout.minimumWidth: 100 * screenScaleFactor
Layout.maximumWidth: 500 * screenScaleFactor
Layout.fillWidth: true
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
Cura.SecondaryButton
@ -94,21 +87,21 @@ Item
anchors.top: dataRow.bottom
}
MessageDialog
Cura.MessageDialog
{
id: confirmDeleteDialog
title: catalog.i18nc("@dialog:title", "Delete Backup")
text: catalog.i18nc("@dialog:info", "Are you sure you want to delete this backup? This cannot be undone.")
standardButtons: StandardButton.Yes | StandardButton.No
onYes: CuraDrive.deleteBackup(modelData.backup_id)
standardButtons: Dialog.Yes | Dialog.No
onAccepted: CuraDrive.deleteBackup(modelData.backup_id)
}
MessageDialog
Cura.MessageDialog
{
id: confirmRestoreDialog
title: catalog.i18nc("@dialog:title", "Restore Backup")
text: catalog.i18nc("@dialog:info", "You will need to restart Cura before your backup is restored. Do you want to close Cura now?")
standardButtons: StandardButton.Yes | StandardButton.No
onYes: CuraDrive.restoreBackup(modelData.backup_id)
standardButtons: Dialog.Yes | Dialog.No
onAccepted: CuraDrive.restoreBackup(modelData.backup_id)
}
}

View file

@ -5,7 +5,7 @@ import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import UM 1.3 as UM
import UM 1.5 as UM
RowLayout
{
@ -26,27 +26,21 @@ RowLayout
color: UM.Theme.getColor("text")
}
Label
UM.Label
{
id: detailName
color: UM.Theme.getColor("text")
elide: Text.ElideRight
Layout.minimumWidth: 50 * screenScaleFactor
Layout.maximumWidth: 100 * screenScaleFactor
Layout.fillWidth: true
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
Label
UM.Label
{
id: detailValue
color: UM.Theme.getColor("text")
elide: Text.ElideRight
Layout.minimumWidth: 50 * screenScaleFactor
Layout.maximumWidth: 100 * screenScaleFactor
Layout.fillWidth: true
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View file

@ -5,7 +5,7 @@ import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
import UM 1.3 as UM
import UM 1.5 as UM
import Cura 1.1 as Cura
import "../components"
@ -23,23 +23,19 @@ Column
{
id: profileImage
fillMode: Image.PreserveAspectFit
source: "../images/icon.png"
source: "../images/backup.svg"
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(parent.width / 4)
}
Label
UM.Label
{
id: welcomeTextLabel
text: catalog.i18nc("@description", "Backup and synchronize your Cura settings.")
width: Math.round(parent.width / 2)
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Label.WordWrap
renderType: Text.NativeRendering
}
Cura.PrimaryButton

View file

@ -205,6 +205,13 @@ class StartSliceJob(Job):
# Get the objects in their groups to print.
object_groups = []
if stack.getProperty("print_sequence", "value") == "one_at_a_time":
modifier_mesh_nodes = []
for node in DepthFirstIterator(self._scene.getRoot()):
build_plate_number = node.callDecoration("getBuildPlateNumber")
if node.callDecoration("isNonPrintingMesh") and build_plate_number == self._build_plate_number:
modifier_mesh_nodes.append(node)
for node in OneAtATimeIterator(self._scene.getRoot()):
temp_list = []
@ -221,7 +228,7 @@ class StartSliceJob(Job):
temp_list.append(child_node)
if temp_list:
object_groups.append(temp_list)
object_groups.append(temp_list + modifier_mesh_nodes)
Job.yieldThread()
if len(object_groups) == 0:
Logger.log("w", "No objects suitable for one at a time found, or no correct order found")

View file

@ -1,10 +1,9 @@
// Copyright (C) 2021 Ultimaker B.V.
//Copyright (C) 2022 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Window 2.2
import QtQuick.Controls 1.4 as OldControls // TableView doesn't exist in the QtQuick Controls 2.x in 5.10, so use the old one
import QtQuick.Controls 2.3
import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM
import Cura 1.6 as Cura

View file

@ -1,10 +1,9 @@
// Copyright (C) 2021 Ultimaker B.V.
//Copyright (C) 2022 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Window 2.2
import QtQuick.Controls 1.4 as OldControls // TableView doesn't exist in the QtQuick Controls 2.x in 5.10, so use the old one
import QtQuick.Controls 2.3
import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM
import Cura 1.6 as Cura

View file

@ -1,10 +1,9 @@
// Copyright (C) 2021 Ultimaker B.V.
//Copyright (C) 2022 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Window 2.2
import QtQuick.Controls 1.4 as OldControls // TableView doesn't exist in the QtQuick Controls 2.x in 5.10, so use the old one
import QtQuick.Controls 2.3
import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM
import Cura 1.6 as Cura

View file

@ -1,10 +1,10 @@
// Copyright (C) 2021 Ultimaker B.V.
//Copyright (C) 2022 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import Qt.labs.qmlmodels 1.0
import QtQuick 2.15
import QtQuick.Window 2.2
import QtQuick.Controls 1.4 as OldControls // TableView doesn't exist in the QtQuick Controls 2.x in 5.10, so use the old one
import QtQuick.Controls 2.3
import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM
import Cura 1.6 as Cura
@ -57,52 +57,32 @@ Item
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
Cura.TableView
//We can't use Cura's TableView here, since in Cura >= 5.0 this uses QtQuick.TableView, while in Cura < 5.0 this uses QtControls1.TableView.
//So we have to define our own. Once support for 4.13 and earlier is dropped, we can switch to Cura.TableView.
Table
{
id: filesTableView
anchors.fill: parent
model: manager.digitalFactoryFileModel
visible: model.count != 0 && manager.retrievingFileStatus != DF.RetrievalStatus.InProgress
selectionMode: OldControls.SelectionMode.SingleSelection
onDoubleClicked:
anchors.margins: parent.border.width
columnHeaders: ["Name", "Uploaded by", "Uploaded at"]
model: TableModel
{
TableModelColumn { display: "fileName" }
TableModelColumn { display: "username" }
TableModelColumn { display: "uploadedAt" }
rows: manager.digitalFactoryFileModel.items
}
onCurrentRowChanged:
{
manager.setSelectedFileIndices([currentRow]);
}
onDoubleClicked: function(row)
{
manager.setSelectedFileIndices([row]);
openFilesButton.clicked();
}
OldControls.TableViewColumn
{
id: fileNameColumn
role: "fileName"
title: "Name"
width: Math.round(filesTableView.width / 3)
}
OldControls.TableViewColumn
{
id: usernameColumn
role: "username"
title: "Uploaded by"
width: Math.round(filesTableView.width / 3)
}
OldControls.TableViewColumn
{
role: "uploadedAt"
title: "Uploaded at"
}
Connections
{
target: filesTableView.selection
function onSelectionChanged()
{
let newSelection = [];
filesTableView.selection.forEach(function(rowIndex) { newSelection.push(rowIndex); });
manager.setSelectedFileIndices(newSelection);
}
}
}
Label
@ -161,7 +141,6 @@ Item
{
// Make sure no files are selected when the file model changes
filesTableView.currentRow = -1
filesTableView.selection.clear()
}
}
}
@ -187,7 +166,7 @@ Item
anchors.bottom: parent.bottom
anchors.right: parent.right
text: "Open"
enabled: filesTableView.selection.count > 0
enabled: filesTableView.currentRow >= 0
onClicked:
{
manager.openSelectedFiles()

View file

@ -44,7 +44,7 @@ Cura.RoundedRectangle
{
id: projectImage
anchors.verticalCenter: parent.verticalCenter
width: UM.Theme.getSize("toolbox_thumbnail_small").width
width: UM.Theme.getSize("card_icon").width
height: Math.round(width * 3/4)
sourceSize.width: width
sourceSize.height: height

View file

@ -1,12 +1,12 @@
// Copyright (C) 2021 Ultimaker B.V.
//Copyright (C) 2022 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher.
import Qt.labs.qmlmodels 1.0
import QtQuick 2.10
import QtQuick.Window 2.2
import QtQuick.Controls 1.4 as OldControls // TableView doesn't exist in the QtQuick Controls 2.x in 5.10, so use the old one
import QtQuick.Controls 2.3
import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM
import UM 1.5 as UM
import Cura 1.6 as Cura
import DigitalFactory 1.0 as DF
@ -67,11 +67,17 @@ Item
}
text: PrintInformation.jobName
font: UM.Theme.getFont("medium")
font: fontMetrics.font
height: fontMetrics.height + 2 * UM.Theme.getSize("thin_margin").height
placeholderText: "Enter the name of the file."
onAccepted: { if (saveButton.enabled) {saveButton.clicked()}}
}
FontMetrics
{
id: fontMetrics
font: UM.Theme.getFont("medium")
}
Rectangle
{
@ -86,35 +92,22 @@ Item
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
Cura.TableView
//We can't use Cura's TableView here, since in Cura >= 5.0 this uses QtQuick.TableView, while in Cura < 5.0 this uses QtControls1.TableView.
//So we have to define our own. Once support for 4.13 and earlier is dropped, we can switch to Cura.TableView.
Table
{
id: filesTableView
anchors.fill: parent
model: manager.digitalFactoryFileModel
visible: model.count != 0 && manager.retrievingFileStatus != DF.RetrievalStatus.InProgress
selectionMode: OldControls.SelectionMode.NoSelection
anchors.margins: parent.border.width
OldControls.TableViewColumn
allowSelection: false
columnHeaders: ["Name", "Uploaded by", "Uploaded at"]
model: TableModel
{
id: fileNameColumn
role: "fileName"
title: "@tableViewColumn:title", "Name"
width: Math.round(filesTableView.width / 3)
}
OldControls.TableViewColumn
{
id: usernameColumn
role: "username"
title: "Uploaded by"
width: Math.round(filesTableView.width / 3)
}
OldControls.TableViewColumn
{
role: "uploadedAt"
title: "Uploaded at"
TableModelColumn { display: "fileName" }
TableModelColumn { display: "username" }
TableModelColumn { display: "uploadedAt" }
rows: manager.digitalFactoryFileModel.items
}
}
@ -173,8 +166,7 @@ Item
function onItemsChanged()
{
// Make sure no files are selected when the file model changes
filesTableView.currentRow = -1
filesTableView.selection.clear()
filesTableView.currentRow = -1;
}
}
}
@ -228,7 +220,7 @@ Item
width: childrenRect.width
spacing: UM.Theme.getSize("default_margin").width
Cura.CheckBox
UM.CheckBox
{
id: asProjectCheckbox
height: UM.Theme.getSize("checkbox").height
@ -238,7 +230,7 @@ Item
font: UM.Theme.getFont("medium")
}
Cura.CheckBox
UM.CheckBox
{
id: asSlicedCheckbox
height: UM.Theme.getSize("checkbox").height

View file

@ -1,15 +1,13 @@
// Copyright (C) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
//Copyright (C) 2022 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Window 2.2
import QtQuick.Controls 1.4 as OldControls // TableView doesn't exist in the QtQuick Controls 2.x in 5.10, so use the old one
import QtQuick.Controls 2.3
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.1
import UM 1.2 as UM
import Cura 1.6 as Cura
import Cura 1.7 as Cura
import DigitalFactory 1.0 as DF
@ -44,33 +42,13 @@ Item
height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").width
Cura.TextField
Cura.SearchBar
{
id: searchBar
Layout.fillWidth: true
implicitHeight: createNewProjectButton.height
leftPadding: searchIcon.width + UM.Theme.getSize("default_margin").width * 2
focus: true
onTextEdited: manager.projectFilter = text //Update the search filter when editing this text field.
placeholderText: "Search"
UM.RecolorImage
{
id: searchIcon
anchors
{
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
}
source: UM.Theme.getIcon("search")
height: UM.Theme.getSize("small_button_icon").height
width: height
color: UM.Theme.getColor("text")
}
}
Cura.SecondaryButton
@ -222,7 +200,7 @@ Item
LoadMoreProjectsCard
{
id: loadMoreProjectsCard
height: UM.Theme.getSize("toolbox_thumbnail_small").height
height: UM.Theme.getSize("card_icon").height
width: parent.width
visible: manager.digitalFactoryProjectModel.count > 0
hasMoreProjectsToLoad: manager.hasMoreProjectsToLoad
@ -244,4 +222,4 @@ Item
x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2)
}
}
}

View file

@ -0,0 +1,203 @@
//Copyright (C) 2022 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher.
import Qt.labs.qmlmodels 1.0
import QtQuick 2.15
import QtQuick.Controls 2.15
import UM 1.2 as UM
/*
* A re-sizeable table of data.
*
* This table combines a list of headers with a TableView to show certain roles in a table.
* The columns of the table can be resized.
* When the table becomes too big, you can scroll through the table. When a column becomes too small, the contents of
* the table are elided.
* The table gets Cura's themeing.
*/
Item
{
id: tableBase
required property var columnHeaders //The text to show in the headers of each column.
property alias model: tableView.model //A TableModel to display in this table. To use a ListModel for the rows, use "rows: listModel.items"
property int currentRow: -1 //The selected row index.
property var onDoubleClicked: function(row) {} //Something to execute when double clicked. Accepts one argument: The index of the row that was clicked on.
property bool allowSelection: true //Whether to allow the user to select items.
Row
{
id: headerBar
Repeater
{
id: headerRepeater
model: columnHeaders
Rectangle
{
//minimumWidth: Math.max(1, Math.round(tableBase.width / headerRepeater.count))
width: 300
height: UM.Theme.getSize("section").height
color: UM.Theme.getColor("secondary")
Label
{
id: contentText
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("narrow_margin").width
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("narrow_margin").width
text: modelData
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
Rectangle //Resize handle.
{
anchors
{
right: parent.right
top: parent.top
bottom: parent.bottom
}
width: UM.Theme.getSize("thick_lining").width
color: UM.Theme.getColor("thick_lining")
MouseArea
{
anchors.fill: parent
cursorShape: Qt.SizeHorCursor
drag
{
target: parent
axis: Drag.XAxis
}
onMouseXChanged:
{
if(drag.active)
{
let new_width = parent.parent.width + mouseX;
let sum_widths = mouseX;
for(let i = 0; i < headerBar.children.length; ++i)
{
sum_widths += headerBar.children[i].width;
}
if(sum_widths > tableBase.width)
{
new_width -= sum_widths - tableBase.width; //Limit the total width to not exceed the view.
}
let width_fraction = new_width / tableBase.width; //Scale with the same fraction along with the total width, if the table is resized.
parent.parent.width = Qt.binding(function() { return Math.max(10, Math.round(tableBase.width * width_fraction)) });
}
}
}
}
onWidthChanged:
{
tableView.forceLayout(); //Rescale table cells underneath as well.
}
}
}
}
TableView
{
id: tableView
anchors
{
top: headerBar.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
}
flickableDirection: Flickable.AutoFlickIfNeeded
clip: true
ScrollBar.vertical: ScrollBar
{
// Vertical ScrollBar, styled similarly to the scrollBar in the settings panel
id: verticalScrollBar
visible: tableView.contentHeight > tableView.height
background: Rectangle
{
implicitWidth: UM.Theme.getSize("scrollbar").width
radius: Math.round(implicitWidth / 2)
color: UM.Theme.getColor("scrollbar_background")
}
contentItem: Rectangle
{
id: scrollViewHandle
implicitWidth: UM.Theme.getSize("scrollbar").width
radius: Math.round(implicitWidth / 2)
color: verticalScrollBar.pressed ? UM.Theme.getColor("scrollbar_handle_down") : verticalScrollBar.hovered ? UM.Theme.getColor("scrollbar_handle_hover") : UM.Theme.getColor("scrollbar_handle")
Behavior on color { ColorAnimation { duration: 50; } }
}
}
columnWidthProvider: function(column)
{
return headerBar.children[column].width; //Cells get the same width as their column header.
}
delegate: Rectangle
{
implicitHeight: Math.max(1, cellContent.height)
color: UM.Theme.getColor((tableBase.currentRow == row) ? "primary" : ((row % 2 == 0) ? "main_background" : "viewport_background"))
Label
{
id: cellContent
width: parent.width
text: display
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
}
TextMetrics
{
id: cellTextMetrics
text: cellContent.text
font: cellContent.font
elide: cellContent.elide
elideWidth: cellContent.width
}
UM.TooltipArea
{
anchors.fill: parent
acceptedButtons: Qt.LeftButton
text: (cellTextMetrics.elidedText == cellContent.text) ? "" : cellContent.text //Show full text in tooltip if it was elided.
onClicked:
{
if(tableBase.allowSelection)
{
tableBase.currentRow = row; //Select this row.
}
}
onDoubleClicked:
{
tableBase.onDoubleClicked(row);
}
}
}
Connections
{
target: model
function onRowCountChanged()
{
tableView.contentY = 0; //When the number of rows is reduced, make sure to scroll back to the start.
}
}
}
}

View file

@ -12,7 +12,7 @@ from urllib.error import URLError
from typing import Dict
import ssl
import certifi
import certifi # type: ignore
from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine
from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage

View file

@ -1,19 +1,19 @@
// Copyright (c) 2018 Ultimaker B.V.
// Copyright (c) 2022 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
import QtQuick.Dialogs 1.2 // For filedialog
import UM 1.2 as UM
import UM 1.5 as UM
import Cura 1.0 as Cura
Cura.MachineAction
{
anchors.fill: parent;
anchors.fill: parent
property bool printerConnected: Cura.MachineManager.printerConnected
property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
property bool canUpdateFirmware: activeOutputDevice ? activeOutputDevice.activePrinter.canUpdateFirmware : false
@ -25,25 +25,22 @@ Cura.MachineAction
UM.I18nCatalog { id: catalog; name: "cura"}
spacing: UM.Theme.getSize("default_margin").height
Label
UM.Label
{
width: parent.width
text: catalog.i18nc("@title", "Update Firmware")
wrapMode: Text.WordWrap
font.pointSize: 18
}
Label
UM.Label
{
width: parent.width
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work.")
}
Label
UM.Label
{
width: parent.width
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "The firmware shipping with new printers works, but new versions tend to have more features and improvements.");
text: catalog.i18nc("@label", "The firmware shipping with new printers works, but new versions tend to have more features and improvements.")
}
Row
@ -52,10 +49,10 @@ Cura.MachineAction
width: childrenRect.width
spacing: UM.Theme.getSize("default_margin").width
property string firmwareName: Cura.MachineManager.activeMachine.getDefaultFirmwareName()
Button
Cura.SecondaryButton
{
id: autoUpgradeButton
text: catalog.i18nc("@action:button", "Automatically upgrade Firmware");
text: catalog.i18nc("@action:button", "Automatically upgrade Firmware")
enabled: parent.firmwareName != "" && canUpdateFirmware
onClicked:
{
@ -63,10 +60,10 @@ Cura.MachineAction
activeOutputDevice.updateFirmware(parent.firmwareName);
}
}
Button
Cura.SecondaryButton
{
id: manualUpgradeButton
text: catalog.i18nc("@action:button", "Upload custom Firmware");
text: catalog.i18nc("@action:button", "Upload custom Firmware")
enabled: canUpdateFirmware
onClicked:
{
@ -75,20 +72,18 @@ Cura.MachineAction
}
}
Label
UM.Label
{
width: parent.width
wrapMode: Text.WordWrap
visible: !printerConnected && !updateProgressDialog.visible
text: catalog.i18nc("@label", "Firmware can not be updated because there is no connection with the printer.");
text: catalog.i18nc("@label", "Firmware can not be updated because there is no connection with the printer.")
}
Label
{
width: parent.width
wrapMode: Text.WordWrap
visible: printerConnected && !canUpdateFirmware
text: catalog.i18nc("@label", "Firmware can not be updated because the connection with the printer does not support upgrading firmware.");
text: catalog.i18nc("@label", "Firmware can not be updated because the connection with the printer does not support upgrading firmware.")
}
}
@ -122,7 +117,7 @@ Cura.MachineAction
{
anchors.fill: parent
Label
UM.Label
{
anchors
{
@ -157,12 +152,10 @@ Cura.MachineAction
wrapMode: Text.Wrap
}
ProgressBar
UM.ProgressBar
{
id: prog
value: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareProgress : 0
minimumValue: 0
maximumValue: 100
value: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareProgress / 100 : 0
indeterminate:
{
if(manager.firmwareUpdater == null)
@ -173,18 +166,18 @@ Cura.MachineAction
}
anchors
{
left: parent.left;
right: parent.right;
left: parent.left
right: parent.right
}
}
}
rightButtons: [
Button
Cura.SecondaryButton
{
text: catalog.i18nc("@action:button","Close");
enabled: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareUpdateState != 1 : true;
onClicked: updateProgressDialog.visible = false;
text: catalog.i18nc("@action:button", "Close")
enabled: manager.firmwareUpdater != null ? manager.firmwareUpdater.firmwareUpdateState != 1 : true
onClicked: updateProgressDialog.visible = false
}
]
}

View file

@ -1,4 +1,4 @@
# Copyright (c) 2021 Ultimaker B.V.
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import math
@ -24,13 +24,15 @@ from cura.Settings.ExtruderManager import ExtruderManager
catalog = i18nCatalog("cura")
PositionOptional = NamedTuple("Position", [("x", Optional[float]), ("y", Optional[float]), ("z", Optional[float]), ("f", Optional[float]), ("e", Optional[float])])
PositionOptional = NamedTuple("PositionOptional", [("x", Optional[float]), ("y", Optional[float]), ("z", Optional[float]), ("f", Optional[float]), ("e", Optional[float])])
Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f", float), ("e", List[float])])
class FlavorParser:
"""This parser is intended to interpret the common firmware codes among all the different flavors"""
MAX_EXTRUDER_COUNT = 16
def __init__(self) -> None:
CuraApplication.getInstance().hideMessageSignal.connect(self._onHideMessage)
self._cancelled = False
@ -53,7 +55,7 @@ class FlavorParser:
def _clearValues(self) -> None:
self._extruder_number = 0
self._extrusion_length_offset = [0] # type: List[float]
self._extrusion_length_offset = [0] * self.MAX_EXTRUDER_COUNT # type: List[float]
self._layer_type = LayerPolygon.Inset0Type
self._layer_number = 0
self._previous_z = 0 # type: float
@ -283,8 +285,9 @@ class FlavorParser:
return func(position, params, path)
return position
def processTCode(self, T: int, line: str, position: Position, path: List[List[Union[float, int]]]) -> Position:
def processTCode(self, global_stack, T: int, line: str, position: Position, path: List[List[Union[float, int]]]) -> Position:
self._extruder_number = T
self._filament_diameter = global_stack.extruderList[self._extruder_number].getProperty("material_diameter", "value")
if self._extruder_number + 1 > len(position.e):
self._extrusion_length_offset.extend([0] * (self._extruder_number - len(position.e) + 1))
position.e.extend([0] * (self._extruder_number - len(position.e) + 1))
@ -354,7 +357,7 @@ class FlavorParser:
Logger.log("d", "Parsing g-code...")
current_position = Position(0, 0, 0, 0, [0])
current_position = Position(0, 0, 0, 0, [0] * self.MAX_EXTRUDER_COUNT)
current_path = [] #type: List[List[float]]
min_layer_number = 0
negative_layers = 0
@ -444,7 +447,7 @@ class FlavorParser:
# When changing tool, store the end point of the previous path, then process the code and finally
# add another point with the new position of the head.
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])
current_position = self.processTCode(T, line, current_position, current_path)
current_position = self.processTCode(global_stack, T, line, current_position, current_path)
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])
if line.startswith("M"):

View file

@ -1,239 +1,333 @@
// Copyright (c) 2015 Ultimaker B.V.
// Copyright (c) 2022 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.3
import QtQuick.Window 2.1
import UM 1.1 as UM
import UM 1.5 as UM
import Cura 1.0 as Cura
UM.Dialog
{
width: minimumWidth;
minimumWidth: 350 * screenScaleFactor;
title: catalog.i18nc("@title:window", "Convert Image")
height: minimumHeight;
minimumHeight: 250 * screenScaleFactor;
title: catalog.i18nc("@title:window", "Convert Image...")
minimumWidth: grid.width + 2 * UM.Theme.getSize("default_margin").height
minimumHeight: UM.Theme.getSize("modal_window_minimum").height
width: minimumWidth
height: minimumHeight
GridLayout
{
UM.I18nCatalog{id: catalog; name: "cura"}
anchors.fill: parent;
Layout.fillWidth: true
columnSpacing: 16 * screenScaleFactor
rowSpacing: 4 * screenScaleFactor
columns: 1
UM.I18nCatalog { id: catalog; name: "cura" }
id: grid
columnSpacing: UM.Theme.getSize("narrow_margin").width
rowSpacing: UM.Theme.getSize("narrow_margin").height
columns: 2
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","The maximum distance of each pixel from \"Base.\"")
Row {
width: parent.width
UM.Label
{
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
text: catalog.i18nc("@action:label", "Height (mm)")
Layout.alignment: Qt.AlignVCenter
Label {
text: catalog.i18nc("@action:label", "Height (mm)")
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
TextField {
id: peak_height
objectName: "Peak_Height"
validator: RegExpValidator {regExp: /^\d{0,3}([\,|\.]\d*)?$/}
width: 180 * screenScaleFactor
onTextChanged: { manager.onPeakHeightChanged(text) }
}
MouseArea {
id: peak_height_label
anchors.fill: parent
hoverEnabled: true
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","The base height from the build plate in millimeters.")
Row {
width: parent.width
Cura.TextField
{
id: peak_height
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
selectByMouse: true
objectName: "Peak_Height"
validator: RegExpValidator { regExp: /^\d{0,3}([\,|\.]\d*)?$/ }
onTextChanged: manager.onPeakHeightChanged(text)
}
Label {
text: catalog.i18nc("@action:label", "Base (mm)")
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
UM.ToolTip
{
text: catalog.i18nc("@info:tooltip", "The maximum distance of each pixel from \"Base.\"")
visible: peak_height.hovered || peak_height_label.containsMouse
targetPoint: Qt.point(peak_height.x + Math.round(peak_height.width / 2), 0)
y: peak_height.y + peak_height.height + UM.Theme.getSize("default_margin").height
}
TextField {
id: base_height
objectName: "Base_Height"
validator: RegExpValidator {regExp: /^\d{0,3}([\,|\.]\d*)?$/}
width: 180 * screenScaleFactor
onTextChanged: { manager.onBaseHeightChanged(text) }
}
UM.Label
{
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
text: catalog.i18nc("@action:label", "Base (mm)")
Layout.alignment: Qt.AlignVCenter
MouseArea
{
id: base_height_label
anchors.fill: parent
hoverEnabled: true
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","The width in millimeters on the build plate.")
Row {
width: parent.width
Cura.TextField
{
id: base_height
selectByMouse: true
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
objectName: "Base_Height"
validator: RegExpValidator { regExp: /^\d{0,3}([\,|\.]\d*)?$/ }
onTextChanged: manager.onBaseHeightChanged(text)
}
Label {
text: catalog.i18nc("@action:label", "Width (mm)")
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
UM.ToolTip
{
text: catalog.i18nc("@info:tooltip", "The base height from the build plate in millimeters.")
visible: base_height.hovered || base_height_label.containsMouse
targetPoint: Qt.point(base_height.x + Math.round(base_height.width / 2), 0)
y: base_height.y + base_height.height + UM.Theme.getSize("default_margin").height
}
TextField {
id: width
objectName: "Width"
focus: true
validator: RegExpValidator {regExp: /^[1-9]\d{0,2}([\,|\.]\d*)?$/}
width: 180 * screenScaleFactor
onTextChanged: { manager.onWidthChanged(text) }
}
UM.Label
{
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
text: catalog.i18nc("@action:label", "Width (mm)")
Layout.alignment: Qt.AlignVCenter
MouseArea {
id: width_label
anchors.fill: parent
hoverEnabled: true
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","The depth in millimeters on the build plate")
Row {
width: parent.width
Cura.TextField
{
id: width
selectByMouse: true
objectName: "Width"
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
focus: true
validator: RegExpValidator { regExp: /^[1-9]\d{0,2}([\,|\.]\d*)?$/ }
onTextChanged: manager.onWidthChanged(text)
}
Label {
text: catalog.i18nc("@action:label", "Depth (mm)")
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
TextField {
id: depth
objectName: "Depth"
focus: true
validator: RegExpValidator {regExp: /^[1-9]\d{0,2}([\,|\.]\d*)?$/}
width: 180 * screenScaleFactor
onTextChanged: { manager.onDepthChanged(text) }
}
UM.ToolTip
{
text: catalog.i18nc("@info:tooltip", "The width in millimeters on the build plate")
visible: width.hovered || width_label.containsMouse
targetPoint: Qt.point(width.x + Math.round(width.width / 2), 0)
y: width.y + width.height + UM.Theme.getSize("default_margin").height
}
UM.Label
{
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
text: catalog.i18nc("@action:label", "Depth (mm)")
Layout.alignment: Qt.AlignVCenter
MouseArea {
id: depth_label
anchors.fill: parent
hoverEnabled: true
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","For lithophanes dark pixels should correspond to thicker locations in order to block more light coming through. For height maps lighter pixels signify higher terrain, so lighter pixels should correspond to thicker locations in the generated 3D model.")
Row {
width: parent.width
Cura.TextField
{
id: depth
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
selectByMouse: true
objectName: "Depth"
focus: true
validator: RegExpValidator { regExp: /^[1-9]\d{0,2}([\,|\.]\d*)?$/ }
onTextChanged: manager.onDepthChanged(text)
}
//Empty label so 2 column layout works.
Label {
text: ""
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
ComboBox {
id: lighter_is_higher
objectName: "Lighter_Is_Higher"
model: [ catalog.i18nc("@item:inlistbox","Darker is higher"), catalog.i18nc("@item:inlistbox","Lighter is higher") ]
width: 180 * screenScaleFactor
onCurrentIndexChanged: { manager.onImageColorInvertChanged(currentIndex) }
}
UM.ToolTip
{
text: catalog.i18nc("@info:tooltip", "The depth in millimeters on the build plate")
visible: depth.hovered || depth_label.containsMouse
targetPoint: Qt.point(depth.x + Math.round(depth.width / 2), 0)
y: depth.y + depth.height + UM.Theme.getSize("default_margin").height
}
UM.Label
{
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
text: ""
Layout.alignment: Qt.AlignVCenter
MouseArea {
id: lighter_is_higher_label
anchors.fill: parent
hoverEnabled: true
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","For lithophanes a simple logarithmic model for translucency is available. For height maps the pixel values correspond to heights linearly.")
Row {
width: parent.width
Cura.ComboBox
{
id: lighter_is_higher
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
Layout.preferredHeight: UM.Theme.getSize("setting_control").height
objectName: "Lighter_Is_Higher"
textRole: "text"
model: [
{ text: catalog.i18nc("@item:inlistbox", "Darker is higher") },
{ text: catalog.i18nc("@item:inlistbox", "Lighter is higher") }
]
onCurrentIndexChanged: { manager.onImageColorInvertChanged(currentIndex) }
}
Label {
text: "Color Model"
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
ComboBox {
id: color_model
objectName: "ColorModel"
model: [ catalog.i18nc("@item:inlistbox","Linear"), catalog.i18nc("@item:inlistbox","Translucency") ]
width: 180 * screenScaleFactor
onCurrentIndexChanged: { manager.onColorModelChanged(currentIndex) }
}
UM.ToolTip
{
text: catalog.i18nc("@info:tooltip", "For lithophanes dark pixels should correspond to thicker locations in order to block more light coming through. For height maps lighter pixels signify higher terrain, so lighter pixels should correspond to thicker locations in the generated 3D model.")
visible: lighter_is_higher.hovered || lighter_is_higher_label.containsMouse
targetPoint: Qt.point(lighter_is_higher.x + Math.round(lighter_is_higher.width / 2), 0)
y: lighter_is_higher.y + lighter_is_higher.height + UM.Theme.getSize("default_margin").height
}
UM.Label
{
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
text: catalog.i18nc("@action:label", "Color Model")
Layout.alignment: Qt.AlignVCenter
MouseArea {
id: color_model_label
anchors.fill: parent
hoverEnabled: true
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter. Lowering this value increases the contrast in dark regions and decreases the contrast in light regions of the image.")
visible: color_model.currentText == catalog.i18nc("@item:inlistbox","Translucency")
Row {
width: parent.width
Cura.ComboBox
{
id: color_model
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
Layout.preferredHeight: UM.Theme.getSize("setting_control").height
objectName: "ColorModel"
textRole: "text"
model: [
{ text: catalog.i18nc("@item:inlistbox", "Linear") },
{ text: catalog.i18nc("@item:inlistbox", "Translucency") }
]
onCurrentIndexChanged: { manager.onColorModelChanged(currentIndex) }
}
Label {
text: catalog.i18nc("@action:label", "1mm Transmittance (%)")
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
TextField {
id: transmittance
objectName: "Transmittance"
focus: true
validator: RegExpValidator {regExp: /^[1-9]\d{0,2}([\,|\.]\d*)?$/}
width: 180 * screenScaleFactor
onTextChanged: { manager.onTransmittanceChanged(text) }
}
UM.ToolTip
{
text: catalog.i18nc("@info:tooltip", "For lithophanes a simple logarithmic model for translucency is available. For height maps the pixel values correspond to heights linearly.")
visible: color_model.hovered || color_model_label.containsMouse
targetPoint: Qt.point(color_model.x + Math.round(color_model.width / 2), 0)
y: color_model.y + color_model.height + UM.Theme.getSize("default_margin").height
}
UM.Label
{
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
text: catalog.i18nc("@action:label", "1mm Transmittance (%)")
Layout.alignment: Qt.AlignVCenter
MouseArea {
id: transmittance_label
anchors.fill: parent
hoverEnabled: true
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","The amount of smoothing to apply to the image.")
Row {
width: parent.width
Cura.TextField
{
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
selectByMouse: true
objectName: "Transmittance"
validator: RegExpValidator { regExp: /^[1-9]\d{0,2}([\,|\.]\d*)?$/ }
onTextChanged: manager.onTransmittanceChanged(text)
Label {
text: catalog.i18nc("@action:label", "Smoothing")
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: 180 * screenScaleFactor
height: 20 * screenScaleFactor
Layout.fillWidth: true
Slider {
id: smoothing
objectName: "Smoothing"
maximumValue: 100.0
stepSize: 1.0
width: 180
onValueChanged: { manager.onSmoothingChanged(value) }
}
}
UM.ToolTip
{
text: catalog.i18nc("@info:tooltip", "The percentage of light penetrating a print with a thickness of 1 millimeter. Lowering this value increases the contrast in dark regions and decreases the contrast in light regions of the image.")
visible: parent.hovered || transmittance_label.containsMouse
targetPoint: Qt.point(parent.x + Math.round(parent.width / 2), 0)
y: parent.y + parent.height + UM.Theme.getSize("default_margin").height
}
}
UM.Label
{
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
text: catalog.i18nc("@action:label", "Smoothing")
Layout.alignment: Qt.AlignVCenter
MouseArea
{
id: smoothing_label
anchors.fill: parent
hoverEnabled: true
}
}
Cura.SpinBox
{
id: smoothing
Layout.fillWidth: true
Layout.minimumWidth: UM.Theme.getSize("setting_control").width
objectName: "Smoothing"
to: 100.0
stepSize: 1.0
onValueChanged: manager.onSmoothingChanged(value)
}
UM.ToolTip
{
text: catalog.i18nc("@info:tooltip", "The amount of smoothing to apply to the image.")
visible: smoothing.hovered || smoothing_label.containsMouse
targetPoint: Qt.point(smoothing.x + Math.round(smoothing.width / 2), 0)
y: smoothing.y + smoothing.height + UM.Theme.getSize("default_margin").height
}
}
Item
{
ButtonGroup
{
buttons: [ok_button, cancel_button]
checkedButton: ok_button
}
}
onAccepted: manager.onOkButtonClicked()
onRejected: manager.onCancelButtonClicked()
buttonSpacing: UM.Theme.getSize("default_margin").width
rightButtons: [
Button
Cura.TertiaryButton
{
id:ok_button
text: catalog.i18nc("@action:button","OK");
onClicked: { manager.onOkButtonClicked() }
enabled: true
id: cancel_button
text: catalog.i18nc("@action:button", "Cancel")
onClicked: manager.onCancelButtonClicked()
},
Button
Cura.PrimaryButton
{
id:cancel_button
text: catalog.i18nc("@action:button","Cancel");
onClicked: { manager.onCancelButtonClicked() }
enabled: true
id: ok_button
text: catalog.i18nc("@action:button", "OK")
onClicked: manager.onOkButtonClicked()
}
]
}

View file

@ -1,11 +1,11 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
//Copyright (c) 2022 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import UM 1.3 as UM
import UM 1.5 as UM
import Cura 1.1 as Cura
@ -88,7 +88,7 @@ Cura.MachineAction
}
}
Label
UM.Label
{
id: machineNameLabel
anchors.top: parent.top
@ -97,7 +97,6 @@ Cura.MachineAction
text: Cura.MachineManager.activeMachine.name
horizontalAlignment: Text.AlignHCenter
font: UM.Theme.getFont("large_bold")
renderType: Text.NativeRendering
}
UM.TabRow
@ -111,6 +110,7 @@ Cura.MachineAction
model: tabNameModel
delegate: UM.TabRowButton
{
checked: model.index == 0
text: model.name
}
}

View file

@ -0,0 +1,12 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from cura.UltimakerCloud import UltimakerCloudConstants
from cura.ApplicationMetadata import CuraSDKVersion
ROOT_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}"
ROOT_CURA_URL = f"{ROOT_URL}/cura/v{CuraSDKVersion}" # Root of all Marketplace API requests.
ROOT_USER_URL = f"{ROOT_URL}/user"
PACKAGES_URL = f"{ROOT_CURA_URL}/packages" # URL to use for requesting the list of packages.
PACKAGE_UPDATES_URL = f"{PACKAGES_URL}/package-updates" # URL to use for requesting the list of packages that can be updated.
USER_PACKAGES_URL = f"{ROOT_USER_URL}/packages"

View file

@ -0,0 +1,126 @@
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Any, Dict, List, Optional, TYPE_CHECKING
from PyQt5.QtCore import pyqtSlot, QObject
from UM.Version import Version
from UM.i18n import i18nCatalog
from UM.TaskManagement.HttpRequestManager import HttpRequestManager
from UM.Logger import Logger
from .PackageList import PackageList
from .PackageModel import PackageModel
from .Constants import PACKAGE_UPDATES_URL
if TYPE_CHECKING:
from PyQt5.QtCore import QObject
from PyQt5.QtNetwork import QNetworkReply
catalog = i18nCatalog("cura")
class LocalPackageList(PackageList):
PACKAGE_CATEGORIES = {
"installed":
{
"plugin": catalog.i18nc("@label", "Installed Plugins"),
"material": catalog.i18nc("@label", "Installed Materials")
},
"bundled":
{
"plugin": catalog.i18nc("@label", "Bundled Plugins"),
"material": catalog.i18nc("@label", "Bundled Materials")
}
} # The section headers to be used for the different package categories
def __init__(self, parent: Optional["QObject"] = None) -> None:
super().__init__(parent)
self._has_footer = False
self._ongoing_requests["check_updates"] = None
self._package_manager.packagesWithUpdateChanged.connect(self._sortSectionsOnUpdate)
self._package_manager.packageUninstalled.connect(self._removePackageModel)
def _sortSectionsOnUpdate(self) -> None:
section_order = dict(zip([i for k, v in self.PACKAGE_CATEGORIES.items() for i in self.PACKAGE_CATEGORIES[k].values()], ["a", "b", "c", "d"]))
self.sort(lambda model: (section_order[model.sectionTitle], model.canUpdate, model.displayName.lower()), key = "package")
def _removePackageModel(self, package_id: str) -> None:
"""
Cleanup function to remove the package model from the list. Note that this is only done if the package can't
be updated, it is in the to remove list and isn't in the to be installed list
"""
package = self.getPackageModel(package_id)
if package and not package.canUpdate and \
package_id in self._package_manager.getToRemovePackageIDs() and \
package_id not in self._package_manager.getPackagesToInstall():
index = self.find("package", package_id)
if index < 0:
Logger.error(f"Could not find card in Listview corresponding with {package_id}")
self.updatePackages()
return
self.removeItem(index)
@pyqtSlot()
def updatePackages(self) -> None:
"""Update the list with local packages, these are materials or plugin, either bundled or user installed. The list
will also contain **to be removed** or **to be installed** packages since the user might still want to interact
with these.
"""
self.setErrorMessage("") # Clear any previous errors.
self.setIsLoading(True)
# Obtain and sort the local packages
self.setItems([{"package": p} for p in [self._makePackageModel(p) for p in self._package_manager.local_packages]])
self._sortSectionsOnUpdate()
self.checkForUpdates(self._package_manager.local_packages)
self.setIsLoading(False)
self.setHasMore(False) # All packages should have been loaded at this time
def _makePackageModel(self, package_info: Dict[str, Any]) -> PackageModel:
""" Create a PackageModel from the package_info and determine its section_title"""
package_id = package_info["package_id"]
bundled_or_installed = "bundled" if self._package_manager.isBundledPackage(package_id) else "installed"
package_type = package_info["package_type"]
section_title = self.PACKAGE_CATEGORIES[bundled_or_installed][package_type]
package = PackageModel(package_info, section_title = section_title, parent = self)
self._connectManageButtonSignals(package)
return package
def checkForUpdates(self, packages: List[Dict[str, Any]]) -> None:
installed_packages = "&".join([f"installed_packages={package['package_id']}:{package['package_version']}" for package in packages])
request_url = f"{PACKAGE_UPDATES_URL}?{installed_packages}"
self._ongoing_requests["check_updates"] = HttpRequestManager.getInstance().get(
request_url,
scope = self._scope,
callback = self._parseResponse
)
def _parseResponse(self, reply: "QNetworkReply") -> None:
"""
Parse the response from the package list API request which can update.
:param reply: A reply containing information about a number of packages.
"""
response_data = HttpRequestManager.readJSON(reply)
if "data" not in response_data:
Logger.error(
f"Could not interpret the server's response. Missing 'data' from response data. Keys in response: {response_data.keys()}")
return
if len(response_data["data"]) == 0:
return
packages = response_data["data"]
for package in packages:
self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"]))
package_model = self.getPackageModel(package["package_id"])
if package_model:
# Also make sure that the local list knows where to get an update
package_model.setDownloadUrl(package["download_url"])
self._ongoing_requests["check_updates"] = None

View file

@ -0,0 +1,117 @@
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from typing import Optional, cast
from cura.CuraApplication import CuraApplication # Creating QML objects and managing packages.
from UM.Extension import Extension # We are implementing the main object of an extension here.
from UM.PluginRegistry import PluginRegistry # To find out where we are stored (the proper way).
from .RemotePackageList import RemotePackageList # To register this type with QML.
from .LocalPackageList import LocalPackageList # To register this type with QML.
class Marketplace(Extension, QObject):
"""
The main managing object for the Marketplace plug-in.
"""
def __init__(self, parent: Optional[QObject] = None) -> None:
QObject.__init__(self, parent)
Extension.__init__(self)
self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here.
self._plugin_registry: Optional[PluginRegistry] = None
self._package_manager = CuraApplication.getInstance().getPackageManager()
self._material_package_list: Optional[RemotePackageList] = None
self._plugin_package_list: Optional[RemotePackageList] = None
# Not entirely the cleanest code, since the localPackage list also checks the server if there are updates
# Since that in turn will trigger notifications to be shown, we do need to construct it here and make sure
# that it checks for updates...
preferences = CuraApplication.getInstance().getPreferences()
preferences.addPreference("info/automatic_plugin_update_check", True)
self._local_package_list = LocalPackageList(self)
if preferences.getValue("info/automatic_plugin_update_check"):
self._local_package_list.checkForUpdates(self._package_manager.local_packages)
self._package_manager.installedPackagesChanged.connect(self.checkIfRestartNeeded)
self._tab_shown: int = 0
self._restart_needed = False
def getTabShown(self) -> int:
return self._tab_shown
def setTabShown(self, tab_shown: int) -> None:
if tab_shown != self._tab_shown:
self._tab_shown = tab_shown
self.tabShownChanged.emit()
tabShownChanged = pyqtSignal()
tabShown = pyqtProperty(int, fget=getTabShown, fset=setTabShown, notify=tabShownChanged)
@pyqtProperty(QObject, constant=True)
def MaterialPackageList(self):
if self._material_package_list is None:
self._material_package_list = RemotePackageList()
self._material_package_list.packageTypeFilter = "material"
return self._material_package_list
@pyqtProperty(QObject, constant=True)
def PluginPackageList(self):
if self._plugin_package_list is None:
self._plugin_package_list = RemotePackageList()
self._plugin_package_list.packageTypeFilter = "plugin"
return self._plugin_package_list
@pyqtProperty(QObject, constant=True)
def LocalPackageList(self):
return self._local_package_list
@pyqtSlot()
def show(self) -> None:
"""
Opens the window of the Marketplace.
If the window hadn't been loaded yet into Qt, it will be created lazily.
"""
if self._window is None:
self._plugin_registry = PluginRegistry.getInstance()
self._plugin_registry.pluginsEnabledOrDisabledChanged.connect(self.checkIfRestartNeeded)
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if plugin_path is None:
plugin_path = os.path.dirname(__file__)
path = os.path.join(plugin_path, "resources", "qml", "Marketplace.qml")
self._window = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
if self._window is None: # Still None? Failed to load the QML then.
return
if not self._window.isVisible():
self.setTabShown(0)
self._window.show()
self._window.requestActivate() # Bring window into focus, if it was already open in the background.
@pyqtSlot()
def setVisibleTabToMaterials(self) -> None:
"""
Set the tab shown to the remote materials one.
Not implemented in a more generic way because it needs the ability to be called with 'callExtensionMethod'.
"""
self.setTabShown(1)
def checkIfRestartNeeded(self) -> None:
if self._package_manager.hasPackagesToRemoveOrInstall or \
cast(PluginRegistry, self._plugin_registry).getCurrentSessionActivationChangedPlugins():
self._restart_needed = True
else:
self._restart_needed = False
self.showRestartNotificationChanged.emit()
showRestartNotificationChanged = pyqtSignal()
@pyqtProperty(bool, notify=showRestartNotificationChanged)
def showRestartNotification(self) -> bool:
return self._restart_needed

View file

@ -0,0 +1,305 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import tempfile
import json
import os.path
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt
from typing import cast, Dict, Optional, Set, TYPE_CHECKING
from UM.i18n import i18nCatalog
from UM.Qt.ListModel import ListModel
from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
from UM.TaskManagement.HttpRequestManager import HttpRequestData, HttpRequestManager
from UM.Logger import Logger
from UM import PluginRegistry
from cura.CuraApplication import CuraApplication
from cura.CuraPackageManager import CuraPackageManager
from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization.
from .PackageModel import PackageModel
from .Constants import USER_PACKAGES_URL, PACKAGES_URL
if TYPE_CHECKING:
from PyQt5.QtCore import QObject
from PyQt5.QtNetwork import QNetworkReply
catalog = i18nCatalog("cura")
class PackageList(ListModel):
""" A List model for Packages, this class serves as parent class for more detailed implementations.
such as Packages obtained from Remote or Local source
"""
PackageRole = Qt.UserRole + 1
DISK_WRITE_BUFFER_SIZE = 256 * 1024 # 256 KB
def __init__(self, parent: Optional["QObject"] = None) -> None:
super().__init__(parent)
self._package_manager: CuraPackageManager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager())
self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry()
self._account = CuraApplication.getInstance().getCuraAPI().account
self._error_message = ""
self.addRoleName(self.PackageRole, "package")
self._is_loading = False
self._has_more = False
self._has_footer = True
self._to_install: Dict[str, str] = {}
self._ongoing_requests: Dict[str, Optional[HttpRequestData]] = {"download_package": None}
self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance()))
self._license_dialogs: Dict[str, QObject] = {}
def __del__(self) -> None:
""" When this object is deleted it will loop through all registered API requests and aborts them """
try:
self.isLoadingChanged.disconnect()
self.hasMoreChanged.disconnect()
except RuntimeError:
pass
self.cleanUpAPIRequest()
def abortRequest(self, request_id: str) -> None:
"""Aborts a single request"""
if request_id in self._ongoing_requests and self._ongoing_requests[request_id]:
HttpRequestManager.getInstance().abortRequest(self._ongoing_requests[request_id])
self._ongoing_requests[request_id] = None
@pyqtSlot()
def cleanUpAPIRequest(self) -> None:
for request_id in self._ongoing_requests:
self.abortRequest(request_id)
@pyqtSlot()
def updatePackages(self) -> None:
""" A Qt slot which will update the List from a source. Actual implementation should be done in the child class"""
pass
def reset(self) -> None:
""" Resets and clears the list"""
self.clear()
isLoadingChanged = pyqtSignal()
def setIsLoading(self, value: bool) -> None:
if self._is_loading != value:
self._is_loading = value
self.isLoadingChanged.emit()
@pyqtProperty(bool, fset = setIsLoading, notify = isLoadingChanged)
def isLoading(self) -> bool:
""" Indicating if the the packages are loading
:return" ``True`` if the list is being obtained, otherwise ``False``
"""
return self._is_loading
hasMoreChanged = pyqtSignal()
def setHasMore(self, value: bool) -> None:
if self._has_more != value:
self._has_more = value
self.hasMoreChanged.emit()
@pyqtProperty(bool, fset = setHasMore, notify = hasMoreChanged)
def hasMore(self) -> bool:
""" Indicating if there are more packages available to load.
:return: ``True`` if there are more packages to load, or ``False``.
"""
return self._has_more
errorMessageChanged = pyqtSignal()
def setErrorMessage(self, error_message: str) -> None:
if self._error_message != error_message:
self._error_message = error_message
self.errorMessageChanged.emit()
@pyqtProperty(str, notify = errorMessageChanged, fset = setErrorMessage)
def errorMessage(self) -> str:
""" If an error occurred getting the list of packages, an error message will be held here.
If no error occurred (yet), this will be an empty string.
:return: An error message, if any, or an empty string if everything went okay.
"""
return self._error_message
@pyqtProperty(bool, constant = True)
def hasFooter(self) -> bool:
""" Indicating if the PackageList should have a Footer visible. For paginated PackageLists
:return: ``True`` if a Footer should be displayed in the ListView, e.q.: paginated lists, ``False`` Otherwise"""
return self._has_footer
def getPackageModel(self, package_id: str) -> Optional[PackageModel]:
index = self.find("package", package_id)
data = self.getItem(index)
if data:
return data.get("package")
return None
def _openLicenseDialog(self, package_id: str, license_content: str) -> None:
plugin_path = self._plugin_registry.getPluginPath("Marketplace")
if plugin_path is None:
plugin_path = os.path.dirname(__file__)
# create a QML component for the license dialog
license_dialog_component_path = os.path.join(plugin_path, "resources", "qml", "LicenseDialog.qml")
dialog = CuraApplication.getInstance().createQmlComponent(license_dialog_component_path, {
"licenseContent": license_content,
"packageId": package_id,
"handler": self
})
dialog.show()
# place dialog in class such that it does not get remove by garbage collector
self._license_dialogs[package_id] = dialog
@pyqtSlot(str)
def onLicenseAccepted(self, package_id: str) -> None:
# close dialog
dialog = self._license_dialogs.pop(package_id)
if dialog is not None:
dialog.deleteLater()
# install relevant package
self._install(package_id)
@pyqtSlot(str)
def onLicenseDeclined(self, package_id: str) -> None:
# close dialog
dialog = self._license_dialogs.pop(package_id)
if dialog is not None:
dialog.deleteLater()
# reset package card
self._package_manager.packageInstallingFailed.emit(package_id)
def _requestInstall(self, package_id: str, update: bool = False) -> None:
package_path = self._to_install[package_id]
license_content = self._package_manager.getPackageLicense(package_path)
if not update and license_content is not None and license_content != "":
# If installation is not and update, and the packages contains a license then
# open dialog, prompting the using to accept the plugin license
self._openLicenseDialog(package_id, license_content)
else:
# Otherwise continue the installation
self._install(package_id, update)
def _install(self, package_id: str, update: bool = False) -> None:
package_path = self._to_install.pop(package_id)
to_be_installed = self._package_manager.installPackage(package_path) is not None
if not to_be_installed:
Logger.warning(f"Could not install {package_id}")
return
package = self.getPackageModel(package_id)
if package:
self.subscribeUserToPackage(package_id, str(package.sdk_version))
else:
Logger.log("w", f"Unable to get data on package {package_id}")
def download(self, package_id: str, url: str, update: bool = False) -> None:
"""Initiate the download request
:param package_id: the package identification string
:param url: the URL from which the package needs to be obtained
:param update: A flag if this is download request is an update process
"""
if url == "":
url = f"{PACKAGES_URL}/{package_id}/download"
def downloadFinished(reply: "QNetworkReply") -> None:
self._downloadFinished(package_id, reply, update)
def downloadError(reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None:
self._downloadError(package_id, update, reply, error)
self._ongoing_requests["download_package"] = HttpRequestManager.getInstance().get(
url,
scope = self._scope,
callback = downloadFinished,
error_callback = downloadError
)
def _downloadFinished(self, package_id: str, reply: "QNetworkReply", update: bool = False) -> None:
with tempfile.NamedTemporaryFile(mode = "wb+", suffix = ".curapackage", delete = False) as temp_file:
try:
bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE)
while bytes_read:
temp_file.write(bytes_read)
bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE)
except IOError as e:
Logger.error(f"Failed to write downloaded package to temp file {e}")
temp_file.close()
self._downloadError(package_id, update)
except RuntimeError:
# Setting the ownership of this object to not qml can still result in a RuntimeError. Which can occur when quickly toggling
# between de-/constructing Remote or Local PackageLists. This try-except is here to prevent a hard crash when the wrapped C++ object
# was deleted when it was still parsing the response
temp_file.close()
return
temp_file.close()
self._to_install[package_id] = temp_file.name
self._ongoing_requests["download_package"] = None
self._requestInstall(package_id, update)
def _downloadError(self, package_id: str, update: bool = False, reply: Optional["QNetworkReply"] = None, error: Optional["QNetworkReply.NetworkError"] = None) -> None:
if reply:
reply_string = bytes(reply.readAll()).decode()
Logger.error(f"Failed to download package: {package_id} due to {reply_string}")
self._package_manager.packageInstallingFailed.emit(package_id)
def subscribeUserToPackage(self, package_id: str, sdk_version: str) -> None:
"""Subscribe the user (if logged in) to the package for a given SDK
:param package_id: the package identification string
:param sdk_version: the SDK version
"""
if self._account.isLoggedIn:
HttpRequestManager.getInstance().put(
url = USER_PACKAGES_URL,
data = json.dumps({"data": {"package_id": package_id, "sdk_version": sdk_version}}).encode(),
scope = self._scope
)
def unsunscribeUserFromPackage(self, package_id: str) -> None:
"""Unsubscribe the user (if logged in) from the package
:param package_id: the package identification string
"""
if self._account.isLoggedIn:
HttpRequestManager.getInstance().delete(url = f"{USER_PACKAGES_URL}/{package_id}", scope = self._scope)
# --- Handle the manage package buttons ---
def _connectManageButtonSignals(self, package: PackageModel) -> None:
package.installPackageTriggered.connect(self.installPackage)
package.uninstallPackageTriggered.connect(self.uninstallPackage)
package.updatePackageTriggered.connect(self.updatePackage)
def installPackage(self, package_id: str, url: str) -> None:
"""Install a package from the Marketplace
:param package_id: the package identification string
"""
if not self._package_manager.reinstallPackage(package_id):
self.download(package_id, url, False)
else:
package = self.getPackageModel(package_id)
if package:
self.subscribeUserToPackage(package_id, str(package.sdk_version))
def uninstallPackage(self, package_id: str) -> None:
"""Uninstall a package from the Marketplace
:param package_id: the package identification string
"""
self._package_manager.removePackage(package_id)
self.unsunscribeUserFromPackage(package_id)
def updatePackage(self, package_id: str, url: str) -> None:
"""Update a package from the Marketplace
:param package_id: the package identification string
"""
self._package_manager.removePackage(package_id, force_add = not self._package_manager.isBundledPackage(package_id))
self.download(package_id, url, True)

View file

@ -0,0 +1,387 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import re
from enum import Enum
from typing import Any, cast, Dict, List, Optional
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal, pyqtSlot
from PyQt5.QtQml import QQmlEngine
from cura.CuraApplication import CuraApplication
from cura.CuraPackageManager import CuraPackageManager
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with.
from UM.i18n import i18nCatalog # To translate placeholder names if data is not present.
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
catalog = i18nCatalog("cura")
class PackageModel(QObject):
"""
Represents a package, containing all the relevant information to be displayed about a package.
"""
def __init__(self, package_data: Dict[str, Any], section_title: Optional[str] = None, parent: Optional[QObject] = None) -> None:
"""
Constructs a new model for a single package.
:param package_data: The data received from the Marketplace API about the package to create.
:param section_title: If the packages are to be categorized per section provide the section_title
:param parent: The parent QML object that controls the lifetime of this model (normally a PackageList).
"""
super().__init__(parent)
QQmlEngine.setObjectOwnership(self, QQmlEngine.CppOwnership)
self._package_manager: CuraPackageManager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager())
self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry()
self._package_id = package_data.get("package_id", "UnknownPackageId")
self._package_type = package_data.get("package_type", "")
self._is_bundled = package_data.get("is_bundled", False)
self._icon_url = package_data.get("icon_url", "")
self._marketplace_url = package_data.get("marketplace_url", "")
self._display_name = package_data.get("display_name", catalog.i18nc("@label:property", "Unknown Package"))
tags = package_data.get("tags", [])
self._is_checked_by_ultimaker = (self._package_type == "plugin" and "verified" in tags) or (
self._package_type == "material" and "certified" in tags)
self._package_version = package_data.get("package_version", "") # Display purpose, no need for 'UM.Version'.
self._package_info_url = package_data.get("website", "") # Not to be confused with 'download_url'.
self._download_count = package_data.get("download_count", 0)
self._description = package_data.get("description", "")
self._formatted_description = self._format(self._description)
self._download_url = package_data.get("download_url", "")
self._release_notes = package_data.get("release_notes", "") # Not used yet, propose to add to description?
subdata = package_data.get("data", {})
self._technical_data_sheet = self._findLink(subdata, "technical_data_sheet")
self._safety_data_sheet = self._findLink(subdata, "safety_data_sheet")
self._where_to_buy = self._findLink(subdata, "where_to_buy")
self._compatible_printers = self._getCompatiblePrinters(subdata)
self._compatible_support_materials = self._getCompatibleSupportMaterials(subdata)
self._is_compatible_material_station = self._isCompatibleMaterialStation(subdata)
self._is_compatible_air_manager = self._isCompatibleAirManager(subdata)
author_data = package_data.get("author", {})
self._author_name = author_data.get("display_name", catalog.i18nc("@label:property", "Unknown Author"))
self._author_info_url = author_data.get("website", "")
if not self._icon_url or self._icon_url == "":
self._icon_url = author_data.get("icon_url", "")
self._can_update = False
self._section_title = section_title
self.sdk_version = package_data.get("sdk_version_semver", "")
# Note that there's a lot more info in the package_data than just these specified here.
self.enablePackageTriggered.connect(self._plugin_registry.enablePlugin)
self.disablePackageTriggered.connect(self._plugin_registry.disablePlugin)
self._plugin_registry.pluginsEnabledOrDisabledChanged.connect(self.stateManageButtonChanged)
self._package_manager.packageInstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id))
self._package_manager.packageUninstalled.connect(lambda pkg_id: self._packageInstalled(pkg_id))
self._package_manager.packageInstallingFailed.connect(lambda pkg_id: self._packageInstalled(pkg_id))
self._package_manager.packagesWithUpdateChanged.connect(self._processUpdatedPackages)
self._is_busy = False
@pyqtSlot()
def _processUpdatedPackages(self):
self.setCanUpdate(self._package_manager.checkIfPackageCanUpdate(self._package_id))
def __del__(self):
self._package_manager.packagesWithUpdateChanged.disconnect(self._processUpdatedPackages)
def __eq__(self, other: object) -> bool:
if isinstance(other, PackageModel):
return other == self
elif isinstance(other, str):
return other == self._package_id
else:
return False
def __repr__(self) -> str:
return f"<{self._package_id} : {self._package_version} : {self._section_title}>"
def _findLink(self, subdata: Dict[str, Any], link_type: str) -> str:
"""
Searches the package data for a link of a certain type.
The links are not in a fixed path in the package data. We need to iterate over the available links to find them.
:param subdata: The "data" element in the package data, which should contain links.
:param link_type: The type of link to find.
:return: A URL of where the link leads, or an empty string if there is no link of that type in the package data.
"""
links = subdata.get("links", [])
for link in links:
if link.get("type", "") == link_type:
return link.get("url", "")
else:
return "" # No link with the correct type was found.
def _format(self, text: str) -> str:
"""
Formats a user-readable block of text for display.
:return: A block of rich text with formatting embedded.
"""
# Turn all in-line hyperlinks into actual links.
url_regex = re.compile(r"(((http|https)://)[a-zA-Z0-9@:%.\-_+~#?&/=]{2,256}\.[a-z]{2,12}(/[a-zA-Z0-9@:%.\-_+~#?&/=]*)?)")
text = re.sub(url_regex, r'<a href="\1">\1</a>', text)
# Turn newlines into <br> so that they get displayed as newlines when rendering as rich text.
text = text.replace("\n", "<br>")
return text
def _getCompatiblePrinters(self, subdata: Dict[str, Any]) -> List[str]:
"""
Gets the list of printers that this package provides material compatibility with.
Any printer is listed, even if it's only for a single nozzle on a single material in the package.
:param subdata: The "data" element in the package data, which should contain this compatibility information.
:return: A list of printer names that this package provides material compatibility with.
"""
result = set()
for material in subdata.get("materials", []):
for compatibility in material.get("compatibility", []):
printer_name = compatibility.get("machine_name")
if printer_name is None:
continue # Missing printer name information. Skip this one.
for subcompatibility in compatibility.get("compatibilities", []):
if subcompatibility.get("hardware_compatible", False):
result.add(printer_name)
break
return list(sorted(result))
def _getCompatibleSupportMaterials(self, subdata: Dict[str, Any]) -> List[str]:
"""
Gets the list of support materials that the materials in this package are compatible with.
Since the materials are individually encoded as keys in the API response, only PVA and Breakaway are currently
supported.
:param subdata: The "data" element in the package data, which should contain this compatibility information.
:return: A list of support materials that the materials in this package are compatible with.
"""
result = set()
container_registry = CuraContainerRegistry.getInstance()
try:
pva_name = container_registry.findContainersMetadata(id = "ultimaker_pva")[0].get("name", "Ultimaker PVA")
except IndexError:
pva_name = "Ultimaker PVA"
try:
breakaway_name = container_registry.findContainersMetadata(id = "ultimaker_bam")[0].get("name", "Ultimaker Breakaway")
except IndexError:
breakaway_name = "Ultimaker Breakaway"
for material in subdata.get("materials", []):
if material.get("pva_compatible", False):
result.add(pva_name)
if material.get("breakaway_compatible", False):
result.add(breakaway_name)
return list(sorted(result))
def _isCompatibleMaterialStation(self, subdata: Dict[str, Any]) -> bool:
"""
Finds out if this package provides any material that is compatible with the material station.
:param subdata: The "data" element in the package data, which should contain this compatibility information.
:return: Whether this package provides any material that is compatible with the material station.
"""
for material in subdata.get("materials", []):
for compatibility in material.get("compatibility", []):
if compatibility.get("material_station_optimized", False):
return True
return False
def _isCompatibleAirManager(self, subdata: Dict[str, Any]) -> bool:
"""
Finds out if this package provides any material that is compatible with the air manager.
:param subdata: The "data" element in the package data, which should contain this compatibility information.
:return: Whether this package provides any material that is compatible with the air manager.
"""
for material in subdata.get("materials", []):
for compatibility in material.get("compatibility", []):
if compatibility.get("air_manager_optimized", False):
return True
return False
@pyqtProperty(str, constant = True)
def packageId(self) -> str:
return self._package_id
@pyqtProperty(str, constant=True)
def marketplaceURL(self)-> str:
return self._marketplace_url
@pyqtProperty(str, constant = True)
def packageType(self) -> str:
return self._package_type
@pyqtProperty(str, constant = True)
def iconUrl(self) -> str:
return self._icon_url
@pyqtProperty(str, constant = True)
def displayName(self) -> str:
return self._display_name
@pyqtProperty(bool, constant = True)
def isCheckedByUltimaker(self):
return self._is_checked_by_ultimaker
@pyqtProperty(str, constant = True)
def packageVersion(self) -> str:
return self._package_version
@pyqtProperty(str, constant = True)
def packageInfoUrl(self) -> str:
return self._package_info_url
@pyqtProperty(int, constant = True)
def downloadCount(self) -> str:
return self._download_count
@pyqtProperty(str, constant = True)
def description(self) -> str:
return self._description
@pyqtProperty(str, constant = True)
def formattedDescription(self) -> str:
return self._formatted_description
@pyqtProperty(str, constant = True)
def authorName(self) -> str:
return self._author_name
@pyqtProperty(str, constant = True)
def authorInfoUrl(self) -> str:
return self._author_info_url
@pyqtProperty(str, constant = True)
def sectionTitle(self) -> Optional[str]:
return self._section_title
@pyqtProperty(str, constant = True)
def technicalDataSheet(self) -> str:
return self._technical_data_sheet
@pyqtProperty(str, constant = True)
def safetyDataSheet(self) -> str:
return self._safety_data_sheet
@pyqtProperty(str, constant = True)
def whereToBuy(self) -> str:
return self._where_to_buy
@pyqtProperty("QStringList", constant = True)
def compatiblePrinters(self) -> List[str]:
return self._compatible_printers
@pyqtProperty("QStringList", constant = True)
def compatibleSupportMaterials(self) -> List[str]:
return self._compatible_support_materials
@pyqtProperty(bool, constant = True)
def isCompatibleMaterialStation(self) -> bool:
return self._is_compatible_material_station
@pyqtProperty(bool, constant = True)
def isCompatibleAirManager(self) -> bool:
return self._is_compatible_air_manager
@pyqtProperty(bool, constant = True)
def isBundled(self) -> bool:
return self._is_bundled
def setDownloadUrl(self, download_url):
self._download_url = download_url
# --- manage buttons signals ---
stateManageButtonChanged = pyqtSignal()
installPackageTriggered = pyqtSignal(str, str)
uninstallPackageTriggered = pyqtSignal(str)
updatePackageTriggered = pyqtSignal(str, str)
enablePackageTriggered = pyqtSignal(str)
disablePackageTriggered = pyqtSignal(str)
busyChanged = pyqtSignal()
@pyqtSlot()
def install(self):
self.setBusy(True)
self.installPackageTriggered.emit(self.packageId, self._download_url)
@pyqtSlot()
def update(self):
self.setBusy(True)
self.updatePackageTriggered.emit(self.packageId, self._download_url)
@pyqtSlot()
def uninstall(self):
self.uninstallPackageTriggered.emit(self.packageId)
@pyqtProperty(bool, notify= busyChanged)
def busy(self):
"""
Property indicating that some kind of upgrade is active.
"""
return self._is_busy
@pyqtSlot()
def enable(self):
self.enablePackageTriggered.emit(self.packageId)
@pyqtSlot()
def disable(self):
self.disablePackageTriggered.emit(self.packageId)
def setBusy(self, value: bool):
if self._is_busy != value:
self._is_busy = value
try:
self.busyChanged.emit()
except RuntimeError:
pass
def _packageInstalled(self, package_id: str) -> None:
if self._package_id != package_id:
return
self.setBusy(False)
try:
self.stateManageButtonChanged.emit()
except RuntimeError:
pass
@pyqtProperty(bool, notify = stateManageButtonChanged)
def isInstalled(self) -> bool:
return self._package_id in self._package_manager.getAllInstalledPackageIDs()
@pyqtProperty(bool, notify = stateManageButtonChanged)
def isToBeInstalled(self) -> bool:
return self._package_id in self._package_manager.getPackagesToInstall()
@pyqtProperty(bool, notify = stateManageButtonChanged)
def isActive(self) -> bool:
return not self._package_id in self._plugin_registry.getDisabledPlugins()
@pyqtProperty(bool, notify = stateManageButtonChanged)
def canDowngrade(self) -> bool:
"""Flag if the installed package can be downgraded to a bundled version"""
return self._package_manager.canDowngrade(self._package_id)
def setCanUpdate(self, value: bool) -> None:
self._can_update = value
self.stateManageButtonChanged.emit()
@pyqtProperty(bool, fset = setCanUpdate, notify = stateManageButtonChanged)
def canUpdate(self) -> bool:
"""Flag indicating if the package can be updated"""
return self._can_update

View file

@ -0,0 +1,148 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtNetwork import QNetworkReply
from typing import Optional, TYPE_CHECKING
from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To request the package list from the API.
from .Constants import PACKAGES_URL # To get the list of packages. Imported this way to prevent circular imports.
from .PackageList import PackageList
from .PackageModel import PackageModel # The contents of this list.
if TYPE_CHECKING:
from PyQt5.QtCore import QObject
catalog = i18nCatalog("cura")
class RemotePackageList(PackageList):
ITEMS_PER_PAGE = 20 # Pagination of number of elements to download at once.
def __init__(self, parent: Optional["QObject"] = None) -> None:
super().__init__(parent)
self._package_type_filter = ""
self._requested_search_string = ""
self._current_search_string = ""
self._request_url = self._initialRequestUrl()
self._ongoing_requests["get_packages"] = None
self.isLoadingChanged.connect(self._onLoadingChanged)
self.isLoadingChanged.emit()
@pyqtSlot()
def updatePackages(self) -> None:
"""
Make a request for the first paginated page of packages.
When the request is done, the list will get updated with the new package models.
"""
self.setErrorMessage("") # Clear any previous errors.
self.setIsLoading(True)
self._ongoing_requests["get_packages"] = HttpRequestManager.getInstance().get(
self._request_url,
scope = self._scope,
callback = self._parseResponse,
error_callback = self._onError
)
def reset(self) -> None:
self.clear()
self._request_url = self._initialRequestUrl()
packageTypeFilterChanged = pyqtSignal()
searchStringChanged = pyqtSignal()
def setPackageTypeFilter(self, new_filter: str) -> None:
if new_filter != self._package_type_filter:
self._package_type_filter = new_filter
self.reset()
self.packageTypeFilterChanged.emit()
def setSearchString(self, new_search: str) -> None:
self._requested_search_string = new_search
self._onLoadingChanged()
@pyqtProperty(str, fset = setPackageTypeFilter, notify = packageTypeFilterChanged)
def packageTypeFilter(self) -> str:
"""
Get the package type this package list is filtering on, like ``plugin`` or ``material``.
:return: The package type this list is filtering on.
"""
return self._package_type_filter
@pyqtProperty(str, fset = setSearchString, notify = searchStringChanged)
def searchString(self) -> str:
"""
Get the string the user is currently searching for (as in: the list is updating) within the packages,
or an empty string if no extra search filter has to be applied. Does not override package-type filter!
:return: String the user is searching for. Empty denotes 'no search filter'.
"""
return self._current_search_string
def _onLoadingChanged(self) -> None:
if self._requested_search_string != self._current_search_string and not self._is_loading:
self._current_search_string = self._requested_search_string
self.reset()
self.updatePackages()
self.searchStringChanged.emit()
def _initialRequestUrl(self) -> str:
"""
Get the URL to request the first paginated page with.
:return: A URL to request.
"""
request_url = f"{PACKAGES_URL}?limit={self.ITEMS_PER_PAGE}"
if self._package_type_filter != "":
request_url += f"&package_type={self._package_type_filter}"
if self._current_search_string != "":
request_url += f"&search={self._current_search_string}"
return request_url
def _parseResponse(self, reply: "QNetworkReply") -> None:
"""
Parse the response from the package list API request.
This converts that response into PackageModels, and triggers the ListModel to update.
:param reply: A reply containing information about a number of packages.
"""
response_data = HttpRequestManager.readJSON(reply)
if "data" not in response_data or "links" not in response_data:
Logger.error(f"Could not interpret the server's response. Missing 'data' or 'links' from response data. Keys in response: {response_data.keys()}")
self.setErrorMessage(catalog.i18nc("@info:error", "Could not interpret the server's response."))
return
for package_data in response_data["data"]:
try:
package = PackageModel(package_data, parent = self)
self._connectManageButtonSignals(package)
self.appendItem({"package": package}) # Add it to this list model.
except RuntimeError:
# Setting the ownership of this object to not qml can still result in a RuntimeError. Which can occur when quickly toggling
# between de-/constructing RemotePackageLists. This try-except is here to prevent a hard crash when the wrapped C++ object
# was deleted when it was still parsing the response
continue
self._request_url = response_data["links"].get("next", "") # Use empty string to signify that there is no next page.
self._ongoing_requests["get_packages"] = None
self.setIsLoading(False)
self.setHasMore(self._request_url != "")
def _onError(self, reply: "QNetworkReply", error: Optional[QNetworkReply.NetworkError]) -> None:
"""
Handles networking and server errors when requesting the list of packages.
:param reply: The reply with packages. This will most likely be incomplete and should be ignored.
:param error: The error status of the request.
"""
if error == QNetworkReply.NetworkError.OperationCanceledError:
Logger.debug("Cancelled request for packages.")
self._ongoing_requests["get_packages"] = None
return # Don't show an error about this to the user.
Logger.error("Could not reach Marketplace server.")
self.setErrorMessage(catalog.i18nc("@info:error", "Could not reach Marketplace."))
self._ongoing_requests["get_packages"] = None
self.setIsLoading(False)

View file

@ -0,0 +1,17 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from .Marketplace import Marketplace
def getMetaData():
"""
Extension-type plug-ins don't have any specific metadata being used by Cura.
"""
return {}
def register(app):
"""
Register the plug-in object with Uranium.
"""
return { "extension": Marketplace() }

View file

@ -0,0 +1,8 @@
{
"name": "Marketplace",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"api": 7,
"description": "Manages extensions to the application and allows browsing extensions from the Ultimaker website.",
"i18n-catalog": "cura"
}

View file

@ -0,0 +1,3 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M39 14V8H27V14H21V8H9V14H7C6.20435 14 5.44129 14.3161 4.87868 14.8787C4.31607 15.4413 4 16.2044 4 17V37C4 37.7956 4.31607 38.5587 4.87868 39.1213C5.44129 39.6839 6.20435 40 7 40H41C41.7957 40 42.5587 39.6839 43.1213 39.1213C43.6839 38.5587 44 37.7956 44 37V17C44 16.2044 43.6839 15.4413 43.1213 14.8787C42.5587 14.3161 41.7957 14 41 14H39ZM29 10H37V14H29V10ZM11 10H19V14H11V10ZM42 38H6V16H42V38Z" fill="#000E1A"/>
</svg>

After

Width:  |  Height:  |  Size: 526 B

View file

@ -0,0 +1,3 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24 4C18.6975 4.00609 13.614 6.11518 9.86459 9.86459C6.11518 13.614 4.00609 18.6975 4 24V42H6V32.66C7.54979 35.8792 9.93405 38.6241 12.9046 40.6092C15.8752 42.5942 19.3236 43.7468 22.8908 43.947C26.4579 44.1472 30.0136 43.3876 33.1876 41.7474C36.3616 40.1071 39.038 37.6462 40.9382 34.6206C42.8385 31.595 43.893 28.1155 43.9922 24.544C44.0914 20.9726 43.2315 17.4399 41.5021 14.3136C39.7727 11.1872 37.237 8.5815 34.1589 6.76765C31.0808 4.9538 27.5728 3.99809 24 4ZM24 42C20.4399 42 16.9598 40.9443 13.9997 38.9665C11.0397 36.9886 8.73255 34.1774 7.37017 30.8883C6.00779 27.5992 5.65133 23.98 6.34586 20.4884C7.0404 16.9967 8.75473 13.7894 11.2721 11.2721C13.7894 8.75473 16.9967 7.0404 20.4884 6.34587C23.98 5.65133 27.5992 6.00779 30.8883 7.37017C34.1774 8.73255 36.9886 11.0397 38.9665 13.9997C40.9443 16.9598 42 20.4399 42 24C41.9947 28.7723 40.0966 33.3476 36.7221 36.7221C33.3476 40.0966 28.7723 41.9947 24 42ZM24 17C22.6155 17 21.2622 17.4105 20.111 18.1797C18.9599 18.9489 18.0627 20.0421 17.5328 21.3212C17.003 22.6003 16.8644 24.0078 17.1345 25.3656C17.4046 26.7235 18.0713 27.9708 19.0503 28.9498C20.0292 29.9287 21.2765 30.5954 22.6344 30.8655C23.9922 31.1356 25.3997 30.997 26.6788 30.4672C27.9579 29.9373 29.0511 29.0401 29.8203 27.889C30.5895 26.7379 31 25.3845 31 24C30.9979 22.1441 30.2598 20.3648 28.9475 19.0525C27.6352 17.7402 25.8559 17.0021 24 17ZM24 29C23.0111 29 22.0444 28.7068 21.2221 28.1574C20.3999 27.6079 19.759 26.8271 19.3806 25.9134C19.0022 24.9998 18.9031 23.9945 19.0961 23.0246C19.289 22.0546 19.7652 21.1637 20.4645 20.4645C21.1637 19.7652 22.0546 19.289 23.0245 19.0961C23.9945 18.9031 24.9998 19.0022 25.9134 19.3806C26.827 19.759 27.6079 20.3999 28.1573 21.2222C28.7068 22.0444 29 23.0111 29 24C28.9984 25.3256 28.4712 26.5965 27.5338 27.5338C26.5965 28.4712 25.3256 28.9984 24 29ZM24 11C25.7079 10.9954 27.3997 11.3295 28.9776 11.9831C30.5554 12.6367 31.988 13.5967 33.1924 14.8076L31.7783 16.2217C30.7592 15.1971 29.547 14.3848 28.2118 13.8318C26.8767 13.2788 25.4451 12.9961 24 13V11Z" fill="#000E1A"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

Before

Width:  |  Height:  |  Size: 184 B

After

Width:  |  Height:  |  Size: 184 B

Before After
Before After

View file

@ -0,0 +1,91 @@
//Copyright (c) 2021 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import UM 1.6 as UM
import Cura 1.6 as Cura
UM.Dialog
{
id: licenseDialog
title: catalog.i18nc("@button", "Plugin license agreement")
minimumWidth: UM.Theme.getSize("modal_window_minimum").width
minimumHeight: UM.Theme.getSize("modal_window_minimum").height
width: minimumWidth
height: minimumHeight
backgroundColor: UM.Theme.getColor("main_background")
property variant catalog: UM.I18nCatalog { name: "cura" }
ColumnLayout
{
anchors.fill: parent
spacing: UM.Theme.getSize("thick_margin").height
Row
{
Layout.fillWidth: true
height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").width
leftPadding: UM.Theme.getSize("narrow_margin").width
UM.RecolorImage
{
id: icon
width: UM.Theme.getSize("marketplace_large_icon").width
height: UM.Theme.getSize("marketplace_large_icon").height
color: UM.Theme.getColor("text")
source: UM.Theme.getIcon("Certificate", "high")
}
Label
{
text: catalog.i18nc("@text", "Please read and agree with the plugin licence.")
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("large")
anchors.verticalCenter: icon.verticalCenter
height: UM.Theme.getSize("marketplace_large_icon").height
verticalAlignment: Qt.AlignVCenter
wrapMode: Text.Wrap
renderType: Text.NativeRendering
}
}
Cura.ScrollableTextArea
{
Layout.fillWidth: true
Layout.fillHeight: true
anchors.topMargin: UM.Theme.getSize("default_margin").height
textArea.text: licenseContent
textArea.readOnly: true
}
}
rightButtons:
[
Cura.PrimaryButton
{
text: catalog.i18nc("@button", "Accept")
onClicked: handler.onLicenseAccepted(packageId)
}
]
leftButtons:
[
Cura.SecondaryButton
{
text: catalog.i18nc("@button", "Decline")
onClicked: handler.onLicenseDeclined(packageId)
}
]
onAccepted: handler.onLicenseAccepted(packageId)
onRejected: handler.onLicenseDeclined(packageId)
onClosing: handler.onLicenseDeclined(packageId)
}

View file

@ -0,0 +1,114 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
import UM 1.6 as UM
import Cura 1.6 as Cura
Item
{
id: manageButton
property bool button_style: true
property string text
property bool busy: false
property bool confirmed: false
implicitWidth: childrenRect.width
implicitHeight: childrenRect.height
signal clicked()
property Component primaryButton: Component
{
Cura.PrimaryButton
{
text: manageButton.text
onClicked: manageButton.clicked()
}
}
property Component secondaryButton: Component
{
Cura.SecondaryButton
{
text: manageButton.text
onClicked: manageButton.clicked()
}
}
property Component busyButton: Component
{
Item
{
height: UM.Theme.getSize("action_button").height
width: childrenRect.width
UM.RecolorImage
{
id: busyIndicator
visible: parent.visible
height: UM.Theme.getSize("action_button").height - 2 * UM.Theme.getSize("narrow_margin").height
width: height
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
source: UM.Theme.getIcon("Spinner")
color: UM.Theme.getColor("primary")
RotationAnimator
{
target: busyIndicator
running: parent.visible
from: 0
to: 360
loops: Animation.Infinite
duration: 2500
}
}
Label
{
visible: parent.visible
anchors.left: busyIndicator.right
anchors.leftMargin: UM.Theme.getSize("narrow_margin").width
anchors.verticalCenter: parent.verticalCenter
text: manageButton.text
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("primary")
}
}
}
property Component confirmButton: Component
{
Item
{
height: UM.Theme.getSize("action_button").height
width: childrenRect.width
Label
{
anchors.verticalCenter: parent.verticalCenter
text: manageButton.text
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("primary")
}
}
}
Loader
{
sourceComponent:
{
if (busy) { return manageButton.busyButton; }
else if (confirmed) { return manageButton.confirmButton; }
else if (manageButton.button_style) { return manageButton.primaryButton; }
else { return manageButton.secondaryButton; }
}
}
}

View file

@ -0,0 +1,49 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import UM 1.5 as UM
import Cura 1.6 as Cura
import QtQuick 2.15
import QtQuick.Controls 2.15
TabButton
{
id: root
width: UM.Theme.getSize("button_icon").width + UM.Theme.getSize("narrow_margin").width
height: UM.Theme.getSize("button_icon").height
hoverEnabled: true
property color inactiveBackgroundColor : hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("detail_background")
property color activeBackgroundColor : UM.Theme.getColor("main_background")
leftInset: UM.Theme.getSize("narrow_margin").width
background: Rectangle
{
color: parent.checked ? activeBackgroundColor : inactiveBackgroundColor
border.color: parent.checked ? UM.Theme.getColor("detail_background") : "transparent"
border.width: UM.Theme.getSize("thick_lining").width
radius: Math.round(width * 0.5)
}
UM.ToolTip
{
id: tooltip
tooltipText: catalog.i18nc("@info:tooltip", "Manage packages")
visible: root.hovered
}
UM.RecolorImage
{
id: icon
width: UM.Theme.getSize("section_icon").width
height: UM.Theme.getSize("section_icon").height
color: UM.Theme.getColor("icon")
source: UM.Theme.getIcon("Settings")
anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenterOffset: Math.round(UM.Theme.getSize("narrow_margin").width /2)
anchors.verticalCenter: parent.verticalCenter
}
}

View file

@ -0,0 +1,26 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import UM 1.4 as UM
Packages
{
pageTitle: catalog.i18nc("@header", "Manage packages")
bannerVisible: UM.Preferences.getValue("cura/market_place_show_manage_packages_banner");
bannerIcon: UM.Theme.getIcon("ArrowDoubleCircleRight")
bannerText: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.")
bannerReadMoreUrl: "https://support.ultimaker.com/hc/en-us/articles/4411125921426/?utm_source=cura&utm_medium=software&utm_campaign=marketplace-learn-manage"
onRemoveBanner: function() {
UM.Preferences.setValue("cura/market_place_show_manage_packages_banner", false);
bannerVisible = false;
}
searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-plugins-browser"
showUpdateButton: true
showInstallButton: true
showDisableButton: true
model: manager.LocalPackageList
}

View file

@ -0,0 +1,283 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Window 2.2
import UM 1.2 as UM
import Cura 1.6 as Cura
Window
{
id: marketplaceDialog
property variant catalog: UM.I18nCatalog { name: "cura" }
signal searchStringChanged(string new_search)
minimumWidth: UM.Theme.getSize("modal_window_minimum").width
minimumHeight: UM.Theme.getSize("modal_window_minimum").height
width: minimumWidth
height: minimumHeight
onVisibleChanged:
{
while(contextStack.depth > 1)
{
contextStack.pop(); //Do NOT use the StackView.Immediate transition here, since it causes the window to stay empty. Seemingly a Qt bug: https://bugreports.qt.io/browse/QTBUG-60670?
}
}
Connections
{
target: Cura.API.account
function onLoginStateChanged()
{
close();
}
}
title: "Marketplace" //Seen by Ultimaker as a brand name, so this doesn't get translated.
modality: Qt.NonModal
// Background color
Rectangle
{
anchors.fill: parent
color: UM.Theme.getColor("main_background")
}
//The Marketplace can have a page in front of everything with package details. The stack view controls its visibility.
StackView
{
id: contextStack
anchors.fill: parent
initialItem: packageBrowse
ColumnLayout
{
id: packageBrowse
spacing: UM.Theme.getSize("narrow_margin").height
// Page title.
Item
{
Layout.preferredWidth: parent.width
Layout.preferredHeight: childrenRect.height + UM.Theme.getSize("default_margin").height
Label
{
id: pageTitle
anchors
{
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
rightMargin: UM.Theme.getSize("default_margin").width
bottom: parent.bottom
}
font: UM.Theme.getFont("large")
color: UM.Theme.getColor("text")
text: content.item ? content.item.pageTitle: catalog.i18nc("@title", "Loading...")
}
}
OnboardBanner
{
visible: content.item && content.item.bannerVisible
text: content.item && content.item.bannerText
icon: content.item && content.item.bannerIcon
onRemove: content.item && content.item.onRemoveBanner
readMoreUrl: content.item && content.item.bannerReadMoreUrl
Layout.fillWidth: true
Layout.leftMargin: UM.Theme.getSize("default_margin").width
Layout.rightMargin: UM.Theme.getSize("default_margin").width
}
// Search & Top-Level Tabs
Item
{
implicitHeight: childrenRect.height
implicitWidth: parent.width - 2 * UM.Theme.getSize("default_margin").width
Layout.alignment: Qt.AlignHCenter
RowLayout
{
width: parent.width
height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("default_margin").height
spacing: UM.Theme.getSize("thin_margin").width
Cura.SearchBar
{
id: searchBar
implicitHeight: UM.Theme.getSize("button_icon").height
Layout.fillWidth: true
onTextEdited: searchStringChanged(text)
}
// Page selection.
TabBar
{
id: pageSelectionTabBar
Layout.alignment: Qt.AlignRight
height: UM.Theme.getSize("button_icon").height
spacing: 0
background: Rectangle { color: "transparent" }
currentIndex: manager.tabShown
onCurrentIndexChanged:
{
manager.tabShown = currentIndex
searchBar.text = "";
searchBar.visible = currentItem.hasSearch;
content.source = currentItem.sourcePage;
}
PackageTypeTab
{
id: pluginTabText
width: implicitWidth
text: catalog.i18nc("@button", "Plugins")
property string sourcePage: "Plugins.qml"
property bool hasSearch: true
}
PackageTypeTab
{
id: materialsTabText
width: implicitWidth
text: catalog.i18nc("@button", "Materials")
property string sourcePage: "Materials.qml"
property bool hasSearch: true
}
ManagePackagesButton
{
property string sourcePage: "ManagedPackages.qml"
property bool hasSearch: false
Cura.NotificationIcon
{
anchors
{
horizontalCenter: parent.right
verticalCenter: parent.top
}
visible: CuraApplication.getPackageManager().packagesWithUpdate.length > 0
labelText:
{
const itemCount = CuraApplication.getPackageManager().packagesWithUpdate.length
return itemCount > 9 ? "9+" : itemCount
}
}
}
}
}
}
FontMetrics
{
id: fontMetrics
font: UM.Theme.getFont("default")
}
Cura.TertiaryButton
{
text: catalog.i18nc("@info", "Search in the browser")
iconSource: UM.Theme.getIcon("LinkExternal")
visible: pageSelectionTabBar.currentItem.hasSearch
isIconOnRightSide: true
height: fontMetrics.height
textFont: fontMetrics.font
textColor: UM.Theme.getColor("text")
onClicked: content.item && Qt.openUrlExternally(content.item.searchInBrowserUrl)
}
// Page contents.
Rectangle
{
Layout.preferredWidth: parent.width
Layout.fillHeight: true
color: UM.Theme.getColor("detail_background")
// Page contents.
Loader
{
id: content
anchors.fill: parent
anchors.margins: UM.Theme.getSize("default_margin").width
source: "Plugins.qml"
Connections
{
target: content
function onLoaded()
{
pageTitle.text = content.item.pageTitle
searchStringChanged.connect(handleSearchStringChanged)
}
function handleSearchStringChanged(new_search)
{
content.item.model.searchString = new_search
}
}
}
}
}
}
Rectangle
{
height: quitButton.height + 2 * UM.Theme.getSize("default_margin").width
color: UM.Theme.getColor("primary")
visible: manager.showRestartNotification
anchors
{
left: parent.left
right: parent.right
bottom: parent.bottom
}
RowLayout
{
anchors
{
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
margins: UM.Theme.getSize("default_margin").width
}
spacing: UM.Theme.getSize("default_margin").width
UM.RecolorImage
{
id: bannerIcon
source: UM.Theme.getIcon("Plugin")
color: UM.Theme.getColor("primary_button_text")
implicitWidth: UM.Theme.getSize("banner_icon_size").width
implicitHeight: UM.Theme.getSize("banner_icon_size").height
}
Text
{
color: UM.Theme.getColor("primary_button_text")
text: catalog.i18nc("@button", "In order to use the package you will need to restart Cura")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
Layout.fillWidth: true
}
Cura.SecondaryButton
{
id: quitButton
text: catalog.i18nc("@info:button, %1 is the application name", "Quit %1").arg(CuraApplication.applicationDisplayName)
onClicked:
{
marketplaceDialog.hide();
CuraApplication.closeApplication();
}
}
}
}
}

View file

@ -0,0 +1,23 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import UM 1.4 as UM
Packages
{
pageTitle: catalog.i18nc("@header", "Install Materials")
bannerVisible: UM.Preferences.getValue("cura/market_place_show_material_banner")
bannerIcon: UM.Theme.getIcon("Spool")
bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.")
bannerReadMoreUrl: "https://support.ultimaker.com/hc/en-us/articles/360011968360/?utm_source=cura&utm_medium=software&utm_campaign=marketplace-learn-materials"
onRemoveBanner: function() {
UM.Preferences.setValue("cura/market_place_show_material_banner", false);
bannerVisible = false;
}
searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/materials?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-materials-browser"
showUpdateButton: true
showInstallButton: true
model: manager.MaterialPackageList
}

View file

@ -0,0 +1,116 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
import UM 1.6 as UM
import Cura 1.6 as Cura
// Onboarding banner.
Rectangle
{
property alias icon: onboardingIcon.source
property alias text: infoText.text
property var onRemove
property string readMoreUrl
implicitHeight: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height
color: UM.Theme.getColor("action_panel_secondary")
// Icon
UM.RecolorImage
{
id: onboardingIcon
anchors
{
top: parent.top
left: parent.left
margins: UM.Theme.getSize("default_margin").width
}
width: UM.Theme.getSize("banner_icon_size").width
height: UM.Theme.getSize("banner_icon_size").height
}
// Close button
UM.SimpleButton
{
id: onboardingClose
anchors
{
top: parent.top
right: parent.right
margins: UM.Theme.getSize("default_margin").width
}
width: UM.Theme.getSize("message_close").width
height: UM.Theme.getSize("message_close").height
color: UM.Theme.getColor("primary_text")
hoverColor: UM.Theme.getColor("primary_text_hover")
iconSource: UM.Theme.getIcon("Cancel")
onClicked: onRemove()
}
// Body
Label {
id: infoText
anchors
{
top: parent.top
left: onboardingIcon.right
right: onboardingClose.left
margins: UM.Theme.getSize("default_margin").width
}
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
color: UM.Theme.getColor("primary_text")
wrapMode: Text.Wrap
elide: Text.ElideRight
onLineLaidOut:
{
if(line.isLast)
{
// Check if read more button still fits after the body text
if (line.implicitWidth + readMoreButton.width + UM.Theme.getSize("default_margin").width > width)
{
// If it does place it after the body text
readMoreButton.anchors.bottomMargin = -(fontMetrics.height);
readMoreButton.anchors.leftMargin = UM.Theme.getSize("thin_margin").width;
}
else
{
// Otherwise place it under the text
readMoreButton.anchors.leftMargin = line.implicitWidth + UM.Theme.getSize("default_margin").width;
readMoreButton.anchors.bottomMargin = 0;
}
}
}
}
FontMetrics
{
id: fontMetrics
font: UM.Theme.getFont("default")
}
Cura.TertiaryButton
{
id: readMoreButton
anchors.left: infoText.left
anchors.bottom: infoText.bottom
text: "Learn More"
textFont: UM.Theme.getFont("default")
textColor: infoText.color
leftPadding: 0
rightPadding: 0
iconSource: UM.Theme.getIcon("LinkExternal")
isIconOnRightSide: true
height: fontMetrics.height
onClicked: Qt.openUrlExternally(readMoreUrl)
}
}

View file

@ -0,0 +1,53 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
import UM 1.6 as UM
import Cura 1.6 as Cura
Rectangle
{
property alias packageData: packageCardHeader.packageData
property alias showUpdateButton: packageCardHeader.showUpdateButton
property alias showDisableButton: packageCardHeader.showDisableButton
property alias showInstallButton: packageCardHeader.showInstallButton
height: childrenRect.height
color: UM.Theme.getColor("main_background")
radius: UM.Theme.getSize("default_radius").width
PackageCardHeader
{
id: packageCardHeader
Item
{
id: shortDescription
anchors.fill: parent
Label
{
id: descriptionLabel
width: parent.width
text: packageData.description
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
maximumLineCount: 2
wrapMode: Text.Wrap
elide: Text.ElideRight
visible: text !== ""
}
}
}
FontMetrics
{
id: fontMetrics
font: UM.Theme.getFont("default")
}
}

View file

@ -0,0 +1,248 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
import UM 1.6 as UM
import Cura 1.6 as Cura
// As both the PackageCard and Package contain similar components; a package icon, title, author bar. These components
// are combined into the reusable "PackageCardHeader" component
Item
{
default property alias contents: contentItem.children
property var packageData
property bool showDisableButton: false
property bool showInstallButton: false
property bool showUpdateButton: false
width: parent.width
height: UM.Theme.getSize("card").height
// card icon
Item
{
id: packageItem
anchors
{
top: parent.top
left: parent.left
margins: UM.Theme.getSize("default_margin").width
}
width: UM.Theme.getSize("card_icon").width
height: width
property bool packageHasIcon: packageData.iconUrl != ""
Image
{
visible: parent.packageHasIcon
anchors.fill: parent
source: packageData.iconUrl
sourceSize.height: height
sourceSize.width: width
}
UM.RecolorImage
{
visible: !parent.packageHasIcon
anchors.fill: parent
sourceSize.height: height
sourceSize.width: width
color: UM.Theme.getColor("text")
source:
{
switch (packageData.packageType)
{
case "plugin":
return "../images/Plugin.svg";
case "material":
return "../images/Spool.svg";
default:
return "../images/placeholder.svg";
}
}
}
}
ColumnLayout
{
anchors
{
left: packageItem.right
leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
rightMargin: UM.Theme.getSize("default_margin").width
top: parent.top
topMargin: UM.Theme.getSize("narrow_margin").height
}
height: packageItem.height + packageItem.anchors.margins * 2
// Title row.
RowLayout
{
id: titleBar
Layout.preferredWidth: parent.width
Layout.preferredHeight: childrenRect.height
Label
{
text: packageData.displayName
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignTop
}
VerifiedIcon
{
enabled: packageData.isCheckedByUltimaker
visible: packageData.isCheckedByUltimaker
}
Label
{
id: packageVersionLabel
text: packageData.packageVersion
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
Layout.fillWidth: true
}
Button
{
id: externalLinkButton
// For some reason if i set padding, they don't match up. If i set all of them explicitly, it does work?
leftPadding: UM.Theme.getSize("narrow_margin").width
rightPadding: UM.Theme.getSize("narrow_margin").width
topPadding: UM.Theme.getSize("narrow_margin").width
bottomPadding: UM.Theme.getSize("narrow_margin").width
Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width + 2 * padding
Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").width + 2 * padding
contentItem: UM.RecolorImage
{
source: UM.Theme.getIcon("LinkExternal")
color: UM.Theme.getColor("icon")
implicitWidth: UM.Theme.getSize("card_tiny_icon").width
implicitHeight: UM.Theme.getSize("card_tiny_icon").height
}
background: Rectangle
{
color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered"): "transparent"
radius: externalLinkButton.width / 2
}
onClicked: Qt.openUrlExternally(packageData.marketplaceURL)
}
}
// When a package Card companent is created and children are provided to it they are rendered here
Item {
id: contentItem
Layout.fillHeight: true
Layout.preferredWidth: parent.width
}
// Author and action buttons.
RowLayout
{
id: authorAndActionButton
Layout.preferredWidth: parent.width
Layout.preferredHeight: childrenRect.height
spacing: UM.Theme.getSize("narrow_margin").width
// label "By"
Label
{
id: authorBy
Layout.alignment: Qt.AlignCenter
text: catalog.i18nc("@label", "By")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
}
// clickable author name
Item
{
Layout.fillWidth: true
implicitHeight: authorBy.height
Layout.alignment: Qt.AlignTop
Cura.TertiaryButton
{
text: packageData.authorName
textFont: UM.Theme.getFont("default_bold")
textColor: UM.Theme.getColor("text") // override normal link color
leftPadding: 0
rightPadding: 0
iconSource: UM.Theme.getIcon("LinkExternal")
isIconOnRightSide: true
onClicked: Qt.openUrlExternally(packageData.authorInfoUrl)
}
}
ManageButton
{
id: enableManageButton
visible: showDisableButton && packageData.isInstalled && !packageData.isToBeInstalled && packageData.packageType != "material"
enabled: !packageData.busy
button_style: !packageData.isActive
Layout.alignment: Qt.AlignTop
text: button_style ? catalog.i18nc("@button", "Enable") : catalog.i18nc("@button", "Disable")
onClicked: packageData.isActive ? packageData.disable(): packageData.enable()
}
ManageButton
{
id: installManageButton
visible: showInstallButton && (packageData.canDowngrade || !packageData.isBundled)
enabled: !packageData.busy
busy: packageData.busy
button_style: !(packageData.isInstalled || packageData.isToBeInstalled)
Layout.alignment: Qt.AlignTop
text:
{
if (packageData.canDowngrade)
{
if (busy) { return catalog.i18nc("@button", "Downgrading..."); }
else { return catalog.i18nc("@button", "Downgrade"); }
}
if (!(packageData.isInstalled || packageData.isToBeInstalled))
{
if (busy) { return catalog.i18nc("@button", "Installing..."); }
else { return catalog.i18nc("@button", "Install"); }
}
else
{
return catalog.i18nc("@button", "Uninstall");
}
}
onClicked: packageData.isInstalled || packageData.isToBeInstalled ? packageData.uninstall(): packageData.install()
}
ManageButton
{
id: updateManageButton
visible: showUpdateButton && packageData.canUpdate
enabled: !packageData.busy
busy: packageData.busy
Layout.alignment: Qt.AlignTop
text: busy ? catalog.i18nc("@button", "Updating..."): catalog.i18nc("@button", "Update")
onClicked: packageData.update()
}
}
}
}

View file

@ -0,0 +1,96 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.3
import Cura 1.0 as Cura
import UM 1.5 as UM
Item
{
id: detailPage
property var packageData: packages.selectedPackage
property string title: catalog.i18nc("@header", "Package details")
RowLayout
{
id: header
anchors
{
top: parent.top
topMargin: UM.Theme.getSize("default_margin").height
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
rightMargin: anchors.leftMargin
}
spacing: UM.Theme.getSize("default_margin").width
Cura.SecondaryButton
{
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: UM.Theme.getSize("action_button").height
Layout.preferredWidth: height
onClicked: contextStack.pop() //Remove this page, returning to the main package list or whichever thing is beneath it.
tooltip: catalog.i18nc("@button:tooltip", "Back")
toolTipContentAlignment: UM.Enums.ContentAlignment.AlignRight
leftPadding: UM.Theme.getSize("narrow_margin").width
rightPadding: leftPadding
iconSource: UM.Theme.getIcon("ArrowLeft")
iconSize: height - leftPadding * 2
}
Label
{
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
text: detailPage.title
font: UM.Theme.getFont("large")
color: UM.Theme.getColor("text")
}
}
Rectangle
{
anchors
{
top: header.bottom
topMargin: UM.Theme.getSize("default_margin").height
left: parent.left
right: parent.right
bottom: parent.bottom
}
color: UM.Theme.getColor("detail_background")
ScrollView
{
anchors.fill: parent
clip: true //Need to clip, not for the bottom (which is off the window) but for the top (which would overlap the header).
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
contentHeight: packagePage.height + UM.Theme.getSize("default_margin").height * 2
PackagePage
{
id: packagePage
anchors
{
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
rightMargin: anchors.leftMargin
top: parent.top
topMargin: UM.Theme.getSize("default_margin").height
}
packageData: detailPage.packageData
}
}
}
}

View file

@ -0,0 +1,301 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
import UM 1.6 as UM
import Cura 1.6 as Cura
Rectangle
{
id: root
property alias packageData: packageCardHeader.packageData
height: childrenRect.height
color: UM.Theme.getColor("main_background")
radius: UM.Theme.getSize("default_radius").width
Column
{
width: parent.width
spacing: 0
Item
{
width: parent.width
height: UM.Theme.getSize("card").height
PackageCardHeader
{
id: packageCardHeader
showUpdateButton: true
showInstallButton: true
showDisableButton: true
anchors.fill: parent
Row
{
id: downloadCount
Layout.preferredWidth: parent.width
Layout.fillHeight: true
// It's not the perfect way to handle this, since a package really can have 0 downloads
// But we re-use the package page for the manage plugins as well. The one user that doesn't see
// the num downloads is an acceptable "sacrifice" to make this easy to fix.
visible: packageData.downloadCount != "0"
UM.RecolorImage
{
id: downloadsIcon
width: UM.Theme.getSize("card_tiny_icon").width
height: UM.Theme.getSize("card_tiny_icon").height
source: UM.Theme.getIcon("Download")
color: UM.Theme.getColor("text")
}
Label
{
anchors.verticalCenter: downloadsIcon.verticalCenter
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
text: packageData.downloadCount
}
}
}
}
Column
{
id: extendedDescription
width: parent.width
padding: UM.Theme.getSize("default_margin").width
topPadding: 0
spacing: UM.Theme.getSize("default_margin").height
Label
{
width: parent.width - parent.padding * 2
text: catalog.i18nc("@header", "Description")
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
Label
{
width: parent.width - parent.padding * 2
text: packageData.formattedDescription
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
wrapMode: Text.Wrap
textFormat: Text.RichText
onLinkActivated: UM.UrlUtil.openUrl(link, ["http", "https"])
}
Column //Separate column to have no spacing between compatible printers.
{
id: compatiblePrinterColumn
width: parent.width - parent.padding * 2
visible: packageData.packageType === "material"
spacing: 0
Label
{
width: parent.width
text: catalog.i18nc("@header", "Compatible printers")
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
Repeater
{
model: packageData.compatiblePrinters
Label
{
width: compatiblePrinterColumn.width
text: modelData
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
}
Label
{
width: parent.width
visible: packageData.compatiblePrinters.length == 0
text: "(" + catalog.i18nc("@info", "No compatibility information") + ")"
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
}
Column
{
id: compatibleSupportMaterialColumn
width: parent.width - parent.padding * 2
visible: packageData.packageType === "material"
spacing: 0
Label
{
width: parent.width
text: catalog.i18nc("@header", "Compatible support materials")
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
Repeater
{
model: packageData.compatibleSupportMaterials
Label
{
width: compatibleSupportMaterialColumn.width
text: modelData
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
}
Label
{
width: parent.width
visible: packageData.compatibleSupportMaterials.length == 0
text: "(" + catalog.i18nc("@info No materials", "None") + ")"
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
}
Column
{
width: parent.width - parent.padding * 2
visible: packageData.packageType === "material"
spacing: 0
Label
{
width: parent.width
text: catalog.i18nc("@header", "Compatible with Material Station")
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
Label
{
width: parent.width
text: packageData.isCompatibleMaterialStation ? catalog.i18nc("@info", "Yes") : catalog.i18nc("@info", "No")
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
}
Column
{
width: parent.width - parent.padding * 2
visible: packageData.packageType === "material"
spacing: 0
Label
{
width: parent.width
text: catalog.i18nc("@header", "Optimized for Air Manager")
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
Label
{
width: parent.width
text: packageData.isCompatibleAirManager ? catalog.i18nc("@info", "Yes") : catalog.i18nc("@info", "No")
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
}
Row
{
id: externalButtonRow
anchors.horizontalCenter: parent.horizontalCenter
spacing: UM.Theme.getSize("narrow_margin").width
Cura.SecondaryButton
{
text: packageData.packageType === "plugin" ? catalog.i18nc("@button", "Visit plug-in website") : catalog.i18nc("@button", "Website")
iconSource: UM.Theme.getIcon("Globe")
outlineColor: "transparent"
onClicked: Qt.openUrlExternally(packageData.packageInfoUrl)
}
Cura.SecondaryButton
{
visible: packageData.packageType === "material"
text: catalog.i18nc("@button", "Buy spool")
iconSource: UM.Theme.getIcon("ShoppingCart")
outlineColor: "transparent"
onClicked: Qt.openUrlExternally(packageData.whereToBuy)
}
Cura.SecondaryButton
{
visible: packageData.packageType === "material"
text: catalog.i18nc("@button", "Safety datasheet")
iconSource: UM.Theme.getIcon("Warning")
outlineColor: "transparent"
onClicked: Qt.openUrlExternally(packageData.safetyDataSheet)
}
Cura.SecondaryButton
{
visible: packageData.packageType === "material"
text: catalog.i18nc("@button", "Technical datasheet")
iconSource: UM.Theme.getIcon("DocumentFilled")
outlineColor: "transparent"
onClicked: Qt.openUrlExternally(packageData.technicalDataSheet)
}
}
}
}
FontMetrics
{
id: fontMetrics
font: UM.Theme.getFont("default")
}
}

View file

@ -0,0 +1,33 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.15
import QtQuick.Controls 2.15
import UM 1.0 as UM
TabButton
{
property string pageTitle
padding: UM.Theme.getSize("narrow_margin").width
horizontalPadding: UM.Theme.getSize("default_margin").width
hoverEnabled: true
property color inactiveBackgroundColor : hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("detail_background")
property color activeBackgroundColor : UM.Theme.getColor("main_background")
background: Rectangle
{
anchors.fill: parent
color: parent.checked ? activeBackgroundColor : inactiveBackgroundColor
border.color: UM.Theme.getColor("detail_background")
border.width: UM.Theme.getSize("thick_lining").width
}
contentItem: Label
{
text: parent.text
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("text")
width: contentWidth
anchors.centerIn: parent
}
}

View file

@ -0,0 +1,246 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.15
import QtQuick.Controls 2.15
import UM 1.4 as UM
ListView
{
id: packages
width: parent.width
property string pageTitle
property var selectedPackage
property string searchInBrowserUrl
property bool bannerVisible
property var bannerIcon
property string bannerText
property string bannerReadMoreUrl
property var onRemoveBanner
property bool showUpdateButton
property bool showDisableButton
property bool showInstallButton
clip: true
Component.onCompleted: model.updatePackages()
Component.onDestruction: model.cleanUpAPIRequest()
spacing: UM.Theme.getSize("default_margin").height
section.property: "package.sectionTitle"
section.delegate: Rectangle
{
width: packages.width
height: sectionHeaderText.height + UM.Theme.getSize("default_margin").height
color: UM.Theme.getColor("detail_background")
Label
{
id: sectionHeaderText
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
text: section
font: UM.Theme.getFont("large")
color: UM.Theme.getColor("text")
}
}
ScrollBar.vertical: ScrollBar
{
// Vertical ScrollBar, styled similarly to the scrollBar in the settings panel
id: verticalScrollBar
visible: packages.contentHeight > packages.height
anchors.right: parent.right
background: Item {}
contentItem: Rectangle
{
id: scrollViewHandle
implicitWidth: UM.Theme.getSize("scrollbar").width
radius: Math.round(implicitWidth / 2)
color: verticalScrollBar.pressed ? UM.Theme.getColor("scrollbar_handle_down") : verticalScrollBar.hovered ? UM.Theme.getColor("scrollbar_handle_hover") : UM.Theme.getColor("scrollbar_handle")
Behavior on color { ColorAnimation { duration: 50; } }
}
}
delegate: MouseArea
{
id: cardMouseArea
width: parent ? parent.width : 0
height: childrenRect.height
hoverEnabled: true
onClicked:
{
packages.selectedPackage = model.package;
contextStack.push(packageDetailsComponent);
}
PackageCard
{
showUpdateButton: packages.showUpdateButton
showDisableButton: packages.showDisableButton
showInstallButton: packages.showInstallButton
packageData: model.package
width: {
if (verticalScrollBar.visible)
{
return parent.width - UM.Theme.getSize("default_margin").width - UM.Theme.getSize("default_margin").width
}
else
{
return parent.width - UM.Theme.getSize("default_margin").width
}
}
color: cardMouseArea.containsMouse ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("main_background")
}
}
Component
{
id: packageDetailsComponent
PackageDetails
{
packageData: packages.selectedPackage
title: packages.pageTitle
}
}
//Wrapper item to add spacing between content and footer.
footer: Item
{
width: parent.width - UM.Theme.getSize("default_margin").width - UM.Theme.getSize("narrow_margin").width
height: model.hasFooter || packages.model.errorMessage != "" ? UM.Theme.getSize("card").height + packages.spacing : 0
visible: model.hasFooter || packages.model.errorMessage != ""
Button
{
id: loadMoreButton
width: parent.width
height: UM.Theme.getSize("card").height
anchors.bottom: parent.bottom
enabled: packages.model.hasMore && !packages.model.isLoading || packages.model.errorMessage != ""
onClicked: packages.model.updatePackages() //Load next page in plug-in list.
background: Rectangle
{
anchors.fill: parent
radius: UM.Theme.getSize("default_radius").width
color: UM.Theme.getColor("main_background")
}
Row
{
anchors.centerIn: parent
spacing: UM.Theme.getSize("thin_margin").width
states:
[
State
{
name: "Error"
when: packages.model.errorMessage != ""
PropertyChanges
{
target: errorIcon
visible: true
}
PropertyChanges
{
target: loadMoreIcon
visible: false
}
PropertyChanges
{
target: loadMoreLabel
text: catalog.i18nc("@button", "Failed to load packages:") + " " + packages.model.errorMessage + "\n" + catalog.i18nc("@button", "Retry?")
}
},
State
{
name: "Loading"
when: packages.model.isLoading
PropertyChanges
{
target: loadMoreIcon
source: UM.Theme.getIcon("ArrowDoubleCircleRight")
color: UM.Theme.getColor("action_button_disabled_text")
}
PropertyChanges
{
target: loadMoreLabel
text: catalog.i18nc("@button", "Loading")
color: UM.Theme.getColor("action_button_disabled_text")
}
},
State
{
name: "LastPage"
when: !packages.model.hasMore
PropertyChanges
{
target: loadMoreIcon
visible: false
}
PropertyChanges
{
target: loadMoreLabel
text: packages.model.count > 0 ? catalog.i18nc("@message", "No more results to load") : catalog.i18nc("@message", "No results found with current filter")
color: UM.Theme.getColor("action_button_disabled_text")
}
}
]
Item
{
width: (errorIcon.visible || loadMoreIcon.visible) ? UM.Theme.getSize("small_button_icon").width : 0
height: UM.Theme.getSize("small_button_icon").height
anchors.verticalCenter: loadMoreLabel.verticalCenter
UM.StatusIcon
{
id: errorIcon
anchors.fill: parent
status: UM.StatusIcon.Status.ERROR
visible: false
}
UM.RecolorImage
{
id: loadMoreIcon
anchors.fill: parent
source: UM.Theme.getIcon("ArrowDown")
color: UM.Theme.getColor("secondary_button_text")
RotationAnimator
{
target: loadMoreIcon
from: 0
to: 360
duration: 1000
loops: Animation.Infinite
running: packages.model.isLoading
alwaysRunToEnd: true
}
}
}
Label
{
id: loadMoreLabel
text: catalog.i18nc("@button", "Load more")
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("secondary_button_text")
}
}
}
}
}

View file

@ -0,0 +1,23 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import UM 1.4 as UM
Packages
{
pageTitle: catalog.i18nc("@header", "Install Plugins")
bannerVisible: UM.Preferences.getValue("cura/market_place_show_plugin_banner")
bannerIcon: UM.Theme.getIcon("Shop")
bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.")
bannerReadMoreUrl: "https://support.ultimaker.com/hc/en-us/articles/360011968360/?utm_source=cura&utm_medium=software&utm_campaign=marketplace-learn-plugins"
onRemoveBanner: function() {
UM.Preferences.setValue("cura/market_place_show_plugin_banner", false)
bannerVisible = false;
}
searchInBrowserUrl: "https://marketplace.ultimaker.com/app/cura/plugins?utm_source=cura&utm_medium=software&utm_campaign=marketplace-search-plugins-browser"
showUpdateButton: true
showInstallButton: true
model: manager.PluginPackageList
}

View file

@ -0,0 +1,45 @@
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
import UM 1.6 as UM
import Cura 1.6 as Cura
Control
{
implicitWidth: UM.Theme.getSize("card_tiny_icon").width
implicitHeight: UM.Theme.getSize("card_tiny_icon").height
UM.ToolTip
{
tooltipText:
{
switch(packageData.packageType)
{
case "plugin": return catalog.i18nc("@info", "Ultimaker Verified Plug-in");
case "material": return catalog.i18nc("@info", "Ultimaker Certified Material");
default: return catalog.i18nc("@info", "Ultimaker Verified Package");
}
}
visible: parent.hovered
targetPoint: Qt.point(0, Math.round(parent.y + parent.height / 4))
}
Rectangle
{
anchors.fill: parent
color: UM.Theme.getColor("action_button_hovered")
radius: width
UM.RecolorImage
{
anchors.fill: parent
color: UM.Theme.getColor("primary")
source: packageData.packageType == "plugin" ? UM.Theme.getIcon("CheckCircle") : UM.Theme.getIcon("Certified")
}
}
//NOTE: Can we link to something here? (Probably a static link explaining what verified is):
// onClicked: Qt.openUrlExternally( XXXXXX )
}

View file

@ -2,43 +2,20 @@
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.2 as UM
Button
UM.SimpleButton
{
id: modelCheckerButton
UM.I18nCatalog
{
id: catalog
name: "cura"
}
visible: manager.hasWarnings
tooltip: catalog.i18nc("@info:tooltip", "Some things could be problematic in this print. Click to see tips for adjustment.")
onClicked: manager.showWarnings()
width: UM.Theme.getSize("save_button_specs_icons").width
height: UM.Theme.getSize("save_button_specs_icons").height
iconSource: "model_checker.svg"
anchors.verticalCenter: parent ? parent.verticalCenter : undefined
style: ButtonStyle
{
background: Item
{
UM.RecolorImage
{
width: UM.Theme.getSize("save_button_specs_icons").width;
height: UM.Theme.getSize("save_button_specs_icons").height;
sourceSize.height: width;
color: control.hovered ? UM.Theme.getColor("text_scene_hover") : UM.Theme.getColor("text_scene");
source: "model_checker.svg"
}
}
}
color: UM.Theme.getColor("text_scene")
hoverColor: UM.Theme.getColor("text_scene_hover")
}

View file

@ -3,7 +3,7 @@
import QtQuick 2.10
import QtQuick.Controls 2.0
import UM 1.3 as UM
import UM 1.5 as UM
import Cura 1.0 as Cura
// We show a nice overlay on the 3D viewer when the current output device has no monitor view
@ -90,7 +90,7 @@ Rectangle
visible: monitorViewComponent.sourceComponent == null
// CASE 2: CAN MONITOR & NOT CONNECTED
Label
UM.Label
{
anchors
{
@ -99,14 +99,10 @@ Rectangle
visible: isNetworkConfigured && !isConnected
text: catalog.i18nc("@info", "Please make sure your printer has a connection:\n- Check if the printer is turned on.\n- Check if the printer is connected to the network.\n- Check if you are signed in to discover cloud-connected printers.")
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
wrapMode: Text.WordWrap
lineHeight: UM.Theme.getSize("monitor_text_line_large").height
lineHeightMode: Text.FixedHeight
width: contentWidth
}
Label
UM.Label
{
id: noNetworkLabel
anchors
@ -116,11 +112,7 @@ Rectangle
visible: !isNetworkConfigured && isNetworkConfigurable
text: catalog.i18nc("@info", "Please connect your printer to the network.")
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
wrapMode: Text.WordWrap
width: contentWidth
lineHeight: UM.Theme.getSize("monitor_text_line_large").height
lineHeightMode: Text.FixedHeight
}
Item
{
@ -129,7 +121,6 @@ Rectangle
left: noNetworkLabel.left
}
visible: !isNetworkConfigured && isNetworkConfigurable
height: UM.Theme.getSize("monitor_text_line").height
width: childrenRect.width
UM.RecolorImage
@ -138,8 +129,8 @@ Rectangle
anchors.verticalCenter: parent.verticalCenter
color: UM.Theme.getColor("text_link")
source: UM.Theme.getIcon("LinkExternal")
width: UM.Theme.getSize("monitor_external_link_icon").width
height: UM.Theme.getSize("monitor_external_link_icon").height
width: UM.Theme.getSize("icon_indicator").width
height: UM.Theme.getSize("icon_indicator").height
}
Label
{

View file

@ -1,62 +1,24 @@
// Copyright (c) 2015 Ultimaker B.V.
// Copyright (c) 2022 Ultimaker B.V.
// Uranium is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
import UM 1.1 as UM
import QtQuick.Controls 2.1
import Cura 1.5 as Cura
import UM 1.5 as UM
import ".."
Button {
Cura.CategoryButton
{
id: base;
style: ButtonStyle {
background: Item { }
label: Row
{
spacing: UM.Theme.getSize("default_lining").width
categoryIcon: definition ? UM.Theme.getIcon(definition.icon) : ""
labelText: definition ? definition.label : ""
expanded: definition ? definition.expanded : false
UM.RecolorImage
{
anchors.verticalCenter: parent.verticalCenter
height: (label.height / 2) | 0
width: height
source: control.checked ? UM.Theme.getIcon("ChevronSingleDown") : UM.Theme.getIcon("ChevronSingleRight");
color: control.hovered ? palette.highlight : palette.buttonText
}
UM.RecolorImage
{
anchors.verticalCenter: parent.verticalCenter
height: label.height
width: height
source: control.iconSource
color: control.hovered ? palette.highlight : palette.buttonText
}
Label
{
id: label
anchors.verticalCenter: parent.verticalCenter
text: control.text
color: control.hovered ? palette.highlight : palette.buttonText
font.bold: true
}
SystemPalette { id: palette }
}
}
signal showTooltip(string text);
signal hideTooltip();
signal showTooltip(string text)
signal hideTooltip()
signal contextMenuRequested()
text: definition.label
iconSource: UM.Theme.getIcon(definition.icon)
checkable: true
checked: definition.expanded
onClicked: definition.expanded ? settingDefinitionsModel.collapseRecursive(definition.key) : settingDefinitionsModel.expandRecursive(definition.key)
onClicked: expanded ? settingDefinitionsModel.collapseRecursive(definition.key) : settingDefinitionsModel.expandRecursive(definition.key)
}

View file

@ -3,23 +3,22 @@
import QtQuick 2.1
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Controls 2.1
import UM 1.2 as UM
import UM 1.5 as UM
import Cura 1.0 as Cura
UM.TooltipArea
{
x: model.depth * UM.Theme.getSize("default_margin").width;
text: model.description;
x: model.depth * UM.Theme.getSize("narrow_margin").width
text: model.description
width: childrenRect.width;
height: childrenRect.height;
width: childrenRect.width
height: childrenRect.height
CheckBox
UM.CheckBox
{
id: check
text: definition.label
checked: addedSettingsModel.getVisible(model.key)

View file

@ -62,7 +62,7 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
all_instances = settings.findInstances()
visibility_changed = False # Flag to check if at the end the signal needs to be emitted
# Remove all instances that are not in visibility list
# Remove all SettingInstances that are not in visibility list
for instance in all_instances:
# exceptionally skip setting
if instance.definition.key in self._skip_reset_setting_set:
@ -71,29 +71,30 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
settings.removeInstance(instance.definition.key)
visibility_changed = True
# Add all instances that are not added, but are in visibility list
# Add all SettingInstances that are not added, but are in visibility list
for item in visible:
if settings.getInstance(item) is not None: # Setting was added already.
continue
definition = self._stack.getSettingDefinition(item)
if not definition:
Logger.log("w", f"Unable to add instance ({item}) to per-object visibility because we couldn't find the matching definition.")
Logger.log("w", f"Unable to add SettingInstance ({item}) to the per-object visibility because we couldn't find the matching SettingDefinition.")
continue
new_instance = SettingInstance(definition, settings)
stack_nr = -1
stack = None
# Check from what stack we should copy the raw property of the setting from.
# Check from what ContainerStack we should copy the raw property of the setting from.
if self._stack.getProperty("machine_extruder_count", "value") > 1:
if definition.limit_to_extruder != "-1":
# A limit to extruder function was set and it's a multi extrusion machine. Check what stack we do need to use.
# A limit_to_extruder function was set and it's a multi extrusion machine. Check what stack we
# do need to use.
stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder")))))
# Check if the found stack_number is in the extruder list of extruders.
if stack_nr not in ExtruderManager.getInstance().extruderIds and self._stack.getProperty("extruder_nr", "value") is not None:
stack_nr = -1
# Use the found stack number to get the right stack to copy the value from.
# Use the found stack_number to get the right ContainerStack to copy the value from.
if stack_nr in ExtruderManager.getInstance().extruderIds:
stack = ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0]
else:

View file

@ -1,11 +1,10 @@
// Copyright (c) 2021 Ultimaker B.V.
// Uranium is released under the terms of the LGPLv3 or higher.
//Copyright (c) 2022 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
import QtQuick.Controls 2.15
import UM 1.2 as UM
import UM 1.5 as UM
import Cura 1.0 as Cura
import ".."
@ -76,63 +75,72 @@ Item
id: meshTypeButtons
spacing: UM.Theme.getSize("default_margin").width
Button
UM.ToolbarButton
{
id: normalButton
text: catalog.i18nc("@label", "Normal model")
iconSource: UM.Theme.getIcon("Infill0");
toolItem: UM.RecolorImage
{
source: UM.Theme.getIcon("Infill0")
color: UM.Theme.getColor("icon")
}
property bool needBorder: true
checkable: true
onClicked: setMeshType(normalMeshType);
style: UM.Theme.styles.tool_button;
z: 4
}
Button
UM.ToolbarButton
{
id: supportMeshButton
text: catalog.i18nc("@label", "Print as support")
iconSource: UM.Theme.getIcon("MeshTypeSupport");
toolItem: UM.RecolorImage
{
source: UM.Theme.getIcon("MeshTypeSupport")
color: UM.Theme.getColor("icon")
}
property bool needBorder: true
checkable:true
onClicked: setMeshType(supportMeshType)
style: UM.Theme.styles.tool_button;
z: 3
}
Button
UM.ToolbarButton
{
id: overlapMeshButton
text: catalog.i18nc("@label", "Modify settings for overlaps")
iconSource: UM.Theme.getIcon("MeshTypeIntersect");
toolItem: UM.RecolorImage
{
source: UM.Theme.getIcon("MeshTypeIntersect")
color: UM.Theme.getColor("icon")
}
property bool needBorder: true
checkable:true
onClicked: setMeshType(infillMeshType)
style: UM.Theme.styles.tool_button;
z: 2
}
Button
UM.ToolbarButton
{
id: antiOverhangMeshButton
text: catalog.i18nc("@label", "Don't support overlaps")
iconSource: UM.Theme.getIcon("BlockSupportOverlaps");
toolItem: UM.RecolorImage
{
source: UM.Theme.getIcon("BlockSupportOverlaps")
color: UM.Theme.getColor("icon")
}
property bool needBorder: true
checkable: true
onClicked: setMeshType(antiOverhangMeshType)
style: UM.Theme.styles.tool_button;
z: 1
}
}
Label
UM.Label
{
id: meshTypeLabel
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
height: UM.Theme.getSize("setting").height
verticalAlignment: Text.AlignVCenter
}
@ -179,192 +187,187 @@ Item
// It kinda looks ugly otherwise (big panel, no content on it)
id: currentSettings
property int maximumHeight: 200 * screenScaleFactor
height: Math.min(contents.count * (UM.Theme.getSize("section").height + UM.Theme.getSize("default_lining").height), maximumHeight)
height: Math.min(contents.count * (UM.Theme.getSize("section").height + UM.Theme.getSize("narrow_margin").height + UM.Theme.getSize("default_lining").height), maximumHeight)
visible: currentMeshType != "anti_overhang_mesh"
ScrollView
ListView
{
id: contents
height: parent.height
width: UM.Theme.getSize("setting").width + UM.Theme.getSize("default_margin").width
style: UM.Theme.styles.scrollview
ListView
ScrollBar.vertical: UM.ScrollBar {}
clip: true
spacing: UM.Theme.getSize("default_lining").height
model: UM.SettingDefinitionsModel
{
id: contents
spacing: UM.Theme.getSize("default_lining").height
model: UM.SettingDefinitionsModel
id: addedSettingsModel
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
expanded: [ "*" ]
filter:
{
id: addedSettingsModel
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
expanded: [ "*" ]
filter:
if (printSequencePropertyProvider.properties.value == "one_at_a_time")
{
if (printSequencePropertyProvider.properties.value == "one_at_a_time")
return {"settable_per_meshgroup": true}
}
return {"settable_per_mesh": true}
}
exclude:
{
var excluded_settings = [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]
if (currentMeshType == "support_mesh")
{
excluded_settings = excluded_settings.concat(base.allCategoriesExceptSupport)
}
return excluded_settings
}
visibilityHandler: Cura.PerObjectSettingVisibilityHandler
{
id: visibility_handler
selectedObjectId: UM.ActiveTool.properties.getValue("SelectedObjectId")
}
// For some reason the model object is updated after removing him from the memory and
// it happens only on Windows. For this reason, set the destroyed value manually.
Component.onDestruction:
{
setDestroyed(true)
}
}
delegate: Row
{
spacing: - UM.Theme.getSize("default_margin").width
Loader
{
id: settingLoader
width: UM.Theme.getSize("setting").width
height: UM.Theme.getSize("section").height + UM.Theme.getSize("narrow_margin").height
enabled: provider.properties.enabled === "True"
property var definition: model
property var settingDefinitionsModel: addedSettingsModel
property var propertyProvider: provider
property var globalPropertyProvider: inheritStackProvider
property var externalResetHandler: false
//Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989
//In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes,
//causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
asynchronous: model.type != "enum" && model.type != "extruder"
onLoaded:
{
settingLoader.item.showRevertButton = false
settingLoader.item.showInheritButton = false
settingLoader.item.showLinkedSettingIcon = false
settingLoader.item.doDepthIndentation = false
settingLoader.item.doQualityUserSettingEmphasis = false
}
sourceComponent:
{
switch(model.type)
{
return {"settable_per_meshgroup": true}
case "int":
return settingTextField
case "[int]":
return settingTextField
case "float":
return settingTextField
case "enum":
return settingComboBox
case "extruder":
return settingExtruder
case "optional_extruder":
return settingOptionalExtruder
case "bool":
return settingCheckBox
case "str":
return settingTextField
case "category":
return settingCategory
default:
return settingUnknown
}
return {"settable_per_mesh": true}
}
exclude:
{
var excluded_settings = [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]
if (currentMeshType == "support_mesh")
{
excluded_settings = excluded_settings.concat(base.allCategoriesExceptSupport)
}
return excluded_settings
}
visibilityHandler: Cura.PerObjectSettingVisibilityHandler
{
id: visibility_handler
selectedObjectId: UM.ActiveTool.properties.getValue("SelectedObjectId")
}
// For some reason the model object is updated after removing him from the memory and
// it happens only on Windows. For this reason, set the destroyed value manually.
Component.onDestruction:
{
setDestroyed(true)
}
}
delegate: Row
Button
{
spacing: - UM.Theme.getSize("default_margin").width
Loader
width: Math.round(UM.Theme.getSize("setting").height / 2)
height: UM.Theme.getSize("setting").height
onClicked: addedSettingsModel.setVisible(model.key, false)
background: Item
{
id: settingLoader
width: UM.Theme.getSize("setting").width
height: UM.Theme.getSize("section").height
enabled: provider.properties.enabled === "True"
property var definition: model
property var settingDefinitionsModel: addedSettingsModel
property var propertyProvider: provider
property var globalPropertyProvider: inheritStackProvider
property var externalResetHandler: false
//Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989
//In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes,
//causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
asynchronous: model.type != "enum" && model.type != "extruder"
onLoaded:
UM.RecolorImage
{
settingLoader.item.showRevertButton = false
settingLoader.item.showInheritButton = false
settingLoader.item.showLinkedSettingIcon = false
settingLoader.item.doDepthIndentation = false
settingLoader.item.doQualityUserSettingEmphasis = false
}
sourceComponent:
{
switch(model.type)
{
case "int":
return settingTextField
case "[int]":
return settingTextField
case "float":
return settingTextField
case "enum":
return settingComboBox
case "extruder":
return settingExtruder
case "optional_extruder":
return settingOptionalExtruder
case "bool":
return settingCheckBox
case "str":
return settingTextField
case "category":
return settingCategory
default:
return settingUnknown
}
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: width
sourceSize.height: width
color: parent.hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button")
source: UM.Theme.getIcon("Minus")
}
}
}
Button
// Specialty provider that only watches global_inherits (we can't filter on what property changed we get events
// so we bypass that to make a dedicated provider).
UM.SettingPropertyProvider
{
id: provider
containerStackId: UM.ActiveTool.properties.getValue("ContainerID")
key: model.key
watchedProperties: [ "value", "enabled", "validationState" ]
storeIndex: 0
removeUnusedValue: false
}
UM.SettingPropertyProvider
{
id: inheritStackProvider
containerStackId: UM.ActiveTool.properties.getValue("ContainerID")
key: model.key
watchedProperties: [ "limit_to_extruder" ]
}
Connections
{
target: inheritStackProvider
function onPropertiesChanged() { provider.forcePropertiesChanged() }
}
Connections
{
target: UM.ActiveTool
function onPropertiesChanged()
{
width: Math.round(UM.Theme.getSize("setting").height / 2)
height: UM.Theme.getSize("setting").height
onClicked: addedSettingsModel.setVisible(model.key, false)
style: ButtonStyle
// the values cannot be bound with UM.ActiveTool.properties.getValue() calls,
// so here we connect to the signal and update the those values.
if (typeof UM.ActiveTool.properties.getValue("SelectedObjectId") !== "undefined")
{
background: Item
const selectedObjectId = UM.ActiveTool.properties.getValue("SelectedObjectId")
if (addedSettingsModel.visibilityHandler.selectedObjectId != selectedObjectId)
{
UM.RecolorImage
{
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: width
sourceSize.height: width
color: control.hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button")
source: UM.Theme.getIcon("Minus")
}
addedSettingsModel.visibilityHandler.selectedObjectId = selectedObjectId
}
}
}
// Specialty provider that only watches global_inherits (we can't filter on what property changed we get events
// so we bypass that to make a dedicated provider).
UM.SettingPropertyProvider
{
id: provider
containerStackId: UM.ActiveTool.properties.getValue("ContainerID")
key: model.key
watchedProperties: [ "value", "enabled", "validationState" ]
storeIndex: 0
removeUnusedValue: false
}
UM.SettingPropertyProvider
{
id: inheritStackProvider
containerStackId: UM.ActiveTool.properties.getValue("ContainerID")
key: model.key
watchedProperties: [ "limit_to_extruder" ]
}
Connections
{
target: inheritStackProvider
function onPropertiesChanged() { provider.forcePropertiesChanged() }
}
Connections
{
target: UM.ActiveTool
function onPropertiesChanged()
if (typeof UM.ActiveTool.properties.getValue("ContainerID") !== "undefined")
{
// the values cannot be bound with UM.ActiveTool.properties.getValue() calls,
// so here we connect to the signal and update the those values.
if (typeof UM.ActiveTool.properties.getValue("SelectedObjectId") !== "undefined")
const containerId = UM.ActiveTool.properties.getValue("ContainerID")
if (provider.containerStackId != containerId)
{
const selectedObjectId = UM.ActiveTool.properties.getValue("SelectedObjectId")
if (addedSettingsModel.visibilityHandler.selectedObjectId != selectedObjectId)
{
addedSettingsModel.visibilityHandler.selectedObjectId = selectedObjectId
}
provider.containerStackId = containerId
}
if (typeof UM.ActiveTool.properties.getValue("ContainerID") !== "undefined")
if (inheritStackProvider.containerStackId != containerId)
{
const containerId = UM.ActiveTool.properties.getValue("ContainerID")
if (provider.containerStackId != containerId)
{
provider.containerStackId = containerId
}
if (inheritStackProvider.containerStackId != containerId)
{
inheritStackProvider.containerStackId = containerId
}
inheritStackProvider.containerStackId = containerId
}
}
}
@ -422,8 +425,6 @@ Item
storeIndex: 0
}
SystemPalette { id: palette }
Component
{
id: settingTextField

View file

@ -1,8 +1,10 @@
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
//Copyright (c) 2022 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher.
import UM 1.2 as UM
import QtQuick 2.2
import QtQuick.Controls 2.2
import UM 1.5 as UM
import Cura 1.0 as Cura
import ".."
@ -10,8 +12,11 @@ UM.Dialog
{
id: settingPickDialog
margin: UM.Theme.getSize("default_margin").width
title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
width: screenScaleFactor * 360
width: UM.Theme.getSize("small_popup_dialog").width
backgroundColor: UM.Theme.getColor("background_1")
property var additional_excluded_settings
@ -40,9 +45,10 @@ UM.Dialog
listview.model.filter = new_filter
}
TextField
Cura.TextField
{
id: filterInput
selectByMouse: true
anchors
{
@ -57,76 +63,77 @@ UM.Dialog
onTextChanged: settingPickDialog.updateFilter()
}
CheckBox
UM.CheckBox
{
id: toggleShowAll
anchors
{
top: parent.top
right: parent.right
verticalCenter: filterInput.verticalCenter
}
text: catalog.i18nc("@label:checkbox", "Show all")
}
ScrollView
ListView
{
id: scrollView
id: listview
anchors
{
top: filterInput.bottom
topMargin: UM.Theme.getSize("default_margin").height
left: parent.left
right: parent.right
bottom: parent.bottom
}
ListView
ScrollBar.vertical: UM.ScrollBar { id: scrollBar }
clip: true
model: UM.SettingDefinitionsModel
{
id: listview
model: UM.SettingDefinitionsModel
id: definitionsModel
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
expanded: [ "*" ]
exclude:
{
id: definitionsModel
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
expanded: [ "*" ]
exclude:
{
var excluded_settings = [ "machine_settings", "command_line_settings", "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]
excluded_settings = excluded_settings.concat(settingPickDialog.additional_excluded_settings)
return excluded_settings
}
showAll: toggleShowAll.checked || filterInput.text !== ""
var excluded_settings = [ "machine_settings", "command_line_settings", "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]
excluded_settings = excluded_settings.concat(settingPickDialog.additional_excluded_settings)
return excluded_settings
}
delegate: Loader
{
id: loader
width: listview.width
height: model.type != undefined ? UM.Theme.getSize("section").height : 0
property var definition: model
property var settingDefinitionsModel: definitionsModel
asynchronous: true
source:
{
switch(model.type)
{
case "category":
return "PerObjectCategory.qml"
default:
return "PerObjectItem.qml"
}
}
}
Component.onCompleted: settingPickDialog.updateFilter()
showAll: toggleShowAll.checked || filterInput.text !== ""
}
delegate: Loader
{
id: loader
width: listview.width - scrollBar.width
height: model.type != undefined ? UM.Theme.getSize("section").height : 0
property var definition: model
property var settingDefinitionsModel: definitionsModel
asynchronous: true
source:
{
switch(model.type)
{
case "category":
return "PerObjectCategory.qml"
default:
return "PerObjectItem.qml"
}
}
}
Component.onCompleted: settingPickDialog.updateFilter()
}
rightButtons: [
Button
Cura.TertiaryButton
{
text: catalog.i18nc("@action:button", "Close")
onClicked: settingPickDialog.visible = false
onClicked: reject()
}
]
}

View file

@ -193,6 +193,8 @@ class PostProcessingPlugin(QObject, Extension):
spec = importlib.util.spec_from_file_location(__name__ + "." + script_name,
file_path)
if spec is None:
continue
loaded_script = importlib.util.module_from_spec(spec)
if spec.loader is None:
continue

View file

@ -1,16 +1,13 @@
// Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
// Copyright (c) 2022 Jaime van Kessel, Ultimaker B.V.
// The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Controls.Styles 1.1
import QtQuick.Controls 2.15
import QtQml.Models 2.15 as Models
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import UM 1.2 as UM
import UM 1.5 as UM
import Cura 1.0 as Cura
UM.Dialog
@ -18,14 +15,15 @@ UM.Dialog
id: dialog
title: catalog.i18nc("@title:window", "Post Processing Plugin")
width: 700 * screenScaleFactor;
height: 500 * screenScaleFactor;
minimumWidth: 400 * screenScaleFactor;
minimumHeight: 250 * screenScaleFactor;
width: 700 * screenScaleFactor
height: 500 * screenScaleFactor
minimumWidth: 400 * screenScaleFactor
minimumHeight: 250 * screenScaleFactor
onVisibleChanged:
{
if(!visible) //Whenever the window is closed (either via the "Close" button or the X on the window frame), we want to update it in the stack.
// Whenever the window is closed (either via the "Close" button or the X on the window frame), we want to update it in the stack.
if (!visible)
{
manager.writeScriptsToStack()
}
@ -36,234 +34,211 @@ UM.Dialog
UM.I18nCatalog{id: catalog; name: "cura"}
id: base
property int columnWidth: Math.round((base.width / 2) - UM.Theme.getSize("default_margin").width)
property int textMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
property int textMargin: UM.Theme.getSize("narrow_margin").width
property string activeScriptName
SystemPalette{ id: palette }
SystemPalette{ id: disabledPalette; colorGroup: SystemPalette.Disabled }
anchors.fill: parent
ExclusiveGroup
ButtonGroup
{
id: selectedScriptGroup
}
Item
Column
{
id: activeScripts
anchors.left: parent.left
width: base.columnWidth
height: parent.height
Label
spacing: base.textMargin
UM.Label
{
id: activeScriptsHeader
text: catalog.i18nc("@label", "Post Processing Scripts")
anchors.top: parent.top
anchors.topMargin: base.textMargin
anchors.left: parent.left
anchors.leftMargin: base.textMargin
anchors.right: parent.right
anchors.rightMargin: base.textMargin
font: UM.Theme.getFont("large_bold")
elide: Text.ElideRight
}
ListView
{
id: activeScriptsList
anchors
{
top: activeScriptsHeader.bottom
left: parent.left
right: parent.right
rightMargin: base.textMargin
topMargin: base.textMargin
leftMargin: UM.Theme.getSize("default_margin").width
}
height: Math.min(contentHeight, parent.height - parent.spacing * 2 - activeScriptsHeader.height - addButton.height) //At the window height, start scrolling this one.
height: childrenRect.height
model: manager.scriptList
delegate: Item
clip: true
ScrollBar.vertical: UM.ScrollBar
{
width: parent.width
height: activeScriptButton.height
Button
{
id: activeScriptButton
text: manager.getScriptLabelByKey(modelData.toString())
exclusiveGroup: selectedScriptGroup
width: parent.width
height: UM.Theme.getSize("setting").height
checkable: true
id: activeScriptsScrollBar
}
model: manager.scriptList
checked:
delegate: Button
{
id: activeScriptButton
width: parent.width - activeScriptsScrollBar.width
height: UM.Theme.getSize("standard_list_lineheight").height
ButtonGroup.group: selectedScriptGroup
checkable: true
checked:
{
if (manager.selectedScriptIndex == index)
{
if (manager.selectedScriptIndex == index)
{
base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
return true
}
else
{
return false
}
}
onClicked:
{
forceActiveFocus()
manager.setSelectedScriptIndex(index)
base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
return true
}
style: ButtonStyle
else
{
background: Rectangle
{
color: activeScriptButton.checked ? palette.highlight : "transparent"
width: parent.width
height: parent.height
}
label: Label
{
wrapMode: Text.Wrap
text: control.text
elide: Text.ElideRight
color: activeScriptButton.checked ? palette.highlightedText : palette.text
}
return false
}
}
Button
background: Rectangle
{
id: removeButton
text: "x"
width: 20 * screenScaleFactor
height: 20 * screenScaleFactor
anchors.right:parent.right
anchors.rightMargin: base.textMargin
anchors.verticalCenter: parent.verticalCenter
onClicked: manager.removeScriptByIndex(index)
style: ButtonStyle
{
label: Item
{
UM.RecolorImage
{
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(control.width / 2.7)
height: Math.round(control.height / 2.7)
sourceSize.height: width
color: palette.text
source: UM.Theme.getIcon("Cancel")
}
}
}
color: activeScriptButton.checked ? UM.Theme.getColor("background_3") : "transparent"
}
Button
onClicked:
{
id: downButton
text: ""
anchors.right: removeButton.left
anchors.verticalCenter: parent.verticalCenter
enabled: index != manager.scriptList.length - 1
width: 20 * screenScaleFactor
height: 20 * screenScaleFactor
onClicked:
{
if (manager.selectedScriptIndex == index)
{
manager.setSelectedScriptIndex(index + 1)
}
return manager.moveScript(index, index + 1)
}
style: ButtonStyle
{
label: Item
{
UM.RecolorImage
{
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(control.width / 2.5)
height: Math.round(control.height / 2.5)
sourceSize.height: width
color: control.enabled ? palette.text : disabledPalette.text
source: UM.Theme.getIcon("ChevronSingleDown")
}
}
}
forceActiveFocus()
manager.setSelectedScriptIndex(index)
base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
}
Button
RowLayout
{
id: upButton
text: ""
enabled: index != 0
width: 20 * screenScaleFactor
height: 20 * screenScaleFactor
anchors.right: downButton.left
anchors.verticalCenter: parent.verticalCenter
onClicked:
anchors.fill: parent
UM.Label
{
if (manager.selectedScriptIndex == index)
{
manager.setSelectedScriptIndex(index - 1)
}
return manager.moveScript(index, index - 1)
Layout.fillWidth: true
text: manager.getScriptLabelByKey(modelData.toString())
}
style: ButtonStyle
Item
{
label: Item
{
UM.RecolorImage
id: downButton
Layout.preferredWidth: height
Layout.fillHeight: true
enabled: index != manager.scriptList.length - 1
MouseArea
{
anchors.fill: parent
onClicked:
{
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(control.width / 2.5)
height: Math.round(control.height / 2.5)
sourceSize.height: width
color: control.enabled ? palette.text : disabledPalette.text
source: UM.Theme.getIcon("ChevronSingleUp")
if (manager.selectedScriptIndex == index)
{
manager.setSelectedScriptIndex(index + 1)
}
return manager.moveScript(index, index + 1)
}
}
UM.RecolorImage
{
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width
sourceSize.height: height
color: parent.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("text_disabled")
source: UM.Theme.getIcon("ChevronSingleDown")
}
}
Item
{
id: upButton
Layout.preferredWidth: height
Layout.fillHeight: true
enabled: index != 0
MouseArea
{
anchors.fill: parent
onClicked:
{
if (manager.selectedScriptIndex == index)
{
manager.setSelectedScriptIndex(index - 1)
}
return manager.moveScript(index, index - 1)
}
}
UM.RecolorImage
{
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width
sourceSize.height: height
color: upButton.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("text_disabled")
source: UM.Theme.getIcon("ChevronSingleUp")
}
}
Item
{
id: removeButton
Layout.preferredWidth: height
Layout.fillHeight: true
MouseArea
{
anchors.fill: parent
onClicked: manager.removeScriptByIndex(index)
}
UM.RecolorImage
{
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width
sourceSize.height: height
color: UM.Theme.getColor("text")
source: UM.Theme.getIcon("Cancel")
}
}
}
}
}
Button
Cura.SecondaryButton
{
id: addButton
text: catalog.i18nc("@action", "Add a script")
anchors.left: parent.left
anchors.leftMargin: base.textMargin
anchors.top: activeScriptsList.bottom
anchors.topMargin: base.textMargin
onClicked: scriptsMenu.open()
style: ButtonStyle
{
label: Label
{
text: control.text
}
}
}
QQC2.Menu
}
Cura.Menu
{
id: scriptsMenu
Models.Instantiator
{
id: scriptsMenu
width: parent.width
model: manager.loadedScriptList
Models.Instantiator
Cura.MenuItem
{
model: manager.loadedScriptList
QQC2.MenuItem
{
text: manager.getScriptLabelByKey(modelData.toString())
onTriggered: manager.addScriptToList(modelData.toString())
}
onObjectAdded: scriptsMenu.insertItem(index, object)
onObjectRemoved: scriptsMenu.removeItem(object)
text: manager.getScriptLabelByKey(modelData.toString())
onTriggered: manager.addScriptToList(modelData.toString())
}
onObjectAdded: scriptsMenu.insertItem(index, object)
onObjectRemoved: scriptsMenu.removeItem(object)
}
}
@ -296,9 +271,9 @@ UM.Dialog
color: UM.Theme.getColor("text")
}
ScrollView
ListView
{
id: scrollView
id: listview
anchors
{
top: scriptSpecsHeader.bottom
@ -309,124 +284,114 @@ UM.Dialog
bottom: parent.bottom
}
ScrollBar.vertical: UM.ScrollBar {}
clip: true
visible: manager.selectedScriptDefinitionId != ""
style: UM.Theme.styles.scrollview;
spacing: UM.Theme.getSize("default_lining").height
ListView
model: UM.SettingDefinitionsModel
{
id: listview
spacing: UM.Theme.getSize("default_lining").height
model: UM.SettingDefinitionsModel
id: definitionsModel
containerId: manager.selectedScriptDefinitionId
showAll: true
}
delegate: Loader
{
id: settingLoader
width: listview.width
height:
{
id: definitionsModel
containerId: manager.selectedScriptDefinitionId
showAll: true
if (provider.properties.enabled == "True" && model.type != undefined)
{
return UM.Theme.getSize("section").height;
}
else
{
return 0
}
}
Behavior on height { NumberAnimation { duration: 100 } }
opacity: provider.properties.enabled == "True" ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 100 } }
enabled: opacity > 0
property var definition: model
property var settingDefinitionsModel: definitionsModel
property var propertyProvider: provider
property var globalPropertyProvider: inheritStackProvider
//Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989
//In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes,
//causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
asynchronous: model.type != "enum" && model.type != "extruder"
onLoaded:
{
settingLoader.item.showRevertButton = false
settingLoader.item.showInheritButton = false
settingLoader.item.showLinkedSettingIcon = false
settingLoader.item.doDepthIndentation = false
settingLoader.item.doQualityUserSettingEmphasis = false
}
delegate: Loader
sourceComponent:
{
id: settingLoader
width: parent.width
height:
switch(model.type)
{
if(provider.properties.enabled == "True")
{
if(model.type != undefined)
{
return UM.Theme.getSize("section").height
}
else
{
return 0
}
}
else
{
return 0
}
case "int":
return settingTextField
case "float":
return settingTextField
case "enum":
return settingComboBox
case "extruder":
return settingExtruder
case "bool":
return settingCheckBox
case "str":
return settingTextField
case "category":
return settingCategory
default:
return settingUnknown
}
Behavior on height { NumberAnimation { duration: 100 } }
opacity: provider.properties.enabled == "True" ? 1 : 0
}
Behavior on opacity { NumberAnimation { duration: 100 } }
enabled: opacity > 0
UM.SettingPropertyProvider
{
id: provider
containerStackId: manager.selectedScriptStackId
key: model.key ? model.key : "None"
watchedProperties: [ "value", "enabled", "state", "validationState" ]
storeIndex: 0
}
property var definition: model
property var settingDefinitionsModel: definitionsModel
property var propertyProvider: provider
property var globalPropertyProvider: inheritStackProvider
// Specialty provider that only watches global_inherits (we can't filter on what property changed we get events
// so we bypass that to make a dedicated provider).
UM.SettingPropertyProvider
{
id: inheritStackProvider
containerStack: Cura.MachineManager.activeMachine
key: model.key ? model.key : "None"
watchedProperties: [ "limit_to_extruder" ]
}
//Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989
//In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes,
//causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
asynchronous: model.type != "enum" && model.type != "extruder"
Connections
{
target: item
onLoaded:
function onShowTooltip(text)
{
settingLoader.item.showRevertButton = false
settingLoader.item.showInheritButton = false
settingLoader.item.showLinkedSettingIcon = false
settingLoader.item.doDepthIndentation = false
settingLoader.item.doQualityUserSettingEmphasis = false
tooltip.text = text;
var position = settingLoader.mapToItem(settingsPanel, settingsPanel.x, 0);
tooltip.show(position);
tooltip.target.x = position.x + 1;
}
sourceComponent:
{
switch(model.type)
{
case "int":
return settingTextField
case "float":
return settingTextField
case "enum":
return settingComboBox
case "extruder":
return settingExtruder
case "bool":
return settingCheckBox
case "str":
return settingTextField
case "category":
return settingCategory
default:
return settingUnknown
}
}
UM.SettingPropertyProvider
{
id: provider
containerStackId: manager.selectedScriptStackId
key: model.key ? model.key : "None"
watchedProperties: [ "value", "enabled", "state", "validationState" ]
storeIndex: 0
}
// Specialty provider that only watches global_inherits (we can't filter on what property changed we get events
// so we bypass that to make a dedicated provider).
UM.SettingPropertyProvider
{
id: inheritStackProvider
containerStack: Cura.MachineManager.activeMachine
key: model.key ? model.key : "None"
watchedProperties: [ "limit_to_extruder" ]
}
Connections
{
target: item
function onShowTooltip(text)
{
tooltip.text = text
var position = settingLoader.mapToItem(settingsPanel, settingsPanel.x, 0)
tooltip.show(position)
tooltip.target.x = position.x + 1
}
function onHideTooltip() { tooltip.hide() }
}
function onHideTooltip() { tooltip.hide() }
}
}
}
@ -480,10 +445,9 @@ UM.Dialog
}
}
rightButtons: Button
rightButtons: Cura.TertiaryButton
{
text: catalog.i18nc("@action:button", "Close")
iconName: "dialog-close"
onClicked: dialog.accept()
}
@ -515,7 +479,7 @@ UM.Dialog
}
return tipText
}
toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignLeft
toolTipContentAlignment: UM.Enums.ContentAlignment.AlignLeft
onClicked: dialog.show()
iconSource: "Script.svg"
fixedWidthMode: false
@ -527,10 +491,8 @@ UM.Dialog
visible: activeScriptsList.count > 0
anchors
{
top: parent.top
right: parent.right
rightMargin: (-0.5 * width) | 0
topMargin: (-0.5 * height) | 0
horizontalCenter: parent.right
verticalCenter: parent.top
}
labelText: activeScriptsList.count

View file

@ -298,7 +298,7 @@ class ChangeAtZ(Script):
},
"caz_change_retract": {
"label": "Change Retraction",
"description": "Indicates you would like to modify retraction properties.",
"description": "Indicates you would like to modify retraction properties. Does not work when using relative extrusion.",
"type": "bool",
"default_value": false
},

View file

@ -1,7 +1,7 @@
# Cura PostProcessingPlugin
# Author: Mathias Lyngklip Kjeldgaard, Alexander Gee, Kimmo Toivanen
# Author: Mathias Lyngklip Kjeldgaard, Alexander Gee, Kimmo Toivanen, Inigo Martinez
# Date: July 31, 2019
# Modified: Okt 22, 2020
# Modified: Nov 30, 2021
# Description: This plugin displays progress on the LCD. It can output the estimated time remaining and the completion percentage.
@ -37,7 +37,8 @@ class DisplayProgressOnLCD(Script):
"type": "enum",
"options": {
"m117":"M117 - All printers",
"m73":"M73 - Prusa, Marlin 2"
"m73":"M73 - Prusa, Marlin 2",
"m118":"M118 - Octoprint"
},
"enabled": "time_remaining",
"default_value": "m117"
@ -77,6 +78,10 @@ class DisplayProgressOnLCD(Script):
current_time_string = "{:d}h{:02d}m{:02d}s".format(int(h), int(m), int(s))
# And now insert that into the GCODE
lines.insert(line_index, "M117 Time Left {}".format(current_time_string))
elif mode == "m118":
current_time_string = "{:d}h{:02d}m{:02d}s".format(int(h), int(m), int(s))
# And now insert that into the GCODE
lines.insert(line_index, "M118 A1 P0 action:notification Time Left {}".format(current_time_string))
else:
mins = int(60 * h + m + s / 30)
lines.insert(line_index, "M73 R{}".format(mins))
@ -107,7 +112,10 @@ class DisplayProgressOnLCD(Script):
if output_percentage:
# Emit 0 percent to sure Marlin knows we are overriding the completion percentage
lines.insert(line_index, "M73 P0")
if output_time_method == "m118":
lines.insert(line_index, "M118 A1 P0 action:notification Data Left 0/100")
else:
lines.insert(line_index, "M73 P0")
elif line.startswith(";TIME_ELAPSED:"):
# We've found one of the time elapsed values which are added at the end of layers
@ -178,7 +186,10 @@ class DisplayProgressOnLCD(Script):
output = min(percentage + previous_layer_end_percentage, 100)
# Now insert the sanitized percentage into the GCODE
lines.insert(percentage_line_index, "M73 P{}".format(output))
if output_time_method == "m118":
lines.insert(percentage_line_index, "M118 A1 P0 action:notification Data Left {}/100".format(output))
else:
lines.insert(percentage_line_index, "M73 P{}".format(output))
previous_layer_end_percentage = layer_end_percentage

View file

@ -1,10 +1,7 @@
//Copyright (c) 2020 Ultimaker B.V.
//Copyright (c) 2021 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura

View file

@ -5,7 +5,7 @@ import QtQuick 2.9
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3
import UM 1.3 as UM
import UM 1.5 as UM
import Cura 1.1 as Cura
@ -131,14 +131,10 @@ Item
height: UM.Theme.getSize("action_button").height
hoverEnabled: true
contentItem: Label
contentItem: UM.Label
{
text: model.displayText
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
verticalAlignment: Text.AlignVCenter
width: contentWidth
height: parent.height
}

View file

@ -1,11 +1,7 @@
// Copyright (c) 2019 Ultimaker B.V.
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura

View file

@ -1,10 +1,8 @@
// Copyright (c) 2017 Ultimaker B.V.
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura

View file

@ -1,10 +1,8 @@
// Copyright (c) 2017 Ultimaker B.V.
// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura

View file

@ -1,14 +1,15 @@
// Copyright (c) 2017 Ultimaker B.V.
// Copyright (c) 2022 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.5
import QtQuick.Controls 1.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
UM.PointingRectangle {
UM.PointingRectangle
{
id: sliderLabelRoot
// custom properties
@ -28,47 +29,41 @@ UM.PointingRectangle {
borderColor: UM.Theme.getColor("lining")
borderWidth: UM.Theme.getSize("default_lining").width
Behavior on height {
NumberAnimation {
duration: 50
}
}
Behavior on height { NumberAnimation { duration: 50 } }
// catch all mouse events so they're not handled by underlying 3D scene
MouseArea {
MouseArea
{
anchors.fill: parent
}
TextMetrics {
TextMetrics
{
id: maxValueMetrics
font: valueLabel.font
text: maximumValue + 1 // layers are 0 based, add 1 for display value
}
TextField {
TextField
{
id: valueLabel
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
alignWhenCentered: false
}
anchors.centerIn: parent
width: maxValueMetrics.width + UM.Theme.getSize("default_margin").width
//width: maxValueMetrics.contentWidth + 2 * UM.Theme.getSize("default_margin").width
text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
horizontalAlignment: TextInput.AlignHCenter
leftPadding: UM.Theme.getSize("narrow_margin").width
rightPadding: UM.Theme.getSize("narrow_margin").width
// key bindings, work when label is currently focused (active handle in LayerSlider)
Keys.onUpPressed: sliderLabelRoot.setValue(sliderLabelRoot.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
Keys.onDownPressed: sliderLabelRoot.setValue(sliderLabelRoot.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
style: TextFieldStyle {
textColor: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
background: Item { }
}
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
background: Item {}
selectByMouse: true
onEditingFinished: {
@ -84,16 +79,18 @@ UM.PointingRectangle {
}
}
validator: IntValidator {
validator: IntValidator
{
bottom: startFrom
top: sliderLabelRoot.maximumValue + startFrom // +startFrom because maybe we want to start in a different value rather than 0
}
}
BusyIndicator {
BusyIndicator
{
id: busyIndicator
anchors {
anchors
{
left: parent.right
leftMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
verticalCenter: parent.verticalCenter

View file

@ -2,9 +2,6 @@
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.4 as UM
import Cura 1.0 as Cura

View file

@ -1,13 +1,12 @@
// Copyright (c) 2018 Ultimaker B.V.
// Copyright (c) 2022 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Controls 2.1
import QtGraphicalEffects 1.0
import UM 1.0 as UM
import UM 1.5 as UM
import Cura 1.0 as Cura
@ -43,22 +42,19 @@ Cura.ExpandableComponent
headerItem: Item
{
Label
UM.Label
{
id: colorSchemeLabel
text: catalog.i18nc("@label", "Color scheme")
verticalAlignment: Text.AlignVCenter
height: parent.height
elide: Text.ElideRight
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
Label
UM.Label
{
text: layerTypeCombobox.currentText
verticalAlignment: Text.AlignVCenter
anchors
{
left: colorSchemeLabel.right
@ -68,8 +64,6 @@ Cura.ExpandableComponent
height: parent.height
elide: Text.ElideRight
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
}
}
@ -99,7 +93,8 @@ Cura.ExpandableComponent
spacing: UM.Theme.getSize("layerview_row_spacing").height
ListModel // matches SimulationView.py
// matches SimulationView.py
ListModel
{
id: layerViewTypes
}
@ -132,18 +127,17 @@ Cura.ExpandableComponent
})
}
ComboBox
Cura.ComboBox
{
id: layerTypeCombobox
textRole: "text"
valueRole: "type_id"
width: parent.width
implicitHeight: UM.Theme.getSize("setting_control").height
model: layerViewTypes
visible: !UM.SimulationView.compatibilityMode
style: UM.Theme.styles.combobox
onActivated:
{
UM.Preferences.setValue("layerview/layer_view_type", index);
}
onActivated: UM.Preferences.setValue("layerview/layer_view_type", index)
Component.onCompleted:
{
@ -165,16 +159,13 @@ Cura.ExpandableComponent
}
}
Label
UM.Label
{
id: compatibilityModeLabel
text: catalog.i18nc("@label", "Compatibility Mode")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
visible: UM.SimulationView.compatibilityMode
height: UM.Theme.getSize("layerview_row").height
width: parent.width
renderType: Text.NativeRendering
}
Item // Spacer
@ -187,7 +178,7 @@ Cura.ExpandableComponent
{
model: CuraApplication.getExtrudersModel()
CheckBox
UM.CheckBox
{
id: extrudersModelCheckBox
checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
@ -201,8 +192,6 @@ Cura.ExpandableComponent
UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
}
style: UM.Theme.styles.checkbox
Rectangle
{
id: swatch
@ -215,12 +204,11 @@ Cura.ExpandableComponent
border.color: UM.Theme.getColor("lining")
}
Label
UM.Label
{
text: model.name
elide: Text.ElideRight
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
anchors
{
verticalCenter: parent.verticalCenter
@ -229,7 +217,6 @@ Cura.ExpandableComponent
leftMargin: UM.Theme.getSize("checkbox").width + Math.round(UM.Theme.getSize("default_margin").width / 2)
rightMargin: UM.Theme.getSize("default_margin").width * 2
}
renderType: Text.NativeRendering
}
}
}
@ -277,7 +264,7 @@ Cura.ExpandableComponent
}
}
CheckBox
UM.CheckBox
{
id: legendModelCheckBox
checked: model.initialValue
@ -285,8 +272,6 @@ Cura.ExpandableComponent
height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
width: parent.width
style: UM.Theme.styles.checkbox
Rectangle
{
anchors.verticalCenter: parent.verticalCenter
@ -299,7 +284,7 @@ Cura.ExpandableComponent
visible: viewSettings.show_legend
}
Label
UM.Label
{
text: label
font: UM.Theme.getFont("default")
@ -315,24 +300,22 @@ Cura.ExpandableComponent
}
}
CheckBox
UM.CheckBox
{
checked: viewSettings.only_show_top_layers
onClicked: UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0)
text: catalog.i18nc("@label", "Only Show Top Layers")
visible: UM.SimulationView.compatibilityMode
style: UM.Theme.styles.checkbox
width: parent.width
}
CheckBox
UM.CheckBox
{
checked: viewSettings.top_layer_count == 5
onClicked: UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1)
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
width: parent.width
visible: UM.SimulationView.compatibilityMode
style: UM.Theme.styles.checkbox
}
Repeater
@ -353,7 +336,7 @@ Cura.ExpandableComponent
}
}
Label
UM.Label
{
text: label
visible: viewSettings.show_legend
@ -362,8 +345,6 @@ Cura.ExpandableComponent
height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
width: parent.width
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
Rectangle
{
anchors.verticalCenter: parent.verticalCenter
@ -388,7 +369,7 @@ Cura.ExpandableComponent
width: parent.width
height: UM.Theme.getSize("layerview_row").height
Label //Minimum value.
UM.Label //Minimum value.
{
text:
{
@ -419,12 +400,9 @@ Cura.ExpandableComponent
return catalog.i18nc("@label","min")
}
anchors.left: parent.left
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
Label //Unit in the middle.
UM.Label //Unit in the middle.
{
text:
{
@ -456,10 +434,9 @@ Cura.ExpandableComponent
anchors.horizontalCenter: parent.horizontalCenter
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
}
Label //Maximum value.
UM.Label //Maximum value.
{
text: {
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
@ -490,7 +467,6 @@ Cura.ExpandableComponent
anchors.right: parent.right
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
}
}

View file

@ -1,15 +0,0 @@
# Copyright (c) 2018 Ultimaker B.V.
# Toolbox is released under the terms of the LGPLv3 or higher.
from .src import Toolbox
from .src.CloudSync.SyncOrchestrator import SyncOrchestrator
def getMetaData():
return {}
def register(app):
return {
"extension": [Toolbox.Toolbox(app), SyncOrchestrator(app)]
}

View file

@ -1,7 +0,0 @@
{
"name": "Toolbox",
"author": "Ultimaker B.V.",
"version": "1.0.1",
"api": 7,
"description": "Find, manage and install new Cura packages."
}

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M19,3H5C3.3,3,2,4.3,2,6v3c0,1.5,0.8,2.7,2,3.4V22h16v-9.6c1.2-0.7,2-2,2-3.4V6C22,4.3,20.7,3,19,3z
M10,5h4v4c0,1.1-0.9,2-2,2s-2-0.9-2-2V5z M4,9V5h4v4c0,1.1-0.9,2-2,2S4,10.1,4,9z M18,20h-4v-5h-4v5H6v-7c1.2,0,2.3-0.5,3-1.4
c0.7,0.8,1.8,1.4,3,1.4s2.3-0.5,3-1.4c0.7,0.8,1.8,1.4,3,1.4V20z M20,9c0,1.1-0.9,2-2,2s-2-0.9-2-2V5h4V9z" />
</svg>

Before

Width:  |  Height:  |  Size: 458 B

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_3" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path d="M0,512h512V0L0,512z M440.4,318.3L331.2,431.6c-1.4,1.4-2.7,2-4.8,2c-2,0-3.4-0.7-4.8-2l-53.3-57.3l-1.4-2
c-1.4-1.4-2-3.4-2-4.8c0-1.4,0.7-3.4,2-4.8l9.6-9.6c2.7-2.7,6.8-2.7,9.6,0l0.7,0.7l37.6,40.2c1.4,1.4,3.4,1.4,4.8,0l91.4-94.9h0.7
c2.7-2.7,6.8-2.7,9.6,0l9.5,9.6C443.1,311.5,443.1,315.6,440.4,318.3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 667 B

View file

@ -1,112 +0,0 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
// Main window for the Toolbox
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import UM 1.1 as UM
import "./pages"
import "./dialogs"
import "./components"
Window
{
id: base
property var selection: null
title: catalog.i18nc("@title", "Marketplace")
modality: Qt.ApplicationModal
flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint
width: UM.Theme.getSize("large_popup_dialog").width
height: UM.Theme.getSize("large_popup_dialog").height
minimumWidth: width
maximumWidth: minimumWidth
minimumHeight: height
maximumHeight: minimumHeight
color: UM.Theme.getColor("main_background")
UM.I18nCatalog
{
id: catalog
name: "cura"
}
Item
{
anchors.fill: parent
WelcomePage
{
visible: toolbox.viewPage === "welcome"
}
ToolboxHeader
{
id: header
visible: toolbox.viewPage !== "welcome"
}
Item
{
id: mainView
width: parent.width
z: parent.z - 1
anchors
{
top: header.bottom
bottom: footer.top
}
// TODO: This could be improved using viewFilter instead of viewCategory
ToolboxLoadingPage
{
id: viewLoading
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "loading"
}
ToolboxErrorPage
{
id: viewErrored
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "errored"
}
ToolboxDownloadsPage
{
id: viewDownloads
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "overview"
}
ToolboxDetailPage
{
id: viewDetail
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "detail"
}
ToolboxAuthorPage
{
id: viewAuthor
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "author"
}
ToolboxInstalledPage
{
id: installedPluginList
visible: toolbox.viewCategory === "installed"
}
}
ToolboxFooter
{
id: footer
visible: toolbox.restartRequired
height: visible ? UM.Theme.getSize("toolbox_footer").height : 0
}
Connections
{
target: toolbox
function onShowLicenseDialog() { licenseDialog.show() }
function onCloseLicenseDialog() { licenseDialog.close() }
}
ToolboxLicenseDialog
{
id: licenseDialog
}
}
}

View file

@ -1,29 +0,0 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
ButtonStyle
{
background: Rectangle
{
implicitWidth: UM.Theme.getSize("toolbox_action_button").width
implicitHeight: UM.Theme.getSize("toolbox_action_button").height
color: "transparent"
border
{
width: UM.Theme.getSize("default_lining").width
color: UM.Theme.getColor("lining")
}
}
label: Label
{
text: control.text
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}

View file

@ -1,84 +0,0 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Item
{
id: sidebar
height: parent.height
width: UM.Theme.getSize("toolbox_back_column").width
anchors
{
top: parent.top
left: parent.left
topMargin: UM.Theme.getSize("wide_margin").height
leftMargin: UM.Theme.getSize("default_margin").width
rightMargin: UM.Theme.getSize("default_margin").width
}
Button
{
id: button
text: catalog.i18nc("@action:button", "Back")
enabled: !toolbox.isDownloading
UM.RecolorImage
{
id: backArrow
anchors
{
verticalCenter: parent.verticalCenter
left: parent.left
rightMargin: UM.Theme.getSize("default_margin").width
}
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize
{
width: width
height: height
}
color: button.enabled ? (button.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")) : UM.Theme.getColor("text_inactive")
source: UM.Theme.getIcon("ChevronSingleLeft")
}
width: UM.Theme.getSize("toolbox_back_button").width
height: UM.Theme.getSize("toolbox_back_button").height
onClicked:
{
toolbox.viewPage = "overview"
if (toolbox.viewCategory == "material")
{
toolbox.filterModelByProp("authors", "package_types", "material")
}
else if (toolbox.viewCategory == "plugin")
{
toolbox.filterModelByProp("packages", "type", "plugin")
}
}
style: ButtonStyle
{
background: Rectangle
{
color: "transparent"
}
label: Label
{
id: labelStyle
text: control.text
color: control.enabled ? (control.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")) : UM.Theme.getColor("text_inactive")
font: UM.Theme.getFont("medium_bold")
horizontalAlignment: Text.AlignLeft
anchors
{
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
}
width: control.width
renderType: Text.NativeRendering
}
}
}
}

View file

@ -1,209 +0,0 @@
// Copyright (c) 2019 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 1.4
import UM 1.5 as UM
Item
{
id: base
property var packageData
property var technicalDataSheetUrl: packageData.links.technicalDataSheet
property var safetyDataSheetUrl: packageData.links.safetyDataSheet
property var printingGuidelinesUrl: packageData.links.printingGuidelines
property var materialWebsiteUrl: packageData.links.website
height: childrenRect.height
onVisibleChanged: packageData.type === "material" && (compatibilityItem.visible || dataSheetLinks.visible)
Column
{
id: compatibilityItem
visible: packageData.has_configs
width: parent.width
// This is a bit of a hack, but the whole QML is pretty messy right now. This needs a big overhaul.
height: visible ? heading.height + table.height: 0
Label
{
id: heading
width: parent.width
text: catalog.i18nc("@label", "Compatibility")
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
}
TableView
{
id: table
width: parent.width
frameVisible: false
// Workaround for scroll issues (QTBUG-49652)
flickableItem.interactive: false
Component.onCompleted:
{
for (var i = 0; i < flickableItem.children.length; ++i)
{
flickableItem.children[i].enabled = false
}
}
selectionMode: 0
model: packageData.supported_configs
headerDelegate: Rectangle
{
color: UM.Theme.getColor("main_background")
height: UM.Theme.getSize("toolbox_chart_row").height
Label
{
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
text: styleData.value || ""
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default_bold")
renderType: Text.NativeRendering
}
Rectangle
{
anchors.bottom: parent.bottom
height: UM.Theme.getSize("default_lining").height
width: parent.width
color: "black"
}
}
rowDelegate: Item
{
height: UM.Theme.getSize("toolbox_chart_row").height
Label
{
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
text: styleData.value || ""
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}
itemDelegate: Item
{
height: UM.Theme.getSize("toolbox_chart_row").height
Label
{
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
text: styleData.value || ""
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}
Component
{
id: columnTextDelegate
Label
{
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
text: styleData.value || ""
elide: Text.ElideRight
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}
TableViewColumn
{
role: "machine"
title: catalog.i18nc("@label:table_header", "Machine")
width: Math.floor(table.width * 0.25)
delegate: columnTextDelegate
}
TableViewColumn
{
role: "print_core"
title: "Print Core" //This term should not be translated.
width: Math.floor(table.width * 0.2)
}
TableViewColumn
{
role: "build_plate"
title: catalog.i18nc("@label:table_header", "Build Plate")
width: Math.floor(table.width * 0.225)
}
TableViewColumn
{
role: "support_material"
title: catalog.i18nc("@label:table_header", "Support")
width: Math.floor(table.width * 0.225)
}
TableViewColumn
{
role: "quality"
title: catalog.i18nc("@label:table_header", "Quality")
width: Math.floor(table.width * 0.1)
}
}
}
Label
{
id: dataSheetLinks
anchors.top: compatibilityItem.bottom
anchors.topMargin: UM.Theme.getSize("narrow_margin").height
visible: base.technicalDataSheetUrl !== undefined ||
base.safetyDataSheetUrl !== undefined ||
base.printingGuidelinesUrl !== undefined ||
base.materialWebsiteUrl !== undefined
text:
{
var result = ""
if (base.technicalDataSheetUrl !== undefined)
{
var tds_name = catalog.i18nc("@action:label", "Technical Data Sheet")
result += "<a href='%1'>%2</a>".arg(base.technicalDataSheetUrl).arg(tds_name)
}
if (base.safetyDataSheetUrl !== undefined)
{
if (result.length > 0)
{
result += "<br/>"
}
var sds_name = catalog.i18nc("@action:label", "Safety Data Sheet")
result += "<a href='%1'>%2</a>".arg(base.safetyDataSheetUrl).arg(sds_name)
}
if (base.printingGuidelinesUrl !== undefined)
{
if (result.length > 0)
{
result += "<br/>"
}
var pg_name = catalog.i18nc("@action:label", "Printing Guidelines")
result += "<a href='%1'>%2</a>".arg(base.printingGuidelinesUrl).arg(pg_name)
}
if (base.materialWebsiteUrl !== undefined)
{
if (result.length > 0)
{
result += "<br/>"
}
var pg_name = catalog.i18nc("@action:label", "Website")
result += "<a href='%1'>%2</a>".arg(base.materialWebsiteUrl).arg(pg_name)
}
return result
}
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: UM.UrlUtil.openUrl(link, ["http", "https"])
renderType: Text.NativeRendering
}
}

View file

@ -1,43 +0,0 @@
// Copyright (c) 2019 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import UM 1.1 as UM
Item
{
id: detailList
ScrollView
{
clip: true
anchors.fill: detailList
Column
{
anchors
{
right: parent.right
topMargin: UM.Theme.getSize("wide_margin").height
bottomMargin: UM.Theme.getSize("wide_margin").height
top: parent.top
}
height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height
spacing: UM.Theme.getSize("default_margin").height
Repeater
{
model: toolbox.packagesModel
delegate: Loader
{
// FIXME: When using asynchronous loading, on Mac and Windows, the tile may fail to load complete,
// leaving an empty space below the title part. We turn it off for now to make it work on Mac and
// Windows.
// Can be related to this QT bug: https://bugreports.qt.io/browse/QTBUG-50992
asynchronous: false
source: "ToolboxDetailTile.qml"
}
}
}
}
}

View file

@ -1,74 +0,0 @@
// Copyright (c) 2019 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import UM 1.1 as UM
Item
{
id: tile
width: detailList.width - UM.Theme.getSize("wide_margin").width
height: normalData.height + 2 * UM.Theme.getSize("wide_margin").height
Column
{
id: normalData
anchors
{
top: parent.top
left: parent.left
right: controls.left
rightMargin: UM.Theme.getSize("wide_margin").width
}
Label
{
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
text: model.name
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("medium_bold")
renderType: Text.NativeRendering
}
Label
{
width: parent.width
text: model.description
maximumLineCount: 25
elide: Text.ElideRight
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
ToolboxCompatibilityChart
{
width: parent.width
packageData: model
}
}
ToolboxDetailTileActions
{
id: controls
anchors.right: tile.right
anchors.top: tile.top
width: childrenRect.width
height: childrenRect.height
packageData: model
}
Rectangle
{
color: UM.Theme.getColor("lining")
width: tile.width
height: UM.Theme.getSize("default_lining").height
anchors.top: normalData.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height + UM.Theme.getSize("wide_margin").height //Normal margin for spacing after chart, wide margin between items.
}
}

View file

@ -1,121 +0,0 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.5 as UM
import Cura 1.1 as Cura
Column
{
property bool installed: toolbox.isInstalled(model.id)
property bool canUpdate: CuraApplication.getPackageManager().packagesWithUpdate.indexOf(model.id) != -1
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
property var packageData
width: UM.Theme.getSize("toolbox_action_button").width
spacing: UM.Theme.getSize("narrow_margin").height
Item
{
width: installButton.width
height: installButton.height
ToolboxProgressButton
{
id: installButton
active: toolbox.isDownloading && toolbox.activePackage == model
onReadyAction:
{
toolbox.activePackage = model
toolbox.startDownload(model.download_url)
}
onActiveAction: toolbox.cancelDownload()
// Don't allow installing while another download is running
enabled: installed || (!(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired)
opacity: enabled ? 1.0 : 0.5
visible: !updateButton.visible && !installed // Don't show when the update button is visible
}
Cura.SecondaryButton
{
id: installedButton
visible: installed
onClicked: toolbox.viewCategory = "installed"
text: catalog.i18nc("@action:button", "Installed")
fixedWidthMode: true
width: installButton.width
height: installButton.height
}
}
Label
{
wrapMode: Text.WordWrap
text: catalog.i18nc("@label:The string between <a href=> and </a> is the highlighted link", "<a href='%1'>Log in</a> is required to install or update")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
visible: loginRequired
width: installButton.width
renderType: Text.NativeRendering
MouseArea
{
anchors.fill: parent
onClicked: Cura.API.account.login()
}
}
Label
{
property var whereToBuyUrl:
{
var pg_name = "whereToBuy"
return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined
}
renderType: Text.NativeRendering
text: catalog.i18nc("@label:The string between <a href=> and </a> is the highlighted link", "<a href='%1'>Buy material spools</a>")
linkColor: UM.Theme.getColor("text_link")
visible: whereToBuyUrl != undefined
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
MouseArea
{
anchors.fill: parent
onClicked: UM.UrlUtil.openUrl(parent.whereToBuyUrl, ["https", "http"])
}
}
ToolboxProgressButton
{
id: updateButton
active: toolbox.isDownloading && toolbox.activePackage == model
readyLabel: catalog.i18nc("@action:button", "Update")
activeLabel: catalog.i18nc("@action:button", "Updating")
completeLabel: catalog.i18nc("@action:button", "Updated")
onReadyAction:
{
toolbox.activePackage = model
toolbox.update(model.id)
}
onActiveAction: toolbox.cancelDownload()
// Don't allow installing while another download is running
enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired
opacity: enabled ? 1.0 : 0.5
visible: canUpdate
}
Connections
{
target: toolbox
function onInstallChanged() { installed = toolbox.isInstalled(model.id) }
function onFilterChanged()
{
installed = toolbox.isInstalled(model.id)
}
}
}

View file

@ -1,45 +0,0 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import UM 1.1 as UM
Column
{
property var heading: ""
property var model
id: gridArea
height: childrenRect.height + 2 * padding
width: parent.width
spacing: UM.Theme.getSize("default_margin").height
padding: UM.Theme.getSize("wide_margin").height
Label
{
id: heading
text: gridArea.heading
width: parent.width
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("large")
renderType: Text.NativeRendering
}
Grid
{
id: grid
width: parent.width - 2 * parent.padding
columns: 2
columnSpacing: UM.Theme.getSize("default_margin").height
rowSpacing: UM.Theme.getSize("default_margin").width
Repeater
{
model: gridArea.model
delegate: Loader
{
asynchronous: true
width: Math.round((grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns)
height: UM.Theme.getSize("toolbox_thumbnail_small").height
source: "ToolboxDownloadsGridTile.qml"
}
}
}
}

View file

@ -1,125 +0,0 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
import UM 1.1 as UM
import Cura 1.1 as Cura
Item
{
id: toolboxDownloadsGridTile
property int packageCount: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getTotalNumberOfMaterialPackagesByAuthor(model.id) : 1
property int installedPackages: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0)
height: childrenRect.height
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
MouseArea
{
anchors.fill: parent
hoverEnabled: true
onEntered: thumbnail.border.color = UM.Theme.getColor("primary")
onExited: thumbnail.border.color = UM.Theme.getColor("lining")
onClicked:
{
base.selection = model
switch(toolbox.viewCategory)
{
case "material":
// If model has a type, it must be a package
if (model.type !== undefined)
{
toolbox.viewPage = "detail"
toolbox.filterModelByProp("packages", "id", model.id)
}
else
{
toolbox.viewPage = "author"
toolbox.setFilters("packages", {
"author_id": model.id,
"type": "material"
})
}
break
default:
toolbox.viewPage = "detail"
toolbox.filterModelByProp("packages", "id", model.id)
break
}
}
}
Rectangle
{
id: thumbnail
width: UM.Theme.getSize("toolbox_thumbnail_small").width
height: UM.Theme.getSize("toolbox_thumbnail_small").height
color: UM.Theme.getColor("main_background")
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
Image
{
anchors.centerIn: parent
width: UM.Theme.getSize("toolbox_thumbnail_small").width - UM.Theme.getSize("wide_margin").width
height: UM.Theme.getSize("toolbox_thumbnail_small").height - UM.Theme.getSize("wide_margin").width
sourceSize.width: width
sourceSize.height: height
fillMode: Image.PreserveAspectFit
source: model.icon_url || "../../images/placeholder.svg"
mipmap: true
}
UM.RecolorImage
{
width: (parent.width * 0.4) | 0
height: (parent.height * 0.4) | 0
anchors
{
bottom: parent.bottom
right: parent.right
}
sourceSize.height: height
visible: installedPackages != 0
color: (installedPackages >= packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
source: "../../images/installed_check.svg"
}
}
Item
{
anchors
{
left: thumbnail.right
leftMargin: Math.floor(UM.Theme.getSize("narrow_margin").width)
right: parent.right
top: parent.top
bottom: parent.bottom
}
Label
{
id: name
text: model.name
width: parent.width
elide: Text.ElideRight
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default_bold")
}
Label
{
id: info
text: model.description
elide: Text.ElideRight
width: parent.width
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
anchors.top: name.bottom
anchors.bottom: parent.bottom
verticalAlignment: Text.AlignVCenter
maximumLineCount: 2
}
}
}

Some files were not shown because too many files have changed in this diff Show more