mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-07 22:13:58 -06:00
Merge branch 'master' into Rigid3D
This commit is contained in:
commit
b7a13e6abc
4879 changed files with 123531 additions and 108540 deletions
4
.github/ISSUE_TEMPLATE/bugreport.yaml
vendored
4
.github/ISSUE_TEMPLATE/bugreport.yaml
vendored
|
@ -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
|
||||
|
|
13
.github/no-response.yml
vendored
13
.github/no-response.yml
vendored
|
@ -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
31
.github/workflows/no-response.yml
vendored
Normal 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
2
.gitignore
vendored
|
@ -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
|
||||
|
|
35
CITATION.cff
35
CITATION.cff
|
@ -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
|
||||
...
|
||||
|
|
|
@ -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
5
SECURITY.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Reporting vulnerabilities
|
||||
|
||||
If you discover a vulnerability, please let us know as soon as possible via `security@ultimaker.com`.
|
||||
Please do not take advantage of the vulnerability and do not reveal the problem to others.
|
||||
To allow us to resolve the issue, please do provide us with sufficient information to reproduce the problem.
|
|
@ -1,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)
|
||||
|
|
|
@ -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}
|
||||
)
|
||||
|
|
|
@ -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&utm_medium=software&utm_campaign=cura-update-linux</url>
|
||||
<translation type="gettext">Cura</translation>
|
||||
<content_rating type="oars-1.1" />
|
||||
</component>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,258 +0,0 @@
|
|||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Optional
|
||||
|
||||
from UM.Decorators import deprecated
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.Polygon import Polygon
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from cura.Arranging.ShapeArray import ShapeArray
|
||||
from cura.BuildVolume import BuildVolume
|
||||
from cura.Scene import ZOffsetDecorator
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy
|
||||
import copy
|
||||
|
||||
LocationSuggestion = namedtuple("LocationSuggestion", ["x", "y", "penalty_points", "priority"])
|
||||
"""Return object for bestSpot"""
|
||||
|
||||
|
||||
class Arrange:
|
||||
"""
|
||||
The Arrange classed is used together with :py:class:`cura.Arranging.ShapeArray.ShapeArray`. Use it to find good locations for objects that you try to put
|
||||
on a build place. Different priority schemes can be defined so it alters the behavior while using the same logic.
|
||||
|
||||
.. note::
|
||||
|
||||
Make sure the scale is the same between :py:class:`cura.Arranging.ShapeArray.ShapeArray` objects and the :py:class:`cura.Arranging.Arrange.Arrange` instance.
|
||||
"""
|
||||
|
||||
build_volume = None # type: Optional[BuildVolume]
|
||||
|
||||
@deprecated("Use the functions in Nest2dArrange instead", "4.8")
|
||||
def __init__(self, x, y, offset_x, offset_y, scale = 0.5):
|
||||
self._scale = scale # convert input coordinates to arrange coordinates
|
||||
world_x, world_y = int(x * self._scale), int(y * self._scale)
|
||||
self._shape = (world_y, world_x)
|
||||
self._priority = numpy.zeros((world_y, world_x), dtype=numpy.int32) # beware: these are indexed (y, x)
|
||||
self._priority_unique_values = []
|
||||
self._occupied = numpy.zeros((world_y, world_x), dtype=numpy.int32) # beware: these are indexed (y, x)
|
||||
self._offset_x = int(offset_x * self._scale)
|
||||
self._offset_y = int(offset_y * self._scale)
|
||||
self._last_priority = 0
|
||||
self._is_empty = True
|
||||
|
||||
@classmethod
|
||||
@deprecated("Use the functions in Nest2dArrange instead", "4.8")
|
||||
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250, min_offset = 8) -> "Arrange":
|
||||
"""Helper to create an :py:class:`cura.Arranging.Arrange.Arrange` instance
|
||||
|
||||
Either fill in scene_root and create will find all sliceable nodes by itself, or use fixed_nodes to provide the
|
||||
nodes yourself.
|
||||
|
||||
:param scene_root: Root for finding all scene nodes default = None
|
||||
:param fixed_nodes: Scene nodes to be placed default = None
|
||||
:param scale: default = 0.5
|
||||
:param x: default = 350
|
||||
:param y: default = 250
|
||||
:param min_offset: default = 8
|
||||
"""
|
||||
|
||||
arranger = Arrange(x, y, x // 2, y // 2, scale = scale)
|
||||
arranger.centerFirst()
|
||||
|
||||
if fixed_nodes is None:
|
||||
fixed_nodes = []
|
||||
for node_ in DepthFirstIterator(scene_root):
|
||||
# Only count sliceable objects
|
||||
if node_.callDecoration("isSliceable"):
|
||||
fixed_nodes.append(node_)
|
||||
|
||||
# Place all objects fixed nodes
|
||||
for fixed_node in fixed_nodes:
|
||||
vertices = fixed_node.callDecoration("getConvexHullHead") or fixed_node.callDecoration("getConvexHull")
|
||||
if not vertices:
|
||||
continue
|
||||
vertices = vertices.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
|
||||
points = copy.deepcopy(vertices._points)
|
||||
|
||||
# After scaling (like up to 0.1 mm) the node might not have points
|
||||
if not points.size:
|
||||
continue
|
||||
try:
|
||||
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
|
||||
except ValueError:
|
||||
Logger.logException("w", "Unable to create polygon")
|
||||
continue
|
||||
arranger.place(0, 0, shape_arr)
|
||||
|
||||
# If a build volume was set, add the disallowed areas
|
||||
if Arrange.build_volume:
|
||||
disallowed_areas = Arrange.build_volume.getDisallowedAreasNoBrim()
|
||||
for area in disallowed_areas:
|
||||
points = copy.deepcopy(area._points)
|
||||
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
|
||||
arranger.place(0, 0, shape_arr, update_empty = False)
|
||||
return arranger
|
||||
|
||||
def resetLastPriority(self):
|
||||
"""This resets the optimization for finding location based on size"""
|
||||
|
||||
self._last_priority = 0
|
||||
|
||||
@deprecated("Use the functions in Nest2dArrange instead", "4.8")
|
||||
def findNodePlacement(self, node: SceneNode, offset_shape_arr: ShapeArray, hull_shape_arr: ShapeArray, step = 1) -> bool:
|
||||
"""Find placement for a node (using offset shape) and place it (using hull shape)
|
||||
|
||||
:param node: The node to be placed
|
||||
:param offset_shape_arr: shape array with offset, for placing the shape
|
||||
:param hull_shape_arr: shape array without offset, used to find location
|
||||
:param step: default = 1
|
||||
:return: the nodes that should be placed
|
||||
"""
|
||||
|
||||
best_spot = self.bestSpot(
|
||||
hull_shape_arr, start_prio = self._last_priority, step = step)
|
||||
x, y = best_spot.x, best_spot.y
|
||||
|
||||
# Save the last priority.
|
||||
self._last_priority = best_spot.priority
|
||||
|
||||
# Ensure that the object is above the build platform
|
||||
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
|
||||
bbox = node.getBoundingBox()
|
||||
if bbox:
|
||||
center_y = node.getWorldPosition().y - bbox.bottom
|
||||
else:
|
||||
center_y = 0
|
||||
|
||||
if x is not None: # We could find a place
|
||||
node.setPosition(Vector(x, center_y, y))
|
||||
found_spot = True
|
||||
self.place(x, y, offset_shape_arr) # place the object in arranger
|
||||
else:
|
||||
Logger.log("d", "Could not find spot!")
|
||||
found_spot = False
|
||||
node.setPosition(Vector(200, center_y, 100))
|
||||
return found_spot
|
||||
|
||||
def centerFirst(self):
|
||||
"""Fill priority, center is best. Lower value is better. """
|
||||
|
||||
# Square distance: creates a more round shape
|
||||
self._priority = numpy.fromfunction(
|
||||
lambda j, i: (self._offset_x - i) ** 2 + (self._offset_y - j) ** 2, self._shape, dtype=numpy.int32)
|
||||
self._priority_unique_values = numpy.unique(self._priority)
|
||||
self._priority_unique_values.sort()
|
||||
|
||||
def backFirst(self):
|
||||
"""Fill priority, back is best. Lower value is better """
|
||||
|
||||
self._priority = numpy.fromfunction(
|
||||
lambda j, i: 10 * j + abs(self._offset_x - i), self._shape, dtype=numpy.int32)
|
||||
self._priority_unique_values = numpy.unique(self._priority)
|
||||
self._priority_unique_values.sort()
|
||||
|
||||
def checkShape(self, x, y, shape_arr) -> Optional[numpy.ndarray]:
|
||||
"""Return the amount of "penalty points" for polygon, which is the sum of priority
|
||||
|
||||
:param x: x-coordinate to check shape
|
||||
:param y: y-coordinate to check shape
|
||||
:param shape_arr: the shape array object to place
|
||||
:return: None if occupied
|
||||
"""
|
||||
|
||||
x = int(self._scale * x)
|
||||
y = int(self._scale * y)
|
||||
offset_x = x + self._offset_x + shape_arr.offset_x
|
||||
offset_y = y + self._offset_y + shape_arr.offset_y
|
||||
if offset_x < 0 or offset_y < 0:
|
||||
return None # out of bounds in self._occupied
|
||||
occupied_x_max = offset_x + shape_arr.arr.shape[1]
|
||||
occupied_y_max = offset_y + shape_arr.arr.shape[0]
|
||||
if occupied_x_max > self._occupied.shape[1] + 1 or occupied_y_max > self._occupied.shape[0] + 1:
|
||||
return None # out of bounds in self._occupied
|
||||
occupied_slice = self._occupied[
|
||||
offset_y:occupied_y_max,
|
||||
offset_x:occupied_x_max]
|
||||
try:
|
||||
if numpy.any(occupied_slice[numpy.where(shape_arr.arr == 1)]):
|
||||
return None
|
||||
except IndexError: # out of bounds if you try to place an object outside
|
||||
return None
|
||||
prio_slice = self._priority[
|
||||
offset_y:offset_y + shape_arr.arr.shape[0],
|
||||
offset_x:offset_x + shape_arr.arr.shape[1]]
|
||||
return numpy.sum(prio_slice[numpy.where(shape_arr.arr == 1)])
|
||||
|
||||
def bestSpot(self, shape_arr, start_prio = 0, step = 1) -> LocationSuggestion:
|
||||
"""Find "best" spot for ShapeArray
|
||||
|
||||
:param shape_arr: shape array
|
||||
:param start_prio: Start with this priority value (and skip the ones before)
|
||||
:param step: Slicing value, higher = more skips = faster but less accurate
|
||||
:return: namedtuple with properties x, y, penalty_points, priority.
|
||||
"""
|
||||
|
||||
start_idx_list = numpy.where(self._priority_unique_values == start_prio)
|
||||
if start_idx_list:
|
||||
try:
|
||||
start_idx = start_idx_list[0][0]
|
||||
except IndexError:
|
||||
start_idx = 0
|
||||
else:
|
||||
start_idx = 0
|
||||
priority = 0
|
||||
for priority in self._priority_unique_values[start_idx::step]:
|
||||
tryout_idx = numpy.where(self._priority == priority)
|
||||
for idx in range(len(tryout_idx[0])):
|
||||
x = tryout_idx[1][idx]
|
||||
y = tryout_idx[0][idx]
|
||||
projected_x = int((x - self._offset_x) / self._scale)
|
||||
projected_y = int((y - self._offset_y) / self._scale)
|
||||
|
||||
penalty_points = self.checkShape(projected_x, projected_y, shape_arr)
|
||||
if penalty_points is not None:
|
||||
return LocationSuggestion(x = projected_x, y = projected_y, penalty_points = penalty_points, priority = priority)
|
||||
return LocationSuggestion(x = None, y = None, penalty_points = None, priority = priority) # No suitable location found :-(
|
||||
|
||||
def place(self, x, y, shape_arr, update_empty = True):
|
||||
"""Place the object.
|
||||
|
||||
Marks the locations in self._occupied and self._priority
|
||||
|
||||
:param x:
|
||||
:param y:
|
||||
:param shape_arr:
|
||||
:param update_empty: updates the _is_empty, used when adding disallowed areas
|
||||
"""
|
||||
|
||||
x = int(self._scale * x)
|
||||
y = int(self._scale * y)
|
||||
offset_x = x + self._offset_x + shape_arr.offset_x
|
||||
offset_y = y + self._offset_y + shape_arr.offset_y
|
||||
shape_y, shape_x = self._occupied.shape
|
||||
|
||||
min_x = min(max(offset_x, 0), shape_x - 1)
|
||||
min_y = min(max(offset_y, 0), shape_y - 1)
|
||||
max_x = min(max(offset_x + shape_arr.arr.shape[1], 0), shape_x - 1)
|
||||
max_y = min(max(offset_y + shape_arr.arr.shape[0], 0), shape_y - 1)
|
||||
occupied_slice = self._occupied[min_y:max_y, min_x:max_x]
|
||||
# we use a slice of shape because it can be out of bounds
|
||||
new_occupied = numpy.where(shape_arr.arr[
|
||||
min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)
|
||||
if update_empty and new_occupied:
|
||||
self._is_empty = False
|
||||
occupied_slice[new_occupied] = 1
|
||||
|
||||
# Set priority to low (= high number), so it won't get picked at trying out.
|
||||
prio_slice = self._priority[min_y:max_y, min_x:max_x]
|
||||
prio_slice[new_occupied] = 999
|
||||
|
||||
@property
|
||||
def isEmpty(self):
|
||||
return self._is_empty
|
|
@ -1,154 +0,0 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Job import Job
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Operations.TranslateOperation import TranslateOperation
|
||||
from UM.Operations.GroupedOperation import GroupedOperation
|
||||
from UM.Message import Message
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
|
||||
from cura.Arranging.Arrange import Arrange
|
||||
from cura.Arranging.ShapeArray import ShapeArray
|
||||
|
||||
from typing import List
|
||||
|
||||
|
||||
class ArrangeArray:
|
||||
"""Do arrangements on multiple build plates (aka builtiplexer)"""
|
||||
|
||||
def __init__(self, x: int, y: int, fixed_nodes: List[SceneNode]) -> None:
|
||||
self._x = x
|
||||
self._y = y
|
||||
self._fixed_nodes = fixed_nodes
|
||||
self._count = 0
|
||||
self._first_empty = None
|
||||
self._has_empty = False
|
||||
self._arrange = [] # type: List[Arrange]
|
||||
|
||||
def _updateFirstEmpty(self):
|
||||
for i, a in enumerate(self._arrange):
|
||||
if a.isEmpty:
|
||||
self._first_empty = i
|
||||
self._has_empty = True
|
||||
return
|
||||
self._first_empty = None
|
||||
self._has_empty = False
|
||||
|
||||
def add(self):
|
||||
new_arrange = Arrange.create(x = self._x, y = self._y, fixed_nodes = self._fixed_nodes)
|
||||
self._arrange.append(new_arrange)
|
||||
self._count += 1
|
||||
self._updateFirstEmpty()
|
||||
|
||||
def count(self):
|
||||
return self._count
|
||||
|
||||
def get(self, index):
|
||||
return self._arrange[index]
|
||||
|
||||
def getFirstEmpty(self):
|
||||
if not self._has_empty:
|
||||
self.add()
|
||||
return self._arrange[self._first_empty]
|
||||
|
||||
|
||||
class ArrangeObjectsAllBuildPlatesJob(Job):
|
||||
def __init__(self, nodes: List[SceneNode], min_offset = 8) -> None:
|
||||
super().__init__()
|
||||
self._nodes = nodes
|
||||
self._min_offset = min_offset
|
||||
|
||||
def run(self):
|
||||
status_message = Message(i18n_catalog.i18nc("@info:status", "Finding new location for objects"),
|
||||
lifetime = 0,
|
||||
dismissable=False,
|
||||
progress = 0,
|
||||
title = i18n_catalog.i18nc("@info:title", "Finding Location"))
|
||||
status_message.show()
|
||||
|
||||
|
||||
# Collect nodes to be placed
|
||||
nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
|
||||
for node in self._nodes:
|
||||
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset)
|
||||
nodes_arr.append((offset_shape_arr.arr.shape[0] * offset_shape_arr.arr.shape[1], node, offset_shape_arr, hull_shape_arr))
|
||||
|
||||
# Sort the nodes with the biggest area first.
|
||||
nodes_arr.sort(key=lambda item: item[0])
|
||||
nodes_arr.reverse()
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
machine_width = global_container_stack.getProperty("machine_width", "value")
|
||||
machine_depth = global_container_stack.getProperty("machine_depth", "value")
|
||||
|
||||
x, y = machine_width, machine_depth
|
||||
|
||||
arrange_array = ArrangeArray(x = x, y = y, fixed_nodes = [])
|
||||
arrange_array.add()
|
||||
|
||||
# Place nodes one at a time
|
||||
start_priority = 0
|
||||
grouped_operation = GroupedOperation()
|
||||
found_solution_for_all = True
|
||||
left_over_nodes = [] # nodes that do not fit on an empty build plate
|
||||
|
||||
for idx, (size, node, offset_shape_arr, hull_shape_arr) in enumerate(nodes_arr):
|
||||
# For performance reasons, we assume that when a location does not fit,
|
||||
# it will also not fit for the next object (while what can be untrue).
|
||||
|
||||
try_placement = True
|
||||
|
||||
current_build_plate_number = 0 # always start with the first one
|
||||
|
||||
while try_placement:
|
||||
# make sure that current_build_plate_number is not going crazy or you'll have a lot of arrange objects
|
||||
while current_build_plate_number >= arrange_array.count():
|
||||
arrange_array.add()
|
||||
arranger = arrange_array.get(current_build_plate_number)
|
||||
|
||||
best_spot = arranger.bestSpot(hull_shape_arr, start_prio=start_priority)
|
||||
x, y = best_spot.x, best_spot.y
|
||||
node.removeDecorator(ZOffsetDecorator)
|
||||
if node.getBoundingBox():
|
||||
center_y = node.getWorldPosition().y - node.getBoundingBox().bottom
|
||||
else:
|
||||
center_y = 0
|
||||
if x is not None: # We could find a place
|
||||
arranger.place(x, y, offset_shape_arr) # place the object in the arranger
|
||||
|
||||
node.callDecoration("setBuildPlateNumber", current_build_plate_number)
|
||||
grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))
|
||||
try_placement = False
|
||||
else:
|
||||
# very naive, because we skip to the next build plate if one model doesn't fit.
|
||||
if arranger.isEmpty:
|
||||
# apparently we can never place this object
|
||||
left_over_nodes.append(node)
|
||||
try_placement = False
|
||||
else:
|
||||
# try next build plate
|
||||
current_build_plate_number += 1
|
||||
try_placement = True
|
||||
|
||||
status_message.setProgress((idx + 1) / len(nodes_arr) * 100)
|
||||
Job.yieldThread()
|
||||
|
||||
for node in left_over_nodes:
|
||||
node.callDecoration("setBuildPlateNumber", -1) # these are not on any build plate
|
||||
found_solution_for_all = False
|
||||
|
||||
grouped_operation.push()
|
||||
|
||||
status_message.hide()
|
||||
|
||||
if not found_solution_for_all:
|
||||
no_full_solution_message = Message(i18n_catalog.i18nc("@info:status",
|
||||
"Unable to find a location within the build volume for all objects"),
|
||||
title = i18n_catalog.i18nc("@info:title", "Can't Find Location"),
|
||||
message_type = Message.MessageType.WARNING)
|
||||
no_full_solution_message.show()
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"],
|
||||
|
|
131
cura/Machines/Models/ActiveIntentQualitiesModel.py
Normal file
131
cura/Machines/Models/ActiveIntentQualitiesModel.py
Normal 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
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
129
cura/Machines/Models/IntentSelectionModel.py
Normal file
129
cura/Machines/Models/IntentSelectionModel.py
Normal 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)
|
||||
|
||||
|
|
@ -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())
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, QObject
|
||||
from PyQt6.QtCore import pyqtProperty, QObject
|
||||
|
||||
|
||||
class MaterialOutputModel(QObject):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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")
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import socket
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
from PyQt6.QtCore import QObject, pyqtSlot
|
||||
|
||||
|
||||
#
|
||||
|
|
15
cura_app.py
15
cura_app.py
|
@ -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()
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue