Merge branch 'main' into CURA-11561_mockup_pap

This commit is contained in:
Saumya Jain 2024-02-16 11:47:32 +01:00 committed by GitHub
commit 80d7536763
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
178 changed files with 1253 additions and 434 deletions

View file

@ -55,7 +55,8 @@ exe = EXE(
target_arch={{ target_arch }},
codesign_identity=os.getenv('CODESIGN_IDENTITY', None),
entitlements_file={{ entitlements_file }},
icon={{ icon }}
icon={{ icon }},
contents_directory='.'
)
coll = COLLECT(
@ -70,188 +71,7 @@ coll = COLLECT(
)
{% if macos == true %}
# PyInstaller seems to copy everything in the resource folder for the MacOS, this causes issues with codesigning and notarizing
# The folder structure should adhere to the one specified in Table 2-5
# https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1
# The class below is basically ducktyping the BUNDLE class of PyInstaller and using our own `assemble` method for more fine-grain and specific
# control. Some code of the method below is copied from:
# https://github.com/pyinstaller/pyinstaller/blob/22d1d2a5378228744cc95f14904dae1664df32c4/PyInstaller/building/osx.py#L115
#-----------------------------------------------------------------------------
# Copyright (c) 2005-2022, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License (version 2
# or later) with exception for distributing the bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
#-----------------------------------------------------------------------------
import plistlib
import shutil
import PyInstaller.utils.osx as osxutils
from pathlib import Path
from PyInstaller.building.osx import BUNDLE
from PyInstaller.building.utils import (_check_path_overlap, _rmtree, add_suffix_to_extension, checkCache)
from PyInstaller.building.datastruct import logger
from PyInstaller.building.icon import normalize_icon_type
class UMBUNDLE(BUNDLE):
def assemble(self):
from PyInstaller.config import CONF
if _check_path_overlap(self.name) and os.path.isdir(self.name):
_rmtree(self.name)
logger.info("Building BUNDLE %s", self.tocbasename)
# Create a minimal Mac bundle structure.
macos_path = Path(self.name, "Contents", "MacOS")
resources_path = Path(self.name, "Contents", "Resources")
frameworks_path = Path(self.name, "Contents", "Frameworks")
os.makedirs(macos_path)
os.makedirs(resources_path)
os.makedirs(frameworks_path)
# Makes sure the icon exists and attempts to convert to the proper format if applicable
self.icon = normalize_icon_type(self.icon, ("icns",), "icns", CONF["workpath"])
# Ensure icon path is absolute
self.icon = os.path.abspath(self.icon)
# Copy icns icon to Resources directory.
shutil.copy(self.icon, os.path.join(self.name, 'Contents', 'Resources'))
# Key/values for a minimal Info.plist file
info_plist_dict = {
"CFBundleDisplayName": self.appname,
"CFBundleName": self.appname,
# Required by 'codesign' utility.
# The value for CFBundleIdentifier is used as the default unique name of your program for Code Signing
# purposes. It even identifies the APP for access to restricted OS X areas like Keychain.
#
# The identifier used for signing must be globally unique. The usual form for this identifier is a
# hierarchical name in reverse DNS notation, starting with the toplevel domain, followed by the company
# name, followed by the department within the company, and ending with the product name. Usually in the
# form: com.mycompany.department.appname
# CLI option --osx-bundle-identifier sets this value.
"CFBundleIdentifier": self.bundle_identifier,
"CFBundleExecutable": os.path.basename(self.exename),
"CFBundleIconFile": os.path.basename(self.icon),
"CFBundleInfoDictionaryVersion": "6.0",
"CFBundlePackageType": "APPL",
"CFBundleVersionString": self.version,
"CFBundleShortVersionString": self.version,
}
# Set some default values. But they still can be overwritten by the user.
if self.console:
# Setting EXE console=True implies LSBackgroundOnly=True.
info_plist_dict['LSBackgroundOnly'] = True
else:
# Let's use high resolution by default.
info_plist_dict['NSHighResolutionCapable'] = True
# Merge info_plist settings from spec file
if isinstance(self.info_plist, dict) and self.info_plist:
info_plist_dict.update(self.info_plist)
plist_filename = os.path.join(self.name, "Contents", "Info.plist")
with open(plist_filename, "wb") as plist_fh:
plistlib.dump(info_plist_dict, plist_fh)
links = []
_QT_BASE_PATH = {'PySide2', 'PySide6', 'PyQt5', 'PyQt6', 'PySide6'}
for inm, fnm, typ in self.toc:
# Adjust name for extensions, if applicable
inm, fnm, typ = add_suffix_to_extension(inm, fnm, typ)
inm = Path(inm)
fnm = Path(fnm)
# Copy files from cache. This ensures that are used files with relative paths to dynamic library
# dependencies (@executable_path)
if typ in ('EXTENSION', 'BINARY') or (typ == 'DATA' and inm.suffix == '.so'):
if any(['.' in p for p in inm.parent.parts]):
inm = Path(inm.name)
fnm = Path(checkCache(
str(fnm),
strip = self.strip,
upx = self.upx,
upx_exclude = self.upx_exclude,
dist_nm = str(inm),
target_arch = self.target_arch,
codesign_identity = self.codesign_identity,
entitlements_file = self.entitlements_file,
strict_arch_validation = (typ == 'EXTENSION'),
))
frame_dst = frameworks_path.joinpath(inm)
if not frame_dst.exists():
if frame_dst.is_dir():
os.makedirs(frame_dst, exist_ok = True)
else:
os.makedirs(frame_dst.parent, exist_ok = True)
shutil.copy(fnm, frame_dst, follow_symlinks = True)
macos_dst = macos_path.joinpath(inm)
if not macos_dst.exists():
if macos_dst.is_dir():
os.makedirs(macos_dst, exist_ok = True)
else:
os.makedirs(macos_dst.parent, exist_ok = True)
# Create relative symlink to the framework
symlink_to = Path(*[".." for p in macos_dst.relative_to(macos_path).parts], "Frameworks").joinpath(
frame_dst.relative_to(frameworks_path))
try:
macos_dst.symlink_to(symlink_to)
except FileExistsError:
pass
else:
if typ == 'DATA':
if any(['.' in p for p in inm.parent.parts]) or inm.suffix == '.so':
# Skip info dist egg and some not needed folders in tcl and tk, since they all contain dots in their files
logger.warning(f"Skipping DATA file {inm}")
continue
res_dst = resources_path.joinpath(inm)
if not res_dst.exists():
if res_dst.is_dir():
os.makedirs(res_dst, exist_ok = True)
else:
os.makedirs(res_dst.parent, exist_ok = True)
shutil.copy(fnm, res_dst, follow_symlinks = True)
macos_dst = macos_path.joinpath(inm)
if not macos_dst.exists():
if macos_dst.is_dir():
os.makedirs(macos_dst, exist_ok = True)
else:
os.makedirs(macos_dst.parent, exist_ok = True)
# Create relative symlink to the resource
symlink_to = Path(*[".." for p in macos_dst.relative_to(macos_path).parts], "Resources").joinpath(
res_dst.relative_to(resources_path))
try:
macos_dst.symlink_to(symlink_to)
except FileExistsError:
pass
else:
macos_dst = macos_path.joinpath(inm)
if not macos_dst.exists():
if macos_dst.is_dir():
os.makedirs(macos_dst, exist_ok = True)
else:
os.makedirs(macos_dst.parent, exist_ok = True)
shutil.copy(fnm, macos_dst, follow_symlinks = True)
# Sign the bundle
logger.info('Signing the BUNDLE...')
try:
osxutils.sign_binary(self.name, self.codesign_identity, self.entitlements_file, deep = True)
except Exception as e:
logger.warning(f"Error while signing the bundle: {e}")
logger.warning("You will need to sign the bundle manually!")
logger.info(f"Building BUNDLE {self.tocbasename} completed successfully.")
app = UMBUNDLE(
app = BUNDLE(
coll,
name='{{ display_name }}.app',
icon={{ icon }},
@ -271,9 +91,10 @@ app = UMBUNDLE(
'CFBundleURLSchemes': ['cura', 'slicer'],
}],
'CFBundleDocumentTypes': [{
'CFBundleTypeRole': 'Viewer',
'CFBundleTypeExtensions': ['*'],
'CFBundleTypeName': 'Model Files',
}]
},
){% endif %}
'CFBundleTypeRole': 'Viewer',
'CFBundleTypeExtensions': ['stl', 'obj', '3mf', 'gcode', 'ufp'],
'CFBundleTypeName': 'Model Files',
}]
},
)
{% endif %}

View file

@ -118,7 +118,6 @@ pyinstaller:
- "sqlite3"
- "trimesh"
- "win32ctypes"
- "PyQt6"
- "PyQt6.QtNetwork"
- "PyQt6.sip"
- "stl"
@ -160,6 +159,10 @@ pycharm_targets:
module_name: Cura
name: pytest in TestGCodeListDecorator.py
script_name: tests/TestGCodeListDecorator.py
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
module_name: Cura
name: pytest in TestHitChecker.py
script_name: tests/TestHitChecker.py
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
module_name: Cura
name: pytest in TestIntentManager.py
@ -188,6 +191,10 @@ pycharm_targets:
module_name: Cura
name: pytest in TestPrintInformation.py
script_name: tests/TestPrintInformation.py
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
module_name: Cura
name: pytest in TestPrintOrderManager.py
script_name: tests/TestPrintOrderManager.py
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
module_name: Cura
name: pytest in TestProfileRequirements.py

View file

@ -104,7 +104,8 @@ from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
from cura.UI import CuraSplashScreen, MachineActionManager, PrintInformation
from cura.UI import CuraSplashScreen, PrintInformation
from cura.UI.MachineActionManager import MachineActionManager
from cura.UI.AddPrinterPagesModel import AddPrinterPagesModel
from cura.UI.MachineSettingsManager import MachineSettingsManager
from cura.UI.ObjectsModel import ObjectsModel
@ -125,6 +126,7 @@ from .Machines.Models.CompatibleMachineModel import CompatibleMachineModel
from .Machines.Models.MachineListModel import MachineListModel
from .Machines.Models.ActiveIntentQualitiesModel import ActiveIntentQualitiesModel
from .Machines.Models.IntentSelectionModel import IntentSelectionModel
from .PrintOrderManager import PrintOrderManager
from .SingleInstance import SingleInstance
if TYPE_CHECKING:
@ -179,6 +181,7 @@ class CuraApplication(QtApplication):
# Variables set from CLI
self._files_to_open = []
self._urls_to_open = []
self._use_single_instance = False
self._single_instance = None
@ -186,7 +189,7 @@ class CuraApplication(QtApplication):
self._cura_formula_functions = None # type: Optional[CuraFormulaFunctions]
self._machine_action_manager = None # type: Optional[MachineActionManager.MachineActionManager]
self._machine_action_manager: Optional[MachineActionManager] = None
self.empty_container = None # type: EmptyInstanceContainer
self.empty_definition_changes_container = None # type: EmptyInstanceContainer
@ -202,6 +205,7 @@ class CuraApplication(QtApplication):
self._container_manager = None
self._object_manager = None
self._print_order_manager = None
self._extruders_model = None
self._extruders_model_with_optional = None
self._build_plate_model = None
@ -333,7 +337,7 @@ class CuraApplication(QtApplication):
for filename in self._cli_args.file:
url = QUrl(filename)
if url.scheme() in self._supported_url_schemes:
self._open_url_queue.append(url)
self._urls_to_open.append(url)
else:
self._files_to_open.append(os.path.abspath(filename))
@ -352,11 +356,11 @@ class CuraApplication(QtApplication):
self.__addAllEmptyContainers()
self.__setLatestResouceVersionsForVersionUpgrade()
self._machine_action_manager = MachineActionManager.MachineActionManager(self)
self._machine_action_manager = MachineActionManager(self)
self._machine_action_manager.initialize()
def __sendCommandToSingleInstance(self):
self._single_instance = SingleInstance(self, self._files_to_open)
self._single_instance = SingleInstance(self, self._files_to_open, self._urls_to_open)
# If we use single instance, try to connect to the single instance server, send commands, and then exit.
# If we cannot find an existing single instance server, this is the only instance, so just keep going.
@ -373,9 +377,15 @@ class CuraApplication(QtApplication):
Resources.addExpectedDirNameInData(dir_name)
app_root = os.path.abspath(os.path.join(os.path.dirname(sys.executable)))
Resources.addSecureSearchPath(os.path.join(app_root, "share", "cura", "resources"))
Resources.addSecureSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources"))
if platform.system() == "Darwin":
Resources.addSecureSearchPath(os.path.join(app_root, "Resources", "share", "cura", "resources"))
Resources.addSecureSearchPath(
os.path.join(self._app_install_dir, "Resources", "share", "cura", "resources"))
else:
Resources.addSecureSearchPath(os.path.join(app_root, "share", "cura", "resources"))
Resources.addSecureSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources"))
if not hasattr(sys, "frozen"):
cura_data_root = os.environ.get('CURA_DATA_ROOT', None)
if cura_data_root:
@ -899,6 +909,7 @@ class CuraApplication(QtApplication):
# initialize info objects
self._print_information = PrintInformation.PrintInformation(self)
self._cura_actions = CuraActions.CuraActions(self)
self._print_order_manager = PrintOrderManager(self.getObjectsModel().getNodes)
self.processEvents()
# Initialize setting visibility presets model.
self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self.getPreferences(), parent = self)
@ -956,6 +967,8 @@ class CuraApplication(QtApplication):
self.callLater(self._openFile, file_name)
for file_name in self._open_file_queue: # Open all the files that were queued up while plug-ins were loading.
self.callLater(self._openFile, file_name)
for url in self._urls_to_open:
self.callLater(self._openUrl, url)
for url in self._open_url_queue:
self.callLater(self._openUrl, url)
@ -979,6 +992,7 @@ class CuraApplication(QtApplication):
t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis])
Selection.selectionChanged.connect(self.onSelectionChanged)
self._print_order_manager.printOrderChanged.connect(self._onPrintOrderChanged)
# Set default background color for scene
self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
@ -1068,6 +1082,10 @@ class CuraApplication(QtApplication):
def getTextManager(self, *args) -> "TextManager":
return self._text_manager
@pyqtSlot(bool)
def getWorkplaceDropToBuildplate(self, drop_to_build_plate: bool) ->None:
return self._physics.setAppPerModelDropDown(drop_to_build_plate)
def getCuraFormulaFunctions(self, *args) -> "CuraFormulaFunctions":
if self._cura_formula_functions is None:
self._cura_formula_functions = CuraFormulaFunctions(self)
@ -1094,6 +1112,10 @@ class CuraApplication(QtApplication):
self._object_manager = ObjectsModel(self)
return self._object_manager
@pyqtSlot(str, result = "QVariantList")
def getSupportedActionMachineList(self, definition_id: str) -> List["MachineAction"]:
return self._machine_action_manager.getSupportedActions(self._machine_manager.getDefinitionByMachineId(definition_id))
@pyqtSlot(result = QObject)
def getExtrudersModel(self, *args) -> "ExtrudersModel":
if self._extruders_model is None:
@ -1129,18 +1151,16 @@ class CuraApplication(QtApplication):
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
return self._setting_inheritance_manager
def getMachineActionManager(self, *args: Any) -> MachineActionManager.MachineActionManager:
@pyqtSlot(result = QObject)
def getMachineActionManager(self, *args: Any) -> MachineActionManager:
"""Get the machine action manager
We ignore any *args given to this, as we also register the machine manager as qml singleton.
It wants to give this function an engine and script engine, but we don't care about that.
"""
return cast(MachineActionManager.MachineActionManager, self._machine_action_manager)
return self._machine_action_manager
@pyqtSlot(result = QObject)
def getMachineActionManagerQml(self)-> MachineActionManager.MachineActionManager:
return cast(QObject, self._machine_action_manager)
@pyqtSlot(result = QObject)
def getMaterialManagementModel(self) -> MaterialManagementModel:
@ -1250,6 +1270,7 @@ class CuraApplication(QtApplication):
self.processEvents()
engine.rootContext().setContextProperty("Printer", self)
engine.rootContext().setContextProperty("CuraApplication", self)
engine.rootContext().setContextProperty("PrintOrderManager", self._print_order_manager)
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
@ -1264,7 +1285,7 @@ class CuraApplication(QtApplication):
qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, self.getIntentManager, "IntentManager")
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, self.getSettingInheritanceManager, "SettingInheritanceManager")
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, self.getSimpleModeSettingsManagerWrapper, "SimpleModeSettingsManager")
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, self.getMachineActionManagerWrapper, "MachineActionManager")
qmlRegisterSingletonType(MachineActionManager, "Cura", 1, 0, self.getMachineActionManagerWrapper, "MachineActionManager")
self.processEvents()
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
@ -1745,8 +1766,12 @@ class CuraApplication(QtApplication):
Selection.remove(node)
Selection.add(group_node)
all_nodes = self.getObjectsModel().getNodes()
PrintOrderManager.updatePrintOrdersAfterGroupOperation(all_nodes, group_node, selected_nodes)
@pyqtSlot()
def ungroupSelected(self) -> None:
all_nodes = self.getObjectsModel().getNodes()
selected_objects = Selection.getAllSelectedObjects().copy()
for node in selected_objects:
if node.callDecoration("isGroup"):
@ -1754,21 +1779,30 @@ class CuraApplication(QtApplication):
group_parent = node.getParent()
children = node.getChildren().copy()
for child in children:
# Ungroup only 1 level deep
if child.getParent() != node:
continue
# Ungroup only 1 level deep
children_to_ungroup = list(filter(lambda child: child.getParent() == node, children))
for child in children_to_ungroup:
# Set the parent of the children to the parent of the group-node
op.addOperation(SetParentOperation(child, group_parent))
# Add all individual nodes to the selection
Selection.add(child)
PrintOrderManager.updatePrintOrdersAfterUngroupOperation(all_nodes, node, children_to_ungroup)
op.push()
# Note: The group removes itself from the scene once all its children have left it,
# see GroupDecorator._onChildrenChanged
def _onPrintOrderChanged(self) -> None:
# update object list
scene = self.getController().getScene()
scene.sceneChanged.emit(scene.getRoot())
# reset if already was sliced
Application.getInstance().getBackend().needsSlicing()
Application.getInstance().getBackend().tickle()
def _createSplashScreen(self) -> Optional[CuraSplashScreen.CuraSplashScreen]:
if self._is_headless:
return None

88
cura/HitChecker.py Normal file
View file

@ -0,0 +1,88 @@
from typing import List, Dict
from cura.Scene.CuraSceneNode import CuraSceneNode
class HitChecker:
"""Checks if nodes can be printed without causing any collisions and interference"""
def __init__(self, nodes: List[CuraSceneNode]) -> None:
self._hit_map = self._buildHitMap(nodes)
def anyTwoNodesBlockEachOther(self, nodes: List[CuraSceneNode]) -> bool:
"""Returns True if any 2 nodes block each other"""
for a in nodes:
for b in nodes:
if self._hit_map[a][b] and self._hit_map[b][a]:
return True
return False
def canPrintBefore(self, node: CuraSceneNode, other_nodes: List[CuraSceneNode]) -> bool:
"""Returns True if node doesn't block other_nodes and can be printed before them"""
no_hits = all(not self._hit_map[node][other_node] for other_node in other_nodes)
return no_hits
def canPrintAfter(self, node: CuraSceneNode, other_nodes: List[CuraSceneNode]) -> bool:
"""Returns True if node doesn't hit other nodes and can be printed after them"""
no_hits = all(not self._hit_map[other_node][node] for other_node in other_nodes)
return no_hits
def calculateScore(self, a: CuraSceneNode, b: CuraSceneNode) -> int:
"""Calculate score simply sums the number of other objects it 'blocks'
:param a: node
:param b: node
:return: sum of the number of other objects
"""
score_a = sum(self._hit_map[a].values())
score_b = sum(self._hit_map[b].values())
return score_a - score_b
def canPrintNodesInProvidedOrder(self, ordered_nodes: List[CuraSceneNode]) -> bool:
"""Returns True If nodes don't have any hits in provided order"""
for node_index, node in enumerate(ordered_nodes):
nodes_before = ordered_nodes[:node_index - 1] if node_index - 1 >= 0 else []
nodes_after = ordered_nodes[node_index + 1:] if node_index + 1 < len(ordered_nodes) else []
if not self.canPrintBefore(node, nodes_after) or not self.canPrintAfter(node, nodes_before):
return False
return True
@staticmethod
def _buildHitMap(nodes: List[CuraSceneNode]) -> Dict[CuraSceneNode, CuraSceneNode]:
"""Pre-computes all hits between all objects
:nodes: nodes that need to be checked for collisions
:return: dictionary where hit_map[node1][node2] is False if there node1 can be printed before node2
"""
hit_map = {j: {i: HitChecker._checkHit(j, i) for i in nodes} for j in nodes}
return hit_map
@staticmethod
def _checkHit(a: CuraSceneNode, b: CuraSceneNode) -> bool:
"""Checks if a can be printed before b
:param a: node
:param b: node
:return: False if a can be printed before b
"""
if a == b:
return False
a_hit_hull = a.callDecoration("getConvexHullBoundary")
b_hit_hull = b.callDecoration("getConvexHullHeadFull")
overlap = a_hit_hull.intersectsPolygon(b_hit_hull)
if overlap:
return True
# Adhesion areas must never overlap, regardless of printing order
# This would cause over-extrusion
a_hit_hull = a.callDecoration("getAdhesionArea")
b_hit_hull = b.callDecoration("getAdhesionArea")
overlap = a_hit_hull.intersectsPolygon(b_hit_hull)
if overlap:
return True
else:
return False

View file

@ -16,6 +16,7 @@ from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To downlo
catalog = i18nCatalog("cura")
TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
REQUEST_TIMEOUT = 5 # Seconds
class AuthorizationHelpers:
@ -53,7 +54,8 @@ class AuthorizationHelpers:
data = urllib.parse.urlencode(data).encode("UTF-8"),
headers_dict = headers,
callback = lambda response: self.parseTokenResponse(response, callback),
error_callback = lambda response, _: self.parseTokenResponse(response, callback)
error_callback = lambda response, _: self.parseTokenResponse(response, callback),
timeout = REQUEST_TIMEOUT
)
def getAccessTokenUsingRefreshToken(self, refresh_token: str, callback: Callable[[AuthenticationResponse], None]) -> None:
@ -77,7 +79,9 @@ class AuthorizationHelpers:
data = urllib.parse.urlencode(data).encode("UTF-8"),
headers_dict = headers,
callback = lambda response: self.parseTokenResponse(response, callback),
error_callback = lambda response, _: self.parseTokenResponse(response, callback)
error_callback = lambda response, _: self.parseTokenResponse(response, callback),
urgent = True,
timeout = REQUEST_TIMEOUT
)
def parseTokenResponse(self, token_response: QNetworkReply, callback: Callable[[AuthenticationResponse], None]) -> None:
@ -122,7 +126,8 @@ class AuthorizationHelpers:
check_token_url,
headers_dict = headers,
callback = lambda reply: self._parseUserProfile(reply, success_callback, failed_callback),
error_callback = lambda _, _2: failed_callback() if failed_callback is not None else None
error_callback = lambda _, _2: failed_callback() if failed_callback is not None else None,
timeout = REQUEST_TIMEOUT
)
def _parseUserProfile(self, reply: QNetworkReply, success_callback: Optional[Callable[[UserProfile], None]], failed_callback: Optional[Callable[[], None]] = None) -> None:

View file

@ -1,4 +1,4 @@
# Copyright (c) 2021 Ultimaker B.V.
# Copyright (c) 2024 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher.
import json
@ -6,13 +6,14 @@ from datetime import datetime, timedelta
from typing import Callable, Dict, Optional, TYPE_CHECKING, Union
from urllib.parse import urlencode, quote_plus
from PyQt6.QtCore import QUrl
from PyQt6.QtCore import QUrl, QTimer
from PyQt6.QtGui import QDesktopServices
from UM.Logger import Logger
from UM.Message import Message
from UM.Signal import Signal
from UM.i18n import i18nCatalog
from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To download log-in tokens.
from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers, TOKEN_TIMESTAMP_FORMAT
from cura.OAuth2.LocalAuthorizationServer import LocalAuthorizationServer
from cura.OAuth2.Models import AuthenticationResponse, BaseModel
@ -25,6 +26,8 @@ if TYPE_CHECKING:
MYCLOUD_LOGOFF_URL = "https://account.ultimaker.com/logoff?utm_source=cura&utm_medium=software&utm_campaign=change-account-before-adding-printers"
REFRESH_TOKEN_MAX_RETRIES = 15
REFRESH_TOKEN_RETRY_INTERVAL = 1000
class AuthorizationService:
"""The authorization service is responsible for handling the login flow, storing user credentials and providing
@ -57,6 +60,12 @@ class AuthorizationService:
self.onAuthStateChanged.connect(self._authChanged)
self._refresh_token_retries = 0
self._refresh_token_retry_timer = QTimer()
self._refresh_token_retry_timer.setInterval(REFRESH_TOKEN_RETRY_INTERVAL)
self._refresh_token_retry_timer.setSingleShot(True)
self._refresh_token_retry_timer.timeout.connect(self.refreshAccessToken)
def _authChanged(self, logged_in):
if logged_in and self._unable_to_get_data_message is not None:
self._unable_to_get_data_message.hide()
@ -167,16 +176,29 @@ class AuthorizationService:
return
def process_auth_data(response: AuthenticationResponse) -> None:
self._currently_refreshing_token = False
if response.success:
self._refresh_token_retries = 0
self._storeAuthData(response)
HttpRequestManager.getInstance().setDelayRequests(False)
self.onAuthStateChanged.emit(logged_in = True)
else:
Logger.warning("Failed to get a new access token from the server.")
self.onAuthStateChanged.emit(logged_in = False)
if self._refresh_token_retries >= REFRESH_TOKEN_MAX_RETRIES:
self._refresh_token_retries = 0
Logger.warning("Failed to get a new access token from the server, giving up.")
HttpRequestManager.getInstance().setDelayRequests(False)
self.onAuthStateChanged.emit(logged_in = False)
else:
# Retry a bit later, network may be offline right now and will hopefully be back soon
Logger.warning("Failed to get a new access token from the server, retrying later.")
self._refresh_token_retries += 1
self._refresh_token_retry_timer.start()
if self._currently_refreshing_token:
Logger.debug("Was already busy refreshing token. Do not start a new request.")
return
HttpRequestManager.getInstance().setDelayRequests(True)
self._currently_refreshing_token = True
self._auth_helpers.getAccessTokenUsingRefreshToken(self._auth_data.refresh_token, process_auth_data)

View file

@ -7,6 +7,11 @@ from UM.Scene.Iterator import Iterator
from UM.Scene.SceneNode import SceneNode
from functools import cmp_to_key
from cura.HitChecker import HitChecker
from cura.PrintOrderManager import PrintOrderManager
from cura.Scene.CuraSceneNode import CuraSceneNode
class OneAtATimeIterator(Iterator.Iterator):
"""Iterator that returns a list of nodes in the order that they need to be printed
@ -16,8 +21,6 @@ class OneAtATimeIterator(Iterator.Iterator):
def __init__(self, scene_node) -> None:
super().__init__(scene_node) # Call super to make multiple inheritance work.
self._hit_map = [[]] # type: List[List[bool]] # For each node, which other nodes this hits. A grid of booleans on which nodes hit which.
self._original_node_list = [] # type: List[SceneNode] # The nodes that need to be checked for collisions.
def _fillStack(self) -> None:
"""Fills the ``_node_stack`` with a list of scene nodes that need to be printed in order. """
@ -38,104 +41,50 @@ class OneAtATimeIterator(Iterator.Iterator):
self._node_stack = node_list[:]
return
# Copy the list
self._original_node_list = node_list[:]
hit_checker = HitChecker(node_list)
# Initialise the hit map (pre-compute all hits between all objects)
self._hit_map = [[self._checkHit(i, j) for i in node_list] for j in node_list]
if PrintOrderManager.isUserDefinedPrintOrderEnabled():
self._node_stack = self._getNodesOrderedByUser(hit_checker, node_list)
else:
self._node_stack = self._getNodesOrderedAutomatically(hit_checker, node_list)
# Check if we have to files that block each other. If this is the case, there is no solution!
for a in range(0, len(node_list)):
for b in range(0, len(node_list)):
if a != b and self._hit_map[a][b] and self._hit_map[b][a]:
return
# update print orders so that user can try to arrange the nodes automatically first
# and if result is not satisfactory he/she can switch to manual mode and change it
for index, node in enumerate(self._node_stack):
node.printOrder = index + 1
@staticmethod
def _getNodesOrderedByUser(hit_checker: HitChecker, node_list: List[CuraSceneNode]) -> List[CuraSceneNode]:
nodes_ordered_by_user = sorted(node_list, key=lambda n: n.printOrder)
if hit_checker.canPrintNodesInProvidedOrder(nodes_ordered_by_user):
return nodes_ordered_by_user
return [] # No solution
@staticmethod
def _getNodesOrderedAutomatically(hit_checker: HitChecker, node_list: List[CuraSceneNode]) -> List[CuraSceneNode]:
# Check if we have two files that block each other. If this is the case, there is no solution!
if hit_checker.anyTwoNodesBlockEachOther(node_list):
return [] # No solution
# Sort the original list so that items that block the most other objects are at the beginning.
# This does not decrease the worst case running time, but should improve it in most cases.
sorted(node_list, key = cmp_to_key(self._calculateScore))
node_list = sorted(node_list, key = cmp_to_key(hit_checker.calculateScore))
todo_node_list = [_ObjectOrder([], node_list)]
while len(todo_node_list) > 0:
current = todo_node_list.pop()
for node in current.todo:
# Check if the object can be placed with what we have and still allows for a solution in the future
if not self._checkHitMultiple(node, current.order) and not self._checkBlockMultiple(node, current.todo):
if hit_checker.canPrintAfter(node, current.order) and hit_checker.canPrintBefore(node, current.todo):
# We found a possible result. Create new todo & order list.
new_todo_list = current.todo[:]
new_todo_list.remove(node)
new_order = current.order[:] + [node]
if len(new_todo_list) == 0:
# We have no more nodes to check, so quit looking.
self._node_stack = new_order
return
return new_order # Solution found!
todo_node_list.append(_ObjectOrder(new_order, new_todo_list))
self._node_stack = [] #No result found!
# Check if first object can be printed before the provided list (using the hit map)
def _checkHitMultiple(self, node: SceneNode, other_nodes: List[SceneNode]) -> bool:
node_index = self._original_node_list.index(node)
for other_node in other_nodes:
other_node_index = self._original_node_list.index(other_node)
if self._hit_map[node_index][other_node_index]:
return True
return False
def _checkBlockMultiple(self, node: SceneNode, other_nodes: List[SceneNode]) -> bool:
"""Check for a node whether it hits any of the other nodes.
:param node: The node to check whether it collides with the other nodes.
:param other_nodes: The nodes to check for collisions.
:return: returns collision between nodes
"""
node_index = self._original_node_list.index(node)
for other_node in other_nodes:
other_node_index = self._original_node_list.index(other_node)
if self._hit_map[other_node_index][node_index] and node_index != other_node_index:
return True
return False
def _calculateScore(self, a: SceneNode, b: SceneNode) -> int:
"""Calculate score simply sums the number of other objects it 'blocks'
:param a: node
:param b: node
:return: sum of the number of other objects
"""
score_a = sum(self._hit_map[self._original_node_list.index(a)])
score_b = sum(self._hit_map[self._original_node_list.index(b)])
return score_a - score_b
def _checkHit(self, a: SceneNode, b: SceneNode) -> bool:
"""Checks if a can be printed before b
:param a: node
:param b: node
:return: true if a can be printed before b
"""
if a == b:
return False
a_hit_hull = a.callDecoration("getConvexHullBoundary")
b_hit_hull = b.callDecoration("getConvexHullHeadFull")
overlap = a_hit_hull.intersectsPolygon(b_hit_hull)
if overlap:
return True
# Adhesion areas must never overlap, regardless of printing order
# This would cause over-extrusion
a_hit_hull = a.callDecoration("getAdhesionArea")
b_hit_hull = b.callDecoration("getAdhesionArea")
overlap = a_hit_hull.intersectsPolygon(b_hit_hull)
if overlap:
return True
else:
return False
return [] # No result found!
class _ObjectOrder:

View file

@ -38,7 +38,14 @@ class PlatformPhysics:
self._minimum_gap = 2 # It is a minimum distance (in mm) between two models, applicable for small models
Application.getInstance().getPreferences().addPreference("physics/automatic_push_free", False)
Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down", True)
Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down", False)
self._app_per_model_drop = Application.getInstance().getPreferences().getValue("physics/automatic_drop_down")
def getAppPerModelDropDown(self):
return self._app_per_model_drop
def setAppPerModelDropDown(self, drop_to_buildplate):
self._app_per_model_drop = drop_to_buildplate
def _onSceneChanged(self, source):
if not source.callDecoration("isSliceable"):
@ -71,6 +78,7 @@ class PlatformPhysics:
# We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A.
# By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve.
random.shuffle(nodes)
for node in nodes:
if node is root or not isinstance(node, SceneNode) or node.getBoundingBox() is None:
continue
@ -80,7 +88,10 @@ class PlatformPhysics:
# Move it downwards if bottom is above platform
move_vector = Vector()
if node.getSetting(SceneNodeSettings.AutoDropDown, app_automatic_drop_down) and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down
# if per model drop is different then app_automatic_drop, in case of 3mf loading when user changes this setting for that model
if (self._app_per_model_drop != app_automatic_drop_down):
node.setSetting(SceneNodeSettings.AutoDropDown, self._app_per_model_drop)
if node.getSetting(SceneNodeSettings.AutoDropDown, self._app_per_model_drop) and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down
z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
move_vector = move_vector.set(y = -bbox.bottom + z_offset)
@ -168,6 +179,8 @@ class PlatformPhysics:
op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
op.push()
# setting this drop to model same as app_automatic_drop_down
self._app_per_model_drop = app_automatic_drop_down
# After moving, we have to evaluate the boundary checks for nodes
build_volume.updateNodeBoundaryCheck()

171
cura/PrintOrderManager.py Normal file
View file

@ -0,0 +1,171 @@
from typing import List, Callable, Optional, Any
from PyQt6.QtCore import pyqtProperty, pyqtSignal, QObject, pyqtSlot
from UM.Application import Application
from UM.Scene.Selection import Selection
from cura.Scene.CuraSceneNode import CuraSceneNode
class PrintOrderManager(QObject):
"""Allows to order the object list to set the print sequence manually"""
def __init__(self, get_nodes: Callable[[], List[CuraSceneNode]]) -> None:
super().__init__()
self._get_nodes = get_nodes
self._configureEvents()
_settingsChanged = pyqtSignal()
_uiActionsOutdated = pyqtSignal()
printOrderChanged = pyqtSignal()
@pyqtSlot()
def swapSelectedAndPreviousNodes(self) -> None:
selected_node, previous_node, next_node = self._getSelectedAndNeighborNodes()
self._swapPrintOrders(selected_node, previous_node)
@pyqtSlot()
def swapSelectedAndNextNodes(self) -> None:
selected_node, previous_node, next_node = self._getSelectedAndNeighborNodes()
self._swapPrintOrders(selected_node, next_node)
@pyqtProperty(str, notify=_uiActionsOutdated)
def previousNodeName(self) -> str:
selected_node, previous_node, next_node = self._getSelectedAndNeighborNodes()
return self._getNodeName(previous_node)
@pyqtProperty(str, notify=_uiActionsOutdated)
def nextNodeName(self) -> str:
selected_node, previous_node, next_node = self._getSelectedAndNeighborNodes()
return self._getNodeName(next_node)
@pyqtProperty(bool, notify=_uiActionsOutdated)
def shouldEnablePrintBeforeAction(self) -> bool:
selected_node, previous_node, next_node = self._getSelectedAndNeighborNodes()
can_swap_with_previous_node = selected_node is not None and previous_node is not None
return can_swap_with_previous_node
@pyqtProperty(bool, notify=_uiActionsOutdated)
def shouldEnablePrintAfterAction(self) -> bool:
selected_node, previous_node, next_node = self._getSelectedAndNeighborNodes()
can_swap_with_next_node = selected_node is not None and next_node is not None
return can_swap_with_next_node
@pyqtProperty(bool, notify=_settingsChanged)
def shouldShowEditPrintOrderActions(self) -> bool:
return PrintOrderManager.isUserDefinedPrintOrderEnabled()
@staticmethod
def isUserDefinedPrintOrderEnabled() -> bool:
stack = Application.getInstance().getGlobalContainerStack()
is_enabled = stack and \
stack.getProperty("print_sequence", "value") == "one_at_a_time" and \
stack.getProperty("user_defined_print_order_enabled", "value")
return bool(is_enabled)
@staticmethod
def initializePrintOrders(nodes: List[CuraSceneNode]) -> None:
"""Just created (loaded from file) nodes have print order 0.
This method initializes print orders with max value to put nodes at the end of object list"""
max_print_order = max(map(lambda n: n.printOrder, nodes), default=0)
for node in nodes:
if node.printOrder == 0:
max_print_order += 1
node.printOrder = max_print_order
@staticmethod
def updatePrintOrdersAfterGroupOperation(
all_nodes: List[CuraSceneNode],
group_node: CuraSceneNode,
grouped_nodes: List[CuraSceneNode]
) -> None:
group_node.printOrder = min(map(lambda n: n.printOrder, grouped_nodes))
all_nodes.append(group_node)
for node in grouped_nodes:
all_nodes.remove(node)
# reassign print orders so there won't be gaps like 1 2 5 6 7
sorted_nodes = sorted(all_nodes, key=lambda n: n.printOrder)
for i, node in enumerate(sorted_nodes):
node.printOrder = i + 1
@staticmethod
def updatePrintOrdersAfterUngroupOperation(
all_nodes: List[CuraSceneNode],
group_node: CuraSceneNode,
ungrouped_nodes: List[CuraSceneNode]
) -> None:
all_nodes.remove(group_node)
nodes_to_update_print_order = filter(lambda n: n.printOrder > group_node.printOrder, all_nodes)
for node in nodes_to_update_print_order:
node.printOrder += len(ungrouped_nodes) - 1
for i, child in enumerate(ungrouped_nodes):
child.printOrder = group_node.printOrder + i
all_nodes.append(child)
def _swapPrintOrders(self, node1: CuraSceneNode, node2: CuraSceneNode) -> None:
if node1 and node2:
node1.printOrder, node2.printOrder = node2.printOrder, node1.printOrder # swap print orders
self.printOrderChanged.emit() # update object list first
self._uiActionsOutdated.emit() # then update UI actions
def _getSelectedAndNeighborNodes(self
) -> (Optional[CuraSceneNode], Optional[CuraSceneNode], Optional[CuraSceneNode]):
nodes = self._get_nodes()
ordered_nodes = sorted(nodes, key=lambda n: n.printOrder)
selected_node = PrintOrderManager._getSingleSelectedNode()
if selected_node and selected_node in ordered_nodes:
selected_node_index = ordered_nodes.index(selected_node)
else:
selected_node_index = None
if selected_node_index is not None and selected_node_index - 1 >= 0:
previous_node = ordered_nodes[selected_node_index - 1]
else:
previous_node = None
if selected_node_index is not None and selected_node_index + 1 < len(ordered_nodes):
next_node = ordered_nodes[selected_node_index + 1]
else:
next_node = None
return selected_node, previous_node, next_node
@staticmethod
def _getNodeName(node: CuraSceneNode, max_length: int = 30) -> str:
node_name = node.getName() if node else ""
truncated_node_name = node_name[:max_length]
return truncated_node_name
@staticmethod
def _getSingleSelectedNode() -> Optional[CuraSceneNode]:
if len(Selection.getAllSelectedObjects()) == 1:
selected_node = Selection.getSelectedObject(0)
return selected_node
return None
def _configureEvents(self) -> None:
Selection.selectionChanged.connect(self._onSelectionChanged)
self._global_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged()
def _onGlobalStackChanged(self) -> None:
if self._global_stack:
self._global_stack.propertyChanged.disconnect(self._onSettingsChanged)
self._global_stack.containersChanged.disconnect(self._onSettingsChanged)
self._global_stack = Application.getInstance().getGlobalContainerStack()
if self._global_stack:
self._global_stack.propertyChanged.connect(self._onSettingsChanged)
self._global_stack.containersChanged.connect(self._onSettingsChanged)
def _onSettingsChanged(self, *args: Any) -> None:
self._settingsChanged.emit()
def _onSelectionChanged(self) -> None:
self._uiActionsOutdated.emit()

View file

@ -25,10 +25,19 @@ class CuraSceneNode(SceneNode):
if not no_setting_override:
self.addDecorator(SettingOverrideDecorator()) # Now we always have a getActiveExtruderPosition, unless explicitly disabled
self._outside_buildarea = False
self._print_order = 0
def setOutsideBuildArea(self, new_value: bool) -> None:
self._outside_buildarea = new_value
@property
def printOrder(self):
return self._print_order
@printOrder.setter
def printOrder(self, new_value):
self._print_order = new_value
def isOutsideBuildArea(self) -> bool:
return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
@ -157,3 +166,6 @@ class CuraSceneNode(SceneNode):
def transformChanged(self) -> None:
self._transformChanged()
def __repr__(self) -> str:
return "{print_order}. {name}".format(print_order = self._print_order, name = self.getName())

View file

@ -5,16 +5,18 @@ import json
import os
from typing import List, Optional
from PyQt6.QtCore import QUrl
from PyQt6.QtNetwork import QLocalServer, QLocalSocket
from UM.Qt.QtApplication import QtApplication #For typing.
from UM.Qt.QtApplication import QtApplication # For typing.
from UM.Logger import Logger
class SingleInstance:
def __init__(self, application: QtApplication, files_to_open: Optional[List[str]]) -> None:
def __init__(self, application: QtApplication, files_to_open: Optional[List[str]], url_to_open: Optional[List[str]]) -> None:
self._application = application
self._files_to_open = files_to_open
self._url_to_open = url_to_open
self._single_instance_server = None
@ -33,7 +35,7 @@ class SingleInstance:
return False
# We only send the files that need to be opened.
if not self._files_to_open:
if not self._files_to_open and not self._url_to_open:
Logger.log("i", "No file need to be opened, do nothing.")
return True
@ -55,8 +57,12 @@ class SingleInstance:
payload = {"command": "open", "filePath": os.path.abspath(filename)}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii"))
for url in self._url_to_open:
payload = {"command": "open-url", "urlPath": url.toString()}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding="ascii"))
payload = {"command": "close-connection"}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii"))
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding="ascii"))
single_instance_socket.flush()
single_instance_socket.waitForDisconnected()
@ -72,7 +78,7 @@ class SingleInstance:
def _onClientConnected(self) -> None:
Logger.log("i", "New connection received on our single-instance server")
connection = None #type: Optional[QLocalSocket]
connection = None # type: Optional[QLocalSocket]
if self._single_instance_server:
connection = self._single_instance_server.nextPendingConnection()
@ -81,7 +87,7 @@ class SingleInstance:
def __readCommands(self, connection: QLocalSocket) -> None:
line = connection.readLine()
while len(line) != 0: # There is also a .canReadLine()
while len(line) != 0: # There is also a .canReadLine()
try:
payload = json.loads(str(line, encoding = "ascii").strip())
command = payload["command"]
@ -94,13 +100,19 @@ class SingleInstance:
elif command == "open":
self._application.callLater(lambda f = payload["filePath"]: self._application._openFile(f))
#command: Load a url link in Cura
elif command == "open-url":
url = QUrl(payload["urlPath"])
self._application.callLater(lambda: self._application._openUrl(url))
# Command: Activate the window and bring it to the top.
elif command == "focus":
# Operating systems these days prevent windows from moving around by themselves.
# 'alert' or flashing the icon in the taskbar is the best thing we do now.
main_window = self._application.getMainWindow()
if main_window is not None:
self._application.callLater(lambda: main_window.alert(0)) # type: ignore # I don't know why MyPy complains here
self._application.callLater(lambda: main_window.alert(0)) # type: ignore # I don't know why MyPy complains here
# Command: Close the socket connection. We're done.
elif command == "close-connection":

View file

@ -14,6 +14,9 @@ from UM.Scene.SceneNode import SceneNode
from UM.Scene.Selection import Selection
from UM.i18n import i18nCatalog
from cura.PrintOrderManager import PrintOrderManager
from cura.Scene.CuraSceneNode import CuraSceneNode
catalog = i18nCatalog("cura")
@ -76,6 +79,9 @@ class ObjectsModel(ListModel):
self._build_plate_number = nr
self._update()
def getNodes(self) -> List[CuraSceneNode]:
return list(map(lambda n: n["node"], self.items))
def _updateSceneDelayed(self, source) -> None:
if not isinstance(source, Camera):
self._update_timer.start()
@ -175,6 +181,10 @@ class ObjectsModel(ListModel):
all_nodes = self._renameNodes(name_to_node_info_dict)
user_defined_print_order_enabled = PrintOrderManager.isUserDefinedPrintOrderEnabled()
if user_defined_print_order_enabled:
PrintOrderManager.initializePrintOrders(all_nodes)
for node in all_nodes:
if hasattr(node, "isOutsideBuildArea"):
is_outside_build_area = node.isOutsideBuildArea() # type: ignore
@ -223,8 +233,13 @@ class ObjectsModel(ListModel):
# for anti overhang meshes and groups the extruder nr is irrelevant
extruder_number = -1
if not user_defined_print_order_enabled:
name = node.getName()
else:
name = "{print_order}. {name}".format(print_order = node.printOrder, name = node.getName())
nodes.append({
"name": node.getName(),
"name": name,
"selected": Selection.isSelected(node),
"outside_build_area": is_outside_build_area,
"buildplate_number": node_build_plate_number,
@ -234,5 +249,5 @@ class ObjectsModel(ListModel):
"node": node
})
nodes = sorted(nodes, key=lambda n: n["name"])
nodes = sorted(nodes, key=lambda n: n["name"] if not user_defined_print_order_enabled else n["node"].printOrder)
self.setItems(nodes)

View file

@ -184,6 +184,9 @@ class ThreeMFReader(MeshReader):
else:
Logger.log("w", "Unable to find extruder in position %s", setting_value)
continue
if key == "print_order":
um_node.printOrder = int(setting_value)
continue
if key in known_setting_keys:
setting_container.setProperty(key, "value", setting_value)
else:

View file

@ -353,6 +353,11 @@ class WorkspaceDialog(QObject):
Application.getInstance().getBackend().close()
@pyqtSlot(bool)
def setDropToBuildPlateForModel(self, drop_to_buildplate: bool) -> None:
CuraApplication.getInstance().getWorkplaceDropToBuildplate(drop_to_buildplate)
def setMaterialConflict(self, material_conflict: bool) -> None:
if self._has_material_conflict != material_conflict:
self._has_material_conflict = material_conflict

View file

@ -351,6 +351,25 @@ UM.Dialog
}
}
Row
{
id: dropToBuildPlate
width: parent.width
height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").width
UM.CheckBox
{
id: checkDropModels
text: catalog.i18nc("@text:window", "Drop models to buildplate")
checked: UM.Preferences.getValue("physics/automatic_drop_down")
onCheckedChanged: manager.setDropToBuildPlateForModel(checked)
}
function reloadValue()
{
checkDropModels.checked = UM.Preferences.getValue("physics/automatic_drop_down")
}
}
Row
{
id: clearBuildPlateWarning
@ -473,6 +492,7 @@ UM.Dialog
materialSection.reloadValues()
profileSection.reloadValues()
printerSection.reloadValues()
dropToBuildPlate.reloadValue()
}
}
}

View file

@ -17,6 +17,7 @@ from cura.CuraApplication import CuraApplication
from cura.CuraPackageManager import CuraPackageManager
from cura.Settings import CuraContainerStack
from cura.Utils.Threading import call_on_qt_thread
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Snapshot import Snapshot
from PyQt6.QtCore import QBuffer
@ -148,6 +149,9 @@ class ThreeMFWriter(MeshWriter):
for key in model_exported_settings:
savitar_node.setSetting("cura:" + key, str(stack.getProperty(key, "value")))
if isinstance(um_node, CuraSceneNode):
savitar_node.setSetting("cura:print_order", str(um_node.printOrder))
# Store the metadata.
for key, value in um_node.metadata.items():
savitar_node.setSetting(key, value)

View file

