Merge branch 'master' into Rigid3D

This commit is contained in:
Mehmet Sutaş 2022-06-28 16:42:39 +03:00
commit b7a13e6abc
4879 changed files with 123531 additions and 108540 deletions

View file

@ -1,6 +1,6 @@
name: Bug Report
description: Create a report to help us fix issues.
labels: "Type: Bug"
labels: ["Type: Bug", "Status: Triage"]
body:
- type: markdown
attributes:
@ -14,7 +14,7 @@ body:
attributes:
label: Application Version
description: The version of Cura this issue occurs with.
placeholder: 4.9.0
placeholder: 5.0.0
validations:
required: true
- type: input

View file

@ -1,13 +0,0 @@
# Configuration for probot-no-response - https://github.com/probot/no-response
# Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 14
# Label requiring a response
responseRequiredLabel: 'Status: Needs Info'
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. With only the
information that is currently in the issue, we don't have enough information
to take action. Please reach out if you have or find the answers we need so
that we can investigate further.

31
.github/workflows/no-response.yml vendored Normal file
View file

@ -0,0 +1,31 @@
name: No Response
# Both `issue_comment` and `scheduled` event types are required for this Action
# to work properly.
on:
issue_comment:
types: [created]
schedule:
# Schedule for ten minutes after the hour, every hour
- cron: '10 * * * *'
# By specifying the access of one of the scopes, all of those that are not
# specified are set to 'none'.
permissions:
issues: write
jobs:
noResponse:
runs-on: ubuntu-latest
steps:
- uses: lee-dohm/no-response@v0.5.0
with:
token: ${{ github.token }}
daysUntilClose: 14
responseRequiredLabel: 'Status: Needs Info'
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. With only the
information that is currently in the issue, we don't have enough information
to take action. Please reach out if you have or find the answers we need so
that we can investigate further.

2
.gitignore vendored
View file

@ -38,6 +38,7 @@ cura.desktop
.settings
#Externally located plug-ins commonly installed by our devs.
plugins/BarbarianPlugin
plugins/cura-big-flame-graph
plugins/cura-camera-position
plugins/cura-god-mode-plugin
@ -64,6 +65,7 @@ plugins/CuraRemoteSupport
plugins/ModelCutter
plugins/PrintProfileCreator
plugins/MultiPrintPlugin
plugins/CuraOrientationPlugin
#Build stuff
CMakeCache.txt

View file

@ -1,11 +1,30 @@
# YAML 1.2
---
authors:
cff-version: "1.1.0"
date-released: 2021-06-28
license: "LGPL-3.0"
message: "If you use this software, please cite it using these metadata."
repository-code: "https://github.com/ultimaker/cura/"
title: "Ultimaker Cura"
version: "4.12.0"
cff-version: 1.2.0
type: software
message: >-
If you use this software, please cite it using the
metadata from this file.
title: Ultimaker Cura
abstract: >-
A state-of-the-art slicer application built on top
of the Uranium framework.
authors:
- name: Ultimaker B.V.
contact:
- email: info@ultimaker.com
name: "Ultimaker B.V."
url: 'https://ultimaker.com/software/ultimaker-cura'
repository-code: 'https://github.com/Ultimaker/Cura'
license: LGPL-3.0
license-url: "https://github.com/Ultimaker/Cura/blob/main/LICENSE"
version: 5.0.0
date-released: '2022-05-17'
keywords:
- Ultimaker
- Cura
- Uranium
- Arachne
- 3DPrinting
- Slicer
...

View file

@ -1,5 +1,8 @@
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
project(cura)
cmake_minimum_required(VERSION 3.6)
cmake_minimum_required(VERSION 3.18)
include(GNUInstallDirs)
@ -8,9 +11,6 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
set(URANIUM_DIR "${CMAKE_SOURCE_DIR}/../Uranium" CACHE PATH "The location of the Uranium repository")
set(URANIUM_SCRIPTS_DIR "${URANIUM_DIR}/scripts" CACHE PATH "The location of the scripts directory of the Uranium repository")
# Tests
include(CuraTests)
option(CURA_DEBUGMODE "Enable debug dialog and other debug features" OFF)
if(CURA_DEBUGMODE)
set(_cura_debugmode "ON")
@ -32,17 +32,25 @@ configure_file(${CMAKE_SOURCE_DIR}/com.ultimaker.cura.desktop.in ${CMAKE_BINARY_
configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY)
if(NOT DEFINED Python_VERSION)
set(Python_VERSION
3.10
CACHE STRING "Python Version" FORCE)
message(STATUS "Setting Python version to ${Python_VERSION}. Set Python_VERSION if you want to compile against an other version.")
endif()
if(APPLE)
set(Python_FIND_FRAMEWORK NEVER)
endif()
find_package(Python ${Python_VERSION} EXACT REQUIRED COMPONENTS Interpreter)
message(STATUS "Linking and building ${project_name} against Python ${Python_VERSION}")
if(NOT DEFINED Python_SITELIB_LOCAL)
set(Python_SITELIB_LOCAL
"${Python_SITELIB}"
CACHE PATH "Local alternative site-package location to install Cura" FORCE)
endif()
# FIXME: The new FindPython3 finds the system's Python3.6 rather than the Python3.5 that we built for Cura's environment.
# So we're using the old method here, with FindPythonInterp for now.
find_package(PythonInterp 3 REQUIRED)
set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE})
set(Python3_VERSION ${PYTHON_VERSION_STRING})
set(Python3_VERSION_MAJOR ${PYTHON_VERSION_MAJOR})
set(Python3_VERSION_MINOR ${PYTHON_VERSION_MINOR})
set(Python3_VERSION_PATCH ${PYTHON_VERSION_PATCH})
# Tests
include(CuraTests)
if(NOT ${URANIUM_DIR} STREQUAL "")
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${URANIUM_DIR}/cmake")
@ -58,30 +66,15 @@ if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "")
endif()
endif()
install(DIRECTORY resources
DESTINATION ${CMAKE_INSTALL_DATADIR}/cura)
install(DIRECTORY resources DESTINATION ${CMAKE_INSTALL_DATADIR}/cura)
include(CuraPluginInstall)
install(FILES cura_app.py DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY cura DESTINATION "${Python_SITELIB_LOCAL}")
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py DESTINATION "${Python_SITELIB_LOCAL}/cura/")
if(NOT APPLE AND NOT WIN32)
install(FILES cura_app.py
DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
RENAME cura)
if(EXISTS /etc/debian_version)
install(DIRECTORY cura
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}/dist-packages
FILES_MATCHING PATTERN *.py)
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}/dist-packages/cura)
else()
install(DIRECTORY cura
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages
FILES_MATCHING PATTERN *.py)
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages/cura)
endif()
install(FILES ${CMAKE_BINARY_DIR}/com.ultimaker.cura.desktop
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES ${CMAKE_SOURCE_DIR}/resources/images/cura-icon.png
@ -91,13 +84,4 @@ if(NOT APPLE AND NOT WIN32)
install(FILES cura.sharedmimeinfo
DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages/
RENAME cura.xml )
else()
install(FILES cura_app.py
DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY cura
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages
FILES_MATCHING PATTERN *.py)
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages/cura)
endif()

5
SECURITY.md Normal file
View file

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

View file

@ -1,4 +1,4 @@
# Copyright (c) 2019 Ultimaker B.V.
# Copyright (c) 2022 Ultimaker B.V.
# CuraPluginInstall.cmake is released under the terms of the LGPLv3 or higher.
#
@ -11,19 +11,6 @@
option(PRINT_PLUGIN_LIST "Should the list of plugins that are installed be printed?" ON)
# FIXME: Remove the code for CMake <3.12 once we have switched over completely.
# FindPython3 is a new module since CMake 3.12. It deprecates FindPythonInterp and FindPythonLibs. The FindPython3
# module is copied from the CMake repository here so in CMake <3.12 we can still use it.
if(${CMAKE_VERSION} VERSION_LESS 3.12)
# Use FindPythonInterp and FindPythonLibs for CMake <3.12
find_package(PythonInterp 3 REQUIRED)
set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE})
else()
# Use FindPython3 for CMake >=3.12
find_package(Python3 REQUIRED COMPONENTS Interpreter)
endif()
# Options or configuration variables
set(CURA_NO_INSTALL_PLUGINS "" CACHE STRING "A list of plugins that should not be installed, separated with ';' or ','.")
@ -97,7 +84,7 @@ foreach(_plugin_json_path ${_plugin_json_list})
if(${PRINT_PLUGIN_LIST})
message(STATUS "[-] PLUGIN TO REMOVE : ${_rel_plugin_dir}")
endif()
execute_process(COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/mod_bundled_packages_json.py
execute_process(COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/mod_bundled_packages_json.py
-d ${CMAKE_CURRENT_SOURCE_DIR}/resources/bundled_packages
${_plugin_dir_name}
RESULT_VARIABLE _mod_json_result)

View file

@ -1,15 +1,9 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
include(CTest)
include(CMakeParseArguments)
# FIXME: The new FindPython3 finds the system's Python3.6 rather than the Python3.5 that we built for Cura's environment.
# So we're using the old method here, with FindPythonInterp for now.
find_package(PythonInterp 3 REQUIRED)
set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE})
add_custom_target(test-verbose COMMAND ${CMAKE_CTEST_COMMAND} --verbose)
function(cura_add_test)
@ -40,7 +34,7 @@ function(cura_add_test)
if (NOT ${test_exists})
add_test(
NAME ${_NAME}
COMMAND ${Python3_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
COMMAND ${Python_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
)
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
@ -53,14 +47,14 @@ endfunction()
#Add code style test.
add_test(
NAME "code-style"
COMMAND ${Python3_EXECUTABLE} run_mypy.py
COMMAND ${Python_EXECUTABLE} run_mypy.py
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
#Add test for import statements which are not compatible with all builds
add_test(
NAME "invalid-imports"
COMMAND ${Python3_EXECUTABLE} scripts/check_invalid_imports.py
COMMAND ${Python_EXECUTABLE} scripts/check_invalid_imports.py
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
@ -78,6 +72,6 @@ endforeach()
#Add test for whether the shortcut alt-keys are unique in every translation.
add_test(
NAME "shortcut-keys"
COMMAND ${Python3_EXECUTABLE} scripts/check_shortcut_keys.py
COMMAND ${Python_EXECUTABLE} scripts/check_shortcut_keys.py
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)

View file

@ -25,9 +25,10 @@
</description>
<screenshots>
<screenshot type="default">
<image>https://raw.githubusercontent.com/Ultimaker/Cura/master/screenshot.png</image>
<image>https://raw.githubusercontent.com/Ultimaker/Cura/main/cura-logo.PNG</image>
</screenshot>
</screenshots>
<url type="homepage">https://ultimaker.com/software/ultimaker-cura?utm_source=cura&amp;utm_medium=software&amp;utm_campaign=cura-update-linux</url>
<translation type="gettext">Cura</translation>
<content_rating type="oars-1.1" />
</component>

View file

@ -1,8 +1,8 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import enum
from datetime import datetime
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer, Q_ENUMS
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer, pyqtEnum
from typing import Any, Optional, Dict, TYPE_CHECKING, Callable
from UM.Logger import Logger
@ -18,7 +18,7 @@ if TYPE_CHECKING:
i18n_catalog = i18nCatalog("cura")
class SyncState:
class SyncState(enum.IntEnum):
"""QML: Cura.AccountSyncState"""
SYNCING = 0
SUCCESS = 1
@ -41,7 +41,7 @@ class Account(QObject):
# The interval in which sync services are automatically triggered
SYNC_INTERVAL = 60.0 # seconds
Q_ENUMS(SyncState)
pyqtEnum(SyncState)
loginStateChanged = pyqtSignal(bool)
"""Signal emitted when user logged in or out"""
@ -269,10 +269,10 @@ class Account(QObject):
return self._authorization_service.getAccessToken()
@pyqtProperty("QVariantMap", notify = userProfileChanged)
def userProfile(self) -> Optional[Dict[str, Optional[str]]]:
def userProfile(self) -> Dict[str, Optional[str]]:
"""None if no user is logged in otherwise the logged in user as a dict containing containing user_id, username and profile_image_url """
if not self._user_profile:
return None
return {}
return self._user_profile.__dict__
@pyqtProperty(str, notify=lastSyncDateTimeChanged)

View file

@ -1,6 +1,6 @@
from typing import Optional
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty
from UM.TaskManagement.HttpRequestManager import HttpRequestManager

View file

@ -2,7 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, TYPE_CHECKING
from PyQt5.QtCore import QObject, pyqtProperty
from PyQt6.QtCore import QObject, pyqtProperty
from cura.API.Backups import Backups
from cura.API.ConnectionStatus import ConnectionStatus
@ -34,12 +34,13 @@ class CuraAPI(QObject):
raise RuntimeError("Tried to create singleton '{class_name}' more than once.".format(class_name = CuraAPI.__name__))
if application is None:
raise RuntimeError("Upon first time creation, the application must be set.")
cls.__instance = super(CuraAPI, cls).__new__(cls)
instance = super(CuraAPI, cls).__new__(cls)
cls._application = application
return cls.__instance
return instance
def __init__(self, application: Optional["CuraApplication"] = None) -> None:
super().__init__(parent = CuraAPI._application)
CuraAPI.__instance = self
self._account = Account(self._application)

View file

@ -1,4 +1,4 @@
# Copyright (c) 2021 Ultimaker B.V.
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
# ---------
@ -13,7 +13,7 @@ DEFAULT_CURA_DEBUG_MODE = False
# Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for
# example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the
# CuraVersion.py.in template.
CuraSDKVersion = "7.9.0"
CuraSDKVersion = "8.0.0"
try:
from cura.CuraVersion import CuraAppName # type: ignore

View file

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

View file

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

View file

@ -1,7 +1,7 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QTimer
from PyQt6.QtCore import QTimer
from typing import Any, TYPE_CHECKING
from UM.Logger import Logger

View file

@ -31,7 +31,7 @@ from cura.Settings.GlobalStack import GlobalStack
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Settings.ExtruderManager import ExtruderManager
from PyQt5.QtCore import QTimer
from PyQt6.QtCore import QTimer
if TYPE_CHECKING:
@ -1113,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
@ -1214,7 +1215,7 @@ 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"]
_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"]

View file

@ -2,8 +2,8 @@
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QVariantAnimation, QEasingCurve
from PyQt5.QtGui import QVector3D
from PyQt6.QtCore import QVariantAnimation, QEasingCurve
from PyQt6.QtGui import QVector3D
from UM.Math.Vector import Vector
@ -13,7 +13,7 @@ class CameraAnimation(QVariantAnimation):
super().__init__(parent)
self._camera_tool = None
self.setDuration(300)
self.setEasingCurve(QEasingCurve.OutQuad)
self.setEasingCurve(QEasingCurve.Type.OutQuad)
def setCameraTool(self, camera_tool):
self._camera_tool = camera_tool

View file

@ -20,9 +20,9 @@ try:
except ImportError:
with_sentry_sdk = False
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QUrl
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
from PyQt5.QtGui import QDesktopServices
from PyQt6.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QUrl
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
from PyQt6.QtGui import QDesktopServices
from UM.Application import Application
from UM.Logger import Logger
@ -136,8 +136,8 @@ class CrashHandler:
# "backup and start clean" and "close" buttons
buttons = QDialogButtonBox()
buttons.addButton(QDialogButtonBox.Close)
buttons.addButton(catalog.i18nc("@action:button", "Backup and Reset Configuration"), QDialogButtonBox.AcceptRole)
buttons.addButton(QDialogButtonBox.StandardButton.Close)
buttons.addButton(catalog.i18nc("@action:button", "Backup and Reset Configuration"), QDialogButtonBox.ButtonRole.AcceptRole)
buttons.rejected.connect(self._closeEarlyCrashDialog)
buttons.accepted.connect(self._backupAndStartClean)
@ -161,7 +161,7 @@ class CrashHandler:
QDesktopServices.openUrl(QUrl.fromLocalFile( path ))
def _showDetailedReport(self):
self.dialog.exec_()
self.dialog.exec()
def _createDialog(self):
"""Creates a modal dialog."""
@ -261,7 +261,7 @@ class CrashHandler:
opengl_instance = OpenGL.getInstance()
if not opengl_instance:
self.data["opengl"] = {"version": "n/a", "vendor": "n/a", "type": "n/a"}
return catalog.i18nc("@label", "Not yet initialized<br/>")
return catalog.i18nc("@label", "Not yet initialized") + "<br />"
info = "<ul>"
info += catalog.i18nc("@label OpenGL version", "<li>OpenGL Version: {version}</li>").format(version = opengl_instance.getOpenGLVersion())
@ -291,6 +291,7 @@ class CrashHandler:
if with_sentry_sdk:
with configure_scope() as scope:
scope.set_tag("opengl_version", opengl_instance.getOpenGLVersion())
scope.set_tag("opengl_version_short", opengl_instance.getOpenGLVersionShort())
scope.set_tag("gpu_vendor", opengl_instance.getGPUVendorName())
scope.set_tag("gpu_type", opengl_instance.getGPUType())
scope.set_tag("active_machine", active_machine_definition_id)
@ -409,12 +410,12 @@ class CrashHandler:
def _buttonsWidget(self):
buttons = QDialogButtonBox()
buttons.addButton(QDialogButtonBox.Close)
buttons.addButton(QDialogButtonBox.StandardButton.Close)
# Like above, this will be served as a separate detailed report dialog if the application has not yet been
# fully loaded. In this case, "send report" will be a check box in the early crash dialog, so there is no
# need for this extra button.
if self.has_started:
buttons.addButton(catalog.i18nc("@action:button", "Send report"), QDialogButtonBox.AcceptRole)
buttons.addButton(catalog.i18nc("@action:button", "Send report"), QDialogButtonBox.ButtonRole.AcceptRole)
buttons.accepted.connect(self._sendCrashReport)
buttons.rejected.connect(self.dialog.close)
@ -456,5 +457,5 @@ class CrashHandler:
def _show(self):
# When the exception is in the skip_exception_types list, the dialog is not created, so we don't need to show it
if self.dialog:
self.dialog.exec_()
self.dialog.exec()
os._exit(1)

View file

@ -1,8 +1,8 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtGui import QDesktopServices
from PyQt6.QtCore import QObject, QUrl
from PyQt6.QtGui import QDesktopServices
from typing import List, cast
from UM.Event import CallFunctionEvent

View file

@ -1,6 +1,6 @@
# Copyright (c) 2021 Ultimaker B.V.
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import enum
import os
import sys
import tempfile
@ -8,10 +8,10 @@ import time
from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict
import numpy
from PyQt5.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
from PyQt5.QtWidgets import QMessageBox
from PyQt6.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, pyqtEnum, QCoreApplication
from PyQt6.QtGui import QColor, QIcon
from PyQt6.QtQml import qmlRegisterUncreatableType, qmlRegisterUncreatableMetaObject, qmlRegisterSingletonType, qmlRegisterType
from PyQt6.QtWidgets import QMessageBox
import UM.Util
import cura.Settings.cura_empty_instance_containers
@ -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
@ -117,6 +115,8 @@ from . import CuraActions
from . import PlatformPhysics
from . import PrintJobPreviewImageProvider
from .AutoSave import AutoSave
from .Machines.Models.ActiveIntentQualitiesModel import ActiveIntentQualitiesModel
from .Machines.Models.IntentSelectionModel import IntentSelectionModel
from .SingleInstance import SingleInstance
if TYPE_CHECKING:
@ -124,16 +124,15 @@ if TYPE_CHECKING:
numpy.seterr(all = "ignore")
class CuraApplication(QtApplication):
# SettingVersion represents the set of settings available in the machine/extruder definitions.
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
# changes of the settings.
SettingVersion = 19
SettingVersion = 20
Created = False
class ResourceTypes:
class ResourceTypes(enum.IntEnum):
QmlFiles = Resources.UserType + 1
Firmware = Resources.UserType + 2
QualityInstanceContainer = Resources.UserType + 3
@ -147,7 +146,7 @@ class CuraApplication(QtApplication):
SettingVisibilityPreset = Resources.UserType + 11
IntentInstanceContainer = Resources.UserType + 12
Q_ENUMS(ResourceTypes)
pyqtEnum(ResourceTypes)
def __init__(self, *args, **kwargs):
super().__init__(name = ApplicationMetadata.CuraAppName,
@ -260,6 +259,7 @@ class CuraApplication(QtApplication):
from UM.CentralFileStorage import CentralFileStorage
CentralFileStorage.setIsEnterprise(ApplicationMetadata.IsEnterpriseVersion)
Resources.setIsEnterprise(ApplicationMetadata.IsEnterpriseVersion)
@pyqtProperty(str, constant=True)
def ultimakerCloudApiRootUrl(self) -> str:
@ -318,7 +318,7 @@ class CuraApplication(QtApplication):
def initialize(self) -> None:
self.__addExpectedResourceDirsAndSearchPaths() # Must be added before init of super
super().initialize()
super().initialize(ApplicationMetadata.IsEnterpriseVersion)
self._preferences.addPreference("cura/single_instance", False)
self._use_single_instance = self._preferences.getValue("cura/single_instance") or self._cli_args.single_instance
@ -351,12 +351,12 @@ class CuraApplication(QtApplication):
Resources.addExpectedDirNameInData(dir_name)
app_root = os.path.abspath(os.path.join(os.path.dirname(sys.executable)))
Resources.addSearchPath(os.path.join(app_root, "share", "cura", "resources"))
Resources.addSecureSearchPath(os.path.join(app_root, "share", "cura", "resources"))
Resources.addSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources"))
Resources.addSecureSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources"))
if not hasattr(sys, "frozen"):
resource_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")
Resources.addSearchPath(resource_path)
Resources.addSecureSearchPath(resource_path)
@classmethod
def _initializeSettingDefinitions(cls):
@ -382,11 +382,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).
@ -623,11 +624,14 @@ class CuraApplication(QtApplication):
# the QML code then gets `null` as the global stack and can deal with that as it deems fit.
self.getMachineManager().setActiveMachine(None)
QtApplication.getInstance().closeAllWindows()
main_window = self.getMainWindow()
if main_window is not None:
main_window.close()
else:
self.exit(0)
QtApplication.closeAllWindows()
QCoreApplication.quit()
# This function first performs all upon-exit checks such as USB printing that is in progress.
# Use this to close the application.
@ -679,6 +683,22 @@ 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.StandardButton.Ok,
icon = QMessageBox.Icon.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:
@ -818,9 +838,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)
@ -862,7 +879,7 @@ class CuraApplication(QtApplication):
self._auto_save = AutoSave(self)
self._auto_save.initialize()
self.exec_()
self.exec()
def __setUpSingleInstanceServer(self):
if self._use_single_instance:
@ -928,6 +945,7 @@ class CuraApplication(QtApplication):
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing engine..."))
self.initializeEngine()
self.getTheme().setCheckIfTrusted(ApplicationMetadata.IsEnterpriseVersion)
# Initialize UI state
controller.setActiveStage("PrepareStage")
@ -1079,12 +1097,18 @@ class CuraApplication(QtApplication):
def event(self, event):
"""Handle Qt events"""
if event.type() == QEvent.FileOpen:
if event.type() == QEvent.Type.FileOpen:
if self._plugins_loaded:
self._openFile(event.file())
else:
self._open_file_queue.append(event.file())
if int(event.type()) == 20: # 'QEvent.Type.Quit' enum isn't there, even though it should be according to docs.
# Once we're at this point, everything should have been flushed already (past OnExitCallbackManager).
# It's more difficult to call sys.exit(0): That requires that it happens as the result of a pyqtSignal-emit.
# (See https://doc.qt.io/qt-6/qcoreapplication.html#quit)
os._exit(0)
return super().event(event)
def getAutoSave(self) -> Optional[AutoSave]:
@ -1125,16 +1149,16 @@ class CuraApplication(QtApplication):
engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
self.processEvents()
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
qmlRegisterUncreatableMetaObject(CuraApplication.staticMetaObject, "Cura", 1, 0, "ResourceTypes", "ResourceTypes is an enum-only type")
self.processEvents()
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 0, "SceneController", self.getCuraSceneController)
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, "IntentManager", self.getIntentManager)
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 0, self.getCuraSceneController, "SceneController")
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, self.getExtruderManager, "ExtruderManager")
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, self.getMachineManager, "MachineManager")
qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, self.getIntentManager, "IntentManager")
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, self.getSettingInheritanceManager, "SettingInheritanceManager")
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, self.getSimpleModeSettingsManager, "SimpleModeSettingsManager")
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, self.getMachineActionManager, "MachineActionManager")
self.processEvents()
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
@ -1157,19 +1181,21 @@ class CuraApplication(QtApplication):
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel")
qmlRegisterSingletonType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel", self.getQualityManagementModel)
qmlRegisterSingletonType(MaterialManagementModel, "Cura", 1, 5, "MaterialManagementModel", self.getMaterialManagementModel)
qmlRegisterSingletonType(QualityManagementModel, "Cura", 1, 0, self.getQualityManagementModel, "QualityManagementModel")
qmlRegisterSingletonType(MaterialManagementModel, "Cura", 1, 5, self.getMaterialManagementModel, "MaterialManagementModel")
self.processEvents()
qmlRegisterType(DiscoveredPrintersModel, "Cura", 1, 0, "DiscoveredPrintersModel")
qmlRegisterType(DiscoveredCloudPrintersModel, "Cura", 1, 7, "DiscoveredCloudPrintersModel")
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
self.getQualityProfilesDropDownMenuModel, "QualityProfilesDropDownMenuModel")
qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,
"CustomQualityProfilesDropDownMenuModel", self.getCustomQualityProfilesDropDownMenuModel)
self.getCustomQualityProfilesDropDownMenuModel, "CustomQualityProfilesDropDownMenuModel")
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
qmlRegisterType(IntentModel, "Cura", 1, 6, "IntentModel")
qmlRegisterType(IntentCategoryModel, "Cura", 1, 6, "IntentCategoryModel")
qmlRegisterType(IntentSelectionModel, "Cura", 1, 7, "IntentSelectionModel")
qmlRegisterType(ActiveIntentQualitiesModel, "Cura", 1, 7, "ActiveIntentQualitiesModel")
self.processEvents()
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
@ -1178,14 +1204,14 @@ class CuraApplication(QtApplication):
qmlRegisterType(FirstStartMachineActionsModel, "Cura", 1, 0, "FirstStartMachineActionsModel")
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance)
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, ContainerManager.getInstance, "ContainerManager")
qmlRegisterType(SidebarCustomMenuItemsModel, "Cura", 1, 0, "SidebarCustomMenuItemsModel")
qmlRegisterType(PrinterOutputDevice, "Cura", 1, 0, "PrinterOutputDevice")
from cura.API import CuraAPI
qmlRegisterSingletonType(CuraAPI, "Cura", 1, 1, "API", self.getCuraAPI)
qmlRegisterUncreatableType(Account, "Cura", 1, 0, "AccountSyncState", "Could not create AccountSyncState")
qmlRegisterSingletonType(CuraAPI, "Cura", 1, 1, self.getCuraAPI, "API")
qmlRegisterUncreatableMetaObject(CuraApplication.staticMetaObject, "Cura", 1, 0, "AccountSyncState", "AccountSyncState is an enum-only type")
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
@ -1376,33 +1402,6 @@ class CuraApplication(QtApplication):
op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1)))
op.push()
@pyqtSlot()
def arrangeObjectsToAllBuildPlates(self) -> None:
"""Arrange all objects."""
nodes_to_arrange = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if not isinstance(node, SceneNode):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesn't have a mesh and is not a group.
parent_node = node.getParent()
if parent_node and parent_node.callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is reset)
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue # i.e. node with layer data
bounding_box = node.getBoundingBox()
# Skip nodes that are too big
if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth:
nodes_to_arrange.append(node)
job = ArrangeObjectsAllBuildPlatesJob(nodes_to_arrange)
job.start()
self.getCuraSceneController().setActiveBuildPlate(0) # Select first build plate
# Single build plate
@pyqtSlot()
def arrangeAll(self) -> None:

View file

@ -1,19 +1,25 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import glob
import os
from pathlib import Path
from typing import Any, cast, Dict, List, Set, Tuple, TYPE_CHECKING, Optional
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
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.i18n import i18nCatalog
catalog = i18nCatalog("cura")
if TYPE_CHECKING:
from UM.Qt.QtApplication import QtApplication
from PyQt5.QtCore import QObject
from PyQt6.QtCore import QObject
class CuraPackageManager(PackageManager):
@ -51,6 +57,49 @@ class CuraPackageManager(PackageManager):
super().initialize()
def isMaterialBundled(self, file_name: str, guid: str):
""" Check if there is a bundled material name with file_name and guid """
for path in Resources.getSecureSearchPaths():
# Secure search paths are install directory paths, if a material is in here it must be bundled.
paths = [Path(p) for p in glob.glob(path + '/**/*.xml.fdm_material', recursive=True)]
for material in paths:
if material.name == file_name:
Logger.info(f"Found bundled material: {material.name}. Located in path: {str(material)}")
with open(material, encoding="utf-8") as f:
# Make sure the file we found has the same guid as our material
# Parsing this xml would be better but the namespace is needed to search it.
parsed_guid = PluginRegistry.getInstance().getPluginObject(
"XmlMaterialProfile").getMetadataFromSerialized(
f.read(), "GUID")
if guid == parsed_guid:
# The material we found matches both filename and GUID
return True
return False
def getMaterialFilePackageId(self, file_name: str, guid: str) -> str:
"""Get the id of the installed material package that contains file_name"""
for material_package in [f for f in os.scandir(self._installation_dirs_dict["materials"]) if f.is_dir()]:
package_id = material_package.name
for root, _, file_names in os.walk(material_package.path):
if file_name not in file_names:
# File with the name we are looking for is not in this directory
continue
with open(os.path.join(root, file_name), encoding="utf-8") as f:
# Make sure the file we found has the same guid as our material
# Parsing this xml would be better but the namespace is needed to search it.
parsed_guid = PluginRegistry.getInstance().getPluginObject("XmlMaterialProfile").getMetadataFromSerialized(
f.read(), "GUID")
if guid == parsed_guid:
return package_id
Logger.error("Could not find package_id for file: {} with GUID: {} ".format(file_name, guid))
return ""
def getMachinesUsingPackage(self, package_id: str) -> Tuple[List[Tuple[GlobalStack, str, str]], List[Tuple[GlobalStack, str, str]]]:
"""Returns a list of where the package is used

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, QUrl
from PyQt6.QtCore import pyqtProperty, QUrl
from UM.Resources import Resources
from UM.View.View import View

View file

@ -4,7 +4,7 @@
import os
from typing import Optional
from PyQt5.QtCore import QObject, QUrl, pyqtSlot, pyqtProperty, pyqtSignal
from PyQt6.QtCore import QObject, QUrl, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Logger import Logger
from UM.PluginObject import PluginObject

View file

@ -5,7 +5,7 @@ import time
from collections import deque
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
from PyQt6.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
from typing import Optional, Any, Set
from UM.Logger import Logger
@ -53,6 +53,8 @@ class MachineErrorChecker(QObject):
self._keys_to_check = set() # type: Set[str]
self._num_keys_to_check_per_update = 10
def initialize(self) -> None:
self._error_check_timer.timeout.connect(self._rescheduleCheck)
@ -162,37 +164,37 @@ class MachineErrorChecker(QObject):
self._check_in_progress = True
# If there is nothing to check any more, it means there is no error.
if not self._stacks_and_keys_to_check:
# Finish
self._setResult(False)
return
for i in range(self._num_keys_to_check_per_update):
# If there is nothing to check any more, it means there is no error.
if not self._stacks_and_keys_to_check:
# Finish
self._setResult(False)
return
# Get the next stack and key to check
stack, key = self._stacks_and_keys_to_check.popleft()
# Get the next stack and key to check
stack, key = self._stacks_and_keys_to_check.popleft()
enabled = stack.getProperty(key, "enabled")
if not enabled:
self._application.callLater(self._checkStack)
return
enabled = stack.getProperty(key, "enabled")
if not enabled:
continue
validation_state = stack.getProperty(key, "validationState")
if validation_state is None:
# Setting is not validated. This can happen if there is only a setting definition.
# We do need to validate it, because a setting definitions value can be set by a function, which could
# be an invalid setting.
definition = stack.getSettingDefinition(key)
validator_type = SettingDefinition.getValidatorForType(definition.type)
if validator_type:
validator = validator_type(key)
validation_state = validator(stack)
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid):
# Since we don't know if any of the settings we didn't check is has an error value, store the list for the
# next check.
keys_to_recheck = {setting_key for stack, setting_key in self._stacks_and_keys_to_check}
keys_to_recheck.add(key)
self._setResult(True, keys_to_recheck = keys_to_recheck)
return
validation_state = stack.getProperty(key, "validationState")
if validation_state is None:
# Setting is not validated. This can happen if there is only a setting definition.
# We do need to validate it, because a setting definitions value can be set by a function, which could
# be an invalid setting.
definition = stack.getSettingDefinition(key)
validator_type = SettingDefinition.getValidatorForType(definition.type)
if validator_type:
validator = validator_type(key)
validation_state = validator(stack)
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid):
# Since we don't know if any of the settings we didn't check is has an error value, store the list for the
# next check.
keys_to_recheck = {setting_key for stack, setting_key in self._stacks_and_keys_to_check}
keys_to_recheck.add(key)
self._setResult(True, keys_to_recheck = keys_to_recheck)
return
# Schedule the check for the next key
self._application.callLater(self._checkStack)

View file

@ -129,7 +129,7 @@ class MachineNode(ContainerNode):
if name not in groups_by_name:
# CURA-6599
# For some reason, QML will get null or fail to convert type for MachineManager.activeQualityChangesGroup() to
# a QObject. Setting the object ownership to QQmlEngine.CppOwnership doesn't work, but setting the object
# a QObject. Setting the object ownership to QQmlEngine.ObjectOwnership.CppOwnership doesn't work, but setting the object
# parent to application seems to work.
from cura.CuraApplication import CuraApplication
groups_by_name[name] = QualityChangesGroup(name, quality_type = quality_changes["quality_type"],

View file

@ -0,0 +1,131 @@
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, Set, Dict, List, Any
from PyQt6.QtCore import Qt, QObject, QTimer
import cura.CuraApplication
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerStack import ContainerStack
from cura.Machines.ContainerTree import ContainerTree
from cura.Machines.Models.MachineModelUtils import fetchLayerHeight
from cura.Machines.MaterialNode import MaterialNode
from cura.Machines.QualityGroup import QualityGroup
from cura.Settings.IntentManager import IntentManager
class ActiveIntentQualitiesModel(ListModel):
NameRole = Qt.ItemDataRole.UserRole + 1
DisplayTextRole = Qt.ItemDataRole.UserRole + 2
QualityTypeRole = Qt.ItemDataRole.UserRole + 3
LayerHeightRole = Qt.ItemDataRole.UserRole + 4
IntentCategeoryRole = Qt.ItemDataRole.UserRole + 5
def __init__(self, parent: Optional[QObject] = None) -> None:
super().__init__(parent)
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.QualityTypeRole, "quality_type")
self.addRoleName(self.LayerHeightRole, "layer_height")
self.addRoleName(self.DisplayTextRole, "display_text")
self.addRoleName(self.IntentCategeoryRole, "intent_category")
self._intent_category = ""
IntentManager.intentCategoryChangedSignal.connect(self._update)
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
machine_manager.activeQualityGroupChanged.connect(self._update)
machine_manager.globalContainerChanged.connect(self._updateDelayed)
machine_manager.extruderChanged.connect(self._updateDelayed) # We also need to update if an extruder gets disabled
self._update_timer = QTimer()
self._update_timer.setInterval(100)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._update)
self._update()
def _updateDelayed(self):
self._update_timer.start()
def _onChanged(self, container: ContainerStack) -> None:
if container.getMetaDataEntry("type") == "intent":
self._updateDelayed()
def _update(self):
active_extruder_stack = cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeStack
if active_extruder_stack:
self._intent_category = active_extruder_stack.intent.getMetaDataEntry("intent_category", "")
new_items: List[Dict[str, Any]] = []
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if not global_stack:
self.setItems(new_items)
return
quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
material_nodes = self._getActiveMaterials()
added_quality_type_set: Set[str] = set()
for material_node in material_nodes:
intents = self._getIntentsForMaterial(material_node, quality_groups)
for intent in intents:
if intent["quality_type"] not in added_quality_type_set:
new_items.append(intent)
added_quality_type_set.add(intent["quality_type"])
new_items = sorted(new_items, key=lambda x: x["layer_height"])
self.setItems(new_items)
def _getActiveMaterials(self) -> Set["MaterialNode"]:
"""Get the active materials for all extruders. No duplicates will be returned"""
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if global_stack is None:
return set()
container_tree = ContainerTree.getInstance()
machine_node = container_tree.machines[global_stack.definition.getId()]
nodes: Set[MaterialNode] = set()
for extruder in global_stack.extruderList:
active_variant_name = extruder.variant.getMetaDataEntry("name")
if active_variant_name not in machine_node.variants:
Logger.log("w", "Could not find the variant %s", active_variant_name)
continue
active_variant_node = machine_node.variants[active_variant_name]
active_material_node = active_variant_node.materials.get(extruder.material.getMetaDataEntry("base_file"))
if active_material_node is None:
Logger.log("w", "Could not find the material %s", extruder.material.getMetaDataEntry("base_file"))
continue
nodes.add(active_material_node)
return nodes
def _getIntentsForMaterial(self, active_material_node: "MaterialNode", quality_groups: Dict[str, "QualityGroup"]) -> List[Dict[str, Any]]:
extruder_intents: List[Dict[str, Any]] = []
for quality_id, quality_node in active_material_node.qualities.items():
if quality_node.quality_type not in quality_groups: # Don't add the empty quality type (or anything else that would crash, defensively).
continue
quality_group = quality_groups[quality_node.quality_type]
if not quality_group.is_available:
continue
layer_height = fetchLayerHeight(quality_group)
for intent_id, intent_node in quality_node.intents.items():
if intent_node.intent_category != self._intent_category:
continue
extruder_intents.append({"name": quality_group.name,
"display_text": f"<b>{quality_group.name}</b> - {layer_height}mm",
"quality_type": quality_group.quality_type,
"layer_height": layer_height,
"intent_category": self._intent_category
})
return extruder_intents

View file

@ -3,7 +3,7 @@
from typing import Dict, Set
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, pyqtProperty
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, pyqtProperty
from UM.Qt.ListModel import ListModel
from UM.Logger import Logger
@ -61,22 +61,22 @@ class BaseMaterialsModel(ListModel):
ContainerTree.getInstance().materialsChanged.connect(self._materialsListChanged)
self._application.getMaterialManagementModel().favoritesChanged.connect(self._onChanged)
self.addRoleName(Qt.UserRole + 1, "root_material_id")
self.addRoleName(Qt.UserRole + 2, "id")
self.addRoleName(Qt.UserRole + 3, "GUID")
self.addRoleName(Qt.UserRole + 4, "name")
self.addRoleName(Qt.UserRole + 5, "brand")
self.addRoleName(Qt.UserRole + 6, "description")
self.addRoleName(Qt.UserRole + 7, "material")
self.addRoleName(Qt.UserRole + 8, "color_name")
self.addRoleName(Qt.UserRole + 9, "color_code")
self.addRoleName(Qt.UserRole + 10, "density")
self.addRoleName(Qt.UserRole + 11, "diameter")
self.addRoleName(Qt.UserRole + 12, "approximate_diameter")
self.addRoleName(Qt.UserRole + 13, "adhesion_info")
self.addRoleName(Qt.UserRole + 14, "is_read_only")
self.addRoleName(Qt.UserRole + 15, "container_node")
self.addRoleName(Qt.UserRole + 16, "is_favorite")
self.addRoleName(Qt.ItemDataRole.UserRole + 1, "root_material_id")
self.addRoleName(Qt.ItemDataRole.UserRole + 2, "id")
self.addRoleName(Qt.ItemDataRole.UserRole + 3, "GUID")
self.addRoleName(Qt.ItemDataRole.UserRole + 4, "name")
self.addRoleName(Qt.ItemDataRole.UserRole + 5, "brand")
self.addRoleName(Qt.ItemDataRole.UserRole + 6, "description")
self.addRoleName(Qt.ItemDataRole.UserRole + 7, "material")
self.addRoleName(Qt.ItemDataRole.UserRole + 8, "color_name")
self.addRoleName(Qt.ItemDataRole.UserRole + 9, "color_code")
self.addRoleName(Qt.ItemDataRole.UserRole + 10, "density")
self.addRoleName(Qt.ItemDataRole.UserRole + 11, "diameter")
self.addRoleName(Qt.ItemDataRole.UserRole + 12, "approximate_diameter")
self.addRoleName(Qt.ItemDataRole.UserRole + 13, "adhesion_info")
self.addRoleName(Qt.ItemDataRole.UserRole + 14, "is_read_only")
self.addRoleName(Qt.ItemDataRole.UserRole + 15, "container_node")
self.addRoleName(Qt.ItemDataRole.UserRole + 16, "is_favorite")
def _onChanged(self) -> None:
self._update_timer.start()

View file

@ -1,14 +1,14 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt
from PyQt6.QtCore import Qt
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
class BuildPlateModel(ListModel):
NameRole = Qt.UserRole + 1
ContainerNodeRole = Qt.UserRole + 2
NameRole = Qt.ItemDataRole.UserRole + 1
ContainerNodeRole = Qt.ItemDataRole.UserRole + 2
def __init__(self, parent = None):
super().__init__(parent)

View file

@ -10,7 +10,7 @@ from cura.Machines.ContainerTree import ContainerTree
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
if TYPE_CHECKING:
from PyQt5.QtCore import QObject
from PyQt6.QtCore import QObject
from UM.Settings.Interfaces import ContainerInterface

View file

@ -1,6 +1,6 @@
from typing import Optional, TYPE_CHECKING, List, Dict
from PyQt5.QtCore import QObject, pyqtSlot, Qt, pyqtSignal, pyqtProperty
from PyQt6.QtCore import QObject, pyqtSlot, Qt, pyqtSignal, pyqtProperty
from UM.Qt.ListModel import ListModel
@ -12,10 +12,10 @@ class DiscoveredCloudPrintersModel(ListModel):
"""Model used to inform the application about newly added cloud printers, which are discovered from the user's
account """
DeviceKeyRole = Qt.UserRole + 1
DeviceNameRole = Qt.UserRole + 2
DeviceTypeRole = Qt.UserRole + 3
DeviceFirmwareVersionRole = Qt.UserRole + 4
DeviceKeyRole = Qt.ItemDataRole.UserRole + 1
DeviceNameRole = Qt.ItemDataRole.UserRole + 2
DeviceTypeRole = Qt.ItemDataRole.UserRole + 3
DeviceFirmwareVersionRole = Qt.ItemDataRole.UserRole + 4
cloudPrintersDetectedChanged = pyqtSignal(bool)

View file

@ -3,7 +3,7 @@
from typing import Callable, Dict, List, Optional, TYPE_CHECKING
from PyQt5.QtCore import pyqtSlot, pyqtProperty, pyqtSignal, QObject, QTimer
from PyQt6.QtCore import pyqtSlot, pyqtProperty, pyqtSignal, QObject, QTimer
from UM.i18n import i18nCatalog
from UM.Logger import Logger

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer
from PyQt6.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer
from typing import Iterable, TYPE_CHECKING
from UM.i18n import i18nCatalog
@ -23,43 +23,43 @@ class ExtrudersModel(ListModel):
"""
# The ID of the container stack for the extruder.
IdRole = Qt.UserRole + 1
IdRole = Qt.ItemDataRole.UserRole + 1
NameRole = Qt.UserRole + 2
NameRole = Qt.ItemDataRole.UserRole + 2
"""Human-readable name of the extruder."""
ColorRole = Qt.UserRole + 3
ColorRole = Qt.ItemDataRole.UserRole + 3
"""Colour of the material loaded in the extruder."""
IndexRole = Qt.UserRole + 4
IndexRole = Qt.ItemDataRole.UserRole + 4
"""Index of the extruder, which is also the value of the setting itself.
An index of 0 indicates the first extruder, an index of 1 the second one, and so on. This is the value that will
be saved in instance containers. """
# The ID of the definition of the extruder.
DefinitionRole = Qt.UserRole + 5
DefinitionRole = Qt.ItemDataRole.UserRole + 5
# The material of the extruder.
MaterialRole = Qt.UserRole + 6
MaterialRole = Qt.ItemDataRole.UserRole + 6
# The variant of the extruder.
VariantRole = Qt.UserRole + 7
StackRole = Qt.UserRole + 8
VariantRole = Qt.ItemDataRole.UserRole + 7
StackRole = Qt.ItemDataRole.UserRole + 8
MaterialBrandRole = Qt.UserRole + 9
ColorNameRole = Qt.UserRole + 10
MaterialBrandRole = Qt.ItemDataRole.UserRole + 9
ColorNameRole = Qt.ItemDataRole.UserRole + 10
EnabledRole = Qt.UserRole + 11
EnabledRole = Qt.ItemDataRole.UserRole + 11
"""Is the extruder enabled?"""
MaterialTypeRole = Qt.UserRole + 12
MaterialTypeRole = Qt.ItemDataRole.UserRole + 12
"""The type of the material (e.g. PLA, ABS, PETG, etc.)."""
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
"""List of colours to display if there is no material or the material has no known colour. """
MaterialNameRole = Qt.UserRole + 13
MaterialNameRole = Qt.ItemDataRole.UserRole + 13
def __init__(self, parent = None):
"""Initialises the extruders model, defining the roles and listening for changes in the data.

View file

@ -3,7 +3,7 @@
from typing import Optional, Dict, Any, TYPE_CHECKING
from PyQt5.QtCore import QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt6.QtCore import QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
from UM.Qt.ListModel import ListModel
@ -19,9 +19,9 @@ class FirstStartMachineActionsModel(ListModel):
- action : the MachineAction object itself
"""
TitleRole = Qt.UserRole + 1
ContentRole = Qt.UserRole + 2
ActionRole = Qt.UserRole + 3
TitleRole = Qt.ItemDataRole.UserRole + 1
ContentRole = Qt.ItemDataRole.UserRole + 2
ActionRole = Qt.ItemDataRole.UserRole + 3
def __init__(self, application: "CuraApplication", parent: Optional[QObject] = None) -> None:
super().__init__(parent)

View file

@ -1,7 +1,7 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt, QTimer, pyqtProperty, pyqtSignal
from PyQt6.QtCore import Qt, QTimer, pyqtProperty, pyqtSignal
from typing import List, Optional
from UM.Qt.ListModel import ListModel
@ -15,14 +15,14 @@ from cura.UltimakerCloud.UltimakerCloudConstants import META_CAPABILITIES # To
class GlobalStacksModel(ListModel):
NameRole = Qt.UserRole + 1
IdRole = Qt.UserRole + 2
HasRemoteConnectionRole = Qt.UserRole + 3
ConnectionTypeRole = Qt.UserRole + 4
MetaDataRole = Qt.UserRole + 5
DiscoverySourceRole = Qt.UserRole + 6 # For separating local and remote printers in the machine management page
RemovalWarningRole = Qt.UserRole + 7
IsOnlineRole = Qt.UserRole + 8
NameRole = Qt.ItemDataRole.UserRole + 1
IdRole = Qt.ItemDataRole.UserRole + 2
HasRemoteConnectionRole = Qt.ItemDataRole.UserRole + 3
ConnectionTypeRole = Qt.ItemDataRole.UserRole + 4
MetaDataRole = Qt.ItemDataRole.UserRole + 5
DiscoverySourceRole = Qt.ItemDataRole.UserRole + 6 # For separating local and remote printers in the machine management page
RemovalWarningRole = Qt.ItemDataRole.UserRole + 7
IsOnlineRole = Qt.ItemDataRole.UserRole + 8
def __init__(self, parent = None) -> None:
super().__init__(parent)
@ -135,7 +135,7 @@ class GlobalStacksModel(ListModel):
continue
device_name = container_stack.getMetaDataEntry("group_name", container_stack.getName())
section_name = "Connected printers" if has_remote_connection else "Preset printers"
section_name = self._catalog.i18nc("@label", "Connected printers") if has_remote_connection else self._catalog.i18nc("@label", "Preset printers")
section_name = self._catalog.i18nc("@info:title", section_name)
default_removal_warning = self._catalog.i18nc(

View file

@ -2,14 +2,14 @@
#Cura is released under the terms of the LGPLv3 or higher.
import collections
from PyQt5.QtCore import Qt, QTimer
from PyQt6.QtCore import Qt, QTimer
from typing import TYPE_CHECKING, Optional, Dict
from cura.Machines.Models.IntentModel import IntentModel
from cura.Settings.IntentManager import IntentManager
from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes.
from PyQt5.QtCore import pyqtSignal
from PyQt6.QtCore import pyqtSignal
import cura.CuraApplication
if TYPE_CHECKING:
from UM.Settings.ContainerRegistry import ContainerInterface
@ -21,11 +21,11 @@ catalog = i18nCatalog("cura")
class IntentCategoryModel(ListModel):
"""Lists the intent categories that are available for the current printer configuration. """
NameRole = Qt.UserRole + 1
IntentCategoryRole = Qt.UserRole + 2
WeightRole = Qt.UserRole + 3
QualitiesRole = Qt.UserRole + 4
DescriptionRole = Qt.UserRole + 5
NameRole = Qt.ItemDataRole.UserRole + 1
IntentCategoryRole = Qt.ItemDataRole.UserRole + 2
WeightRole = Qt.ItemDataRole.UserRole + 3
QualitiesRole = Qt.ItemDataRole.UserRole + 4
DescriptionRole = Qt.ItemDataRole.UserRole + 5
modelUpdated = pyqtSignal()
@ -111,7 +111,7 @@ class IntentCategoryModel(ListModel):
except ValueError:
weight = 99
result.append({
"name": IntentCategoryModel.translation(category, "name", category),
"name": IntentCategoryModel.translation(category, "name", category.title()),
"description": IntentCategoryModel.translation(category, "description", None),
"intent_category": category,
"weight": weight,

View file

@ -2,7 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, Dict, Any, Set, List
from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal, QTimer
from PyQt6.QtCore import Qt, QObject, pyqtProperty, pyqtSignal, QTimer
import cura.CuraApplication
from UM.Qt.ListModel import ListModel
@ -15,11 +15,11 @@ from cura.Machines.QualityGroup import QualityGroup
class IntentModel(ListModel):
NameRole = Qt.UserRole + 1
QualityTypeRole = Qt.UserRole + 2
LayerHeightRole = Qt.UserRole + 3
AvailableRole = Qt.UserRole + 4
IntentRole = Qt.UserRole + 5
NameRole = Qt.ItemDataRole.UserRole + 1
QualityTypeRole = Qt.ItemDataRole.UserRole + 2
LayerHeightRole = Qt.ItemDataRole.UserRole + 3
AvailableRole = Qt.ItemDataRole.UserRole + 4
IntentRole = Qt.ItemDataRole.UserRole + 5
def __init__(self, parent: Optional[QObject] = None) -> None:
super().__init__(parent)

View file

@ -0,0 +1,129 @@
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import collections
from typing import OrderedDict, Optional
from PyQt6.QtCore import Qt, QTimer, QObject
import cura
from UM import i18nCatalog
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import ContainerInterface
from cura.Settings.IntentManager import IntentManager
catalog = i18nCatalog("cura")
class IntentSelectionModel(ListModel):
NameRole = Qt.ItemDataRole.UserRole + 1
IntentCategoryRole = Qt.ItemDataRole.UserRole + 2
WeightRole = Qt.ItemDataRole.UserRole + 3
DescriptionRole = Qt.ItemDataRole.UserRole + 4
IconRole = Qt.ItemDataRole.UserRole + 5
def __init__(self, parent: Optional[QObject] = None) -> None:
super().__init__(parent)
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.IntentCategoryRole, "intent_category")
self.addRoleName(self.WeightRole, "weight")
self.addRoleName(self.DescriptionRole, "description")
self.addRoleName(self.IconRole, "icon")
application = cura.CuraApplication.CuraApplication.getInstance()
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange)
ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange)
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
machine_manager.activeMaterialChanged.connect(self._update)
machine_manager.activeVariantChanged.connect(self._update)
machine_manager.extruderChanged.connect(self._update)
extruder_manager = application.getExtruderManager()
extruder_manager.extrudersChanged.connect(self._update)
self._update_timer: QTimer = QTimer()
self._update_timer.setInterval(100)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._update)
self._onChange()
@staticmethod
def _getDefaultProfileInformation() -> OrderedDict[str, dict]:
""" Default information user-visible string. Ordered by weight. """
default_profile_information = collections.OrderedDict()
default_profile_information["default"] = {
"name": catalog.i18nc("@label", "Default"),
"icon": "GearCheck"
}
default_profile_information["visual"] = {
"name": catalog.i18nc("@label", "Visual"),
"description": catalog.i18nc("@text", "The visual profile is designed to print visual prototypes and models with the intent of high visual and surface quality."),
"icon" : "Visual"
}
default_profile_information["engineering"] = {
"name": catalog.i18nc("@label", "Engineering"),
"description": catalog.i18nc("@text", "The engineering profile is designed to print functional prototypes and end-use parts with the intent of better accuracy and for closer tolerances."),
"icon": "Nut"
}
default_profile_information["quick"] = {
"name": catalog.i18nc("@label", "Draft"),
"description": catalog.i18nc("@text", "The draft profile is designed to print initial prototypes and concept validation with the intent of significant print time reduction."),
"icon": "SpeedOMeter"
}
return default_profile_information
def _onContainerChange(self, container: ContainerInterface) -> None:
"""Updates the list of intents if an intent profile was added or removed."""
if container.getMetaDataEntry("type") == "intent":
self._update()
def _onChange(self) -> None:
self._update_timer.start()
def _update(self) -> None:
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if global_stack is None:
self.setItems([])
Logger.log("d", "No active GlobalStack, set quality profile model as empty.")
return
# Check for material compatibility
if not cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeMaterialsCompatible():
Logger.log("d", "No active material compatibility, set quality profile model as empty.")
self.setItems([])
return
default_profile_info = self._getDefaultProfileInformation()
available_categories = IntentManager.getInstance().currentAvailableIntentCategories()
result = []
for i, category in enumerate(available_categories):
profile_info = default_profile_info.get(category, {})
try:
weight = list(default_profile_info.keys()).index(category)
except ValueError:
weight = len(available_categories) + i
result.append({
"name": profile_info.get("name", category.title()),
"description": profile_info.get("description", None),
"icon" : profile_info.get("icon", ""),
"intent_category": category,
"weight": weight,
})
result.sort(key=lambda k: k["weight"])
self.setItems(result)

View file

@ -1,7 +1,9 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtQml import QQmlEngine
from UM.Qt.ListModel import ListModel
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
@ -9,10 +11,11 @@ class MaterialTypesModel(ListModel):
def __init__(self, parent = None):
super().__init__(parent)
QQmlEngine.setObjectOwnership(self, QQmlEngine.ObjectOwnership.CppOwnership)
self.addRoleName(Qt.UserRole + 1, "name")
self.addRoleName(Qt.UserRole + 2, "brand")
self.addRoleName(Qt.UserRole + 3, "colors")
self.addRoleName(Qt.ItemDataRole.UserRole + 1, "name")
self.addRoleName(Qt.ItemDataRole.UserRole + 2, "brand")
self.addRoleName(Qt.ItemDataRole.UserRole + 3, "colors")
class MaterialBrandsModel(BaseMaterialsModel):
@ -20,9 +23,10 @@ class MaterialBrandsModel(BaseMaterialsModel):
def __init__(self, parent = None):
super().__init__(parent)
QQmlEngine.setObjectOwnership(self, QQmlEngine.ObjectOwnership.CppOwnership)
self.addRoleName(Qt.UserRole + 1, "name")
self.addRoleName(Qt.UserRole + 2, "material_types")
self.addRoleName(Qt.ItemDataRole.UserRole + 1, "name")
self.addRoleName(Qt.ItemDataRole.UserRole + 2, "material_types")
self._update()
@ -74,16 +78,15 @@ class MaterialBrandsModel(BaseMaterialsModel):
material_type_item_list = []
brand_item = {
"name": brand,
"material_types": MaterialTypesModel(self)
"material_types": MaterialTypesModel()
}
for material_type, material_list in material_dict.items():
material_type_item = {
"name": material_type,
"brand": brand,
"colors": BaseMaterialsModel(self)
"colors": BaseMaterialsModel()
}
material_type_item["colors"].clear()
# Sort materials by name
material_list = sorted(material_list, key = lambda x: x["name"].upper())

View file

@ -2,8 +2,8 @@
# Cura is released under the terms of the LGPLv3 or higher.
import copy # To duplicate materials.
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt5.QtGui import QDesktopServices
from PyQt6.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt6.QtGui import QDesktopServices
from typing import Any, Dict, Optional, TYPE_CHECKING
import uuid # To generate new GUIDs for new materials.
@ -34,62 +34,6 @@ class MaterialManagementModel(QObject):
def __init__(self, parent: Optional[QObject] = None) -> None:
super().__init__(parent = parent)
self._material_sync = CloudMaterialSync(parent=self)
self._checkIfNewMaterialsWereInstalled()
def _checkIfNewMaterialsWereInstalled(self) -> None:
"""
Checks whether new material packages were installed in the latest startup. If there were, then it shows
a message prompting the user to sync the materials with their printers.
"""
application = cura.CuraApplication.CuraApplication.getInstance()
for package_id, package_data in application.getPackageManager().getPackagesInstalledOnStartup().items():
if package_data["package_info"]["package_type"] == "material":
# At least one new material was installed
# TODO: This should be enabled again once CURA-8609 is merged
#self._showSyncNewMaterialsMessage()
break
def _showSyncNewMaterialsMessage(self) -> None:
sync_materials_message = Message(
text = catalog.i18nc("@action:button",
"Please sync the material profiles with your printers before starting to print."),
title = catalog.i18nc("@action:button", "New materials installed"),
message_type = Message.MessageType.WARNING,
lifetime = 0
)
sync_materials_message.addAction(
"sync",
name = catalog.i18nc("@action:button", "Sync materials"),
icon = "",
description = "Sync your newly installed materials with your printers.",
button_align = Message.ActionButtonAlignment.ALIGN_RIGHT
)
sync_materials_message.addAction(
"learn_more",
name = catalog.i18nc("@action:button", "Learn more"),
icon = "",
description = "Learn more about syncing your newly installed materials with your printers.",
button_align = Message.ActionButtonAlignment.ALIGN_LEFT,
button_style = Message.ActionButtonStyle.LINK
)
sync_materials_message.actionTriggered.connect(self._onSyncMaterialsMessageActionTriggered)
# Show the message only if there are printers that support material export
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
global_stacks = container_registry.findContainerStacks(type = "machine")
if any([stack.supportsMaterialExport for stack in global_stacks]):
sync_materials_message.show()
def _onSyncMaterialsMessageActionTriggered(self, sync_message: Message, sync_message_action: str):
if sync_message_action == "sync":
QDesktopServices.openUrl(QUrl("https://example.com/openSyncAllWindow"))
# self.openSyncAllWindow()
sync_message.hide()
elif sync_message_action == "learn_more":
QDesktopServices.openUrl(QUrl("https://support.ultimaker.com/hc/en-us/articles/360013137919?utm_source=cura&utm_medium=software&utm_campaign=sync-material-printer-message"))
@pyqtSlot("QVariant", result = bool)
def canMaterialBeRemoved(self, material_node: "MaterialNode") -> bool:

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QTimer, pyqtSignal, pyqtProperty
from PyQt6.QtCore import QTimer, pyqtSignal, pyqtProperty
from UM.Application import Application
from UM.Scene.Camera import Camera

View file

@ -1,7 +1,7 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt
from PyQt6.QtCore import Qt
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
@ -10,9 +10,9 @@ from cura.Machines.ContainerTree import ContainerTree
class NozzleModel(ListModel):
IdRole = Qt.UserRole + 1
HotendNameRole = Qt.UserRole + 2
ContainerNodeRole = Qt.UserRole + 3
IdRole = Qt.ItemDataRole.UserRole + 1
HotendNameRole = Qt.ItemDataRole.UserRole + 2
ContainerNodeRole = Qt.ItemDataRole.UserRole + 3
def __init__(self, parent = None):
super().__init__(parent)

View file

@ -2,7 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Any, cast, Dict, Optional, TYPE_CHECKING
from PyQt5.QtCore import pyqtSlot, QObject, Qt, QTimer
from PyQt6.QtCore import pyqtSlot, QObject, Qt, QTimer
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
@ -29,13 +29,13 @@ if TYPE_CHECKING:
class QualityManagementModel(ListModel):
"""This the QML model for the quality management page."""
NameRole = Qt.UserRole + 1
IsReadOnlyRole = Qt.UserRole + 2
QualityGroupRole = Qt.UserRole + 3
QualityTypeRole = Qt.UserRole + 4
QualityChangesGroupRole = Qt.UserRole + 5
IntentCategoryRole = Qt.UserRole + 6
SectionNameRole = Qt.UserRole + 7
NameRole = Qt.ItemDataRole.UserRole + 1
IsReadOnlyRole = Qt.ItemDataRole.UserRole + 2
QualityGroupRole = Qt.ItemDataRole.UserRole + 3
QualityTypeRole = Qt.ItemDataRole.UserRole + 4
QualityChangesGroupRole = Qt.ItemDataRole.UserRole + 5
IntentCategoryRole = Qt.ItemDataRole.UserRole + 6
SectionNameRole = Qt.ItemDataRole.UserRole + 7
def __init__(self, parent: Optional["QObject"] = None) -> None:
super().__init__(parent)
@ -358,8 +358,9 @@ class QualityManagementModel(ListModel):
"quality_type": quality_type,
"quality_changes_group": None,
"intent_category": intent_category,
"section_name": catalog.i18nc("@label", intent_translations.get(intent_category, {}).get("name", catalog.i18nc("@label", "Unknown"))),
"section_name": catalog.i18nc("@label", intent_translations.get(intent_category, {}).get("name", catalog.i18nc("@label", intent_category.title()))),
})
# Sort by quality_type for each intent category
intent_translations_list = list(intent_translations)

View file

@ -1,7 +1,7 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt, QTimer
from PyQt6.QtCore import Qt, QTimer
import cura.CuraApplication # Imported this way to prevent circular dependencies.
from UM.Logger import Logger
@ -13,14 +13,14 @@ from cura.Machines.Models.MachineModelUtils import fetchLayerHeight
class QualityProfilesDropDownMenuModel(ListModel):
"""QML Model for all built-in quality profiles. This model is used for the drop-down quality menu."""
NameRole = Qt.UserRole + 1
QualityTypeRole = Qt.UserRole + 2
LayerHeightRole = Qt.UserRole + 3
LayerHeightUnitRole = Qt.UserRole + 4
AvailableRole = Qt.UserRole + 5
QualityGroupRole = Qt.UserRole + 6
QualityChangesGroupRole = Qt.UserRole + 7
IsExperimentalRole = Qt.UserRole + 8
NameRole = Qt.ItemDataRole.UserRole + 1
QualityTypeRole = Qt.ItemDataRole.UserRole + 2
LayerHeightRole = Qt.ItemDataRole.UserRole + 3
LayerHeightUnitRole = Qt.ItemDataRole.UserRole + 4
AvailableRole = Qt.ItemDataRole.UserRole + 5
QualityGroupRole = Qt.ItemDataRole.UserRole + 6
QualityChangesGroupRole = Qt.ItemDataRole.UserRole + 7
IsExperimentalRole = Qt.ItemDataRole.UserRole + 8
def __init__(self, parent = None):
super().__init__(parent)

View file

@ -1,7 +1,7 @@
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
from PyQt6.QtCore import pyqtProperty, pyqtSignal, Qt
from typing import Set
import cura.CuraApplication
@ -17,13 +17,13 @@ import os
class QualitySettingsModel(ListModel):
"""This model is used to show details settings of the selected quality in the quality management page."""
KeyRole = Qt.UserRole + 1
LabelRole = Qt.UserRole + 2
UnitRole = Qt.UserRole + 3
ProfileValueRole = Qt.UserRole + 4
ProfileValueSourceRole = Qt.UserRole + 5
UserValueRole = Qt.UserRole + 6
CategoryRole = Qt.UserRole + 7
KeyRole = Qt.ItemDataRole.UserRole + 1
LabelRole = Qt.ItemDataRole.UserRole + 2
UnitRole = Qt.ItemDataRole.UserRole + 3
ProfileValueRole = Qt.ItemDataRole.UserRole + 4
ProfileValueSourceRole = Qt.ItemDataRole.UserRole + 5
UserValueRole = Qt.ItemDataRole.UserRole + 6
CategoryRole = Qt.ItemDataRole.UserRole + 7
GLOBAL_STACK_POSITION = -1

