Merge remote-tracking branch 'origin/main' into optimized-prime-tower

This commit is contained in:
Erwan MATHIEU 2024-02-16 09:10:49 +01:00
commit e642d85fcd
478 changed files with 7034 additions and 722 deletions

View file

@ -5,6 +5,13 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
### 💥 Slicing Crash Analysis Tool 💥
We are taking steps to analyze an increase in reported crashes more systematically. We'll need some help with that. 😇
Before filling out the report below, we want you to try a special Cura 5.7 Alpha.
This version of Cura has an updated slicing engine that will automatically send a report to the Cura Team for analysis.
#### [You can find the downloads here](https://github.com/Ultimaker/Cura/discussions/18080) ####
If you still encounter a crash you are still welcome to report the issue so we can use your model as a test case, you can find instructions on how to do that below.
### Project File ### Project File
**⚠️ Before you continue, we need your project file to troubleshoot a slicing crash.** **⚠️ Before you continue, we need your project file to troubleshoot a slicing crash.**
It contains the printer and settings we need for troubleshooting. It contains the printer and settings we need for troubleshooting.
@ -68,4 +75,3 @@ body:
description: You can add the zip file and additional information that is relevant to the issue in the comments below. description: You can add the zip file and additional information that is relevant to the issue in the comments below.
validations: validations:
required: true required: true

View file

@ -55,7 +55,8 @@ exe = EXE(
target_arch={{ target_arch }}, target_arch={{ target_arch }},
codesign_identity=os.getenv('CODESIGN_IDENTITY', None), codesign_identity=os.getenv('CODESIGN_IDENTITY', None),
entitlements_file={{ entitlements_file }}, entitlements_file={{ entitlements_file }},
icon={{ icon }} icon={{ icon }},
contents_directory='.'
) )
coll = COLLECT( coll = COLLECT(
@ -70,188 +71,7 @@ coll = COLLECT(
) )
{% if macos == true %} {% if macos == true %}
# PyInstaller seems to copy everything in the resource folder for the MacOS, this causes issues with codesigning and notarizing app = BUNDLE(
# 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(
coll, coll,
name='{{ display_name }}.app', name='{{ display_name }}.app',
icon={{ icon }}, icon={{ icon }},
@ -272,8 +92,9 @@ app = UMBUNDLE(
}], }],
'CFBundleDocumentTypes': [{ 'CFBundleDocumentTypes': [{
'CFBundleTypeRole': 'Viewer', 'CFBundleTypeRole': 'Viewer',
'CFBundleTypeExtensions': ['*'], 'CFBundleTypeExtensions': ['stl', 'obj', '3mf', 'gcode', 'ufp'],
'CFBundleTypeName': 'Model Files', 'CFBundleTypeName': 'Model Files',
}] }]
}, },
){% endif %} )
{% endif %}

View file

@ -1,10 +1,10 @@
version: "5.7.0-alpha.0" version: "5.7.0-alpha.1"
requirements: requirements:
- "uranium/(latest)@ultimaker/testing" - "uranium/(latest)@ultimaker/testing"
- "curaengine/(latest)@ultimaker/testing" - "curaengine/(latest)@ultimaker/testing"
- "cura_binary_data/(latest)@ultimaker/testing" - "cura_binary_data/(latest)@ultimaker/testing"
- "fdm_materials/(latest)@ultimaker/testing" - "fdm_materials/(latest)@ultimaker/testing"
- "curaengine_plugin_gradual_flow/(latest)@ultimaker/stable" - "curaengine_plugin_gradual_flow/0.1.0-beta.2"
- "dulcificum/latest@ultimaker/testing" - "dulcificum/latest@ultimaker/testing"
- "pysavitar/5.3.0" - "pysavitar/5.3.0"
- "pynest2d/5.3.0" - "pynest2d/5.3.0"
@ -118,7 +118,6 @@ pyinstaller:
- "sqlite3" - "sqlite3"
- "trimesh" - "trimesh"
- "win32ctypes" - "win32ctypes"
- "PyQt6"
- "PyQt6.QtNetwork" - "PyQt6.QtNetwork"
- "PyQt6.sip" - "PyQt6.sip"
- "stl" - "stl"
@ -160,6 +159,10 @@ pycharm_targets:
module_name: Cura module_name: Cura
name: pytest in TestGCodeListDecorator.py name: pytest in TestGCodeListDecorator.py
script_name: tests/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 - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
module_name: Cura module_name: Cura
name: pytest in TestIntentManager.py name: pytest in TestIntentManager.py
@ -188,6 +191,10 @@ pycharm_targets:
module_name: Cura module_name: Cura
name: pytest in TestPrintInformation.py name: pytest in TestPrintInformation.py
script_name: tests/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 - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
module_name: Cura module_name: Cura
name: pytest in TestProfileRequirements.py name: pytest in TestProfileRequirements.py

View file

@ -1,4 +1,5 @@
import os import os
from io import StringIO
from pathlib import Path from pathlib import Path
from jinja2 import Template from jinja2 import Template
@ -150,6 +151,7 @@ class CuraConan(ConanFile):
return "None" return "None"
def _conan_installs(self): def _conan_installs(self):
self.output.info("Collecting conan installs")
conan_installs = {} conan_installs = {}
# list of conan installs # list of conan installs
@ -161,13 +163,22 @@ class CuraConan(ConanFile):
return conan_installs return conan_installs
def _python_installs(self): def _python_installs(self):
self.output.info("Collecting python installs")
python_installs = {} python_installs = {}
# list of python installs # list of python installs
python_ins_cmd = f"python -c \"import pkg_resources; print(';'.join([(s.key+','+ s.version) for s in pkg_resources.working_set]))\"" run_env = VirtualRunEnv(self)
from six import StringIO env = run_env.environment()
env.prepend_path("PYTHONPATH", str(self._site_packages.as_posix()))
venv_vars = env.vars(self, scope = "run")
outer = '"' if self.settings.os == "Windows" else "'"
inner = "'" if self.settings.os == "Windows" else '"'
buffer = StringIO() buffer = StringIO()
self.run(python_ins_cmd, run_environment= True, env = "conanrun", output=buffer) with venv_vars.apply():
self.run(f"""python -c {outer}import pkg_resources; print({inner};{inner}.join([(s.key+{inner},{inner}+ s.version) for s in pkg_resources.working_set])){outer}""",
env = "conanrun",
output = buffer)
packages = str(buffer.getvalue()).split("-----------------\n") packages = str(buffer.getvalue()).split("-----------------\n")
packages = packages[1].strip('\r\n').split(";") packages = packages[1].strip('\r\n').split(";")
@ -420,7 +431,6 @@ class CuraConan(ConanFile):
) )
if self.options.get_safe("enable_i18n", False) and self._i18n_options["extract"]: if self.options.get_safe("enable_i18n", False) and self._i18n_options["extract"]:
# Update the po and pot files
vb = VirtualBuildEnv(self) vb = VirtualBuildEnv(self)
vb.generate() vb.generate()
@ -505,10 +515,14 @@ echo "CURA_APP_NAME={{ cura_app_name }}" >> ${{ env_prefix }}GITHUB_ENV
if self.in_local_cache: if self.in_local_cache:
self.runenv_info.append_path("PYTHONPATH", os.path.join(self.package_folder, "site-packages")) self.runenv_info.append_path("PYTHONPATH", os.path.join(self.package_folder, "site-packages"))
self.env_info.PYTHONPATH.append(os.path.join(self.package_folder, "site-packages"))
self.runenv_info.append_path("PYTHONPATH", os.path.join(self.package_folder, "plugins")) self.runenv_info.append_path("PYTHONPATH", os.path.join(self.package_folder, "plugins"))
self.env_info.PYTHONPATH.append(os.path.join(self.package_folder, "plugins"))
else: else:
self.runenv_info.append_path("PYTHONPATH", self.source_folder) self.runenv_info.append_path("PYTHONPATH", self.source_folder)
self.env_info.PYTHONPATH.append(self.source_folder)
self.runenv_info.append_path("PYTHONPATH", os.path.join(self.source_folder, "plugins")) self.runenv_info.append_path("PYTHONPATH", os.path.join(self.source_folder, "plugins"))
self.env_info.PYTHONPATH.append(os.path.join(self.source_folder, "plugins"))
def package_id(self): def package_id(self):
self.info.clear() self.info.clear()

View file