@ -76,6 +76,7 @@ class CuraEngineBackend(QObject, Backend):
self._default_engine_location = executable_name
search_path = [
os.path.abspath(os.path.join(os.path.dirname(sys.executable), "..", "Resources")),
os.path.abspath(os.path.dirname(sys.executable)),
os.path.abspath(os.path.join(os.path.dirname(sys.executable), "bin")),
os.path.abspath(os.path.join(os.path.dirname(sys.executable), "..")),

View file

@ -1,5 +1,5 @@
pytest
pyinstaller==5.8.0
pyinstaller==6.3.0
pyinstaller-hooks-contrib
pyyaml
sip==6.5.1

View file

@ -5135,7 +5135,7 @@
"unit": "mm",
"type": "float",
"minimum_value": "0",
"maximum_value_warning": "machine_nozzle_size",
"maximum_value_warning": "5*layer_height",
"default_value": 0.1,
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
"enabled": "support_enable or support_meshes_present",
@ -7246,6 +7246,16 @@
"settable_per_extruder": false,
"settable_per_meshgroup": false
},
"user_defined_print_order_enabled":
{
"label": "Set Print Sequence Manually",
"description": "Allows to order the object list to set the print sequence manually. First object from the list will be printed first.",
"type": "bool",
"default_value": false,
"settable_per_mesh": false,
"settable_per_extruder": false,
"enabled": "print_sequence == 'one_at_a_time'"
},
"infill_mesh":
{
"label": "Infill Mesh",

View file

@ -106,6 +106,7 @@
"retraction_combing_max_distance": { "value": 15 },
"retraction_count_max": { "value": 25 },
"retraction_extrusion_window": { "value": 1 },
"retraction_min_travel": { "value": 5 },
"roofing_layer_count": { "value": "1" },
"roofing_material_flow": { "value": "material_flow" },
"skin_angles": { "value": "[] if infill_pattern not in ['cross', 'cross_3d'] else [20, 110]" },

View file

@ -156,7 +156,6 @@
"retraction_hop": { "value": "2" },
"retraction_hop_enabled": { "value": "extruders_enabled_count > 1" },
"retraction_hop_only_when_collides": { "value": "True" },
"retraction_min_travel": { "value": "5" },
"retraction_prime_speed": { "value": "15" },
"skin_overlap": { "value": "10" },
"speed_prime_tower": { "value": "speed_topbottom" },

View file

@ -373,7 +373,6 @@
"retraction_hop": { "value": 0.4 },
"retraction_hop_enabled": { "value": true },
"retraction_hop_only_when_collides": { "value": false },
"retraction_min_travel": { "value": "line_width * 4" },
"retraction_prime_speed": { "value": "retraction_speed" },
"retraction_speed": { "value": 5 },
"roofing_layer_count": { "value": 2 },

View file

@ -108,7 +108,6 @@
"retraction_hop": { "value": "2" },
"retraction_hop_enabled": { "value": "extruders_enabled_count > 1" },
"retraction_hop_only_when_collides": { "value": "True" },
"retraction_min_travel": { "value": "5" },
"retraction_prime_speed": { "value": "15" },
"retraction_speed": { "value": "45" },
"speed_prime_tower": { "value": "speed_topbottom" },

View file

@ -110,7 +110,6 @@
"retraction_hop": { "value": "2" },
"retraction_hop_enabled": { "value": "extruders_enabled_count > 1" },
"retraction_hop_only_when_collides": { "value": "True" },
"retraction_min_travel": { "value": "5" },
"retraction_prime_speed": { "value": "15" },
"retraction_speed": { "value": "45" },
"speed_prime_tower": { "value": "speed_topbottom" },

View file

@ -4946,6 +4946,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Rozdělit modely"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Tisknout před"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Tisknout po"
msgctxt "@button"
msgid "Uninstall"
msgstr "Odinstalovat"

View file

@ -2583,6 +2583,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Tisková sekvence"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Nastavit tiskovou sekvenci ručně"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Umožňuje řadit seznam objektů pro ruční nastavení tiskové sekvence. První objekt ze seznamu bude vytisknut jako první."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Rychlost tisku"

View file

@ -4565,6 +4565,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr ""
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr ""
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr ""
msgctxt "@button"
msgid "Uninstall"
msgstr ""

View file

@ -4930,6 +4930,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Gruppierung für Modelle aufheben"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Vor dem Drucken"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Nach dem Drucken"
msgctxt "@button"
msgid "Uninstall"
msgstr "Deinstallieren"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Druckreihenfolge"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Druckreihenfolge manuell einstellen"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Ermöglicht das Ordnen der Objektliste, um die Druckreihenfolge manuell festzulegen. Das erste Objekt aus der Liste wird zuerst gedruckt."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Druckgeschwindigkeit"

View file

@ -4931,6 +4931,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Desagrupar modelos"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Imprimir antes"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Imprimir después"
msgctxt "@button"
msgid "Uninstall"
msgstr "Desinstalar"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Secuencia de impresión"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Establecer secuencia de impresión manualmente"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Permite ordenar la lista de objetos para establecer la secuencia de impresión manualmente. El primer objeto de la lista se imprimirá primero."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Velocidad de impresión"

View file

@ -4588,6 +4588,14 @@ msgctxt "print_sequence option one_at_a_time"
msgid "One at a Time"
msgstr ""
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr ""
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr ""
msgctxt "infill_mesh label"
msgid "Infill Mesh"
msgstr ""

View file

@ -4899,6 +4899,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Poista mallien ryhmitys"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Tulosta ennen"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Tulosta jälkeen"
msgctxt "@button"
msgid "Uninstall"
msgstr ""

View file

@ -2578,6 +2578,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Tulostusjärjestys"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Aseta tulostusjärjestys manuaalisesti"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Mahdollistaa kohteiden järjestämisen tulostusjärjestyksen manuaaliseen asettamiseen. Listan ensimmäinen kohde tulostetaan ensin."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Tulostusnopeus"

View file

@ -4928,6 +4928,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Dégrouper les modèles"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Imprimer avant"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Imprimer après"
msgctxt "@button"
msgid "Uninstall"
msgstr "Désinstaller"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Séquence d'impression"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Définir la séquence d'impression manuellement"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Permet de classer la liste des objets pour définir manuellement la séquence d'impression. Le premier objet de la liste sera imprimé en premier."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Vitesse dimpression"

View file

@ -4913,6 +4913,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Csoport bontása"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Nyomtatás előtt"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Nyomtatás után"
msgctxt "@button"
msgid "Uninstall"
msgstr ""

View file

@ -2585,6 +2585,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Nyomtatási sorrend"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Nyomtatási sorrend kézi beállítása"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Lehetővé teszi az objektumlista rendezését a nyomtatási sorrend kézi beállításához. A lista első objektuma lesz először nyomtatva."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Nyomtatási sebesség"

View file

@ -4931,6 +4931,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Separa modelli"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Stampa prima"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Stampa dopo"
msgctxt "@button"
msgid "Uninstall"
msgstr "Disinstalla"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Sequenza di stampa"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Imposta manualmente la sequenza di stampa"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Consente di ordinare l'elenco degli oggetti per impostare manualmente la sequenza di stampa. Il primo oggetto dell'elenco sarà stampato per primo."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Velocità di stampa"

View file

@ -4914,6 +4914,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "モデルを非グループ化"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "印刷前"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "印刷後"
msgctxt "@button"
msgid "Uninstall"
msgstr "アンインストール"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "印刷頻度"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "手動で印刷順序を設定する"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "オブジェクトリストを並べ替えて、手動で印刷順序を設定することができます。リストの最初のオブジェクトが最初に印刷されます。"
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "印刷速度"

View file

@ -4917,6 +4917,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "모델 그룹 해제"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "인쇄 전"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "인쇄 후"
msgctxt "@button"
msgid "Uninstall"
msgstr "설치 제거"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "프린팅 순서"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "수동으로 인쇄 순서 설정"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "객체 목록을 정렬하여 수동으로 인쇄 순서를 설정할 수 있습니다. 목록의 첫 번째 객체가 먼저 인쇄됩니다."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "프린팅 속도"

View file

@ -4925,6 +4925,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Groeperen van Modellen Opheffen"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Afdrukken voor"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Afdrukken na"
msgctxt "@button"
msgid "Uninstall"
msgstr "De-installeren"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Printvolgorde"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Handmatig afdrukvolgorde instellen"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Maakt het mogelijk de objectlijst te ordenen om de afdrukvolgorde handmatig in te stellen. Het eerste object van de lijst wordt als eerste afgedrukt."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Printsnelheid"

View file

@ -4916,6 +4916,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Rozgrupuj modele"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Drukuj przed"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Drukuj po"
msgctxt "@button"
msgid "Uninstall"
msgstr ""

View file

@ -2584,6 +2584,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Sekwencja Wydruku"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Ręczne ustawienie kolejności drukowania"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Umożliwia ręczne ustawienie kolejności drukowania na liście obiektów. Pierwszy obiekt z listy zostanie wydrukowany jako pierwszy."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Prędkość Druku"

View file

@ -4942,6 +4942,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Desagrupar Modelos"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Imprimir antes"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Imprimir depois"
msgctxt "@button"
msgid "Uninstall"
msgstr "Desinstalar"

View file

@ -2585,6 +2585,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Sequência de Impressão"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Definir sequência de impressão manualmente"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Permite ordenar a lista de objetos para definir a sequência de impressão manualmente. O primeiro objeto da lista será impresso primeiro."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Velocidade de Impressão"

View file

@ -4932,6 +4932,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Desagrupar Modelos"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Imprimir antes"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Imprimir depois"
msgctxt "@button"
msgid "Uninstall"
msgstr "Desinstalar"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Sequência de impressão"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Definir sequência de impressão manualmente"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Permite ordenar a lista de objetos para definir a sequência de impressão manualmente. O primeiro objeto da lista será impresso primeiro."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Velocidade de Impressão"

View file

@ -4955,6 +4955,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Разгруппировать модели"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Печатать до"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Печатать после"
msgctxt "@button"
msgid "Uninstall"
msgstr "Удалить"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Последовательная печать"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Установить последовательность печати вручную"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Позволяет упорядочить список объектов для ручной настройки последовательности печати. Первый объект из списка будет напечатан первым."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Скорость печати"

View file

@ -4931,6 +4931,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Model Grubunu Çöz"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "Önce Yazdır"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "Sonra Yazdır"
msgctxt "@button"
msgid "Uninstall"
msgstr "Kaldır"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "Yazdırma Dizisi"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "Baskı Sırasını Manuel Olarak Ayarla"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "Nesne listesini sıralayarak baskı sırasını manuel olarak ayarlamayı sağlar. Listeden ilk nesne ilk olarak basılacak."
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "Yazdırma Hızı"

View file

@ -4919,6 +4919,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "拆分模型"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "打印前"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "打印后"
msgctxt "@button"
msgid "Uninstall"
msgstr "卸载"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "打印序列"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "手动设置打印顺序"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "允许对对象列表进行排序,以手动设置打印顺序。列表中的第一个对象将首先被打印。"
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "打印速度"

View file

@ -4911,6 +4911,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "取消模型群組"
msgctxt "@action:inmenu menubar:edit"
msgid "Print Before"
msgstr "列印前"
msgctxt "@action:inmenu menubar:edit"
msgid "Print After"
msgstr "列印後"
msgctxt "@button"
msgid "Uninstall"
msgstr ""

View file

@ -2585,6 +2585,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence"
msgstr "列印順序"
msgctxt "user_defined_print_order_enabled label"
msgid "Set Print Sequence Manually"
msgstr "手動設置列印順序"
msgctxt "user_defined_print_order_enabled description"
msgid "Allows to order the object list to set the print sequence manually. First object from the list will be printed first."
msgstr "允許手動設置物件列表以設定列印順序。列表中的第一個物件將首先被列印。"
msgctxt "speed_print label"
msgid "Print Speed"
msgstr "列印速度"

View file

@ -35,6 +35,9 @@ Item
property alias mergeObjects: mergeObjectsAction
//property alias unMergeObjects: unMergeObjectsAction
property alias printObjectBeforePrevious: printObjectBeforePreviousAction
property alias printObjectAfterNext: printObjectAfterNextAction
property alias multiplyObject: multiplyObjectAction
property alias selectAll: selectAllAction
@ -405,6 +408,26 @@ Item
onTriggered: CuraApplication.ungroupSelected()
}
Action
{
id: printObjectBeforePreviousAction
text: catalog.i18nc("@action:inmenu menubar:edit","Print Before") + " " + PrintOrderManager.previousNodeName
enabled: PrintOrderManager.shouldEnablePrintBeforeAction
icon.name: "print-before"
shortcut: "PgUp"
onTriggered: PrintOrderManager.swapSelectedAndPreviousNodes()
}
Action
{
id: printObjectAfterNextAction
text: catalog.i18nc("@action:inmenu menubar:edit","Print After") + " " + PrintOrderManager.nextNodeName
enabled: PrintOrderManager.shouldEnablePrintAfterAction
icon.name: "print-after"
shortcut: "PgDown"
onTriggered: PrintOrderManager.swapSelectedAndNextNodes()
}
Action
{
id: mergeObjectsAction

View file

@ -78,6 +78,19 @@ Cura.Menu
Cura.MenuItem { action: Cura.Actions.mergeObjects }
Cura.MenuItem { action: Cura.Actions.unGroupObjects }
// Edit print sequence actions
Cura.MenuSeparator { visible: PrintOrderManager.shouldShowEditPrintOrderActions }
Cura.MenuItem
{
action: Cura.Actions.printObjectBeforePrevious
visible: PrintOrderManager.shouldShowEditPrintOrderActions
}
Cura.MenuItem
{
action: Cura.Actions.printObjectAfterNext
visible: PrintOrderManager.shouldShowEditPrintOrderActions
}
Connections
{
target: UM.Controller

View file

@ -25,4 +25,17 @@ Cura.Menu
Cura.MenuItem { action: Cura.Actions.groupObjects }
Cura.MenuItem { action: Cura.Actions.mergeObjects }
Cura.MenuItem { action: Cura.Actions.unGroupObjects }
// Edit print sequence actions
Cura.MenuSeparator { visible: PrintOrderManager.shouldShowEditPrintOrderActions }
Cura.MenuItem
{
action: Cura.Actions.printObjectBeforePrevious
visible: PrintOrderManager.shouldShowEditPrintOrderActions
}
Cura.MenuItem
{
action: Cura.Actions.printObjectAfterNext
visible: PrintOrderManager.shouldShowEditPrintOrderActions
}
}

View file

@ -509,11 +509,14 @@ UM.PreferencesPage
id: dropDownCheckbox
text: catalog.i18nc("@option:check", "Automatically drop models to the build plate")
checked: boolCheck(UM.Preferences.getValue("physics/automatic_drop_down"))
onCheckedChanged: UM.Preferences.setValue("physics/automatic_drop_down", checked)
onCheckedChanged:
{
UM.Preferences.setValue("physics/automatic_drop_down", checked)
CuraApplication.getWorkplaceDropToBuildplate(checked)
}
}
}
UM.TooltipArea
{
width: childrenRect.width;
@ -627,6 +630,8 @@ UM.PreferencesPage
UM.TooltipArea
{
width: childrenRect.width
// Mac only allows applications to run as a single instance, so providing the option for this os doesn't make much sense
visible: Qt.platform.os !== "osx"
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","Should opening files from the desktop or external applications open in the same instance of Cura?")

View file

@ -12,7 +12,6 @@ import Cura 1.0 as Cura
UM.ManagementPage
{
id: base
property var machineActionManager: CuraApplication.getMachineActionManagerQml()
Item { enabled: false; UM.I18nCatalog { id: catalog; name: "cura"} }
title: catalog.i18nc("@title:tab", "Printers")
@ -63,7 +62,7 @@ UM.ManagementPage
Repeater
{
id: machineActionRepeater
model: base.currentItem ? machineActionManager.getSupportedActions(Cura.MachineManager.getDefinitionByMachineId(base.currentItem.id)) : null
model: base.currentItem ? CuraApplication.getSupportedActionMachineList(base.currentItem.id) : null
Item
{

View file

@ -222,7 +222,7 @@ Item
UM.Label
{
id: toolHint
text: UM.Controller.properties.getValue("ToolHint") != undefined ? UM.ActiveTool.properties.getValue("ToolHint") : ""
text: UM.Controller.properties.getValue("ToolHint") != undefined ? UM.Controller.properties.getValue("ToolHint") : ""
color: UM.Theme.getColor("tooltip_text")
anchors.horizontalCenter: parent.horizontalCenter
}

View file

@ -17,7 +17,6 @@ machine_nozzle_heat_up_speed = 1.4
material_print_temperature = =default_material_print_temperature - 20
ooze_shield_angle = 40
raft_airgap = 0.4
retraction_min_travel = 5
speed_print = 70
speed_topbottom = =math.ceil(speed_print * 30 / 70)
speed_wall = =math.ceil(speed_print * 30 / 70)

View file

@ -25,7 +25,6 @@ prime_tower_wipe_enabled = True
raft_airgap = 0.25
retraction_hop = 2
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
speed_print = 50
speed_topbottom = =math.ceil(speed_print * 25 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50)

View file

@ -28,7 +28,6 @@ retraction_count_max = 15
retraction_extra_prime_amount = 0.2
retraction_hop = 2
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
retraction_prime_speed = 15
speed_print = 25
speed_wall = =math.ceil(speed_print * 25 / 25)

View file

@ -27,7 +27,6 @@ acceleration_wall_x = =acceleration_wall
bridge_skin_speed = =bridge_wall_speed
bridge_sparse_infill_max_density = 50
bridge_wall_speed = 30
cool_fan_speed_0 = 0
cool_min_layer_time = 4
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'grid'
infill_sparse_density = 15
@ -65,7 +64,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1.2 , layer_height * 6)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -63,7 +63,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1.2 , layer_height * 6)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -64,7 +64,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1 , layer_height * 5)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -64,7 +64,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1 , layer_height * 5)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -26,7 +26,6 @@ prime_tower_wipe_enabled = True
raft_airgap = 0.25
retraction_hop = 2
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
speed_print = 50
speed_topbottom = =math.ceil(speed_print * 25 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50)

View file

@ -25,7 +25,6 @@ prime_tower_wipe_enabled = True
raft_airgap = 0.25
retraction_hop = 2
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
speed_print = 50
speed_topbottom = =math.ceil(speed_print * 25 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50)

View file

@ -26,7 +26,6 @@ prime_tower_wipe_enabled = True
raft_airgap = 0.25
retraction_hop = 2
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
speed_print = 50
speed_topbottom = =math.ceil(speed_print * 25 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50)

View file

@ -25,7 +25,6 @@ prime_tower_wipe_enabled = True
raft_airgap = 0.25
retraction_hop = 2
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
speed_print = 50
speed_topbottom = =math.ceil(speed_print * 25 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50)

View file

@ -28,7 +28,6 @@ retraction_count_max = 15
retraction_extra_prime_amount = 0.8
retraction_hop = 2
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
speed_print = 25
speed_topbottom = =math.ceil(speed_print * 25 / 25)
speed_wall = =math.ceil(speed_print * 25 / 25)

View file

@ -29,7 +29,6 @@ retraction_count_max = 15
retraction_extra_prime_amount = 0.8
retraction_hop = 2
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
speed_print = 25
speed_topbottom = =math.ceil(speed_print * 25 / 25)
speed_wall = =math.ceil(speed_print * 25 / 25)

View file

@ -29,7 +29,6 @@ retraction_count_max = 15
retraction_extra_prime_amount = 0.8
retraction_hop = 2
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
speed_print = 25
speed_topbottom = =math.ceil(speed_print * 25 / 25)
speed_wall = =math.ceil(speed_print * 25 / 25)

View file

@ -28,7 +28,6 @@ prime_tower_wipe_enabled = True
retraction_count_max = 15
retraction_extra_prime_amount = 0.8
retraction_hop_only_when_collides = True
retraction_min_travel = =line_width * 2
skin_line_width = =round(line_width / 0.8, 2)
speed_print = 25
speed_topbottom = =math.ceil(speed_print * 0.8)

View file

@ -29,7 +29,6 @@ prime_tower_wipe_enabled = True
retraction_count_max = 15
retraction_extra_prime_amount = 0.8
retraction_hop_only_when_collides = True
retraction_min_travel = =line_width * 2
skin_line_width = =round(line_width / 0.8, 2)
speed_print = 25
speed_topbottom = =math.ceil(speed_print * 0.8)

View file

@ -28,7 +28,6 @@ prime_tower_wipe_enabled = True
retraction_count_max = 15
retraction_extra_prime_amount = 0.8
retraction_hop_only_when_collides = True
retraction_min_travel = =line_width * 2
skin_line_width = =round(line_width / 0.8, 2)
speed_print = 25
speed_topbottom = =math.ceil(speed_print * 0.8)

View file

@ -65,7 +65,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1.2 , layer_height * 6)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -66,7 +66,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1.2 , layer_height * 6)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -65,7 +65,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1.2 , layer_height * 6)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -28,6 +28,7 @@ bridge_skin_speed = =bridge_wall_speed
bridge_sparse_infill_max_density = 50
bridge_wall_speed = 30
cool_min_layer_time = 4
infill_material_flow = =1.05 * material_flow
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'grid'
infill_sparse_density = 15
jerk_infill = =jerk_print
@ -49,6 +50,7 @@ raft_airgap = 0.15
retraction_amount = 6.5
retraction_prime_speed = 15
retraction_speed = 45
skin_material_flow = =material_flow
small_skin_on_surface = False
small_skin_width = 4
speed_infill = =speed_print
@ -67,9 +69,11 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1.2 , layer_height * 6)
wall_0_wipe_dist = 0.8
wall_x_material_flow = =1.05 * wall_material_flow
wall_x_material_flow_roofing = =wall_material_flow
z_seam_relative = True
z_seam_type = back
zig_zaggify_infill = True