View file

@ -3,7 +3,7 @@
from typing import Optional, List
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from UM.Logger import Logger
from UM.Preferences import Preferences

View file

@ -4,7 +4,7 @@
import os
from collections import OrderedDict
from PyQt5.QtCore import pyqtSlot, Qt
from PyQt6.QtCore import pyqtSlot, Qt
from UM.Application import Application
from UM.Logger import Logger
@ -15,12 +15,12 @@ from UM.Qt.ListModel import ListModel
class UserChangesModel(ListModel):
KeyRole = Qt.UserRole + 1
LabelRole = Qt.UserRole + 2
ExtruderRole = Qt.UserRole + 3
OriginalValueRole = Qt.UserRole + 4
UserValueRole = Qt.UserRole + 6
CategoryRole = Qt.UserRole + 7
KeyRole = Qt.ItemDataRole.UserRole + 1
LabelRole = Qt.ItemDataRole.UserRole + 2
ExtruderRole = Qt.ItemDataRole.UserRole + 3
OriginalValueRole = Qt.ItemDataRole.UserRole + 4
UserValueRole = Qt.ItemDataRole.UserRole + 6
CategoryRole = Qt.ItemDataRole.UserRole + 7
def __init__(self, parent = None):
super().__init__(parent = parent)

View file

@ -3,7 +3,7 @@
from typing import Any, Dict, Optional
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal
class QualityChangesGroup(QObject):

View file

@ -4,7 +4,7 @@
from base64 import b64encode
from datetime import datetime
from hashlib import sha512
from PyQt5.QtNetwork import QNetworkReply
from PyQt6.QtNetwork import QNetworkReply
import secrets
from typing import Callable, Optional
import urllib.parse

View file

@ -6,8 +6,8 @@ from datetime import datetime, timedelta
from typing import Callable, Dict, Optional, TYPE_CHECKING, Union
from urllib.parse import urlencode, quote_plus
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
from PyQt6.QtCore import QUrl
from PyQt6.QtGui import QDesktopServices
from UM.Logger import Logger
from UM.Message import Message

View file

@ -1,7 +1,7 @@
# Copyright (c) 2021 Ultimaker B.V.
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QTimer
from PyQt6.QtCore import QTimer
from UM.Application import Application
from UM.Logger import Logger

View file

@ -1,6 +1,6 @@
from PyQt5.QtGui import QImage
from PyQt5.QtQuick import QQuickImageProvider
from PyQt5.QtCore import QSize
from PyQt6.QtGui import QImage
from PyQt6.QtQuick import QQuickImageProvider
from PyQt6.QtCore import QSize
from UM.Application import Application
from typing import Tuple
@ -8,7 +8,7 @@ from typing import Tuple
class PrintJobPreviewImageProvider(QQuickImageProvider):
def __init__(self):
super().__init__(QQuickImageProvider.Image)
super().__init__(QQuickImageProvider.ImageType.Image)
def requestImage(self, id: str, size: QSize) -> Tuple[QImage, QSize]:
"""Request a new image.

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
from PyQt6.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
from enum import IntEnum
from threading import Thread

View file

@ -3,7 +3,7 @@
from typing import TYPE_CHECKING, Set, Union, Optional
from PyQt5.QtCore import QTimer
from PyQt6.QtCore import QTimer
from .PrinterOutputController import PrinterOutputController

View file

@ -2,7 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
from PyQt6.QtCore import pyqtProperty, QObject, pyqtSignal
from .MaterialOutputModel import MaterialOutputModel

View file

@ -3,7 +3,7 @@
from typing import Optional, TYPE_CHECKING
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
from .ExtruderConfigurationModel import ExtruderConfigurationModel

View file

@ -3,7 +3,7 @@
from typing import Optional
from PyQt5.QtCore import pyqtProperty, QObject
from PyQt6.QtCore import pyqtProperty, QObject
class MaterialOutputModel(QObject):

View file

@ -3,8 +3,8 @@
from typing import Optional, TYPE_CHECKING, List
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot, QUrl
from PyQt5.QtGui import QImage
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot, QUrl
from PyQt6.QtGui import QImage
if TYPE_CHECKING:
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
@ -58,7 +58,7 @@ class PrintJobOutputModel(QObject):
# requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
# as new (instead of relying on cached version and thus forces an update.
temp = "image://print_job_preview/" + str(self._preview_image_id) + "/" + self._key
return QUrl(temp, QUrl.TolerantMode)
return QUrl(temp, QUrl.ParsingMode.TolerantMode)
def getPreviewImage(self) -> Optional[QImage]:
return self._preview_image
@ -119,16 +119,16 @@ class PrintJobOutputModel(QObject):
@pyqtProperty(int, notify = timeTotalChanged)
def timeTotal(self) -> int:
return self._time_total
return int(self._time_total)
@pyqtProperty(int, notify = timeElapsedChanged)
def timeElapsed(self) -> int:
return self._time_elapsed
return int(self._time_elapsed)
@pyqtProperty(int, notify = timeElapsedChanged)
def timeRemaining(self) -> int:
# Never get a negative time remaining
return max(self.timeTotal - self.timeElapsed, 0)
return int(max(self.timeTotal - self.timeElapsed, 0))
@pyqtProperty(float, notify = timeElapsedChanged)
def progress(self) -> float:

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
from PyQt6.QtCore import pyqtProperty, QObject, pyqtSignal
from typing import List
MYPY = False

View file

@ -1,7 +1,7 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot, QUrl
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot, QUrl
from typing import List, Dict, Optional, TYPE_CHECKING
from UM.Math.Vector import Vector
from cura.PrinterOutput.Peripheral import Peripheral

View file

@ -1,10 +1,10 @@
# Copyright (c) 2018 Aldo Hoeben / fieldOfView
# NetworkMJPGImage is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QUrl, pyqtProperty, pyqtSignal, pyqtSlot, QRect, QByteArray
from PyQt5.QtGui import QImage, QPainter
from PyQt5.QtQuick import QQuickPaintedItem
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
from PyQt6.QtCore import QUrl, pyqtProperty, pyqtSignal, pyqtSlot, QRect, QByteArray
from PyQt6.QtGui import QImage, QPainter
from PyQt6.QtQuick import QQuickPaintedItem
from PyQt6.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
from UM.Logger import Logger

View file

@ -9,8 +9,8 @@ from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication
from PyQt6.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication
from time import time
from typing import Callable, Dict, List, Optional, Union
from enum import IntEnum
@ -146,8 +146,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
url = QUrl("http://" + self._address + self._api_prefix + target)
request = QNetworkRequest(url)
if content_type is not None:
request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
request.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, content_type)
request.setHeader(QNetworkRequest.KnownHeaders.UserAgentHeader, self._user_agent)
return request
def createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart:
@ -162,10 +162,10 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
if not content_header.startswith("form-data;"):
content_header = "form-data; " + content_header
part.setHeader(QNetworkRequest.ContentDispositionHeader, content_header)
part.setHeader(QNetworkRequest.KnownHeaders.ContentDispositionHeader, content_header)
if content_type is not None:
part.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
part.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, content_type)
part.setBody(data)
return part
@ -290,7 +290,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
on_progress: Optional[Callable[[int, int], None]] = None) -> QNetworkReply:
self._validateManager()
request = self._createEmptyRequest(target, content_type=None)
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
multi_post_part = QHttpMultiPart(QHttpMultiPart.ContentType.FormDataType)
for part in parts:
multi_post_part.append(part)
@ -311,7 +311,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
def postForm(self, target: str, header_data: str, body_data: bytes, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
post_part = QHttpPart()
post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data)
post_part.setHeader(QNetworkRequest.KnownHeaders.ContentDispositionHeader, header_data)
post_part.setBody(body_data)
self.postFormWithParts(target, [post_part], on_finished, on_progress)
@ -357,10 +357,10 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
def _handleOnFinished(self, reply: QNetworkReply) -> None:
# Due to garbage collection, we need to cache certain bits of post operations.
# As we don't want to keep them around forever, delete them if we get a reply.
if reply.operation() == QNetworkAccessManager.PostOperation:
if reply.operation() == QNetworkAccessManager.Operation.PostOperation:
self._clearCachedMultiPart(reply)
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) is None:
if reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute) is None:
# No status code means it never even reached remote.
return

View file

@ -1,11 +1,11 @@
# Copyright (c) 2021 Ultimaker B.V.
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from enum import IntEnum
from typing import Callable, List, Optional, Union
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl
from PyQt5.QtWidgets import QMessageBox
from PyQt6.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl
from PyQt6.QtWidgets import QMessageBox
import cura.CuraApplication # Imported like this to prevent circular imports.
from UM.Logger import Logger
@ -137,7 +137,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
"""
if self.connectionState != connection_state:
self._connection_state = connection_state
cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().setMetaDataEntry("is_online", self.isConnected())
application = cura.CuraApplication.CuraApplication.getInstance()
if application is not None: # Might happen during the closing of Cura or in a test.
global_stack = application.getGlobalContainerStack()
if global_stack is not None:
global_stack.setMetaDataEntry("is_online", self.isConnected())
self.connectionStateChanged.emit(self._id)
@pyqtProperty(int, constant = True)

View file

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

View file

@ -5,7 +5,7 @@ import enum
import functools # For partial methods to use as callbacks with information pre-filled.
import json # To serialise metadata for API calls.
import os # To delete the archive when we're done.
from PyQt5.QtCore import QUrl
from PyQt6.QtCore import QUrl
import tempfile # To create an archive before we upload it.
import cura.CuraApplication # Imported like this to prevent circular imports.
@ -21,7 +21,7 @@ from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from PyQt5.QtNetwork import QNetworkReply
from PyQt6.QtNetwork import QNetworkReply
from cura.UltimakerCloud.CloudMaterialSync import CloudMaterialSync
catalog = i18nCatalog("cura")

View file

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

View file

@ -1,7 +1,7 @@
# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QTimer
from PyQt6.QtCore import QTimer
from UM.Application import Application
from UM.Math.Polygon import Polygon
@ -383,9 +383,9 @@ class ConvexHullDecorator(SceneNodeDecorator):
# Shrinkage compensation.
if not self._global_stack: # Should never happen.
return convex_hull
scale_factor = self._global_stack.getProperty("material_shrinkage_percentage", "value") / 100.0
scale_factor = self._global_stack.getProperty("material_shrinkage_percentage_xy", "value") / 100.0
result = convex_hull
if scale_factor != 1.0 and not self.getNode().callDecoration("isGroup"):
if scale_factor != 1.0 and scale_factor > 0 and not self.getNode().callDecoration("isGroup"):
center = None
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time":
# Find the root node that's placed in the scene; the root of the mesh group.
@ -498,7 +498,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
"adhesion_type", "raft_margin", "print_sequence",
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]
_influencing_settings = {"xy_offset", "xy_offset_layer_0", "mold_enabled", "mold_width", "anti_overhang_mesh", "infill_mesh", "cutting_mesh", "material_shrinkage_percentage"}
_influencing_settings = {"xy_offset", "xy_offset_layer_0", "mold_enabled", "mold_width", "anti_overhang_mesh", "infill_mesh", "cutting_mesh", "material_shrinkage_percentage_xy"}
"""Settings that change the convex hull.
If these settings change, the convex hull should be recalculated.

View file

@ -1,7 +1,7 @@
from UM.Logger import Logger
from PyQt5.QtCore import Qt, pyqtSlot, QObject, QTimer
from PyQt5.QtWidgets import QApplication
from PyQt6.QtCore import Qt, pyqtSlot, QObject, QTimer
from PyQt6.QtWidgets import QApplication
from UM.Scene.Camera import Camera
from cura.UI.ObjectsModel import ObjectsModel
@ -107,8 +107,8 @@ class CuraSceneController(QObject):
"""Either select or deselect an item"""
modifiers = QApplication.keyboardModifiers()
ctrl_is_active = modifiers & Qt.ControlModifier
shift_is_active = modifiers & Qt.ShiftModifier
ctrl_is_active = modifiers & Qt.KeyboardModifier.ControlModifier
shift_is_active = modifiers & Qt.KeyboardModifier.ShiftModifier
if ctrl_is_active:
item = self._objects_model.getItem(index)

View file

@ -6,8 +6,8 @@ import urllib.parse
import uuid
from typing import Any, cast, Dict, List, TYPE_CHECKING, Union
from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtWidgets import QMessageBox
from PyQt6.QtCore import QObject, QUrl
from PyQt6.QtWidgets import QMessageBox
from UM.i18n import i18nCatalog
from UM.FlameProfiler import pyqtSlot
@ -47,11 +47,11 @@ class ContainerManager(QObject):
def __init__(self, application: "CuraApplication") -> None:
if ContainerManager.__instance is not None:
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
ContainerManager.__instance = self
try:
super().__init__(parent = application)
except TypeError:
super().__init__()
ContainerManager.__instance = self
self._container_name_filters = {} # type: Dict[str, Dict[str, Any]]
@ -114,7 +114,7 @@ class ContainerManager(QObject):
for _ in range(len(entries)):
item = item.get(entries.pop(0), {})
if item[entry_name] != entry_value:
if entry_name not in item or item[entry_name] != entry_value:
sub_item_changed = True
item[entry_name] = entry_value
@ -206,7 +206,7 @@ class ContainerManager(QObject):
if os.path.exists(file_url):
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_url))
if result == QMessageBox.No:
if result == QMessageBox.StandardButton.No:
return {"status": "cancelled", "message": "User cancelled"}
try:

View file

@ -6,7 +6,7 @@ import re
import configparser
from typing import Any, cast, Dict, Optional, List, Union, Tuple
from PyQt5.QtWidgets import QMessageBox
from PyQt6.QtWidgets import QMessageBox
from UM.Decorators import override
from UM.Settings.ContainerFormatError import ContainerFormatError
@ -139,7 +139,7 @@ class CuraContainerRegistry(ContainerRegistry):
if os.path.exists(file_name):
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
if result == QMessageBox.No:
if result == QMessageBox.StandardButton.No:
return False
profile_writer = self._findProfileWriter(extension, description)

View file

@ -2,7 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Any, cast, List, Optional, Dict
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject
from PyQt6.QtCore import pyqtProperty, pyqtSignal, QObject
from UM.Application import Application
from UM.Decorators import override
@ -75,7 +75,7 @@ class CuraContainerStack(ContainerStack):
self.replaceContainer(_ContainerIndexes.UserChanges, new_user_changes)
@pyqtProperty(InstanceContainer, fset = setUserChanges, notify = pyqtContainersChanged)
@pyqtProperty(QObject, fset = setUserChanges, notify = pyqtContainersChanged)
def userChanges(self) -> InstanceContainer:
"""Get the user changes container.
@ -92,7 +92,7 @@ class CuraContainerStack(ContainerStack):
self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit)
@pyqtProperty(InstanceContainer, fset = setQualityChanges, notify = pyqtContainersChanged)
@pyqtProperty(QObject, fset = setQualityChanges, notify = pyqtContainersChanged)
def qualityChanges(self) -> InstanceContainer:
"""Get the quality changes container.
@ -109,7 +109,7 @@ class CuraContainerStack(ContainerStack):
self.replaceContainer(_ContainerIndexes.Intent, new_intent, postpone_emit = postpone_emit)
@pyqtProperty(InstanceContainer, fset = setIntent, notify = pyqtContainersChanged)
@pyqtProperty(QObject, fset = setIntent, notify = pyqtContainersChanged)
def intent(self) -> InstanceContainer:
"""Get the quality container.
@ -126,7 +126,7 @@ class CuraContainerStack(ContainerStack):
self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit)
@pyqtProperty(InstanceContainer, fset = setQuality, notify = pyqtContainersChanged)
@pyqtProperty(QObject, fset = setQuality, notify = pyqtContainersChanged)
def quality(self) -> InstanceContainer:
"""Get the quality container.
@ -143,7 +143,7 @@ class CuraContainerStack(ContainerStack):
self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit)
@pyqtProperty(InstanceContainer, fset = setMaterial, notify = pyqtContainersChanged)
@pyqtProperty(QObject, fset = setMaterial, notify = pyqtContainersChanged)
def material(self) -> InstanceContainer:
"""Get the material container.
@ -160,7 +160,7 @@ class CuraContainerStack(ContainerStack):
self.replaceContainer(_ContainerIndexes.Variant, new_variant)
@pyqtProperty(InstanceContainer, fset = setVariant, notify = pyqtContainersChanged)
@pyqtProperty(QObject, fset = setVariant, notify = pyqtContainersChanged)
def variant(self) -> InstanceContainer:
"""Get the variant container.
@ -177,7 +177,7 @@ class CuraContainerStack(ContainerStack):
self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes)
@pyqtProperty(InstanceContainer, fset = setDefinitionChanges, notify = pyqtContainersChanged)
@pyqtProperty(QObject, fset = setDefinitionChanges, notify = pyqtContainersChanged)
def definitionChanges(self) -> InstanceContainer:
"""Get the definition changes container.

View file

@ -17,7 +17,7 @@ class CuraStackBuilder:
"""Contains helper functions to create new machines."""
@classmethod
def createMachine(cls, name: str, definition_id: str, machine_extruder_count: Optional[int] = None) -> Optional[GlobalStack]:
def createMachine(cls, name: str, definition_id: str, machine_extruder_count: Optional[int] = None, show_warning_message: bool = True) -> Optional[GlobalStack]:
"""Create a new instance of a machine.
:param name: The name of the new machine.
@ -34,7 +34,8 @@ class CuraStackBuilder:
definitions = registry.findDefinitionContainers(id = definition_id)
if not definitions:
ConfigurationErrorMessage.getInstance().addFaultyContainers(definition_id)
if show_warning_message:
ConfigurationErrorMessage.getInstance().addFaultyContainers(definition_id)
Logger.log("w", "Definition {definition} was not found!", definition = definition_id)
return None

View file

@ -1,7 +1,7 @@
# 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 pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
from UM.FlameProfiler import pyqtSlot
import cura.CuraApplication # To get the global container stack to find the current machine.
@ -31,9 +31,9 @@ class ExtruderManager(QObject):
if ExtruderManager.__instance is not None:
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
ExtruderManager.__instance = self
super().__init__(parent)
ExtruderManager.__instance = self
self._application = cura.CuraApplication.CuraApplication.getInstance()
@ -382,7 +382,10 @@ class ExtruderManager(QObject):
# "fdmextruder". We need to check a machine here so its extruder definition is correct according to this.
def fixSingleExtrusionMachineExtruderDefinition(self, global_stack: "GlobalStack") -> None:
container_registry = ContainerRegistry.getInstance()
expected_extruder_definition_0_id = global_stack.getMetaDataEntry("machine_extruder_trains")["0"]
expected_extruder_stack = global_stack.getMetaDataEntry("machine_extruder_trains")
if expected_extruder_stack is None:
return
expected_extruder_definition_0_id = expected_extruder_stack["0"]
try:
extruder_stack_0 = global_stack.extruderList[0]
except IndexError:

View file

@ -3,7 +3,7 @@
from typing import Any, Dict, TYPE_CHECKING, Optional
from PyQt5.QtCore import pyqtProperty, pyqtSignal
from PyQt6.QtCore import pyqtProperty, pyqtSignal
from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase

View file

@ -6,7 +6,7 @@ import threading
from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List
import uuid
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
from PyQt6.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
from UM.Decorators import deprecated, override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
@ -60,16 +60,6 @@ class GlobalStack(CuraContainerStack):
extrudersChanged = pyqtSignal()
configuredConnectionTypesChanged = pyqtSignal()
@pyqtProperty("QVariantMap", notify = extrudersChanged)
@deprecated("Please use extruderList instead.", "4.4")
def extruders(self) -> Dict[str, "ExtruderStack"]:
"""Get the list of extruders of this stack.
:return: The extruders registered with this stack.
"""
return self._extruders
@pyqtProperty("QVariantList", notify = extrudersChanged)
def extruderList(self) -> List["ExtruderStack"]:
result_tuple_list = sorted(list(self._extruders.items()), key=lambda x: int(x[0]))

View file

@ -1,13 +1,14 @@
# Copyright (c) 2019 Ultimaker B.V.
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
from typing import Any, Dict, List, Set, Tuple, TYPE_CHECKING
from UM.Logger import Logger
from UM.Settings.InstanceContainer import InstanceContainer
import cura.CuraApplication
from UM.Signal import Signal
from cura.Machines.ContainerTree import ContainerTree
from cura.Settings.cura_empty_instance_containers import empty_intent_container
@ -29,6 +30,7 @@ class IntentManager(QObject):
return cls.__instance
intentCategoryChanged = pyqtSignal() #Triggered when we switch categories.
intentCategoryChangedSignal = Signal()
def intentMetadatas(self, definition_id: str, nozzle_name: str, material_base_file: str) -> List[Dict[str, Any]]:
"""Gets the metadata dictionaries of all intent profiles for a given
@ -189,3 +191,4 @@ class IntentManager(QObject):
application.getMachineManager().setQualityGroupByQualityType(quality_type)
if old_intent_category != intent_category:
self.intentCategoryChanged.emit()
self.intentCategoryChangedSignal.emit()

View file

@ -6,7 +6,7 @@ import re
import unicodedata
from typing import Any, List, Dict, TYPE_CHECKING, Optional, cast, Set
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@ -1611,7 +1611,7 @@ class MachineManager(QObject):
if intent_category != "default":
intent_display_name = IntentCategoryModel.translation(intent_category,
"name",
catalog.i18nc("@label", "Unknown"))
intent_category.title())
display_name = "{intent_name} - {the_rest}".format(intent_name = intent_display_name,
the_rest = display_name)
@ -1778,3 +1778,31 @@ class MachineManager(QObject):
abbr_machine += stripped_word
return abbr_machine
@pyqtSlot(str, str, result = bool)
def intentCategoryHasQuality(self, intent_category: str, quality_type: str) -> bool:
""" Checks if there are any quality groups for active extruders that have an intent category """
quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
if quality_type in quality_groups:
quality_group = quality_groups[quality_type]
for node in quality_group.nodes_for_extruders.values():
if any(intent.intent_category == intent_category for intent in node.intents.values()):
return True
return False
@pyqtSlot(str, result = str)
def getDefaultQualityTypeForIntent(self, intent_category) -> str:
""" If there is an intent category for the default machine quality return it, otherwise return the first quality for this intent category """
machine = ContainerTree.getInstance().machines.get(self._global_container_stack.definition.getId())
if self.intentCategoryHasQuality(intent_category, machine.preferred_quality_type):
return machine.preferred_quality_type
for quality_type, quality_group in ContainerTree.getInstance().getCurrentQualityGroups().items():
for node in quality_group.nodes_for_extruders.values():
if any(intent.intent_category == intent_category for intent in node.intents.values()):
return quality_type
return ""

View file

@ -1,8 +1,8 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSlot, pyqtProperty, QObject, pyqtSignal, QRegExp
from PyQt5.QtGui import QValidator
from PyQt6.QtCore import pyqtSlot, pyqtProperty, QObject, pyqtSignal
from PyQt6.QtGui import QValidator
import os #For statvfs.
import urllib #To escape machine names for how they're saved to file.
@ -65,6 +65,6 @@ class MachineNameValidator(QObject):
self.validation_regex = "a^" #Never matches (unless you manage to get "a" before the start of the string... good luck).
self.validationChanged.emit()
@pyqtProperty("QRegExp", notify=validationChanged)
@pyqtProperty(str, notify=validationChanged)
def machineNameRegex(self):
return QRegExp(self.machine_name_regex)
return str(self.machine_name_regex)

View file

@ -2,7 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from typing import List, Optional, TYPE_CHECKING
from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal
from PyQt6.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
from UM.Logger import Logger

View file

@ -3,7 +3,7 @@ import urllib.parse
from configparser import ConfigParser
from typing import List
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
from PyQt6.QtCore import pyqtProperty, QObject, pyqtSignal
from UM.Logger import Logger
from UM.MimeTypeDatabase import MimeTypeDatabase

View file

@ -4,14 +4,14 @@
from typing import Any
from UM.Qt.ListModel import ListModel
from PyQt5.QtCore import pyqtSlot, Qt
from PyQt6.QtCore import pyqtSlot, Qt
class SidebarCustomMenuItemsModel(ListModel):
name_role = Qt.UserRole + 1
actions_role = Qt.UserRole + 2
menu_item_role = Qt.UserRole + 3
menu_item_icon_name_role = Qt.UserRole + 5
name_role = Qt.ItemDataRole.UserRole + 1
actions_role = Qt.ItemDataRole.UserRole + 2
menu_item_role = Qt.ItemDataRole.UserRole + 3
menu_item_icon_name_role = Qt.ItemDataRole.UserRole + 5
def __init__(self, parent=None):
super().__init__(parent)

View file

@ -1,7 +1,7 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty
from UM.Application import Application

View file

@ -5,7 +5,7 @@ import json
import os
from typing import List, Optional
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
from PyQt6.QtNetwork import QLocalServer, QLocalSocket
from UM.Qt.QtApplication import QtApplication #For typing.
from UM.Logger import Logger
@ -29,7 +29,7 @@ class SingleInstance:
single_instance_socket.connectToServer("ultimaker-cura")
single_instance_socket.waitForConnected(msecs = 3000) # wait for 3 seconds
if single_instance_socket.state() != QLocalSocket.ConnectedState:
if single_instance_socket.state() != QLocalSocket.LocalSocketState.ConnectedState:
return False
# We only send the files that need to be opened.
@ -37,7 +37,7 @@ class SingleInstance:
Logger.log("i", "No file need to be opened, do nothing.")
return True
if single_instance_socket.state() == QLocalSocket.ConnectedState:
if single_instance_socket.state() == QLocalSocket.LocalSocketState.ConnectedState:
Logger.log("i", "Connection has been made to the single-instance Cura socket.")
# Protocol is one line of JSON terminated with a carriage return.

View file

@ -2,10 +2,11 @@
# Cura is released under the terms of the LGPLv3 or higher.
import numpy
from PyQt5 import QtCore
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import QImage
from PyQt6 import QtCore
from PyQt6.QtCore import QCoreApplication
from PyQt6.QtGui import QImage
from UM.Logger import Logger
from cura.PreviewPass import PreviewPass
from UM.Application import Application
@ -20,7 +21,7 @@ class Snapshot:
def getImageBoundaries(image: QImage):
# Look at the resulting image to get a good crop.
# Get the pixels as byte array
pixel_array = image.bits().asarray(image.byteCount())
pixel_array = image.bits().asarray(image.sizeInBytes())
width, height = image.width(), image.height()
# Convert to numpy array, assume it's 32 bit (it should always be)
pixels = numpy.frombuffer(pixel_array, dtype=numpy.uint8).reshape([height, width, 4])
@ -64,6 +65,7 @@ class Snapshot:
bbox = bbox + node.getBoundingBox()
# If there is no bounding box, it means that there is no model in the buildplate
if bbox is None:
Logger.log("w", "Unable to create snapshot as we seem to have an empty buildplate")
return None
look_at = bbox.center
@ -96,6 +98,7 @@ class Snapshot:
try:
min_x, max_x, min_y, max_y = Snapshot.getImageBoundaries(pixel_output)
except (ValueError, AttributeError):
Logger.logException("w", "Failed to crop the snapshot!")
return None
size = max((max_x - min_x) / render_width, (max_y - min_y) / render_height)
@ -117,7 +120,7 @@ class Snapshot:
# Scale it to the correct size
scaled_image = cropped_image.scaled(
width, height,
aspectRatioMode = QtCore.Qt.IgnoreAspectRatio,
transformMode = QtCore.Qt.SmoothTransformation)
aspectRatioMode = QtCore.Qt.AspectRatioMode.IgnoreAspectRatio,
transformMode = QtCore.Qt.TransformationMode.SmoothTransformation)
return scaled_image

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, QUrl
from PyQt6.QtCore import pyqtProperty, QUrl
from UM.Stage import Stage

View file

@ -1,9 +1,9 @@
# Copyright (c) 2020 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt, QCoreApplication, QTimer
from PyQt5.QtGui import QPixmap, QColor, QFont, QPen, QPainter
from PyQt5.QtWidgets import QSplashScreen
from PyQt6.QtCore import Qt, QCoreApplication, QTimer
from PyQt6.QtGui import QPixmap, QColor, QFont, QPen, QPainter
from PyQt6.QtWidgets import QSplashScreen
from UM.Resources import Resources
from UM.Application import Application
@ -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:
@ -63,29 +63,27 @@ class CuraSplashScreen(QSplashScreen):
painter.save()
painter.setPen(QColor(255, 255, 255, 255))
painter.setRenderHint(QPainter.Antialiasing)
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
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.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.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.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop, f"{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,8 +93,8 @@ class CuraSplashScreen(QSplashScreen):
pen.setColor(QColor(255, 255, 255, 255))
painter.setPen(pen)
painter.setFont(font)
painter.drawText(100, 128, 170, 64,
Qt.AlignLeft | Qt.AlignVCenter | Qt.TextWordWrap,
painter.drawText(70, 308, 170, 48,
Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter | Qt.TextFlag.TextWordWrap,
self._current_message)
painter.restore()
@ -108,7 +106,7 @@ class CuraSplashScreen(QSplashScreen):
self._current_message = message
self.messageChanged.emit(message)
QCoreApplication.flush()
QCoreApplication.processEvents() # Used to be .flush() -- this might be the closest alternative, but uncertain.
self.repaint()
def close(self):

View file

@ -3,7 +3,7 @@
from typing import TYPE_CHECKING, Optional, List, Set, Dict
from PyQt5.QtCore import QObject
from PyQt6.QtCore import QObject
from UM.FlameProfiler import pyqtSlot
from UM.Logger import Logger

View file

@ -2,7 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, TYPE_CHECKING
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt6.QtCore import QObject, pyqtSlot
from UM.i18n import i18nCatalog

View file

@ -4,7 +4,7 @@ from UM.Logger import Logger
import re
from typing import Dict, List, Optional, Union
from PyQt5.QtCore import QTimer, Qt
from PyQt6.QtCore import QTimer, Qt
from UM.Application import Application
from UM.Qt.ListModel import ListModel
@ -34,14 +34,14 @@ class _NodeInfo:
class ObjectsModel(ListModel):
"""Keep track of all objects in the project"""
NameRole = Qt.UserRole + 1
SelectedRole = Qt.UserRole + 2
OutsideAreaRole = Qt.UserRole + 3
BuilplateNumberRole = Qt.UserRole + 4
NodeRole = Qt.UserRole + 5
PerObjectSettingsCountRole = Qt.UserRole + 6
MeshTypeRole = Qt.UserRole + 7
ExtruderNumberRole = Qt.UserRole + 8
NameRole = Qt.ItemDataRole.UserRole + 1
SelectedRole = Qt.ItemDataRole.UserRole + 2
OutsideAreaRole = Qt.ItemDataRole.UserRole + 3
BuilplateNumberRole = Qt.ItemDataRole.UserRole + 4
NodeRole = Qt.ItemDataRole.UserRole + 5
PerObjectSettingsCountRole = Qt.ItemDataRole.UserRole + 6
MeshTypeRole = Qt.ItemDataRole.UserRole + 7
ExtruderNumberRole = Qt.ItemDataRole.UserRole + 8
def __init__(self, parent = None) -> None:
super().__init__(parent)

View file

@ -6,7 +6,7 @@ import math
import os
from typing import Dict, List, Optional, TYPE_CHECKING
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot, QTimer
from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot, QTimer
from UM.Logger import Logger
from UM.Qt.Duration import Duration
@ -132,7 +132,7 @@ class PrintInformation(QObject):
self._updateJobName()
self.preSlicedChanged.emit()
@pyqtProperty(Duration, notify = currentPrintTimeChanged)
@pyqtProperty(QObject, notify = currentPrintTimeChanged)
def currentPrintTime(self) -> Duration:
return self._current_print_time[self._active_build_plate]

View file

@ -1,7 +1,7 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt6.QtCore import QObject, pyqtSlot
from cura import CuraApplication

View file

@ -4,7 +4,7 @@
import collections
from typing import Optional, Dict, List, cast
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt6.QtCore import QObject, pyqtSlot
from UM.i18n import i18nCatalog
from UM.Resources import Resources

View file

@ -6,7 +6,7 @@ import os
from collections import deque
from typing import TYPE_CHECKING, Optional, List, Dict, Any
from PyQt5.QtCore import QUrl, Qt, pyqtSlot, pyqtProperty, pyqtSignal
from PyQt6.QtCore import QUrl, Qt, pyqtSlot, pyqtProperty, pyqtSignal
from UM.i18n import i18nCatalog
from UM.Logger import Logger
@ -14,7 +14,7 @@ from UM.Qt.ListModel import ListModel
from UM.Resources import Resources
if TYPE_CHECKING:
from PyQt5.QtCore import QObject
from PyQt6.QtCore import QObject
from cura.CuraApplication import CuraApplication
@ -36,11 +36,11 @@ class WelcomePagesModel(ListModel):
Note that in any case, a page that has its "should_show_function" == False will ALWAYS be skipped.
"""
IdRole = Qt.UserRole + 1 # Page ID
PageUrlRole = Qt.UserRole + 2 # URL to the page's QML file
NextPageIdRole = Qt.UserRole + 3 # The next page ID it should go to
NextPageButtonTextRole = Qt.UserRole + 4 # The text for the next page button
PreviousPageButtonTextRole = Qt.UserRole + 5 # The text for the previous page button
IdRole = Qt.ItemDataRole.UserRole + 1 # Page ID
PageUrlRole = Qt.ItemDataRole.UserRole + 2 # URL to the page's QML file
NextPageIdRole = Qt.ItemDataRole.UserRole + 3 # The next page ID it should go to
NextPageButtonTextRole = Qt.ItemDataRole.UserRole + 4 # The text for the next page button
PreviousPageButtonTextRole = Qt.ItemDataRole.UserRole + 5 # The text for the previous page button
def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
super().__init__(parent)

View file

@ -4,7 +4,7 @@
import os
from typing import Optional, Dict, List, Tuple, TYPE_CHECKING
from PyQt5.QtCore import pyqtProperty, pyqtSlot
from PyQt6.QtCore import pyqtProperty, pyqtSlot
from UM.Logger import Logger
from UM.Resources import Resources
@ -12,7 +12,7 @@ from UM.Resources import Resources
from cura.UI.WelcomePagesModel import WelcomePagesModel
if TYPE_CHECKING:
from PyQt5.QtCore import QObject
from PyQt6.QtCore import QObject
from cura.CuraApplication import CuraApplication

View file

@ -1,8 +1,8 @@
# Copyright (c) 2021 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, pyqtSlot, QObject, QUrl
from PyQt5.QtGui import QDesktopServices
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt6.QtGui import QDesktopServices
from typing import Dict, Optional, TYPE_CHECKING
import zipfile # To export all materials in a .zip archive.
@ -18,6 +18,7 @@ if TYPE_CHECKING:
from UM.Signal import Signal
catalog = i18nCatalog("cura")
class CloudMaterialSync(QObject):
"""
Handles the synchronisation of material profiles with cloud accounts.
@ -44,7 +45,6 @@ class CloudMaterialSync(QObject):
break
def openSyncAllWindow(self):
self.reset()
if self.sync_all_dialog is None:

View file

@ -1,7 +1,7 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtNetwork import QNetworkRequest
from PyQt6.QtNetwork import QNetworkRequest
from UM.Logger import Logger
from UM.TaskManagement.HttpRequestScope import DefaultUserAgentScope

View file

@ -4,7 +4,7 @@
import socket
from typing import Optional
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt6.QtCore import QObject, pyqtSlot
#

View file

@ -18,8 +18,9 @@ import os
if sys.platform != "linux": # Turns out the Linux build _does_ use this, but we're not making an Enterprise release for that system anyway.
os.environ["QT_PLUGIN_PATH"] = "" # Security workaround: Don't need it, and introduces an attack vector, so set to nul.
os.environ["QML2_IMPORT_PATH"] = "" # Security workaround: Don't need it, and introduces an attack vector, so set to nul.
os.environ["QT_OPENGL_DLL"] = "" # Security workaround: Don't need it, and introduces an attack vector, so set to nul.
from PyQt5.QtNetwork import QSslConfiguration, QSslSocket
from PyQt6.QtNetwork import QSslConfiguration, QSslSocket
from UM.Platform import Platform
from cura import ApplicationMetadata
@ -151,15 +152,15 @@ def exceptHook(hook_type, value, traceback):
# The flag "CuraApplication.Created" is set to True when CuraApplication finishes its constructor call.
#
# Before the "started" flag is set to True, the Qt event loop has not started yet. The event loop is a blocking
# call to the QApplication.exec_(). In this case, we need to:
# call to the QApplication.exec(). In this case, we need to:
# 1. Remove all scheduled events so no more unnecessary events will be processed, such as loading the main dialog,
# loading the machine, etc.
# 2. Start the Qt event loop with exec_() and show the Crash Dialog.
# 2. Start the Qt event loop with exec() and show the Crash Dialog.
#
# If the application has finished its initialization and was running fine, and then something causes a crash,
# we run the old routine to show the Crash Dialog.
#
from PyQt5.Qt import QApplication
from PyQt6.QtWidgets import QApplication
if CuraApplication.Created:
_crash_handler = CrashHandler(hook_type, value, traceback, has_started)
if CuraApplication.splash is not None:
@ -167,7 +168,7 @@ def exceptHook(hook_type, value, traceback):
if not has_started:
CuraApplication.getInstance().removePostedEvents(None)
_crash_handler.early_crash_dialog.show()
sys.exit(CuraApplication.getInstance().exec_())
sys.exit(CuraApplication.getInstance().exec())
else:
_crash_handler.show()
else:
@ -178,7 +179,7 @@ def exceptHook(hook_type, value, traceback):
if CuraApplication.splash is not None:
CuraApplication.splash.close()
_crash_handler.early_crash_dialog.show()
sys.exit(application.exec_())
sys.exit(application.exec())
# Set exception hook to use the crash dialog handler
@ -231,7 +232,7 @@ if Platform.isLinux():
if ApplicationMetadata.CuraDebugMode:
ssl_conf = QSslConfiguration.defaultConfiguration()
ssl_conf.setPeerVerifyMode(QSslSocket.VerifyNone)
ssl_conf.setPeerVerifyMode(QSslSocket.PeerVerifyMode.VerifyNone)
QSslConfiguration.setDefaultConfiguration(ssl_conf)
app = CuraApplication()

View file

@ -1,11 +1,11 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
# Copyright (c) 2021-2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
import zipfile
from typing import List, Optional, Union, TYPE_CHECKING, cast
import Savitar
import pySavitar as Savitar
import numpy
from UM.Logger import Logger
@ -304,4 +304,4 @@ class ThreeMFReader(MeshReader):
unit = "millimeter"
scale = conversion_to_mm[unit]
return Vector(scale, scale, scale)
return Vector(scale, scale, scale)

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