@ -104,7 +104,8 @@ from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager 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.AddPrinterPagesModel import AddPrinterPagesModel
from cura.UI.MachineSettingsManager import MachineSettingsManager from cura.UI.MachineSettingsManager import MachineSettingsManager
from cura.UI.ObjectsModel import ObjectsModel 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.MachineListModel import MachineListModel
from .Machines.Models.ActiveIntentQualitiesModel import ActiveIntentQualitiesModel from .Machines.Models.ActiveIntentQualitiesModel import ActiveIntentQualitiesModel
from .Machines.Models.IntentSelectionModel import IntentSelectionModel from .Machines.Models.IntentSelectionModel import IntentSelectionModel
from .PrintOrderManager import PrintOrderManager
from .SingleInstance import SingleInstance from .SingleInstance import SingleInstance
if TYPE_CHECKING: if TYPE_CHECKING:
@ -179,6 +181,7 @@ class CuraApplication(QtApplication):
# Variables set from CLI # Variables set from CLI
self._files_to_open = [] self._files_to_open = []
self._urls_to_open = []
self._use_single_instance = False self._use_single_instance = False
self._single_instance = None self._single_instance = None
@ -186,7 +189,7 @@ class CuraApplication(QtApplication):
self._cura_formula_functions = None # type: Optional[CuraFormulaFunctions] 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_container = None # type: EmptyInstanceContainer
self.empty_definition_changes_container = None # type: EmptyInstanceContainer self.empty_definition_changes_container = None # type: EmptyInstanceContainer
@ -202,6 +205,7 @@ class CuraApplication(QtApplication):
self._container_manager = None self._container_manager = None
self._object_manager = None self._object_manager = None
self._print_order_manager = None
self._extruders_model = None self._extruders_model = None
self._extruders_model_with_optional = None self._extruders_model_with_optional = None
self._build_plate_model = None self._build_plate_model = None
@ -333,7 +337,7 @@ class CuraApplication(QtApplication):
for filename in self._cli_args.file: for filename in self._cli_args.file:
url = QUrl(filename) url = QUrl(filename)
if url.scheme() in self._supported_url_schemes: if url.scheme() in self._supported_url_schemes:
self._open_url_queue.append(url) self._urls_to_open.append(url)
else: else:
self._files_to_open.append(os.path.abspath(filename)) self._files_to_open.append(os.path.abspath(filename))
@ -352,11 +356,11 @@ class CuraApplication(QtApplication):
self.__addAllEmptyContainers() self.__addAllEmptyContainers()
self.__setLatestResouceVersionsForVersionUpgrade() self.__setLatestResouceVersionsForVersionUpgrade()
self._machine_action_manager = MachineActionManager.MachineActionManager(self) self._machine_action_manager = MachineActionManager(self)
self._machine_action_manager.initialize() self._machine_action_manager.initialize()
def __sendCommandToSingleInstance(self): 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 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. # 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) Resources.addExpectedDirNameInData(dir_name)
app_root = os.path.abspath(os.path.join(os.path.dirname(sys.executable))) app_root = os.path.abspath(os.path.join(os.path.dirname(sys.executable)))
Resources.addSecureSearchPath(os.path.join(app_root, "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")) Resources.addSecureSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources"))
if not hasattr(sys, "frozen"): if not hasattr(sys, "frozen"):
cura_data_root = os.environ.get('CURA_DATA_ROOT', None) cura_data_root = os.environ.get('CURA_DATA_ROOT', None)
if cura_data_root: if cura_data_root:
@ -899,6 +909,7 @@ class CuraApplication(QtApplication):
# initialize info objects # initialize info objects
self._print_information = PrintInformation.PrintInformation(self) self._print_information = PrintInformation.PrintInformation(self)
self._cura_actions = CuraActions.CuraActions(self) self._cura_actions = CuraActions.CuraActions(self)
self._print_order_manager = PrintOrderManager(self.getObjectsModel().getNodes)
self.processEvents() self.processEvents()
# Initialize setting visibility presets model. # Initialize setting visibility presets model.
self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self.getPreferences(), parent = self) self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self.getPreferences(), parent = self)
@ -956,6 +967,8 @@ class CuraApplication(QtApplication):
self.callLater(self._openFile, file_name) 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. 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) 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: for url in self._open_url_queue:
self.callLater(self._openUrl, url) self.callLater(self._openUrl, url)
@ -979,6 +992,7 @@ class CuraApplication(QtApplication):
t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis]) t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis])
Selection.selectionChanged.connect(self.onSelectionChanged) Selection.selectionChanged.connect(self.onSelectionChanged)
self._print_order_manager.printOrderChanged.connect(self._onPrintOrderChanged)
# Set default background color for scene # Set default background color for scene
self.getRenderer().setBackgroundColor(QColor(245, 245, 245)) self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
@ -1068,6 +1082,10 @@ class CuraApplication(QtApplication):
def getTextManager(self, *args) -> "TextManager": def getTextManager(self, *args) -> "TextManager":
return self._text_manager 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": def getCuraFormulaFunctions(self, *args) -> "CuraFormulaFunctions":
if self._cura_formula_functions is None: if self._cura_formula_functions is None:
self._cura_formula_functions = CuraFormulaFunctions(self) self._cura_formula_functions = CuraFormulaFunctions(self)
@ -1094,6 +1112,10 @@ class CuraApplication(QtApplication):
self._object_manager = ObjectsModel(self) self._object_manager = ObjectsModel(self)
return self._object_manager 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) @pyqtSlot(result = QObject)
def getExtrudersModel(self, *args) -> "ExtrudersModel": def getExtrudersModel(self, *args) -> "ExtrudersModel":
if self._extruders_model is None: if self._extruders_model is None:
@ -1129,18 +1151,16 @@ class CuraApplication(QtApplication):
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager() self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
return self._setting_inheritance_manager 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 """Get the machine action manager
We ignore any *args given to this, as we also register the machine manager as qml singleton. 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. 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) @pyqtSlot(result = QObject)
def getMaterialManagementModel(self) -> MaterialManagementModel: def getMaterialManagementModel(self) -> MaterialManagementModel:
@ -1250,6 +1270,7 @@ class CuraApplication(QtApplication):
self.processEvents() self.processEvents()
engine.rootContext().setContextProperty("Printer", self) engine.rootContext().setContextProperty("Printer", self)
engine.rootContext().setContextProperty("CuraApplication", self) engine.rootContext().setContextProperty("CuraApplication", self)
engine.rootContext().setContextProperty("PrintOrderManager", self._print_order_manager)
engine.rootContext().setContextProperty("PrintInformation", self._print_information) engine.rootContext().setContextProperty("PrintInformation", self._print_information)
engine.rootContext().setContextProperty("CuraActions", self._cura_actions) engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion) engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
@ -1264,7 +1285,7 @@ class CuraApplication(QtApplication):
qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, self.getIntentManager, "IntentManager") qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, self.getIntentManager, "IntentManager")
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, self.getSettingInheritanceManager, "SettingInheritanceManager") qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, self.getSettingInheritanceManager, "SettingInheritanceManager")
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, self.getSimpleModeSettingsManagerWrapper, "SimpleModeSettingsManager") 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() self.processEvents()
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil") qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
@ -1745,8 +1766,12 @@ class CuraApplication(QtApplication):
Selection.remove(node) Selection.remove(node)
Selection.add(group_node) Selection.add(group_node)
all_nodes = self.getObjectsModel().getNodes()
PrintOrderManager.updatePrintOrdersAfterGroupOperation(all_nodes, group_node, selected_nodes)
@pyqtSlot() @pyqtSlot()
def ungroupSelected(self) -> None: def ungroupSelected(self) -> None:
all_nodes = self.getObjectsModel().getNodes()
selected_objects = Selection.getAllSelectedObjects().copy() selected_objects = Selection.getAllSelectedObjects().copy()
for node in selected_objects: for node in selected_objects:
if node.callDecoration("isGroup"): if node.callDecoration("isGroup"):
@ -1754,21 +1779,30 @@ class CuraApplication(QtApplication):
group_parent = node.getParent() group_parent = node.getParent()
children = node.getChildren().copy() 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 # Set the parent of the children to the parent of the group-node
op.addOperation(SetParentOperation(child, group_parent)) op.addOperation(SetParentOperation(child, group_parent))
# Add all individual nodes to the selection # Add all individual nodes to the selection
Selection.add(child) Selection.add(child)
PrintOrderManager.updatePrintOrdersAfterUngroupOperation(all_nodes, node, children_to_ungroup)
op.push() op.push()
# Note: The group removes itself from the scene once all its children have left it, # Note: The group removes itself from the scene once all its children have left it,
# see GroupDecorator._onChildrenChanged # 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]: def _createSplashScreen(self) -> Optional[CuraSplashScreen.CuraSplashScreen]:
if self._is_headless: if self._is_headless:
return None 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") catalog = i18nCatalog("cura")
TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S" TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
REQUEST_TIMEOUT = 5 # Seconds
class AuthorizationHelpers: class AuthorizationHelpers:
@ -53,7 +54,8 @@ class AuthorizationHelpers:
data = urllib.parse.urlencode(data).encode("UTF-8"), data = urllib.parse.urlencode(data).encode("UTF-8"),
headers_dict = headers, headers_dict = headers,
callback = lambda response: self.parseTokenResponse(response, callback), 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: 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"), data = urllib.parse.urlencode(data).encode("UTF-8"),
headers_dict = headers, headers_dict = headers,
callback = lambda response: self.parseTokenResponse(response, callback), 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: def parseTokenResponse(self, token_response: QNetworkReply, callback: Callable[[AuthenticationResponse], None]) -> None:
@ -122,7 +126,8 @@ class AuthorizationHelpers:
check_token_url, check_token_url,
headers_dict = headers, headers_dict = headers,
callback = lambda reply: self._parseUserProfile(reply, success_callback, failed_callback), 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: 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. # Cura is released under the terms of the LGPLv3 or higher.
import json import json
@ -6,13 +6,14 @@ from datetime import datetime, timedelta
from typing import Callable, Dict, Optional, TYPE_CHECKING, Union from typing import Callable, Dict, Optional, TYPE_CHECKING, Union
from urllib.parse import urlencode, quote_plus from urllib.parse import urlencode, quote_plus
from PyQt6.QtCore import QUrl from PyQt6.QtCore import QUrl, QTimer
from PyQt6.QtGui import QDesktopServices from PyQt6.QtGui import QDesktopServices
from UM.Logger import Logger from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.Signal import Signal from UM.Signal import Signal
from UM.i18n import i18nCatalog 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.AuthorizationHelpers import AuthorizationHelpers, TOKEN_TIMESTAMP_FORMAT
from cura.OAuth2.LocalAuthorizationServer import LocalAuthorizationServer from cura.OAuth2.LocalAuthorizationServer import LocalAuthorizationServer
from cura.OAuth2.Models import AuthenticationResponse, BaseModel 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" 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: class AuthorizationService:
"""The authorization service is responsible for handling the login flow, storing user credentials and providing """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.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): def _authChanged(self, logged_in):
if logged_in and self._unable_to_get_data_message is not None: if logged_in and self._unable_to_get_data_message is not None:
self._unable_to_get_data_message.hide() self._unable_to_get_data_message.hide()
@ -167,16 +176,29 @@ class AuthorizationService:
return return
def process_auth_data(response: AuthenticationResponse) -> None: def process_auth_data(response: AuthenticationResponse) -> None:
self._currently_refreshing_token = False
if response.success: if response.success:
self._refresh_token_retries = 0
self._storeAuthData(response) self._storeAuthData(response)
HttpRequestManager.getInstance().setDelayRequests(False)
self.onAuthStateChanged.emit(logged_in = True) self.onAuthStateChanged.emit(logged_in = True)
else: else:
Logger.warning("Failed to get a new access token from the server.") 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) 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: if self._currently_refreshing_token:
Logger.debug("Was already busy refreshing token. Do not start a new request.") Logger.debug("Was already busy refreshing token. Do not start a new request.")
return return
HttpRequestManager.getInstance().setDelayRequests(True)
self._currently_refreshing_token = True self._currently_refreshing_token = True
self._auth_helpers.getAccessTokenUsingRefreshToken(self._auth_data.refresh_token, process_auth_data) 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 UM.Scene.SceneNode import SceneNode
from functools import cmp_to_key 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): class OneAtATimeIterator(Iterator.Iterator):
"""Iterator that returns a list of nodes in the order that they need to be printed """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: def __init__(self, scene_node) -> None:
super().__init__(scene_node) # Call super to make multiple inheritance work. 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: def _fillStack(self) -> None:
"""Fills the ``_node_stack`` with a list of scene nodes that need to be printed in order. """ """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[:] self._node_stack = node_list[:]
return return
# Copy the list hit_checker = HitChecker(node_list)
self._original_node_list = node_list[:]
# Initialise the hit map (pre-compute all hits between all objects) if PrintOrderManager.isUserDefinedPrintOrderEnabled():
self._hit_map = [[self._checkHit(i, j) for i in node_list] for j in node_list] 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! # update print orders so that user can try to arrange the nodes automatically first
for a in range(0, len(node_list)): # and if result is not satisfactory he/she can switch to manual mode and change it
for b in range(0, len(node_list)): for index, node in enumerate(self._node_stack):
if a != b and self._hit_map[a][b] and self._hit_map[b][a]: node.printOrder = index + 1
return
@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. # 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. # 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)] todo_node_list = [_ObjectOrder([], node_list)]
while len(todo_node_list) > 0: while len(todo_node_list) > 0:
current = todo_node_list.pop() current = todo_node_list.pop()
for node in current.todo: 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 # 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. # We found a possible result. Create new todo & order list.
new_todo_list = current.todo[:] new_todo_list = current.todo[:]
new_todo_list.remove(node) new_todo_list.remove(node)
new_order = current.order[:] + [node] new_order = current.order[:] + [node]
if len(new_todo_list) == 0: if len(new_todo_list) == 0:
# We have no more nodes to check, so quit looking. # We have no more nodes to check, so quit looking.
self._node_stack = new_order return new_order # Solution found!
return
todo_node_list.append(_ObjectOrder(new_order, new_todo_list)) todo_node_list.append(_ObjectOrder(new_order, new_todo_list))
self._node_stack = [] #No result found! return [] # 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
class _ObjectOrder: 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 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_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): def _onSceneChanged(self, source):
if not source.callDecoration("isSliceable"): 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. # 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. # By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve.
random.shuffle(nodes) random.shuffle(nodes)
for node in nodes: for node in nodes:
if node is root or not isinstance(node, SceneNode) or node.getBoundingBox() is None: if node is root or not isinstance(node, SceneNode) or node.getBoundingBox() is None:
continue continue
@ -80,7 +88,10 @@ class PlatformPhysics:
# Move it downwards if bottom is above platform # Move it downwards if bottom is above platform
move_vector = Vector() 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 z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
move_vector = move_vector.set(y = -bbox.bottom + z_offset) move_vector = move_vector.set(y = -bbox.bottom + z_offset)
@ -168,6 +179,8 @@ class PlatformPhysics:
op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector) op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
op.push() 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 # After moving, we have to evaluate the boundary checks for nodes
build_volume.updateNodeBoundaryCheck() 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: if not no_setting_override:
self.addDecorator(SettingOverrideDecorator()) # Now we always have a getActiveExtruderPosition, unless explicitly disabled self.addDecorator(SettingOverrideDecorator()) # Now we always have a getActiveExtruderPosition, unless explicitly disabled
self._outside_buildarea = False self._outside_buildarea = False
self._print_order = 0
def setOutsideBuildArea(self, new_value: bool) -> None: def setOutsideBuildArea(self, new_value: bool) -> None:
self._outside_buildarea = new_value 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: def isOutsideBuildArea(self) -> bool:
return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0 return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
@ -157,3 +166,6 @@ class CuraSceneNode(SceneNode):
def transformChanged(self) -> None: def transformChanged(self) -> None:
self._transformChanged() self._transformChanged()
def __repr__(self) -> str:
return "{print_order}. {name}".format(print_order = self._print_order, name = self.getName())

View file

@ -316,7 +316,13 @@ class ExtruderManager(QObject):
# Starts with the adhesion extruder. # Starts with the adhesion extruder.
adhesion_type = global_stack.getProperty("adhesion_type", "value") adhesion_type = global_stack.getProperty("adhesion_type", "value")
if adhesion_type in {"skirt", "brim"}: if adhesion_type in {"skirt", "brim"}:
return max(0, int(global_stack.getProperty("skirt_brim_extruder_nr", "value"))) # optional skirt/brim extruder defaults to zero skirt_brim_extruder_nr = global_stack.getProperty("skirt_brim_extruder_nr", "value")
# if the skirt_brim_extruder_nr is -1, then we use the first used extruder
if skirt_brim_extruder_nr == -1:
used_extruders = self.getUsedExtruderStacks()
return used_extruders[0].position
else:
return skirt_brim_extruder_nr
if adhesion_type == "raft": if adhesion_type == "raft":
return global_stack.getProperty("raft_base_extruder_nr", "value") return global_stack.getProperty("raft_base_extruder_nr", "value")

View file

@ -5,16 +5,18 @@ import json
import os import os
from typing import List, Optional from typing import List, Optional
from PyQt6.QtCore import QUrl
from PyQt6.QtNetwork import QLocalServer, QLocalSocket 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 from UM.Logger import Logger
class SingleInstance: 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._application = application
self._files_to_open = files_to_open self._files_to_open = files_to_open
self._url_to_open = url_to_open
self._single_instance_server = None self._single_instance_server = None
@ -33,7 +35,7 @@ class SingleInstance:
return False return False
# We only send the files that need to be opened. # 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.") Logger.log("i", "No file need to be opened, do nothing.")
return True return True
@ -55,8 +57,12 @@ class SingleInstance:
payload = {"command": "open", "filePath": os.path.abspath(filename)} payload = {"command": "open", "filePath": os.path.abspath(filename)}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii")) 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"} 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.flush()
single_instance_socket.waitForDisconnected() single_instance_socket.waitForDisconnected()
@ -72,7 +78,7 @@ class SingleInstance:
def _onClientConnected(self) -> None: def _onClientConnected(self) -> None:
Logger.log("i", "New connection received on our single-instance server") 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: if self._single_instance_server:
connection = self._single_instance_server.nextPendingConnection() connection = self._single_instance_server.nextPendingConnection()
@ -94,6 +100,12 @@ class SingleInstance:
elif command == "open": elif command == "open":
self._application.callLater(lambda f = payload["filePath"]: self._application._openFile(f)) 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. # Command: Activate the window and bring it to the top.
elif command == "focus": elif command == "focus":
# Operating systems these days prevent windows from moving around by themselves. # Operating systems these days prevent windows from moving around by themselves.

View file

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

View file

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

View file

@ -299,6 +299,11 @@ class WorkspaceDialog(QObject):
Application.getInstance().getBackend().close() 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: def setMaterialConflict(self, material_conflict: bool) -> None:
if self._has_material_conflict != material_conflict: if self._has_material_conflict != material_conflict:
self._has_material_conflict = material_conflict self._has_material_conflict = material_conflict

View file

@ -300,6 +300,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 Row
{ {
id: clearBuildPlateWarning id: clearBuildPlateWarning
@ -422,6 +441,7 @@ UM.Dialog
materialSection.reloadValues() materialSection.reloadValues()
profileSection.reloadValues() profileSection.reloadValues()
printerSection.reloadValues() printerSection.reloadValues()
dropToBuildPlate.reloadValue()
} }
} }
} }

View file

@ -10,16 +10,14 @@ from UM.Math.Vector import Vector
from UM.Logger import Logger from UM.Logger import Logger
from UM.Math.Matrix import Matrix from UM.Math.Matrix import Matrix
from UM.Application import Application from UM.Application import Application
from UM.Message import Message
from UM.Resources import Resources
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.EmptyInstanceContainer import EmptyInstanceContainer
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.CuraPackageManager import CuraPackageManager from cura.CuraPackageManager import CuraPackageManager
from cura.Settings import CuraContainerStack from cura.Settings import CuraContainerStack
from cura.Utils.Threading import call_on_qt_thread from cura.Utils.Threading import call_on_qt_thread
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Snapshot import Snapshot from cura.Snapshot import Snapshot
from PyQt6.QtCore import QBuffer from PyQt6.QtCore import QBuffer
@ -137,6 +135,9 @@ class ThreeMFWriter(MeshWriter):
for key in changed_setting_keys: for key in changed_setting_keys:
savitar_node.setSetting("cura:" + key, str(stack.getProperty(key, "value"))) 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. # Store the metadata.
for key, value in um_node.metadata.items(): for key, value in um_node.metadata.items():
savitar_node.setSetting(key, value) savitar_node.setSetting(key, value)

View file

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

View file

@ -1,7 +1,6 @@
# Copyright (c) 2021 Ultimaker B.V. # Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from .src import DigitalFactoryFileProvider, DigitalFactoryOutputDevicePlugin, DigitalFactoryController from .src import DigitalFactoryFileProvider, DigitalFactoryOutputDevicePlugin, DigitalFactoryController

View file

@ -3,7 +3,6 @@
import json import json
from json import JSONDecodeError from json import JSONDecodeError
import re
from time import time from time import time
from typing import List, Any, Optional, Union, Type, Tuple, Dict, cast, TypeVar, Callable from typing import List, Any, Optional, Union, Type, Tuple, Dict, cast, TypeVar, Callable

View file

@ -4,7 +4,6 @@ from typing import List, Optional
from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtCore import Qt, pyqtSignal
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from .DigitalFactoryProjectResponse import DigitalFactoryProjectResponse from .DigitalFactoryProjectResponse import DigitalFactoryProjectResponse

View file

@ -2,7 +2,6 @@
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Platform import Platform
from . import GCodeGzWriter from . import GCodeGzWriter

View file

@ -11,7 +11,6 @@ from UM.Settings.InstanceContainer import InstanceContainer
from cura.Machines.ContainerTree import ContainerTree from cura.Machines.ContainerTree import ContainerTree
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from cura.Settings.CuraStackBuilder import CuraStackBuilder
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")

View file

@ -3,12 +3,10 @@
from typing import Optional, TYPE_CHECKING, Dict, List from typing import Optional, TYPE_CHECKING, Dict, List
from .Constants import PACKAGES_URL
from .PackageModel import PackageModel from .PackageModel import PackageModel
from .RemotePackageList import RemotePackageList from .RemotePackageList import RemotePackageList
from PyQt6.QtCore import pyqtSignal, QObject, pyqtProperty, QCoreApplication from PyQt6.QtCore import pyqtSignal, QObject, pyqtProperty, QCoreApplication
from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To request the package list from the API.
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
if TYPE_CHECKING: if TYPE_CHECKING:

View file