View file

@ -67,7 +67,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.4
top_bottom_thickness = =max(1.2 , layer_height * 6)
wall_0_wipe_dist = 0.8
wall_line_width_0 = =line_width * (1 + magic_spiralize * 0.25)

View file

@ -64,7 +64,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1.2 , layer_height * 6)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -65,7 +65,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1.2 , layer_height * 6)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -64,7 +64,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1.2 , layer_height * 6)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -28,6 +28,7 @@ bridge_skin_speed = =bridge_wall_speed
bridge_sparse_infill_max_density = 50
bridge_wall_speed = 30
cool_min_layer_time = 4
infill_material_flow = =1.1 * material_flow
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'grid'
infill_sparse_density = 15
jerk_infill = =jerk_print
@ -48,6 +49,7 @@ prime_tower_enable = False
retraction_amount = 8
retraction_prime_speed = 15
retraction_speed = 45
skin_material_flow = =1.05 * material_flow
small_skin_on_surface = False
small_skin_width = 4
speed_infill = =speed_print
@ -66,9 +68,11 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1.2 , layer_height * 6)
wall_0_wipe_dist = 0.8
wall_x_material_flow = =1.1 * wall_material_flow
wall_x_material_flow_roofing = =wall_material_flow
z_seam_relative = True
z_seam_type = back
zig_zaggify_infill = True

View file

@ -66,7 +66,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.4
top_bottom_thickness = =max(1.2 , layer_height * 6)
wall_0_wipe_dist = 0.8
wall_line_width_0 = =line_width * (1 + magic_spiralize * 0.25)

View file

@ -65,7 +65,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1 , layer_height * 5)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -66,7 +66,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1 , layer_height * 5)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -65,7 +65,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1 , layer_height * 5)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -28,6 +28,7 @@ bridge_skin_speed = =bridge_wall_speed
bridge_sparse_infill_max_density = 50
bridge_wall_speed = 30
cool_min_layer_time = 6
infill_material_flow = =1.1 * material_flow
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'grid'
infill_sparse_density = 15
jerk_infill = =jerk_print
@ -49,6 +50,7 @@ raft_airgap = 0.25
retraction_amount = 6.5
retraction_prime_speed = =retraction_speed
retraction_speed = 45
skin_material_flow = =1.05 * material_flow
small_skin_on_surface = False
small_skin_width = 4
speed_infill = =speed_print
@ -67,9 +69,11 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1 , layer_height * 5)
wall_0_wipe_dist = 0.8
wall_x_material_flow = =1.1 * wall_material_flow
wall_x_material_flow_roofing = =wall_material_flow
z_seam_relative = True
z_seam_type = back
zig_zaggify_infill = True

View file

@ -67,7 +67,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1 , layer_height * 5)
wall_0_wipe_dist = 0.8
wall_line_width_0 = =line_width * (1 + magic_spiralize * 0.25)

View file

@ -65,7 +65,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.3
top_bottom_thickness = =max(1 , layer_height * 5)
wall_0_wipe_dist = 0.8
z_seam_relative = True

View file

@ -66,7 +66,7 @@ support_bottom_distance = =support_z_distance
support_interface_enable = True
support_structure = tree
support_top_distance = =support_z_distance
support_z_distance = =math.ceil(0.3/layer_height)*layer_height
support_z_distance = 0.35
top_bottom_thickness = =max(1 , layer_height * 5)
wall_0_wipe_dist = 0.8
z_seam_relative = True

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