Merge branch 'Ultimaker:master' into master
|
@ -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
|
@ -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.
|
|
@ -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
|
|
@ -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()
|
|
@ -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"]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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]))
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
BIN
docs/resources/PerObjectStack.png
Normal file
After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 2.3 KiB |
BIN
icons/cura.icns
BIN
icons/cura.ico
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 35 KiB |
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
1
plugins/CuraDrive/src/qml/images/backup.svg
Normal file
After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 21 KiB |
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
203
plugins/DigitalLibrary/resources/qml/Table.qml
Normal 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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"):
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
12
plugins/Marketplace/Constants.py
Normal 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"
|
126
plugins/Marketplace/LocalPackageList.py
Normal 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
|
117
plugins/Marketplace/Marketplace.py
Normal 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
|
305
plugins/Marketplace/PackageList.py
Normal 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)
|
387
plugins/Marketplace/PackageModel.py
Normal 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
|
148
plugins/Marketplace/RemotePackageList.py
Normal 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)
|
17
plugins/Marketplace/__init__.py
Normal 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() }
|
8
plugins/Marketplace/plugin.json
Normal 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"
|
||||
}
|
3
plugins/Marketplace/resources/images/Plugin.svg
Normal 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 |
3
plugins/Marketplace/resources/images/Spool.svg
Normal 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 |
Before Width: | Height: | Size: 184 B After Width: | Height: | Size: 184 B |
91
plugins/Marketplace/resources/qml/LicenseDialog.qml
Normal 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)
|
||||
}
|
114
plugins/Marketplace/resources/qml/ManageButton.qml
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
49
plugins/Marketplace/resources/qml/ManagePackagesButton.qml
Normal 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
|
||||
}
|
||||
}
|
26
plugins/Marketplace/resources/qml/ManagedPackages.qml
Normal 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
|
||||
}
|
283
plugins/Marketplace/resources/qml/Marketplace.qml
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
plugins/Marketplace/resources/qml/Materials.qml
Normal 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
|
||||
}
|
116
plugins/Marketplace/resources/qml/OnboardBanner.qml
Normal 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)
|
||||
}
|
||||
}
|
53
plugins/Marketplace/resources/qml/PackageCard.qml
Normal 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")
|
||||
}
|
||||
}
|
248
plugins/Marketplace/resources/qml/PackageCardHeader.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
96
plugins/Marketplace/resources/qml/PackageDetails.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
301
plugins/Marketplace/resources/qml/PackagePage.qml
Normal 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")
|
||||
}
|
||||
}
|
33
plugins/Marketplace/resources/qml/PackageTypeTab.qml
Normal 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
|
||||
}
|
||||
}
|
246
plugins/Marketplace/resources/qml/Packages.qml
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
plugins/Marketplace/resources/qml/Plugins.qml
Normal 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
|
||||
}
|
45
plugins/Marketplace/resources/qml/VerifiedIcon.qml
Normal 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 )
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"name": "Toolbox",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"api": 7,
|
||||
"description": "Find, manage and install new Cura packages."
|
||||
}
|
|
@ -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 |
|
@ -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 |
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|