@ -2,7 +2,6 @@
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import re import re
from enum import Enum
from typing import Any, cast, Dict, List, Optional from typing import Any, cast, Dict, List, Optional
from PyQt6.QtCore import pyqtProperty, QObject, pyqtSignal, pyqtSlot from PyQt6.QtCore import pyqtProperty, QObject, pyqtSignal, pyqtSlot
@ -12,7 +11,6 @@ from cura.CuraApplication import CuraApplication
from cura.CuraPackageManager import CuraPackageManager from cura.CuraPackageManager import CuraPackageManager
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with. from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To get names of materials we're compatible with.
from UM.i18n import i18nCatalog # To translate placeholder names if data is not present. from UM.i18n import i18nCatalog # To translate placeholder names if data is not present.
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry from UM.PluginRegistry import PluginRegistry
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")

View file

@ -11,7 +11,7 @@ from UM.Settings.SettingInstance import SettingInstance
from UM.Logger import Logger from UM.Logger import Logger
import UM.Settings.Models.SettingVisibilityHandler import UM.Settings.Models.SettingVisibilityHandler
from cura.Settings.ExtruderManager import ExtruderManager #To get global-inherits-stack setting values from different extruders. from cura.Settings.ExtruderManager import ExtruderManager # To get global-inherits-stack setting values from different extruders.
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator

View file

@ -21,7 +21,7 @@
# M163 - Set Mix Factor # M163 - Set Mix Factor
# M164 - Save Mix - saves to T2 as a unique mix # M164 - Save Mix - saves to T2 as a unique mix
import re #To perform the search and replace. import re # To perform the search and replace.
from ..Script import Script from ..Script import Script
class ColorMix(Script): class ColorMix(Script):

View file

@ -6,7 +6,6 @@
# Description: This plugin is now an option in 'Display Info on LCD' # Description: This plugin is now an option in 'Display Info on LCD'
from ..Script import Script from ..Script import Script
from UM.Application import Application
from UM.Message import Message from UM.Message import Message
class DisplayFilenameAndLayerOnLCD(Script): class DisplayFilenameAndLayerOnLCD(Script):

View file

@ -30,9 +30,6 @@
from ..Script import Script from ..Script import Script
from UM.Application import Application from UM.Application import Application
from UM.Qt.Duration import DurationFormat from UM.Qt.Duration import DurationFormat
import UM.Util
import configparser
from UM.Preferences import Preferences
import time import time
import datetime import datetime
import math import math

View file

@ -7,8 +7,6 @@
from ..Script import Script from ..Script import Script
import re
import datetime
from UM.Message import Message from UM.Message import Message
class DisplayProgressOnLCD(Script): class DisplayProgressOnLCD(Script):

View file

@ -7,7 +7,7 @@
from typing import List from typing import List
from ..Script import Script from ..Script import Script
from UM.Application import Application #To get the current printer's settings. from UM.Application import Application # To get the current printer's settings.
class FilamentChange(Script): class FilamentChange(Script):

View file

@ -7,7 +7,7 @@
from ..Script import Script from ..Script import Script
import re import re
from UM.Application import Application #To get the current printer's settings. from UM.Application import Application # To get the current printer's settings.
from UM.Logger import Logger from UM.Logger import Logger
from typing import List, Tuple from typing import List, Tuple

View file

@ -1,7 +1,7 @@
# Copyright (c) 2017 Ghostkeeper # Copyright (c) 2017 Ghostkeeper
# The PostProcessingPlugin is released under the terms of the LGPLv3 or higher. # The PostProcessingPlugin is released under the terms of the LGPLv3 or higher.
import re #To perform the search and replace. import re # To perform the search and replace.
from ..Script import Script from ..Script import Script

View file

@ -8,7 +8,7 @@ from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.FileHandler.WriteFileJob import WriteFileJob from UM.FileHandler.WriteFileJob import WriteFileJob
from UM.FileHandler.FileWriter import FileWriter #To check against the write modes (text vs. binary). from UM.FileHandler.FileWriter import FileWriter # To check against the write modes (text vs. binary).
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.OutputDevice.OutputDevice import OutputDevice from UM.OutputDevice.OutputDevice import OutputDevice
from UM.OutputDevice import OutputDeviceError from UM.OutputDevice import OutputDeviceError

View file

@ -372,7 +372,10 @@ class SimulationView(CuraView):
self._minimum_path_num = min(self._minimum_path_num, self._current_path_num) self._minimum_path_num = min(self._minimum_path_num, self._current_path_num)
# update _current time when the path is changed by user # update _current time when the path is changed by user
if self._current_path_num < self._max_paths and round(self._current_path_num)== self._current_path_num: if self._current_path_num < self._max_paths and round(self._current_path_num)== self._current_path_num:
self._current_time = self.cumulativeLineDuration()[int(self._current_path_num)] actual_path_num = int(self._current_path_num)
cumulative_line_duration = self.cumulativeLineDuration()
if actual_path_num < len(cumulative_line_duration):
self._current_time = cumulative_line_duration[actual_path_num]
self._startUpdateTopLayers() self._startUpdateTopLayers()
self.currentPathNumChanged.emit() self.currentPathNumChanged.emit()

View file

@ -5,7 +5,7 @@ import json
import os import os
import platform import platform
import time import time
from typing import cast, Optional, Set, TYPE_CHECKING from typing import Optional, Set, TYPE_CHECKING
from PyQt6.QtCore import pyqtSlot, QObject from PyQt6.QtCore import pyqtSlot, QObject
from PyQt6.QtNetwork import QNetworkRequest from PyQt6.QtNetwork import QNetworkRequest

View file

@ -16,8 +16,6 @@ from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.Math.Color import Color from UM.Math.Color import Color
from UM.PluginRegistry import PluginRegistry
from UM.Platform import Platform
from UM.Event import Event from UM.Event import Event
from UM.View.RenderBatch import RenderBatch from UM.View.RenderBatch import RenderBatch

View file

@ -22,7 +22,6 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
from cura.Utils.Threading import call_on_qt_thread from cura.Utils.Threading import call_on_qt_thread

View file

@ -9,8 +9,8 @@ try:
except ImportError: except ImportError:
Logger.log("w", "Could not import UFPWriter; libCharon may be missing") Logger.log("w", "Could not import UFPWriter; libCharon may be missing")
from UM.i18n import i18nCatalog #To translate the file format description. from UM.i18n import i18nCatalog # To translate the file format description.
from UM.Mesh.MeshWriter import MeshWriter #For the binary mode flag. from UM.Mesh.MeshWriter import MeshWriter # For the binary mode flag.
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")

View file

@ -4,9 +4,6 @@
from UM.Job import Job from UM.Job import Job
from UM.Logger import Logger from UM.Logger import Logger
from .avr_isp import ispBase
from .avr_isp.stk500v2 import Stk500v2
from time import time, sleep from time import time, sleep
from serial import Serial, SerialException from serial import Serial, SerialException

View file

@ -5,9 +5,9 @@ import os
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Logger import Logger from UM.Logger import Logger
from UM.Mesh.MeshWriter import MeshWriter #To get the g-code output. from UM.Mesh.MeshWriter import MeshWriter # To get the g-code output.
from UM.Message import Message #Show an error when already printing. from UM.Message import Message # Show an error when already printing.
from UM.PluginRegistry import PluginRegistry #To get the g-code output. from UM.PluginRegistry import PluginRegistry # To get the g-code output.
from UM.Qt.Duration import DurationFormat from UM.Qt.Duration import DurationFormat
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
@ -19,7 +19,7 @@ from cura.PrinterOutput.GenericOutputController import GenericOutputController
from .AutoDetectBaudJob import AutoDetectBaudJob from .AutoDetectBaudJob import AutoDetectBaudJob
from .AvrFirmwareUpdater import AvrFirmwareUpdater from .AvrFirmwareUpdater import AvrFirmwareUpdater
from io import StringIO #To write the g-code output. from io import StringIO # To write the g-code output.
from queue import Queue from queue import Queue
from serial import Serial, SerialException, SerialTimeoutException from serial import Serial, SerialException, SerialTimeoutException
from threading import Thread, Event from threading import Thread, Event

View file

@ -1,16 +1,16 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import configparser #To read config files. import configparser # To read config files.
import io #To write config files to strings as if they were files. import io # To write config files to strings as if they were files.
import os.path #To get the path to write new user profiles to. import os.path # To get the path to write new user profiles to.
from typing import Dict, List, Optional, Set, Tuple from typing import Dict, List, Optional, Set, Tuple
import urllib #To serialise the user container file name properly. import urllib # To serialise the user container file name properly.
import urllib.parse import urllib.parse
import UM.VersionUpgrade #To indicate that a file is of incorrect format. import UM.VersionUpgrade # To indicate that a file is of incorrect format.
import UM.VersionUpgradeManager #To schedule more files to be upgraded. import UM.VersionUpgradeManager # To schedule more files to be upgraded.
from UM.Resources import Resources #To get the config storage path. from UM.Resources import Resources # To get the config storage path.
## Creates a new machine instance instance by parsing a serialised machine ## Creates a new machine instance instance by parsing a serialised machine
# instance in version 1 of the file format. # instance in version 1 of the file format.

View file

@ -1,11 +1,11 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import configparser #To read config files. import configparser # To read config files.
import io #To output config files to string. import io # To output config files to string.
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
import UM.VersionUpgrade #To indicate that a file is of the wrong format. import UM.VersionUpgrade # To indicate that a file is of the wrong format.
## Creates a new preferences instance by parsing a serialised preferences file ## Creates a new preferences instance by parsing a serialised preferences file
# in version 1 of the file format. # in version 1 of the file format.

View file

@ -1,8 +1,8 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import configparser #To read config files. import configparser # To read config files.
import io #To write config files to strings as if they were files. import io # To write config files to strings as if they were files.
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
import UM.VersionUpgrade import UM.VersionUpgrade

View file

@ -7,7 +7,6 @@ from PyQt6.QtGui import QOpenGLContext, QImage
from UM.Application import Application from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
from UM.Math.Color import Color from UM.Math.Color import Color
from UM.PluginRegistry import PluginRegistry
from UM.Resources import Resources from UM.Resources import Resources
from UM.Platform import Platform from UM.Platform import Platform
from UM.Event import Event from UM.Event import Event

View file

@ -3,9 +3,9 @@
import copy import copy
import io import io
import json #To parse the product-to-id mapping file. import json # To parse the product-to-id mapping file.
import os.path #To find the product-to-id mapping. import os.path # To find the product-to-id mapping.
from typing import Any, Dict, List, Optional, Tuple, cast, Set, Union from typing import Any, Dict, List, Optional, Tuple, cast, Set
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from UM.PluginRegistry import PluginRegistry from UM.PluginRegistry import PluginRegistry

View file

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

View file

@ -9,16 +9,20 @@
}, },
"overrides": "overrides":
{ {
"acceleration_layer_0": { "value": 3000 }, "acceleration_print":
"acceleration_print": { "value": 3000 }, {
"acceleration_travel": { "value": 5000 }, "maximum_value_warning": "20000",
"value": 10000
},
"acceleration_wall": { "value": "acceleration_print/2" },
"cool_fan_full_layer": { "value": 2 }, "cool_fan_full_layer": { "value": 2 },
"infill_line_width": { "value": "line_width + 0.05" }, "infill_line_width": { "value": "line_width + 0.05" },
"infill_overlap": { "value": "0 if infill_sparse_density < 40.01 and infill_pattern != 'concentric' else -5" }, "infill_overlap": { "value": "0 if infill_sparse_density < 40.01 and infill_pattern != 'concentric' else -5" },
"infill_pattern": { "value": "'lines' if infill_sparse_density > 35 else 'grid'" },
"initial_layer_line_width_factor": { "value": "100.0 if resolveOrValue('adhesion_type') == 'raft' else 125 if line_width < 0.5 else 110" }, "initial_layer_line_width_factor": { "value": "100.0 if resolveOrValue('adhesion_type') == 'raft' else 125 if line_width < 0.5 else 110" },
"machine_acceleration": { "value": 3000 }, "machine_acceleration": { "value": 5000 },
"machine_depth": { "default_value": 230 }, "machine_depth": { "default_value": 230 },
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z2 ;Raise Z more\nG90 ;Absolute positionning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" }, "machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z2 ;Raise Z more\nG90 ;Absolute positionning\nG1 X0 Y{machine_depth - 5} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {
@ -32,12 +36,12 @@
"machine_heated_bed": { "default_value": true }, "machine_heated_bed": { "default_value": true },
"machine_height": { "default_value": 270 }, "machine_height": { "default_value": 270 },
"machine_max_acceleration_e": { "value": 5000 }, "machine_max_acceleration_e": { "value": 5000 },
"machine_max_acceleration_x": { "value": 5000 }, "machine_max_acceleration_x": { "value": 20000 },
"machine_max_acceleration_y": { "value": 5000 }, "machine_max_acceleration_y": { "value": 20000 },
"machine_name": { "default_value": "ELEGOO NEPTUNE 4" }, "machine_name": { "default_value": "ELEGOO NEPTUNE 4" },
"machine_nozzle_cool_down_speed": { "value": 0.75 }, "machine_nozzle_cool_down_speed": { "value": 0.75 },
"machine_nozzle_heat_up_speed": { "value": 1.6 }, "machine_nozzle_heat_up_speed": { "value": 1.6 },
"machine_start_gcode": { "default_value": "G28 ;home\nG92 E0 ;Reset Extruder\nG1 Z4.0 F3000 ;Move Z Axis up\nG92 E0 ;Reset Extruder\nG1 X1.1 Y20 Z0.28 F5000.0 ;Move to start position\nG1 X1.1 Y80.0 Z0.28 F1500.0 E10 ;Draw the first line\nG1 X1.4 Y80.0 Z0.28 F5000.0 ;Move to side a little\nG1 X1.4 Y20 Z0.28 F1500.0 E20 ;Draw the second line\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up" }, "machine_start_gcode": { "default_value": ";ELEGOO NEPTUNE 4 / 4 PRO\nM220 S100 ;Set the feed speed to 100%\nM221 S100 ;Set the flow rate to 100%\nM104 S140 ;Start heating extruder\nM190 S{material_bed_temperature_layer_0} ;Wait for the bed to reach print temp\nG90\nG28 ;home\nG1 Z10 F300\nG1 X67.5 Y0 F6000\nG1 Z0 F300\nM109 S{material_print_temperature_layer_0} ;Wait for extruder to reach print temp\nG92 E0 ;Reset Extruder\nG1 X67.5 Y0 Z0.4 F300 ;Move to start position\nG1 X167.5 E30 F400 ;Draw the first line\nG1 Z0.6 F120.0 ;Move to side a little\nG1 X162.5 F3000\nG92 E0 ;Reset Extruder" },
"machine_width": { "default_value": 235 }, "machine_width": { "default_value": 235 },
"retraction_amount": { "default_value": 0.5 }, "retraction_amount": { "default_value": 0.5 },
"retraction_count_max": { "value": 80 }, "retraction_count_max": { "value": 80 },

View file

@ -0,0 +1,62 @@
{
"version": 2,
"name": "ELEGOO NEPTUNE 4 Max",
"inherits": "elegoo_neptune_4",
"metadata":
{
"visible": true,
"author": "mastercaution",
"platform": "elegoo_platform_max.3mf",
"platform_offset": [
-2.1,
-0.2,
0
],
"quality_definition": "elegoo_neptune_4"
},
"overrides":
{
"acceleration_print":
{
"maximum_value_warning": "15000",
"value": 2500
},
"machine_depth": { "default_value": 430 },
"machine_height": { "default_value": 482 },
"machine_max_acceleration_e": { "value": 5000 },
"machine_max_acceleration_x": { "value": 15000 },
"machine_max_acceleration_y": { "value": 15000 },
"machine_name": { "default_value": "ELEGOO NEPTUNE 4 Max" },
"machine_start_gcode": { "default_value": ";ELEGOO NEPTUNE 4 MAX\nM220 S100 ;Set the feed speed to 100%\nM221 S100 ;Set the flow rate to 100%\nM104 S140 ;Start heating extruder\nM190 S{material_bed_temperature_layer_0} ;Wait for the bed to reach print temp\nG90\nG28 ;home\nG1 Z10 F300\nG1 X165 Y0 F6000\nG1 Z0 F300\nM109 S{material_print_temperature_layer_0} ;Wait for extruder to reach print temp\nG92 E0 ;Reset Extruder\nG1 X165 Y0 Z0.4 F300 ;Move to start position\nG1 X265 E30 F400 ;Draw the first line\nG1 Z0.6 F120.0 ;Move to side a little\nG1 X260 F3000\nG92 E0 ;Reset Extruder" },
"machine_width": { "default_value": 430 },
"nozzle_disallowed_areas":
{
"default_value": [
[
[-215, -215],
[-215, 215],
[-211, 215],
[-211, -215]
],
[
[215, 215],
[215, -215],
[211, -215],
[211, 215]
],
[
[-215, -215],
[215, -215],
[-215, -211],
[215, -211]
],
[
[-215, 215],
[215, 215],
[-215, 211],
[215, 211]
]
]
}
}
}

View file

@ -0,0 +1,59 @@
{
"version": 2,
"name": "ELEGOO NEPTUNE 4 Plus",
"inherits": "elegoo_neptune_4",
"metadata":
{
"visible": true,
"author": "mastercaution",
"platform": "elegoo_platform_max.3mf",
"platform_offset": [
-2.1,
-0.2,
0
],
"quality_definition": "elegoo_neptune_4"
},
"overrides":
{
"acceleration_print": { "value": 4000 },
"machine_acceleration": { "value": 4000 },
"machine_depth": { "default_value": 330 },
"machine_height": { "default_value": 387 },
"machine_max_acceleration_e": { "value": 5000 },
"machine_max_acceleration_x": { "value": 20000 },
"machine_max_acceleration_y": { "value": 20000 },
"machine_name": { "default_value": "ELEGOO NEPTUNE 4 Plus" },
"machine_start_gcode": { "default_value": ";ELEGOO NEPTUNE 4 PLUS\nM220 S100 ;Set the feed speed to 100%\nM221 S100 ;Set the flow rate to 100%\nM104 S140 ;Start heating extruder\nM190 S{material_bed_temperature_layer_0} ;Wait for the bed to reach print temp\nG90\nG28 ;home\nG1 Z10 F300\nG1 X115 Y0 F6000\nG1 Z0 F300\nM109 S{material_print_temperature_layer_0} ;Wait for extruder to reach print temp\nG92 E0 ;Reset Extruder\nG1 X115 Y0 Z0.4 F300 ;Move to start position\nG1 X215 E30 F400 ;Draw the first line\nG1 Z0.6 F120.0 ;Move to side a little\nG1 X210 F3000\nG92 E0 ;Reset Extruder" },
"machine_width": { "default_value": 330 },
"nozzle_disallowed_areas":
{
"default_value": [
[
[-165, -165],
[-165, 165],
[-161, 165],
[-161, -165]
],
[
[165, 165],
[165, -165],
[161, -165],
[161, 165]
],
[
[-165, -165],
[165, -165],
[-165, -161],
[165, -161]
],
[
[-165, 165],
[165, 165],
[-165, 161],
[165, 161]
]
]
}
}
}

View file

@ -1684,7 +1684,7 @@
"value": "skin_line_width * 2", "value": "skin_line_width * 2",
"default_value": 1, "default_value": 1,
"minimum_value": "0", "minimum_value": "0",
"maximum_value_warning": "skin_line_width * 3", "maximum_value_warning": "skin_line_width * 10",
"type": "float", "type": "float",
"enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern != 'concentric'", "enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern != 'concentric'",
"limit_to_extruder": "top_bottom_extruder_nr", "limit_to_extruder": "top_bottom_extruder_nr",
@ -5151,7 +5151,7 @@
"unit": "mm", "unit": "mm",
"type": "float", "type": "float",
"minimum_value": "0", "minimum_value": "0",
"maximum_value_warning": "machine_nozzle_size", "maximum_value_warning": "5*layer_height",
"default_value": 0.1, "default_value": 0.1,
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr", "limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
"enabled": "support_enable or support_meshes_present", "enabled": "support_enable or support_meshes_present",
@ -6469,6 +6469,18 @@
"settable_per_extruder": true, "settable_per_extruder": true,
"limit_to_extruder": "raft_surface_extruder_nr" "limit_to_extruder": "raft_surface_extruder_nr"
}, },
"raft_surface_monotonic":
{
"label": "Monotonic Raft Top Surface Order",
"description": "Print raft top surface lines in an ordering that causes them to always overlap with adjacent lines in a single direction. This takes slightly more time to print, but makes the surface look more consistent, which is also visible on the model bottom surface.",
"type": "bool",
"default_value": false,
"value": "skin_monotonic",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and raft_surface_layers > 0",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_surface_extruder_nr"
},
"raft_wall_count": "raft_wall_count":
{ {
"label": "Raft Wall Count", "label": "Raft Wall Count",
@ -7269,6 +7281,16 @@
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": 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": "infill_mesh":
{ {
"label": "Infill Mesh", "label": "Infill Mesh",

View file

@ -0,0 +1,15 @@
{
"version": 2,
"name": "Adventurer 3",
"inherits": "flashforge_adventurer3c",
"metadata":
{
"visible": true,
"author": "Jeremie-C",
"supports_network_connection": true
},
"overrides":
{
"machine_name": { "default_value": "Adventurer 3" }
}
}

View file

@ -0,0 +1,33 @@
{
"version": 2,
"name": "Adventurer 3C",
"inherits": "flashforge_adventurer_base",
"metadata":
{
"visible": true,
"author": "Jeremie-C",
"quality_definition": "flashforge_adventurer3"
},
"overrides":
{
"default_material_bed_temperature": { "maximum_value_warning": "100" },
"gantry_height": { "value": "150" },
"machine_center_is_zero": { "default_value": true },
"machine_depth": { "default_value": 150 },
"machine_end_gcode": { "default_value": ";end gcode\nM104 S0 T0\nM140 S0 T0\nG162 Z F1800\nG28 X Y\nM132 X Y A B\nM652\nG91\nM18" },
"machine_head_with_fans_polygon":
{
"default_value": [
[-20, 10],
[-20, -10],
[10, 10],
[10, -10]
]
},
"machine_height": { "default_value": 150 },
"machine_name": { "default_value": "Adventurer 3C" },
"machine_start_gcode": { "default_value": ";Start Gcode\nG28\nM132 X Y Z A B\nG1 Z50.000 F420\nG161 X Y F3300\nM7 T0\nM6 T0\nM651 S255\n;End Start" },
"machine_width": { "default_value": 150 },
"speed_print": { "maximum_value_warning": 100 }
}
}

View file

@ -0,0 +1,33 @@
{
"version": 2,
"name": "Adventurer 4",
"inherits": "flashforge_adventurer_base",
"metadata":
{
"visible": true,
"author": "Jeremie-C",
"quality_definition": "flashforge_adventurer4",
"supports_network_connection": true
},
"overrides":
{
"default_material_bed_temperature": { "maximum_value_warning": "110" },
"gantry_height": { "value": "250" },
"machine_depth": { "default_value": 200 },
"machine_end_gcode": { "default_value": ";End Gcode\nM104 S0 T0\nM140 S0 T0\nG162 Z F1800\nG28 X Y\nM132 X Y A B\nM652\nG91\nM18" },
"machine_head_with_fans_polygon":
{
"default_value": [
[-20, 10],
[-20, -10],
[10, 10],
[10, -10]
]
},
"machine_height": { "default_value": 250 },
"machine_name": { "default_value": "Adventurer 4" },
"machine_start_gcode": { "default_value": ";Start Gcode\nG28\nM132 X Y Z A B\nG1 Z50.000 F420\nG161 X Y F3300\nM7 T0\nM6 T0\nM651 S255\n;End Start" },
"machine_use_extruder_offset_to_offset_coords": { "default_value": false },
"machine_width": { "default_value": 220 }
}
}

View file

@ -0,0 +1,14 @@
{
"version": 2,
"name": "Adventurer 4 Lite",
"inherits": "flashforge_adventurer4",
"metadata":
{
"visible": true,
"author": "Jeremie-C"
},
"overrides":
{
"machine_name": { "default_value": "Adventurer 4 Lite" }
}
}

View file

@ -0,0 +1,34 @@
{
"version": 2,
"name": "Flashforge Adventurer Base",
"inherits": "fdmprinter",
"metadata":
{
"visible": false,
"author": "Jeremie-C",
"manufacturer": "Flashforge",
"file_formats": "application/gx;text/x-gcode",
"first_start_actions": [ "MachineSettingsAction" ],
"has_machine_quality": true,
"has_materials": true,
"has_variants": true,
"machine_extruder_trains": { "0": "flashforge_adventurer_extruder_0" },
"preferred_material": "generic_pla",
"preferred_quality_type": "normal",
"preferred_variant_name": "0.4mm Nozzle",
"variants_name": "Nozzle Size"
},
"overrides":
{
"adhesion_type": { "default_value": "skirt" },
"default_material_print_temperature": { "maximum_value_warning": "265" },
"layer_height":
{
"maximum_value_warning": "0.4",
"minimum_value_warning": "0.1"
},
"machine_center_is_zero": { "default_value": true },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_heated_bed": { "default_value": true }
}
}

View file

@ -0,0 +1,20 @@
{
"version": 2,
"name": "RatRig Printer",
"inherits": "fdmprinter",
"metadata":
{
"visible": false,
"author": "nu-hin",
"manufacturer": "RatRig",
"file_formats": "text/x-gcode",
"exclude_materials": [],
"first_start_actions": [ "MachineSettingsAction" ],
"has_materials": true,
"machine_extruder_trains": { "0": "ratrig_base_extruder_0" },
"preferred_material": "generic_pla",
"preferred_quality_type": "standard",
"quality_definition": "ratrig_base",
"supported_actions": [ "MachineSettingsAction" ]
}
}

View file

@ -0,0 +1,23 @@
{
"version": 2,
"name": "RatRig V-Core 3 200mm",
"inherits": "ratrig_vcore3_base",
"metadata":
{
"visible": true,
"platform": "ratrig_vcore3_200.stl",
"platform_offset": [
0,
5,
0
],
"weight": 16
},
"overrides":
{
"machine_depth": { "default_value": 200 },
"machine_height": { "default_value": 200 },
"machine_name": { "default_value": "RatRig V-Core 3 200mm" },
"machine_width": { "default_value": 200 }
}
}

View file

@ -0,0 +1,23 @@
{
"version": 2,
"name": "RatRig V-Core 3 300mm",
"inherits": "ratrig_vcore3_base",
"metadata":
{
"visible": true,
"platform": "ratrig_vcore3_300.stl",
"platform_offset": [
0,
5,
0
],
"weight": 16
},
"overrides":
{
"machine_depth": { "default_value": 300 },
"machine_height": { "default_value": 300 },
"machine_name": { "default_value": "RatRig V-Core 3 300mm" },
"machine_width": { "default_value": 300 }
}
}

View file

@ -0,0 +1,23 @@
{
"version": 2,
"name": "RatRig V-Core 3 400mm",
"inherits": "ratrig_vcore3_base",
"metadata":
{
"visible": true,
"platform": "ratrig_vcore3_400.stl",
"platform_offset": [
0,
3,
0
],
"weight": 16
},
"overrides":
{
"machine_depth": { "default_value": 400 },
"machine_height": { "default_value": 400 },
"machine_name": { "default_value": "RatRig V-Core 3 400mm" },
"machine_width": { "default_value": 400 }
}
}

View file

@ -0,0 +1,23 @@
{
"version": 2,
"name": "RatRig V-Core 3 500mm",
"inherits": "ratrig_vcore3_base",
"metadata":
{
"visible": true,
"platform": "ratrig_vcore3_500.stl",
"platform_offset": [
0,
0,
0
],
"weight": 16
},
"overrides":
{
"machine_depth": { "default_value": 500 },
"machine_height": { "default_value": 500 },
"machine_name": { "default_value": "RatRig V-Core 3 500mm" },
"machine_width": { "default_value": 500 }
}
}

View file

@ -0,0 +1,116 @@
{
"version": 2,
"name": "RatRig V-Core 3",
"inherits": "ratrig_base",
"metadata":
{
"visible": false,
"has_machine_quality": true,
"has_variants": true,
"machine_extruder_trains": { "0": "ratrig_base_extruder_0" },
"preferred_variant_name": "0.4mm Nozzle",
"variants_name": "Nozzle Size"
},
"overrides":
{
"acceleration_enabled": { "value": true },
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_roofing": { "enabled": "acceleration_enabled and roofing_layer_count > 0 and top_layers > 0" },
"acceleration_topbottom": { "value": "acceleration_print / 3" },
"acceleration_travel": { "value": 3000 },
"acceleration_travel_layer_0": { "value": "acceleration_travel / 3" },
"adaptive_layer_height_variation": { "value": 0.04 },
"adaptive_layer_height_variation_step": { "value": 0.04 },
"adhesion_type": { "value": "'skirt'" },
"brim_replaces_support": { "value": false },
"brim_width": { "value": "3" },
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
"cool_min_layer_time": { "value": 2 },
"fill_outline_gaps": { "value": false },
"gantry_height": { "value": 30 },
"infill_before_walls": { "value": false },
"infill_overlap": { "value": 30 },
"infill_pattern": { "value": "'lines' if infill_sparse_density > 50 else 'cubic'" },
"infill_wipe_dist": { "value": 0 },
"layer_height": { "default_value": 0.2 },
"layer_height_0": { "default_value": 0.2 },
"machine_acceleration": { "value": 3000 },
"machine_end_gcode": { "default_value": "END_PRINT" },
"machine_extruder_count": { "default_value": 1 },
"machine_head_with_fans_polygon":
{
"default_value": [
[-40, 90],
[-40, -30],
[40, -30],
[40, 90]
]
},
"machine_heated_bed": { "default_value": true },
"machine_max_acceleration_e": { "value": 5000 },
"machine_max_acceleration_x": { "value": 9000 },
"machine_max_acceleration_y": { "value": 9000 },
"machine_max_acceleration_z": { "value": 100 },
"machine_max_feedrate_e": { "value": 60 },
"machine_max_feedrate_x": { "value": 500 },
"machine_max_feedrate_y": { "value": 500 },
"machine_max_feedrate_z": { "value": 10 },
"machine_max_jerk_e": { "value": 5 },
"machine_max_jerk_xy": { "value": 5 },
"machine_max_jerk_z": { "value": 0.4 },
"machine_name": { "default_value": "RatRig V-Core 3" },
"machine_shape": { "default_value": "rectangular" },
"machine_show_variants": { "default_value": true },
"machine_start_gcode": { "default_value": "START_PRINT EXTRUDER_TEMP={material_print_temperature_layer_0} BED_TEMP={material_bed_temperature_layer_0}" },
"material_diameter": { "default_value": 1.75 },
"material_final_print_temperature": { "value": "material_print_temperature" },
"material_initial_print_temperature": { "value": "material_print_temperature" },
"meshfix_maximum_resolution": { "value": "0.25" },
"meshfix_maximum_travel_resolution": { "value": "meshfix_maximum_resolution" },
"minimum_interface_area": { "value": 10 },
"minimum_support_area": { "value": 2 },
"optimize_wall_printing_order": { "value": true },
"retraction_amount": { "value": "machine_nozzle_size * 2" },
"retraction_combing": { "value": "'off' if retraction_hop_enabled else 'noskin'" },
"retraction_combing_max_distance": { "value": 30 },
"retraction_count_max": { "value": 100 },
"retraction_extrusion_window": { "value": 10 },
"retraction_speed": { "value": 40 },
"roofing_layer_count": { "value": 1 },
"skin_overlap": { "value": 18 },
"skirt_brim_minimal_length": { "default_value": 30 },
"skirt_gap": { "value": 10 },
"skirt_line_count": { "value": 3 },
"speed_layer_0": { "value": "math.floor(speed_print * 3 / 10)" },
"speed_print": { "value": 100 },
"speed_roofing": { "value": "math.floor(speed_print * 3 / 10)" },
"speed_support": { "value": "math.floor(speed_print * 3 / 10)" },
"speed_support_interface": { "value": "speed_topbottom" },
"speed_topbottom": { "value": "math.floor(speed_print / 2)" },
"speed_travel": { "value": 250 },
"speed_travel_layer_0": { "value": "100 if speed_layer_0 < 20 else 150 if speed_layer_0 > 30 else speed_layer_0 * 5" },
"speed_wall_x": { "value": "speed_wall" },
"speed_z_hop": { "value": 5 },
"support_angle": { "value": "math.floor(math.degrees(math.atan(line_width/2.0/layer_height)))" },
"support_brim_width": { "value": 4 },
"support_infill_rate": { "value": "0 if support_enable and support_structure == 'tree' else 20" },
"support_interface_density": { "value": 33.333 },
"support_interface_enable": { "value": true },
"support_interface_height": { "value": "layer_height * 4" },
"support_interface_pattern": { "value": "'grid'" },
"support_pattern": { "value": "'zigzag'" },
"support_use_towers": { "value": false },
"support_xy_distance": { "value": "wall_line_width_0 * 2" },
"support_xy_distance_overhang": { "value": "wall_line_width_0" },
"support_xy_overrides_z": { "value": "'xy_overrides_z'" },
"support_z_distance": { "value": "layer_height if layer_height >= 0.16 else layer_height * 2" },
"top_bottom_pattern": { "value": "'lines'" },
"top_bottom_thickness": { "value": "layer_height_0 + layer_height * 3" },
"travel_avoid_supports": { "value": true },
"travel_retract_before_outer_wall": { "value": true },
"wall_0_wipe_dist": { "value": 0 },
"wall_thickness": { "value": "line_width * 2" },
"z_seam_corner": { "value": "'z_seam_corner_weighted'" },
"z_seam_type": { "value": "'back'" }
}
}

View file

@ -0,0 +1,127 @@
{
"version": 2,
"name": "RatRig V-Minion",
"inherits": "ratrig_base",
"metadata":
{
"visible": true,
"platform": "ratrig_vminion.stl",
"has_machine_quality": true,
"has_variants": true,
"machine_extruder_trains": { "0": "ratrig_base_extruder_0" },
"platform_offset": [
0,
5,
0
],
"preferred_variant_name": "0.4mm Nozzle",
"variants_name": "Nozzle Size",
"weight": 8
},
"overrides":
{
"acceleration_enabled": { "value": true },
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_roofing": { "enabled": "acceleration_enabled and roofing_layer_count > 0 and top_layers > 0" },
"acceleration_topbottom": { "value": "acceleration_print / 3" },
"acceleration_travel": { "value": 3000 },
"acceleration_travel_layer_0": { "value": "acceleration_travel / 3" },
"adaptive_layer_height_variation": { "value": 0.04 },
"adaptive_layer_height_variation_step": { "value": 0.04 },
"adhesion_type": { "value": "'skirt'" },
"brim_replaces_support": { "value": false },
"brim_width": { "value": "3" },
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
"cool_min_layer_time": { "value": 2 },
"fill_outline_gaps": { "value": false },
"gantry_height": { "value": 30 },
"infill_before_walls": { "value": false },
"infill_overlap": { "value": 30 },
"infill_pattern": { "value": "'lines' if infill_sparse_density > 50 else 'cubic'" },
"infill_wipe_dist": { "value": 0 },
"layer_height": { "default_value": 0.2 },
"layer_height_0": { "default_value": 0.2 },
"machine_acceleration": { "value": 3000 },
"machine_depth": { "default_value": 180 },
"machine_end_gcode": { "default_value": "END_PRINT" },
"machine_extruder_count": { "default_value": 1 },
"machine_head_with_fans_polygon":
{
"default_value": [
[-40, 90],
[-40, -30],
[40, -30],
[40, 90]
]
},
"machine_heated_bed": { "default_value": true },
"machine_height": { "default_value": 180 },
"machine_max_acceleration_e": { "value": 5000 },
"machine_max_acceleration_x": { "value": 9000 },
"machine_max_acceleration_y": { "value": 9000 },
"machine_max_acceleration_z": { "value": 100 },
"machine_max_feedrate_e": { "value": 60 },
"machine_max_feedrate_x": { "value": 500 },
"machine_max_feedrate_y": { "value": 500 },
"machine_max_feedrate_z": { "value": 10 },
"machine_max_jerk_e": { "value": 5 },
"machine_max_jerk_xy": { "value": 5 },
"machine_max_jerk_z": { "value": 0.4 },
"machine_name": { "default_value": "RatRig V-Minion" },
"machine_shape": { "default_value": "rectangular" },
"machine_show_variants": { "default_value": true },
"machine_start_gcode": { "default_value": "START_PRINT EXTRUDER_TEMP={material_print_temperature_layer_0} BED_TEMP={material_bed_temperature_layer_0}" },
"machine_width": { "default_value": 180 },
"material_diameter": { "default_value": 1.75 },
"material_final_print_temperature": { "value": "material_print_temperature" },
"material_initial_print_temperature": { "value": "material_print_temperature" },
"meshfix_maximum_resolution": { "value": "0.25" },
"meshfix_maximum_travel_resolution": { "value": "meshfix_maximum_resolution" },
"minimum_interface_area": { "value": 10 },
"minimum_support_area": { "value": 2 },
"optimize_wall_printing_order": { "value": true },
"retraction_amount": { "value": "machine_nozzle_size * 2" },
"retraction_combing": { "value": "'off' if retraction_hop_enabled else 'noskin'" },
"retraction_combing_max_distance": { "value": 30 },
"retraction_count_max": { "value": 100 },
"retraction_extrusion_window": { "value": 10 },
"retraction_speed": { "value": 40 },
"roofing_layer_count": { "value": 1 },
"skin_overlap": { "value": 18 },
"skirt_brim_minimal_length": { "default_value": 30 },
"skirt_gap": { "value": 10 },
"skirt_line_count": { "value": 3 },
"speed_layer_0": { "value": "math.floor(speed_print * 3 / 10)" },
"speed_print": { "value": 100 },
"speed_roofing": { "value": "math.floor(speed_print * 3 / 10)" },
"speed_support": { "value": "math.floor(speed_print * 3 / 10)" },
"speed_support_interface": { "value": "speed_topbottom" },
"speed_topbottom": { "value": "math.floor(speed_print / 2)" },
"speed_travel": { "value": 250 },
"speed_travel_layer_0": { "value": "100 if speed_layer_0 < 20 else 150 if speed_layer_0 > 30 else speed_layer_0 * 5" },
"speed_wall": { "value": "math.floor(speed_print / 2)" },
"speed_wall_x": { "value": "speed_wall" },
"speed_z_hop": { "value": 5 },
"support_angle": { "value": "math.floor(math.degrees(math.atan(line_width/2.0/layer_height)))" },
"support_brim_width": { "value": 4 },
"support_infill_rate": { "value": "0 if support_enable and support_structure == 'tree' else 20" },
"support_interface_density": { "value": 33.333 },
"support_interface_enable": { "value": true },
"support_interface_height": { "value": "layer_height * 4" },
"support_interface_pattern": { "value": "'grid'" },
"support_pattern": { "value": "'zigzag'" },
"support_use_towers": { "value": false },
"support_xy_distance": { "value": "wall_line_width_0 * 2" },
"support_xy_distance_overhang": { "value": "wall_line_width_0" },
"support_xy_overrides_z": { "value": "'xy_overrides_z'" },
"support_z_distance": { "value": "layer_height if layer_height >= 0.16 else layer_height * 2" },
"top_bottom_pattern": { "value": "'lines'" },
"top_bottom_thickness": { "value": "layer_height_0 + layer_height * 3" },
"travel_avoid_supports": { "value": true },
"travel_retract_before_outer_wall": { "value": true },
"wall_0_wipe_dist": { "value": 0 },
"wall_thickness": { "value": "line_width * 2" },
"z_seam_corner": { "value": "'z_seam_corner_weighted'" },
"z_seam_type": { "value": "'back'" }
}
}

View file

@ -106,6 +106,7 @@
"retraction_combing_max_distance": { "value": 15 }, "retraction_combing_max_distance": { "value": 15 },
"retraction_count_max": { "value": 25 }, "retraction_count_max": { "value": 25 },
"retraction_extrusion_window": { "value": 1 }, "retraction_extrusion_window": { "value": 1 },
"retraction_min_travel": { "value": 5 },
"roofing_layer_count": { "value": "1" }, "roofing_layer_count": { "value": "1" },
"roofing_material_flow": { "value": "material_flow" }, "roofing_material_flow": { "value": "material_flow" },
"skin_angles": { "value": "[] if infill_pattern not in ['cross', 'cross_3d'] else [20, 110]" }, "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": { "value": "2" },
"retraction_hop_enabled": { "value": "extruders_enabled_count > 1" }, "retraction_hop_enabled": { "value": "extruders_enabled_count > 1" },
"retraction_hop_only_when_collides": { "value": "True" }, "retraction_hop_only_when_collides": { "value": "True" },
"retraction_min_travel": { "value": "5" },
"retraction_prime_speed": { "value": "15" }, "retraction_prime_speed": { "value": "15" },
"skin_overlap": { "value": "10" }, "skin_overlap": { "value": "10" },
"speed_prime_tower": { "value": "speed_topbottom" }, "speed_prime_tower": { "value": "speed_topbottom" },

View file

@ -373,7 +373,6 @@
"retraction_hop": { "value": 0.4 }, "retraction_hop": { "value": 0.4 },
"retraction_hop_enabled": { "value": true }, "retraction_hop_enabled": { "value": true },
"retraction_hop_only_when_collides": { "value": false }, "retraction_hop_only_when_collides": { "value": false },
"retraction_min_travel": { "value": "line_width * 4" },
"retraction_prime_speed": { "value": "retraction_speed" }, "retraction_prime_speed": { "value": "retraction_speed" },
"retraction_speed": { "value": 5 }, "retraction_speed": { "value": 5 },
"roofing_layer_count": { "value": 2 }, "roofing_layer_count": { "value": 2 },
@ -386,6 +385,7 @@
"skin_preshrink": { "value": 0 }, "skin_preshrink": { "value": 0 },
"skirt_brim_material_flow": { "value": "material_flow" }, "skirt_brim_material_flow": { "value": "material_flow" },
"skirt_brim_minimal_length": { "value": 500 }, "skirt_brim_minimal_length": { "value": 500 },
"small_skin_width": { "value": 4 },
"speed_equalize_flow_width_factor": { "value": 0 }, "speed_equalize_flow_width_factor": { "value": 0 },
"speed_prime_tower": { "value": "speed_topbottom" }, "speed_prime_tower": { "value": "speed_topbottom" },
"speed_print": { "value": 50 }, "speed_print": { "value": 50 },
@ -426,7 +426,7 @@
"travel_avoid_other_parts": { "value": false }, "travel_avoid_other_parts": { "value": false },
"wall_0_inset": { "value": 0 }, "wall_0_inset": { "value": 0 },
"wall_0_material_flow": { "value": "material_flow" }, "wall_0_material_flow": { "value": "material_flow" },
"wall_0_wipe_dist": { "value": 0 }, "wall_0_wipe_dist": { "value": 0.8 },
"wall_material_flow": { "value": "material_flow" }, "wall_material_flow": { "value": "material_flow" },
"wall_x_material_flow": { "value": "material_flow" }, "wall_x_material_flow": { "value": "material_flow" },
"xy_offset": { "value": 0 }, "xy_offset": { "value": 0 },

View file

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

View file

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

View file

@ -55,7 +55,7 @@
"machine_endstop_positive_direction_y": { "default_value": true }, "machine_endstop_positive_direction_y": { "default_value": true },
"machine_endstop_positive_direction_z": { "default_value": false }, "machine_endstop_positive_direction_z": { "default_value": false },
"machine_feeder_wheel_diameter": { "default_value": 7.5 }, "machine_feeder_wheel_diameter": { "default_value": 7.5 },
"machine_gcode_flavor": { "default_value": "RepRap (RepRap)" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {
"default_value": [ "default_value": [
@ -76,7 +76,7 @@
"machine_max_jerk_xy": { "default_value": 20 }, "machine_max_jerk_xy": { "default_value": 20 },
"machine_max_jerk_z": { "default_value": 1 }, "machine_max_jerk_z": { "default_value": 1 },
"machine_name": { "default_value": "VORON2" }, "machine_name": { "default_value": "VORON2" },
"machine_start_gcode": { "default_value": "print_start" }, "machine_start_gcode": { "default_value": ";Nozzle diameter = {machine_nozzle_size}\n;Filament type = {material_type}\n;Filament name = {material_name}\n;Filament weight = {filament_weight}\n; M190 S{material_bed_temperature_layer_0}\n; M109 S{material_print_temperature_layer_0}\nprint_start EXTRUDER={material_print_temperature_layer_0} BED={material_bed_temperature_layer_0} CHAMBER={build_volume_temperature}" },
"machine_steps_per_mm_x": { "default_value": 80 }, "machine_steps_per_mm_x": { "default_value": 80 },
"machine_steps_per_mm_y": { "default_value": 80 }, "machine_steps_per_mm_y": { "default_value": 80 },
"machine_steps_per_mm_z": { "default_value": 400 }, "machine_steps_per_mm_z": { "default_value": 400 },

View file

@ -0,0 +1,16 @@
{
"version": 2,
"name": "Extruder",
"inherits": "fdmextruder",
"metadata":
{
"machine": "flashforge_adventurer_base",
"position": "0"
},
"overrides":
{
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
"material_diameter": { "default_value": 1.75 }
}
}

View file

@ -0,0 +1,16 @@
{
"version": 2,
"name": "Extruder 1",
"inherits": "fdmextruder",
"metadata":
{
"machine": "ratrig_base",
"position": "0"
},
"overrides":
{
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
"material_diameter": { "default_value": 1.75 }
}
}

View file

@ -4946,6 +4946,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models" msgid "Ungroup Models"
msgstr "Rozdělit modely" 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" msgctxt "@button"
msgid "Uninstall" msgid "Uninstall"
msgstr "Odinstalovat" msgstr "Odinstalovat"

View file

@ -2583,6 +2583,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence" msgid "Print Sequence"
msgstr "Tisková sekvence" 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" msgctxt "speed_print label"
msgid "Print Speed" msgid "Print Speed"
msgstr "Rychlost tisku" msgstr "Rychlost tisku"

View file

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

View file

@ -4930,6 +4930,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models" msgid "Ungroup Models"
msgstr "Gruppierung für Modelle aufheben" 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" msgctxt "@button"
msgid "Uninstall" msgid "Uninstall"
msgstr "Deinstallieren" msgstr "Deinstallieren"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence" msgid "Print Sequence"
msgstr "Druckreihenfolge" 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" msgctxt "speed_print label"
msgid "Print Speed" msgid "Print Speed"
msgstr "Druckgeschwindigkeit" msgstr "Druckgeschwindigkeit"

View file

@ -4931,6 +4931,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models" msgid "Ungroup Models"
msgstr "Desagrupar modelos" 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" msgctxt "@button"
msgid "Uninstall" msgid "Uninstall"
msgstr "Desinstalar" msgstr "Desinstalar"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence" msgid "Print Sequence"
msgstr "Secuencia de impresión" 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" msgctxt "speed_print label"
msgid "Print Speed" msgid "Print Speed"
msgstr "Velocidad de impresión" msgstr "Velocidad de impresión"

View file

@ -4588,6 +4588,14 @@ msgctxt "print_sequence option one_at_a_time"
msgid "One at a Time" msgid "One at a Time"
msgstr "" 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" msgctxt "infill_mesh label"
msgid "Infill Mesh" msgid "Infill Mesh"
msgstr "" msgstr ""

View file

@ -4899,6 +4899,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models" msgid "Ungroup Models"
msgstr "Poista mallien ryhmitys" 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" msgctxt "@button"
msgid "Uninstall" msgid "Uninstall"
msgstr "" msgstr ""

View file

@ -2578,6 +2578,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence" msgid "Print Sequence"
msgstr "Tulostusjärjestys" 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" msgctxt "speed_print label"
msgid "Print Speed" msgid "Print Speed"
msgstr "Tulostusnopeus" msgstr "Tulostusnopeus"

View file

@ -4928,6 +4928,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models" msgid "Ungroup Models"
msgstr "Dégrouper les modèles" 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" msgctxt "@button"
msgid "Uninstall" msgid "Uninstall"
msgstr "Désinstaller" msgstr "Désinstaller"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence" msgid "Print Sequence"
msgstr "Séquence d'impression" 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" msgctxt "speed_print label"
msgid "Print Speed" msgid "Print Speed"
msgstr "Vitesse dimpression" msgstr "Vitesse dimpression"

View file

@ -4913,6 +4913,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models" msgid "Ungroup Models"
msgstr "Csoport bontása" 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" msgctxt "@button"
msgid "Uninstall" msgid "Uninstall"
msgstr "" msgstr ""

View file

@ -2585,6 +2585,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence" msgid "Print Sequence"
msgstr "Nyomtatási sorrend" 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" msgctxt "speed_print label"
msgid "Print Speed" msgid "Print Speed"
msgstr "Nyomtatási sebesség" msgstr "Nyomtatási sebesség"

View file

@ -4931,6 +4931,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models" msgid "Ungroup Models"
msgstr "Separa modelli" 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" msgctxt "@button"
msgid "Uninstall" msgid "Uninstall"
msgstr "Disinstalla" msgstr "Disinstalla"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence" msgid "Print Sequence"
msgstr "Sequenza di stampa" 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" msgctxt "speed_print label"
msgid "Print Speed" msgid "Print Speed"
msgstr "Velocità di stampa" msgstr "Velocità di stampa"

View file

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

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence" msgid "Print Sequence"
msgstr "印刷頻度" 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" msgctxt "speed_print label"
msgid "Print Speed" msgid "Print Speed"
msgstr "印刷速度" msgstr "印刷速度"

View file

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

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence" msgid "Print Sequence"
msgstr "프린팅 순서" 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" msgctxt "speed_print label"
msgid "Print Speed" msgid "Print Speed"
msgstr "프린팅 속도" msgstr "프린팅 속도"

View file

@ -4925,6 +4925,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models" msgid "Ungroup Models"
msgstr "Groeperen van Modellen Opheffen" 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" msgctxt "@button"
msgid "Uninstall" msgid "Uninstall"
msgstr "De-installeren" msgstr "De-installeren"

View file

@ -2580,6 +2580,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence" msgid "Print Sequence"
msgstr "Printvolgorde" 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" msgctxt "speed_print label"
msgid "Print Speed" msgid "Print Speed"
msgstr "Printsnelheid" msgstr "Printsnelheid"

View file

@ -4916,6 +4916,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models" msgid "Ungroup Models"
msgstr "Rozgrupuj modele" 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" msgctxt "@button"
msgid "Uninstall" msgid "Uninstall"
msgstr "" msgstr ""

View file

@ -2584,6 +2584,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence" msgid "Print Sequence"
msgstr "Sekwencja Wydruku" 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" msgctxt "speed_print label"
msgid "Print Speed" msgid "Print Speed"
msgstr "Prędkość Druku" msgstr "Prędkość Druku"

View file

@ -4942,6 +4942,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models" msgid "Ungroup Models"
msgstr "Desagrupar Modelos" 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" msgctxt "@button"
msgid "Uninstall" msgid "Uninstall"
msgstr "Desinstalar" msgstr "Desinstalar"

View file

@ -2585,6 +2585,14 @@ msgctxt "print_sequence label"
msgid "Print Sequence" msgid "Print Sequence"
msgstr "Sequência de Impressão" 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" msgctxt "speed_print label"
msgid "Print Speed" msgid "Print Speed"
msgstr "Velocidade de Impressão" msgstr "Velocidade de Impressão"

View file

@ -4932,6 +4932,14 @@ msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models" msgid "Ungroup Models"
msgstr "Desagrupar Modelos" 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" msgctxt "@button"
msgid "Uninstall" msgid "Uninstall"
msgstr "Desinstalar" msgstr "Desinstalar"

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