diff --git a/.github/ISSUE_TEMPLATE/SlicingCrash.yaml b/.github/ISSUE_TEMPLATE/SlicingCrash.yaml index 06025886a2..dddd0f8827 100644 --- a/.github/ISSUE_TEMPLATE/SlicingCrash.yaml +++ b/.github/ISSUE_TEMPLATE/SlicingCrash.yaml @@ -5,38 +5,25 @@ body: - type: markdown attributes: value: | - ### ✨Try our improved Cura 5.7✨ - Before filling out the report below, we want you to try the latest Cura 5.7. - This version of Cura has become significantly more reliable and 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/releases/latest) #### - 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. + ### ✨Are you stuck? Have you tried these two things? ✨ + 1- Are you on a Cura version lower than Cura 5.7? We really recommend updating because it resolves a lot of slicing crashes! + 2- Have you tried fixing the model with software that repairs 3d files and makes them watertight? + Are you seeing spots and dots on your model? That is Cura indicating that your model is not watertight. + You can try doing a quick [Mesh Fix with the Meshtools Plugin](https://marketplace.ultimaker.com/app/cura/plugins/fieldofview/MeshTools) or other mesh editing software. - ### Project File - **⚠️ Before you continue, we need your project file to troubleshoot a slicing crash.** - It contains the printer and settings we need for troubleshooting. + If you still encounter a crash you are welcome to report the issue so we can use your model as a test case. + You can find instructions on how to share your model in a Package for Technical Support below. - ![Alt Text](https://user-images.githubusercontent.com/40423138/240616958-5a9751f2-bd34-4808-9752-6fde2e27516e.gif) - - To save a project file go to File -> Save project. - Please make sure to .zip your project file. - For big files, you may need to use [WeTransfer](https://wetransfer.com/) or similar file-sharing sites. - - 🤔 Before you share, please think to yourself. Is this a model that can be shared? - Unfortunately we cannot help if this file is missing. - Do you have the project file? Than let's continue ⬇️ - - ### Questions + 🤔 Before you share, please think to yourself. Is this a model that can be shared on the internet? + **Unfortunately, we cannot help if this file is missing.** + +### Questions - type: input attributes: label: Cura Version - placeholder: 5.6.0 + description: We work hard on improving our slicing crashes. If you are not on the latest version of Cura, [you can download it here](https://github.com/Ultimaker/Cura/releases/latest) validations: required: true -- type: markdown - attributes: - value: | - We work hard on improving our slicing crashes. Our most recent release is 5.7.1. - If you are not on the latest version of Cura, [you can download it here](https://github.com/Ultimaker/Cura/releases/latest) - type: input attributes: label: Operating System @@ -50,27 +37,13 @@ body: description: Which printer was selected in Cura? validations: required: true -- type: input - attributes: - label: Name abnormal settings - description: Are there any settings that you might have changed that caused the crash? Does your model slice when you select the default profiles? - placeholder: - validations: -- type: input - attributes: - label: Describe model location - description: Does your model slice if you rotate the model 90 degrees or if you move it away from the center of the buildplate? - placeholder: - validations: -- type: input - attributes: - label: Describe your model - description: Have you sliced your model succesfully before? Is it watertight? Have you tried doing a quick [Mesh Fix with the Meshtools Plugin](https://marketplace.ultimaker.com/app/cura/plugins/fieldofview/MeshTools)? - validations: - required: true + - type: textarea attributes: - label: Add your .zip here ⬇️ - description: You can add the zip file and additional information that is relevant to the issue in the comments below. + label: Describe your problem and add the package for technical support as a .zip here ⬇️ + description: | + If you still have Cura open with your crash > Click on Help on top bar > Click on Export Package For Technical Support > Compress the file into a zip > Add the file here to your GitHub issue 🔗 + + If you closed Cura, please open Cura to recreate the crash> Select your printer > Load your model > Select your print settings > Click on Help on top bar > Click on Export Package For Technical Support > Compress the file into a zip > Add the file here to your GitHub issue 🔗 validations: required: true diff --git a/.github/workflows/conan-package-resources.yml b/.github/workflows/conan-package-resources.yml index 7394a81ab7..cb1ebefd64 100644 --- a/.github/workflows/conan-package-resources.yml +++ b/.github/workflows/conan-package-resources.yml @@ -30,3 +30,13 @@ jobs: platform_mac: false install_system_dependencies: false secrets: inherit + + signal-curator: + needs: conan-package + runs-on: ubuntu-latest + steps: + - name: Trigger Curator Workflow + run: | + gh workflow run --repo ultimaker/curator -r main package.yml + env: + GITHUB_TOKEN: ${{ secrets.CURATOR_TRIGGER_PAT_C3PO }} diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml new file mode 100644 index 0000000000..93a5bdde2b --- /dev/null +++ b/.github/workflows/find-packages.yml @@ -0,0 +1,51 @@ +name: All installers (based on Jira ticket) +run-name: ${{ inputs.jira_ticket_number }} by @${{ github.actor }} + +on: + workflow_dispatch: + inputs: + jira_ticket_number: + description: 'Jira ticket number (e.g. CURA-15432 or cura_12345)' + required: true + type: string + start_builds: + description: 'Start installers build based on found packages' + default: true + required: false + type: boolean + conan_args: + description: 'Conan args' + default: '' + type: string + enterprise: + description: 'Build Cura as an Enterprise edition' + default: false + type: boolean + staging: + description: 'Use staging API' + default: false + type: boolean + +permissions: + contents: read + +jobs: + find-packages: + name: Find packages for Jira ticket + uses: ultimaker/cura-workflows/.github/workflows/find-package-by-ticket.yml@main + with: + jira_ticket_number: ${{ inputs.jira_ticket_number }} + secrets: inherit + + installers: + name: Create installers + needs: find-packages + if: ${{ inputs.start_builds == true && needs.find-packages.outputs.discovered_packages != '' }} + uses: ultimaker/cura-workflows/.github/workflows/cura-installers.yml@main + with: + cura_conan_version: ${{ needs.find-packages.outputs.cura_package }} + package_overrides: ${{ needs.find-packages.outputs.package_overrides }} + conan_args: ${{ inputs.conan_args }} + enterprise: ${{ inputs.enterprise }} + staging: ${{ inputs.staging }} + secrets: inherit diff --git a/.github/workflows/nightly-stable.yml b/.github/workflows/nightly-stable.yml index 1535daa415..badcef44e6 100644 --- a/.github/workflows/nightly-stable.yml +++ b/.github/workflows/nightly-stable.yml @@ -2,9 +2,10 @@ name: Nightly build - stable release run-name: Nightly build - stable release on: - schedule: - # Daily at 5:15 CET - - cron: '15 4 * * *' + workflow_dispatch: +# schedule: +# # Daily at 5:15 CET +# - cron: '15 4 * * *' jobs: build-nightly: diff --git a/.github/workflows/nightly-testing.yml b/.github/workflows/nightly-testing.yml index c1ccb67f1c..08d43570ec 100644 --- a/.github/workflows/nightly-testing.yml +++ b/.github/workflows/nightly-testing.yml @@ -2,9 +2,10 @@ name: Nightly build - dev release run-name: Nightly build - dev release on: - schedule: - # Daily at 4:15 CET - - cron: '15 3 * * *' + workflow_dispatch: +# schedule: +# # Daily at 5:15 CET +# - cron: '15 4 * * *' jobs: build-nightly: diff --git a/.github/workflows/printer-linter-pr-diagnose.yml b/.github/workflows/printer-linter-pr-diagnose.yml index 8feecdb3ee..666383c8f9 100644 --- a/.github/workflows/printer-linter-pr-diagnose.yml +++ b/.github/workflows/printer-linter-pr-diagnose.yml @@ -2,7 +2,7 @@ name: printer-linter-pr-diagnose on: pull_request: - path: + paths: - "resources/**" permissions: @@ -47,7 +47,7 @@ jobs: path: printer-linter-result/ - name: Run clang-tidy-pr-comments action - uses: platisd/clang-tidy-pr-comments@bc0bb7da034a8317d54e7fe1e819159002f4cc40 + uses: platisd/clang-tidy-pr-comments@v1.8.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} clang_tidy_fixes: result.yml diff --git a/.github/workflows/slicing-error-check.yml b/.github/workflows/slicing-error-check.yml new file mode 100644 index 0000000000..9869ef9721 --- /dev/null +++ b/.github/workflows/slicing-error-check.yml @@ -0,0 +1,65 @@ +name: Slicing Error Check + +on: + issues: + types: [opened, edited] + +permissions: + issues: write + +jobs: + processSlicingError: + runs-on: ubuntu-latest + steps: + - name: Check for project file and set output + id: check_issue_details + uses: actions/github-script@v7 + with: + script: | + const issue = context.payload.issue; + const issueNumber = issue.number; + + console.log(`Processing issue #${issueNumber}: "${issue.title}"`); + + const hasSlicingErrorLabel = issue.labels.some(label => label.name.toLowerCase().includes('slicing error')); + const titleContainsSliceFailed = issue.title.toLowerCase().includes('slice failed'); + const bodyText = issue.body || ""; + const bodyContainsSliceFailed = bodyText.toLowerCase().includes('slice failed'); + let setNeedsInfoOutput = false; + + if (hasSlicingErrorLabel || titleContainsSliceFailed || bodyContainsSliceFailed) { + console.log(`Issue #${issueNumber} matches slicing error criteria.`); + + const zipRegex = /(\[[^\]]*?\]\(.*?\.zip\)|https?:\/\/[^\s]*?\.zip)/i; + let hasZipAttachment = zipRegex.test(bodyText); + + if (hasZipAttachment) { + console.log(`Issue #${issueNumber} appears to have a .zip file linked in the body.`); + } else { + console.log(`Issue #${issueNumber} does not appear to have a .zip file linked in the body. Flagging for further action.`); + setNeedsInfoOutput = true; + } + } else { + console.log(`Issue #${issueNumber} does not match slicing error criteria. No action needed.`); + } + core.setOutput('needs_info', setNeedsInfoOutput.toString()); + + - name: Add comment if project file is missing + if: ${{ steps.check_issue_details.outputs.needs_info == 'true' }} + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number }} + body: | + This issue is related to a slicing error, but it seems a project file (`.zip`) is missing. + Please attach a `.zip` file containing your project (including models and profiles) so we can reproduce the issue. + This will help us investigate and resolve the problem more effectively. + Have Cura open with your project that fails to slice, go to `Help` > `Export Package For Technical Support`, and save the package. + Then create a .zip file with the package, attach the `.zip` file to this issue. + If you have already attached a `.zip` file, please ensure it is correctly linked in the issue body. + + - name: Add Status Needs Info Label + if: ${{ steps.check_issue_details.outputs.needs_info == 'true' }} + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: | + Status: Needs Info diff --git a/.github/workflows/update-translation.yml b/.github/workflows/update-translation.yml index 189390410b..2134467ec9 100644 --- a/.github/workflows/update-translation.yml +++ b/.github/workflows/update-translation.yml @@ -11,5 +11,5 @@ on: jobs: update-translations: uses: ultimaker/cura-workflows/.github/workflows/update-translations.yml@main - with: - branch: ${{ inputs.branch }} + with: + branch: ${{ inputs.branch }} diff --git a/conandata.yml b/conandata.yml index 9d709096f4..924082efea 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,16 +1,17 @@ -version: "5.10.2" +version: "5.11.0-alpha.0" +commit: "unknown" requirements: - - "cura_resources/5.10.2" - - "uranium/5.10.2" - - "curaengine/5.10.2" - - "cura_binary_data/5.10.2" - - "fdm_materials/5.10.2" + - "cura_resources/5.11.0-alpha.0@ultimaker/testing" + - "uranium/5.11.0-alpha.0@ultimaker/testing" + - "curaengine/5.11.0-alpha.0@ultimaker/testing" + - "cura_binary_data/5.11.0-alpha.0@ultimaker/testing" + - "fdm_materials/5.11.0-alpha.0@ultimaker/testing" - "dulcificum/5.10.0" - - "pysavitar/5.10.0" + - "pysavitar/5.11.0-alpha.0" - "pynest2d/5.10.0" requirements_internal: - - "fdm_materials/5.10.2" - - "cura_private_data/5.10.0-alpha.0@internal/testing" + - "fdm_materials/5.11.0-alpha.0@ultimaker/testing" + - "cura_private_data/5.11.0-alpha.0@internal/testing" requirements_enterprise: - "native_cad_plugin/2.0.0" urls: @@ -99,6 +100,7 @@ pyinstaller: - "pyArcus" - "pyDulcificum" - "pynest2d" + - "pyUvula" - "PyQt6" - "PyQt6.QtNetwork" - "PyQt6.sip" diff --git a/conanfile.py b/conanfile.py index b3e070e3b3..dbe524b2f1 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,8 +1,10 @@ +import json import os import requests import yaml import tempfile import tarfile +from datetime import datetime from io import StringIO from pathlib import Path from git import Repo @@ -14,7 +16,7 @@ from conan import ConanFile from conan.tools.files import copy, rmdir, save, mkdir, rm, update_conandata from conan.tools.microsoft import unix_path from conan.tools.env import VirtualRunEnv, Environment, VirtualBuildEnv -from conan.tools.scm import Version +from conan.tools.scm import Version, Git from conan.errors import ConanInvalidConfiguration, ConanException required_conan_version = ">=2.7.0" # When changing the version, also change the one in conandata.yml/extra_dependencies @@ -327,10 +329,16 @@ class CuraConan(ConanFile): # If you want a specific Cura version to show up on the splash screen add the user configuration `user.cura:version=VERSION` # the global.conf, profile, package_info (of dependency) or via the cmd line `-c user.cura:version=VERSION` cura_version = Version(self.conf.get("user.cura:version", default = self.version, check_type = str)) - pre_tag = f"-{cura_version.pre}" if cura_version.pre else "" - build_tag = f"+{cura_version.build}" if cura_version.build else "" - internal_tag = f"+internal" if self.options.internal else "" - cura_version = f"{cura_version.major}.{cura_version.minor}.{cura_version.patch}{pre_tag}{build_tag}{internal_tag}" + extra_build_identifiers = [] + + if self.options.internal: + extra_build_identifiers.append("internal") + if str(cura_version.pre).startswith("alpha") and self.conan_data["commit"] != "unknown": + extra_build_identifiers.append(self.conan_data["commit"][:6]) + + if extra_build_identifiers: + separator = "+" if not cura_version.build else "." + cura_version = Version(f"{cura_version}{separator}{'.'.join(extra_build_identifiers)}") self.output.info(f"Write CuraVersion.py to {self.recipe_folder}") @@ -338,7 +346,7 @@ class CuraConan(ConanFile): f.write(cura_version_py.render( cura_app_name = self.name, cura_app_display_name = self._app_name, - cura_version = cura_version, + cura_version = str(cura_version), cura_version_full = self.version, cura_build_type = "Enterprise" if self.options.enterprise else "", cura_debug_mode = self.options.cura_debug_mode, @@ -525,7 +533,7 @@ class CuraConan(ConanFile): )) def export(self): - update_conandata(self, {"version": self.version}) + update_conandata(self, {"version": self.version, "commit": Git(self).get_commit()}) def export_sources(self): copy(self, "*", os.path.join(self.recipe_folder, "plugins"), os.path.join(self.export_sources_folder, "plugins")) @@ -562,6 +570,30 @@ class CuraConan(ConanFile): self.cpp.package.bindirs = ["bin"] self.cpp.package.resdirs = ["resources", "plugins", "packaging"] + def _make_internal_distinct(self): + test_colors_path = Path(self.source_folder, "resources", "themes", "daily_test_colors.json") + if not test_colors_path.exists(): + print(f"Could not find '{str(test_colors_path)}'. Won't generate rotating colors for alpha builds.") + return + if "alpha" in self.version: + with test_colors_path.open("r") as test_colors_file: + test_colors = json.load(test_colors_file) + biweekly_day = (datetime.now() - datetime(2025, 3, 14)).days % len(test_colors) + for theme_dir in Path(self.source_folder, "resources", "themes").iterdir(): + if not theme_dir.is_dir(): + continue + theme_path = Path(theme_dir, "theme.json") + if not theme_path.exists(): + print(f"('Colorize-by-day' alpha builds): Skipping {str(theme_path)}, could not find file.") + continue + with theme_path.open("r") as theme_file: + theme = json.load(theme_file) + if theme["colors"]: + theme["colors"]["main_window_header_background"] = test_colors[biweekly_day] + with theme_path.open("w") as theme_file: + json.dump(theme, theme_file) + test_colors_path.unlink() + def generate(self): copy(self, "cura_app.py", self.source_folder, str(self._script_dir)) @@ -581,6 +613,9 @@ class CuraConan(ConanFile): copy(self, "bundled_*.json", native_cad_plugin.resdirs[1], str(Path(self.source_folder, "resources", "bundled_packages")), keep_path = False) + # Make internal versions built on different days distinct, so people don't get confused while testing. + self._make_internal_distinct() + # Copy resources of cura_binary_data cura_binary_data = self.dependencies["cura_binary_data"].cpp_info copy(self, "*", cura_binary_data.resdirs[0], str(self._share_dir.joinpath("cura")), keep_path = True) diff --git a/cura/API/Backups.py b/cura/API/Backups.py index 1940d38a36..a52dcbfb6b 100644 --- a/cura/API/Backups.py +++ b/cura/API/Backups.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. from typing import Tuple, Optional, TYPE_CHECKING, Dict, Any @@ -9,14 +9,10 @@ if TYPE_CHECKING: class Backups: - """The back-ups API provides a version-proof bridge between Cura's - - BackupManager and plug-ins that hook into it. + """The back-ups API provides a version-proof bridge between Cura's BackupManager and plug-ins that hook into it. Usage: - .. code-block:: python - from cura.API import CuraAPI api = CuraAPI() api.backups.createBackup() @@ -26,19 +22,22 @@ class Backups: def __init__(self, application: "CuraApplication") -> None: self.manager = BackupsManager(application) - def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, Any]]]: + def createBackup(self, available_remote_plugins: frozenset[str] = frozenset()) -> Tuple[Optional[bytes], Optional[Dict[str, Any]]]: """Create a new back-up using the BackupsManager. :return: Tuple containing a ZIP file with the back-up data and a dict with metadata about the back-up. """ - return self.manager.createBackup() + return self.manager.createBackup(available_remote_plugins) - def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, Any]) -> None: + def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, Any], auto_close: bool = True) -> None: """Restore a back-up using the BackupsManager. :param zip_file: A ZIP file containing the actual back-up data. :param meta_data: Some metadata needed for restoring a back-up, like the Cura version number. """ - return self.manager.restoreBackup(zip_file, meta_data) + return self.manager.restoreBackup(zip_file, meta_data, auto_close=auto_close) + + def shouldReinstallDownloadablePlugins(self) -> bool: + return self.manager.shouldReinstallDownloadablePlugins() diff --git a/cura/API/Interface/Settings.py b/cura/API/Interface/Settings.py index 084023b9bd..6437de268a 100644 --- a/cura/API/Interface/Settings.py +++ b/cura/API/Interface/Settings.py @@ -3,7 +3,7 @@ from dataclasses import asdict -from typing import cast, Dict, TYPE_CHECKING +from typing import cast, Dict, TYPE_CHECKING, Any from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.SettingFunction import SettingFunction @@ -54,6 +54,15 @@ class Settings: return self.application.getSidebarCustomMenuItems() + def getAllGlobalSettings(self) -> Dict[str, Any]: + global_stack = cast(GlobalStack, self.application.getGlobalContainerStack()) + + all_settings = {} + for setting in global_stack.getAllKeys(): + all_settings[setting] = self._retrieveValue(global_stack, setting) + + return all_settings + def getSliceMetadata(self) -> Dict[str, Dict[str, Dict[str, str]]]: """Get all changed settings and all settings. For each extruder and the global stack""" print_information = self.application.getPrintInformation() @@ -71,24 +80,16 @@ class Settings: "quality": asdict(machine_manager.activeQualityDisplayNameMap()), } - def _retrieveValue(container: InstanceContainer, setting_: str): - value_ = container.getProperty(setting_, "value") - for _ in range(0, 1024): # Prevent possibly endless loop by not using a limit. - if not isinstance(value_, SettingFunction): - return value_ # Success! - value_ = value_(container) - return 0 # Fallback value after breaking possibly endless loop. - global_stack = cast(GlobalStack, self.application.getGlobalContainerStack()) # Add global user or quality changes global_flattened_changes = InstanceContainer.createMergedInstanceContainer(global_stack.userChanges, global_stack.qualityChanges) for setting in global_flattened_changes.getAllKeys(): - settings["global"]["changes"][setting] = _retrieveValue(global_flattened_changes, setting) + settings["global"]["changes"][setting] = self._retrieveValue(global_flattened_changes, setting) # Get global all settings values without user or quality changes for setting in global_stack.getAllKeys(): - settings["global"]["all_settings"][setting] = _retrieveValue(global_stack, setting) + settings["global"]["all_settings"][setting] = self._retrieveValue(global_stack, setting) for i, extruder in enumerate(global_stack.extruderList): # Add extruder fields to settings dictionary @@ -100,10 +101,19 @@ class Settings: # Add extruder user or quality changes extruder_flattened_changes = InstanceContainer.createMergedInstanceContainer(extruder.userChanges, extruder.qualityChanges) for setting in extruder_flattened_changes.getAllKeys(): - settings[f"extruder_{i}"]["changes"][setting] = _retrieveValue(extruder_flattened_changes, setting) + settings[f"extruder_{i}"]["changes"][setting] = self._retrieveValue(extruder_flattened_changes, setting) # Get extruder all settings values without user or quality changes for setting in extruder.getAllKeys(): - settings[f"extruder_{i}"]["all_settings"][setting] = _retrieveValue(extruder, setting) + settings[f"extruder_{i}"]["all_settings"][setting] = self._retrieveValue(extruder, setting) return settings + + @staticmethod + def _retrieveValue(container: InstanceContainer, setting_: str): + value_ = container.getProperty(setting_, "value") + for _ in range(0, 1024): # Prevent possibly endless loop by not using a limit. + if not isinstance(value_, SettingFunction): + return value_ # Success! + value_ = value_(container) + return 0 # Fallback value after breaking possibly endless loop. \ No newline at end of file diff --git a/cura/Backups/Backup.py b/cura/Backups/Backup.py index 19655df531..1438ce293a 100644 --- a/cura/Backups/Backup.py +++ b/cura/Backups/Backup.py @@ -1,5 +1,8 @@ -# Copyright (c) 2021 Ultimaker B.V. +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. +import tempfile + +import json import io import os @@ -7,12 +10,13 @@ import re import shutil from copy import deepcopy from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile -from typing import Dict, Optional, TYPE_CHECKING, List +from typing import Callable, Dict, Optional, TYPE_CHECKING, List from UM import i18nCatalog from UM.Logger import Logger from UM.Message import Message from UM.Platform import Platform +from UM.PluginRegistry import PluginRegistry from UM.Resources import Resources from UM.Version import Version @@ -30,6 +34,7 @@ class Backup: """These files should be ignored when making a backup.""" IGNORED_FOLDERS = [] # type: List[str] + """These folders should be ignored when making a backup.""" SECRETS_SETTINGS = ["general/ultimaker_auth_data"] """Secret preferences that need to obfuscated when making a backup of Cura""" @@ -42,7 +47,7 @@ class Backup: self.zip_file = zip_file # type: Optional[bytes] self.meta_data = meta_data # type: Optional[Dict[str, str]] - def makeFromCurrent(self) -> None: + def makeFromCurrent(self, available_remote_plugins: frozenset[str] = frozenset()) -> None: """Create a back-up from the current user config folder.""" cura_release = self._application.getVersion() @@ -68,7 +73,7 @@ class Backup: # Create an empty buffer and write the archive to it. buffer = io.BytesIO() - archive = self._makeArchive(buffer, version_data_dir) + archive = self._makeArchive(buffer, version_data_dir, available_remote_plugins) if archive is None: return files = archive.namelist() @@ -77,9 +82,7 @@ class Backup: machine_count = max(len([s for s in files if "machine_instances/" in s]) - 1, 0) # If people delete their profiles but not their preferences, it can still make a backup, and report -1 profiles. Server crashes on this. material_count = max(len([s for s in files if "materials/" in s]) - 1, 0) profile_count = max(len([s for s in files if "quality_changes/" in s]) - 1, 0) - # We don't store plugins anymore, since if you can make backups, you have an account (and the plugins are - # on the marketplace anyway) - plugin_count = 0 + plugin_count = len([s for s in files if "plugin.json" in s]) # Store the archive and metadata so the BackupManager can fetch them when needed. self.zip_file = buffer.getvalue() self.meta_data = { @@ -92,22 +95,72 @@ class Backup: # Restore the obfuscated settings self._illuminate(**secrets) - def _makeArchive(self, buffer: "io.BytesIO", root_path: str) -> Optional[ZipFile]: + def _fillToInstallsJson(self, file_path: str, reinstall_on_restore: frozenset[str], add_to_archive: Callable[[str, str], None]) -> Optional[str]: + """ Moves all plugin-data (in a config-file) for plugins that could be (re)installed from the Marketplace from + 'installed' to 'to_installs' before adding that file to the archive. + + Note that the 'filename'-entry in the package-data (of the plugins) might not be valid anymore on restore. + We'll replace it on restore instead, as that's the time when the new package is downloaded. + + :param file_path: Absolute path to the packages-file. + :param reinstall_on_restore: A set of plugins that _can_ be reinstalled from the Marketplace. + :param add_to_archive: A function/lambda that takes a filename and adds it to the archive (as the 2nd name). + """ + with open(file_path, "r") as file: + data = json.load(file) + reinstall, keep_in = {}, {} + for install_id, install_info in data["installed"].items(): + (reinstall if install_id in reinstall_on_restore else keep_in)[install_id] = install_info + data["installed"] = keep_in + data["to_install"].update(reinstall) + if data is not None: + tmpfile = tempfile.NamedTemporaryFile(delete_on_close=False) + with open(tmpfile.name, "w") as outfile: + json.dump(data, outfile) + add_to_archive(tmpfile.name, file_path) + return tmpfile.name + return None + + def _findRedownloadablePlugins(self, available_remote_plugins: frozenset) -> (frozenset[str], frozenset[str]): + """ Find all plugins that should be able to be reinstalled from the Marketplace. + + :param plugins_path: Path to all plugins in the user-space. + :return: Tuple of a set of plugin-ids and a set of plugin-paths. + """ + plugin_reg = PluginRegistry.getInstance() + id = "id" + plugins = [v for v in plugin_reg.getAllMetaData() + if v[id] in available_remote_plugins and not plugin_reg.isBundledPlugin(v[id])] + return frozenset([v[id] for v in plugins]), frozenset([v["location"] for v in plugins]) + + def _makeArchive(self, buffer: "io.BytesIO", root_path: str, available_remote_plugins: frozenset) -> Optional[ZipFile]: """Make a full archive from the given root path with the given name. :param root_path: The root directory to archive recursively. :return: The archive as bytes. """ ignore_string = re.compile("|".join(self.IGNORED_FILES + self.IGNORED_FOLDERS)) + reinstall_instead_ids, reinstall_instead_paths = self._findRedownloadablePlugins(available_remote_plugins) + tmpfiles = [] try: archive = ZipFile(buffer, "w", ZIP_DEFLATED) - for root, folders, files in os.walk(root_path): + add_path_to_archive = lambda path, alt_path: archive.write(path, alt_path[len(root_path) + len(os.sep):]) + for root, folders, files in os.walk(root_path, topdown=True): for item_name in folders + files: absolute_path = os.path.join(root, item_name) - if ignore_string.search(absolute_path): + if ignore_string.search(absolute_path) or any([absolute_path.startswith(x) for x in reinstall_instead_paths]): continue - archive.write(absolute_path, absolute_path[len(root_path) + len(os.sep):]) + if item_name == "packages.json": + tmpfiles.append( + self._fillToInstallsJson(absolute_path, reinstall_instead_ids, add_path_to_archive)) + else: + add_path_to_archive(absolute_path, absolute_path) archive.close() + for tmpfile_path in tmpfiles: + try: + os.remove(tmpfile_path) + except IOError as ex: + Logger.warning(f"Couldn't remove temporary file '{tmpfile_path}' because '{ex}'.") return archive except (IOError, OSError, BadZipfile) as error: Logger.log("e", "Could not create archive from user data directory: %s", error) diff --git a/cura/Backups/BackupsManager.py b/cura/Backups/BackupsManager.py index 6c4670edb6..67d6c84601 100644 --- a/cura/Backups/BackupsManager.py +++ b/cura/Backups/BackupsManager.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. from typing import Dict, Optional, Tuple, TYPE_CHECKING @@ -22,7 +22,10 @@ class BackupsManager: def __init__(self, application: "CuraApplication") -> None: self._application = application - def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, str]]]: + def shouldReinstallDownloadablePlugins(self) -> bool: + return True + + def createBackup(self, available_remote_plugins: frozenset[str] = frozenset()) -> Tuple[Optional[bytes], Optional[Dict[str, str]]]: """ Get a back-up of the current configuration. @@ -31,17 +34,18 @@ class BackupsManager: self._disableAutoSave() backup = Backup(self._application) - backup.makeFromCurrent() + backup.makeFromCurrent(available_remote_plugins if self.shouldReinstallDownloadablePlugins() else frozenset()) self._enableAutoSave() # We don't return a Backup here because we want plugins only to interact with our API and not full objects. return backup.zip_file, backup.meta_data - def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, str]) -> None: + def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, str], auto_close: bool = True) -> None: """ Restore a back-up from a given ZipFile. :param zip_file: A bytes object containing the actual back-up. :param meta_data: A dict containing some metadata that is needed to restore the back-up correctly. + :param auto_close: Normally, Cura will need to close immediately after restoring the back-up. """ if not meta_data.get("cura_release", None): @@ -54,7 +58,7 @@ class BackupsManager: backup = Backup(self._application, zip_file = zip_file, meta_data = meta_data) restored = backup.restore() - if restored: + if restored and auto_close: # At this point, Cura will need to restart for the changes to take effect. # We don't want to store the data at this point as that would override the just-restored backup. self._application.windowClosed(save_data = False) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 6e4da621ff..57d4773cb3 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -9,7 +9,6 @@ import time import platform from pathlib import Path from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict -import requests import numpy from PyQt6.QtCore import QObject, QTimer, QUrl, QUrlQuery, pyqtSignal, pyqtProperty, QEvent, pyqtEnum, QCoreApplication, \ @@ -60,6 +59,7 @@ from cura import ApplicationMetadata from cura.API import CuraAPI from cura.API.Account import Account from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob +from cura.CuraRenderer import CuraRenderer from cura.Machines.MachineErrorChecker import MachineErrorChecker from cura.Machines.Models.BuildPlateModel import BuildPlateModel from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel @@ -188,6 +188,7 @@ class CuraApplication(QtApplication): self._single_instance = None self._open_project_mode: Optional[str] = None + self._read_operation_is_project_file: Optional[bool] = None self._cura_formula_functions = None # type: Optional[CuraFormulaFunctions] @@ -361,6 +362,9 @@ class CuraApplication(QtApplication): self._machine_action_manager = MachineActionManager(self) self._machine_action_manager.initialize() + def makeRenderer(self) -> CuraRenderer: + return CuraRenderer(self) + def __sendCommandToSingleInstance(self): self._single_instance = SingleInstance(self, self._files_to_open, self._urls_to_open) @@ -1034,7 +1038,6 @@ class CuraApplication(QtApplication): # Initialize UI state controller.setActiveStage("PrepareStage") - controller.setActiveView("SolidView") controller.setCameraTool("CameraTool") controller.setSelectionTool("SelectionTool") @@ -1644,14 +1647,10 @@ class CuraApplication(QtApplication): Logger.log("w", "Unable to reload data because we don't have a filename.") for file_name, nodes in objects_in_filename.items(): - file_path = os.path.normpath(os.path.dirname(file_name)) - job = ReadMeshJob(file_name, - add_to_recent_files=file_path != tempfile.gettempdir()) # Don't add temp files to the recent files list - job._nodes = nodes # type: ignore - job.finished.connect(self._reloadMeshFinished) + on_done = None if has_merged_nodes: - job.finished.connect(self.updateOriginOfMergedMeshes) - job.start() + on_done = self.updateOriginOfMergedMeshes + self.getController().getScene().reloadNodes(nodes, file_name, on_done) @pyqtSlot("QStringList") def setExpandedCategories(self, categories: List[str]) -> None: @@ -1834,53 +1833,6 @@ class CuraApplication(QtApplication): fileLoaded = pyqtSignal(str) fileCompleted = pyqtSignal(str) - def _reloadMeshFinished(self, job) -> None: - """ - Function called when ReadMeshJob finishes reloading a file in the background, then update node objects in the - scene from its source file. The function gets all the nodes that exist in the file through the job result, and - then finds the scene nodes that need to be refreshed by their name. Each job refreshes all nodes of a file. - Nodes that are not present in the updated file are kept in the scene. - - :param job: The :py:class:`Uranium.UM.ReadMeshJob.ReadMeshJob` running in the background that reads all the - meshes in a file - """ - - job_result = job.getResult() # nodes that exist inside the file read by this job - if len(job_result) == 0: - Logger.log("e", "Reloading the mesh failed.") - return - renamed_nodes = {} # type: Dict[str, int] - # Find the node to be refreshed based on its id - for job_result_node in job_result: - mesh_data = job_result_node.getMeshData() - if not mesh_data: - Logger.log("w", "Could not find a mesh in reloaded node.") - continue - - # Solves issues with object naming - result_node_name = job_result_node.getName() - if not result_node_name: - result_node_name = os.path.basename(mesh_data.getFileName()) - if result_node_name in renamed_nodes: # objects may get renamed by ObjectsModel._renameNodes() when loaded - renamed_nodes[result_node_name] += 1 - result_node_name = "{0}({1})".format(result_node_name, renamed_nodes[result_node_name]) - else: - renamed_nodes[job_result_node.getName()] = 0 - - # Find the matching scene node to replace - scene_node = None - for replaced_node in job._nodes: - if replaced_node.getName() == result_node_name: - scene_node = replaced_node - break - - if scene_node: - scene_node.setMeshData(mesh_data) - else: - # Current node is a new one in the file, or it's name has changed - # TODO: Load this mesh into the scene. Also alter the "_reloadJobFinished" action in UM.Scene - Logger.log("w", "Could not find matching node for object '{0}' in the scene.".format(result_node_name)) - def _openFile(self, filename): self.readLocalFile(QUrl.fromLocalFile(filename)) @@ -1894,36 +1846,39 @@ class CuraApplication(QtApplication): query = QUrlQuery(url.query()) model_url = QUrl(query.queryItemValue("file", options=QUrl.ComponentFormattingOption.FullyDecoded)) - def on_finish(response): - content_disposition_header_key = QByteArray("content-disposition".encode()) - - filename = model_url.path().split("/")[-1] + ".stl" - - if response.hasRawHeader(content_disposition_header_key): - # content_disposition is in the format - # ``` - # content_disposition attachment; filename="[FILENAME]" - # ``` - # Use a regex to extract the filename - content_disposition = str(response.rawHeader(content_disposition_header_key).data(), - encoding='utf-8') - content_disposition_match = re.match(r'attachment; filename=(?P.*)', - content_disposition) - if content_disposition_match is not None: - filename = content_disposition_match.group("filename").strip("\"") - - tmp = tempfile.NamedTemporaryFile(suffix=filename, delete=False) - with open(tmp.name, "wb") as f: - f.write(response.readAll()) - - self.readLocalFile(QUrl.fromLocalFile(tmp.name), add_to_recent_files=False) - def on_error(*args, **kwargs): - Logger.log("w", "Could not download file from {0}".format(model_url.url())) - Message("Could not download file: " + str(model_url.url()), + Logger.warning(f"Could not download file from {model_url.url()}") + Message(f"Could not download file: {str(model_url.url())}", title= "Loading Model failed", message_type=Message.MessageType.ERROR).show() - return + + def on_finish(response): + try: + content_disposition_header_key = QByteArray("content-disposition".encode()) + + filename = model_url.path().split("/")[-1] + ".stl" + + if response.hasRawHeader(content_disposition_header_key): + # content_disposition is in the format + # ``` + # content_disposition attachment; filename="[FILENAME]" + # ``` + # Use a regex to extract the filename + content_disposition = str(response.rawHeader(content_disposition_header_key).data(), + encoding='utf-8') + content_disposition_match = re.match(r'attachment; filename=(?P.*)', + content_disposition) + if content_disposition_match is not None: + filename = content_disposition_match.group("filename").strip("\"") + + tmp = tempfile.NamedTemporaryFile(suffix=filename, delete=False) + with open(tmp.name, "wb") as f: + f.write(response.readAll()) + + self.readLocalFile(QUrl.fromLocalFile(tmp.name), add_to_recent_files=False) + except Exception as ex: + Logger.warning(f"Exception {str(ex)}") + on_error() self.getHttpRequestManager().get( model_url.url(), @@ -2015,18 +1970,18 @@ class CuraApplication(QtApplication): self.deleteAll() break - is_project_file = self.checkIsValidProjectFile(file) + self._read_operation_is_project_file = self.checkIsValidProjectFile(file) if self._open_project_mode is None: self._open_project_mode = self.getPreferences().getValue("cura/choice_on_open_project") - if is_project_file and self._open_project_mode == "open_as_project": + if self._read_operation_is_project_file and self._open_project_mode == "open_as_project": # open as project immediately without presenting a dialog workspace_handler = self.getWorkspaceFileHandler() workspace_handler.readLocalFile(file, add_to_recent_files_hint = add_to_recent_files) return - if is_project_file and self._open_project_mode == "always_ask": + if self._read_operation_is_project_file and self._open_project_mode == "always_ask": # present a dialog asking to open as project or import models self.callLater(self.openProjectFile.emit, file, add_to_recent_files) return @@ -2133,9 +2088,7 @@ class CuraApplication(QtApplication): is_non_sliceable = "." + file_extension in self._non_sliceable_extensions if is_non_sliceable: - # Need to switch first to the preview stage and then to layer view - self.callLater(lambda: (self.getController().setActiveStage("PreviewStage"), - self.getController().setActiveView("SimulationView"))) + self.callLater(lambda: (self.getController().setActiveStage("PreviewStage"))) block_slicing_decorator = BlockSlicingDecorator() node.addDecorator(block_slicing_decorator) @@ -2164,7 +2117,7 @@ class CuraApplication(QtApplication): nodes_to_arrange.append(node) # If the file is a project,and models are to be loaded from a that project, # models inside file should be arranged in buildplate. - elif self._open_project_mode == "open_as_model": + elif self._read_operation_is_project_file and self._open_project_mode == "open_as_model": nodes_to_arrange.append(node) # This node is deep copied from some other node which already has a BuildPlateDecorator, but the deepcopy diff --git a/cura/CuraRenderer.py b/cura/CuraRenderer.py new file mode 100644 index 0000000000..77030b3fe8 --- /dev/null +++ b/cura/CuraRenderer.py @@ -0,0 +1,46 @@ +# Copyright (c) 2025 UltiMaker +# Uranium is released under the terms of the LGPLv3 or higher. + + +from typing import TYPE_CHECKING + +from cura.PickingPass import PickingPass +from UM.Qt.QtRenderer import QtRenderer +from UM.View.RenderPass import RenderPass +from UM.View.SelectionPass import SelectionPass + +if TYPE_CHECKING: + from cura.CuraApplication import CuraApplication + + +class CuraRenderer(QtRenderer): + """An overridden Renderer implementation that adds some behaviors specific to Cura.""" + + def __init__(self, application: "CuraApplication") -> None: + super().__init__() + + self._controller = application.getController() + self._controller.activeToolChanged.connect(self._onActiveToolChanged) + self._extra_rendering_passes: list[RenderPass] = [] + + def _onActiveToolChanged(self) -> None: + tool_extra_rendering_passes = [] + + active_tool = self._controller.getActiveTool() + if active_tool is not None: + tool_extra_rendering_passes = active_tool.getRequiredExtraRenderingPasses() + + for extra_rendering_pass in self._extra_rendering_passes: + extra_rendering_pass.setEnabled(extra_rendering_pass.getName() in tool_extra_rendering_passes) + + def _makeRenderPasses(self) -> list[RenderPass]: + self._extra_rendering_passes = [ + SelectionPass(self._viewport_width, self._viewport_height, SelectionPass.SelectionMode.FACES), + PickingPass(self._viewport_width, self._viewport_height, only_selected_objects=True), + PickingPass(self._viewport_width, self._viewport_height, only_selected_objects=False) + ] + + for extra_rendering_pass in self._extra_rendering_passes: + extra_rendering_pass.setEnabled(False) + + return super()._makeRenderPasses() + self._extra_rendering_passes diff --git a/cura/LayerDataBuilder.py b/cura/LayerDataBuilder.py index d8801c9e7b..ff80307223 100755 --- a/cura/LayerDataBuilder.py +++ b/cura/LayerDataBuilder.py @@ -80,9 +80,13 @@ class LayerDataBuilder(MeshBuilder): material_colors = numpy.zeros((line_dimensions.shape[0], 4), dtype=numpy.float32) for extruder_nr in range(material_color_map.shape[0]): material_colors[extruders == extruder_nr] = material_color_map[extruder_nr] - # Set material_colors with indices where line_types (also numpy array) == MoveCombingType - material_colors[line_types == LayerPolygon.MoveCombingType] = colors[line_types == LayerPolygon.MoveCombingType] - material_colors[line_types == LayerPolygon.MoveRetractionType] = colors[line_types == LayerPolygon.MoveRetractionType] + # Set material_colors with indices where line_types (also numpy array) == MoveUnretractedType + material_colors[line_types == LayerPolygon.MoveUnretractedType] = colors[line_types == LayerPolygon.MoveUnretractedType] + material_colors[line_types == LayerPolygon.MoveRetractedType] = colors[line_types == LayerPolygon.MoveRetractedType] + material_colors[line_types == LayerPolygon.MoveWhileRetractingType] = colors[ + line_types == LayerPolygon.MoveWhileRetractingType] + material_colors[line_types == LayerPolygon.MoveWhileUnretractingType] = colors[ + line_types == LayerPolygon.MoveWhileUnretractingType] attributes = { "line_dimensions": { diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py index e772a8b78e..cd4642d719 100644 --- a/cura/LayerPolygon.py +++ b/cura/LayerPolygon.py @@ -19,15 +19,22 @@ class LayerPolygon: SkirtType = 5 InfillType = 6 SupportInfillType = 7 - MoveCombingType = 8 - MoveRetractionType = 9 + MoveUnretractedType = 8 + MoveRetractedType = 9 SupportInterfaceType = 10 PrimeTowerType = 11 - __number_of_types = 12 + MoveWhileRetractingType = 12 + MoveWhileUnretractingType = 13 + StationaryRetractUnretract = 14 + __number_of_types = 15 - __jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, - numpy.arange(__number_of_types) == MoveCombingType), - numpy.arange(__number_of_types) == MoveRetractionType) + __jump_map = numpy.logical_or(numpy.logical_or(numpy.logical_or( + numpy.arange(__number_of_types) == NoneType, + numpy.arange(__number_of_types) == MoveUnretractedType), + numpy.logical_or( + numpy.arange(__number_of_types) == MoveRetractedType, + numpy.arange(__number_of_types) == MoveWhileRetractingType)), + numpy.arange(__number_of_types) == MoveWhileUnretractingType) def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray, line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None: @@ -269,10 +276,13 @@ class LayerPolygon: theme.getColor("layerview_skirt").getRgbF(), # SkirtType theme.getColor("layerview_infill").getRgbF(), # InfillType theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType - theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType - theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType + theme.getColor("layerview_move_combing").getRgbF(), # MoveUnretractedType + theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractedType theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType - theme.getColor("layerview_prime_tower").getRgbF() # PrimeTowerType + theme.getColor("layerview_prime_tower").getRgbF(), # PrimeTowerType + theme.getColor("layerview_move_while_retracting").getRgbF(), # MoveWhileRetracting + theme.getColor("layerview_move_while_unretracting").getRgbF(), # MoveWhileUnretracting + theme.getColor("layerview_move_retraction").getRgbF(), # StationaryRetractUnretract ]) return cls.__color_map diff --git a/cura/Machines/MachineErrorChecker.py b/cura/Machines/MachineErrorChecker.py index 5edee0778f..87d50e46d4 100644 --- a/cura/Machines/MachineErrorChecker.py +++ b/cura/Machines/MachineErrorChecker.py @@ -61,6 +61,7 @@ class MachineErrorChecker(QObject): self._machine_manager.globalContainerChanged.connect(self.startErrorCheck) self._onMachineChanged() + self.startErrorCheck() def _setCheckTimer(self) -> None: """A QTimer to regulate error check frequency diff --git a/cura/PickingPass.py b/cura/PickingPass.py index 4d6ef671df..e585e72269 100644 --- a/cura/PickingPass.py +++ b/cura/PickingPass.py @@ -7,6 +7,7 @@ from UM.Qt.QtApplication import QtApplication from UM.Logger import Logger from UM.Math.Vector import Vector from UM.Resources import Resources +from UM.Scene.Selection import Selection from UM.View.RenderPass import RenderPass from UM.View.GL.OpenGL import OpenGL @@ -27,13 +28,14 @@ class PickingPass(RenderPass): .. note:: that in order to increase precision, the 24 bit depth value is encoded into all three of the R,G & B channels """ - def __init__(self, width: int, height: int) -> None: - super().__init__("picking", width, height) + def __init__(self, width: int, height: int, only_selected_objects: bool = False) -> None: + super().__init__("picking" if not only_selected_objects else "picking_selected", width, height) self._renderer = QtApplication.getInstance().getRenderer() self._shader = None #type: Optional[ShaderProgram] self._scene = QtApplication.getInstance().getController().getScene() + self._only_selected_objects = only_selected_objects def render(self) -> None: if not self._shader: @@ -53,7 +55,7 @@ class PickingPass(RenderPass): # Fill up the batch with objects that can be sliced. ` for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. - if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible(): + if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible() and (not self._only_selected_objects or Selection.isSelected(node)): batch.addItem(node.getWorldTransformation(copy = False), node.getMeshData(), normal_transformation=node.getCachedNormalMatrix()) self.bind() diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index 3dc245d468..1d0be1389e 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -33,8 +33,8 @@ class AuthState(IntEnum): class NetworkedPrinterOutputDevice(PrinterOutputDevice): authenticationStateChanged = pyqtSignal() - def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType = ConnectionType.NetworkConnection, parent: QObject = None) -> None: - super().__init__(device_id = device_id, connection_type = connection_type, parent = parent) + def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType = ConnectionType.NetworkConnection, parent: QObject = None, active: bool = True) -> None: + super().__init__(device_id = device_id, connection_type = connection_type, parent = parent, active = active) self._manager = None # type: Optional[QNetworkAccessManager] self._timeout_time = 10 # After how many seconds of no response should a timeout occur? diff --git a/cura/PrinterOutput/PrinterOutputDevice.py b/cura/PrinterOutput/PrinterOutputDevice.py index 9c1727f569..b369fc1129 100644 --- a/cura/PrinterOutput/PrinterOutputDevice.py +++ b/cura/PrinterOutput/PrinterOutputDevice.py @@ -72,7 +72,10 @@ class PrinterOutputDevice(QObject, OutputDevice): # Signal to indicate that the configuration of one of the printers has changed. uniqueConfigurationsChanged = pyqtSignal() - def __init__(self, device_id: str, connection_type: "ConnectionType" = ConnectionType.NotConnected, parent: QObject = None) -> None: + # Signal to indicate that the printer has become active or inactive + activeChanged = pyqtSignal() + + def __init__(self, device_id: str, connection_type: "ConnectionType" = ConnectionType.NotConnected, parent: QObject = None, active: bool = True) -> None: super().__init__(device_id = device_id, parent = parent) # type: ignore # MyPy complains with the multiple inheritance self._printers = [] # type: List[PrinterOutputModel] @@ -88,6 +91,8 @@ class PrinterOutputDevice(QObject, OutputDevice): self._accepts_commands = False # type: bool + self._active: bool = active + self._update_timer = QTimer() # type: QTimer self._update_timer.setInterval(2000) # TODO; Add preference for update interval self._update_timer.setSingleShot(False) @@ -295,3 +300,17 @@ class PrinterOutputDevice(QObject, OutputDevice): return self._firmware_updater.updateFirmware(firmware_file) + + @pyqtProperty(bool, notify = activeChanged) + def active(self) -> bool: + """ + Indicates whether the printer is active, which is not the same as "being the active printer". In this case, + active means that the printer can be used. An example of an inactive printer is one that cannot be used because + the user doesn't have enough seats on Digital Factory. + """ + return self._active + + def _setActive(self, active: bool) -> None: + if active != self._active: + self._active = active + self.activeChanged.emit() diff --git a/cura/Scene/SliceableObjectDecorator.py b/cura/Scene/SliceableObjectDecorator.py index ad51f7d755..7ee77795e7 100644 --- a/cura/Scene/SliceableObjectDecorator.py +++ b/cura/Scene/SliceableObjectDecorator.py @@ -1,12 +1,63 @@ +import copy +import json + +from typing import Optional, Dict + +from PyQt6.QtCore import QBuffer +from PyQt6.QtGui import QImage, QImageWriter + +import UM.View.GL.Texture from UM.Scene.SceneNodeDecorator import SceneNodeDecorator +from UM.View.GL.OpenGL import OpenGL +from UM.View.GL.Texture import Texture class SliceableObjectDecorator(SceneNodeDecorator): def __init__(self) -> None: super().__init__() + self._paint_texture = None + self._texture_data_mapping: Dict[str, tuple[int, int]] = {} def isSliceable(self) -> bool: return True + def getPaintTexture(self) -> Optional[Texture]: + return self._paint_texture + + def setPaintTexture(self, texture: Texture) -> None: + self._paint_texture = texture + + def getTextureDataMapping(self) -> Dict[str, tuple[int, int]]: + return self._texture_data_mapping + + def setTextureDataMapping(self, mapping: Dict[str, tuple[int, int]]) -> None: + self._texture_data_mapping = mapping + + def prepareTexture(self, width: int, height: int) -> None: + if self._paint_texture is None: + self._paint_texture = OpenGL.getInstance().createTexture(width, height) + image = QImage(width, height, QImage.Format.Format_RGB32) + image.fill(0) + self._paint_texture.setImage(image) + + def packTexture(self) -> Optional[bytearray]: + if self._paint_texture is None: + return None + + texture_image = self._paint_texture.getImage() + if texture_image is None: + return None + + texture_buffer = QBuffer() + texture_buffer.open(QBuffer.OpenModeFlag.ReadWrite) + image_writer = QImageWriter(texture_buffer, b"png") + image_writer.setText("Description", json.dumps(self._texture_data_mapping)) + image_writer.write(texture_image) + + return texture_buffer.data() + def __deepcopy__(self, memo) -> "SliceableObjectDecorator": - return type(self)() + copied_decorator = SliceableObjectDecorator() + copied_decorator.setPaintTexture(copy.deepcopy(self.getPaintTexture())) + copied_decorator.setTextureDataMapping(copy.deepcopy(self.getTextureDataMapping())) + return copied_decorator diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index ef1c9561af..fd7f7c7575 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -402,6 +402,9 @@ class CuraContainerStack(ContainerStack): return super().getProperty(key, property_name, context) + def getValue(self, key: str, context = None) -> Any: + return self.getProperty(key, "value", context) + class _ContainerIndexes: """Private helper class to keep track of container positions and their types.""" diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 3181016f69..cd39947bf8 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -15,6 +15,7 @@ from UM.Scene.Selection import Selection from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID. from cura.Machines.ContainerTree import ContainerTree +from cura.Settings.ExtruderStack import ExtruderStack from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING, Union @@ -304,6 +305,11 @@ class ExtruderManager(QObject): Logger.log("e", "Unable to find one or more of the extruders in %s", used_extruder_stack_ids) return [] + def getFirstUsedExtruderStack(self)-> ExtruderStack: + used_extruders = self.getUsedExtruderStacks() + sorted_extruders = sorted(used_extruders, key=lambda extruder: extruder.getValue("extruder_nr")) + return sorted_extruders[0] + def getInitialExtruderNr(self) -> int: """Get the extruder that the print will start with. @@ -320,8 +326,7 @@ class ExtruderManager(QObject): 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 + return self.getFirstUsedExtruderStack().getValue("extruder_nr") else: return skirt_brim_extruder_nr if adhesion_type == "raft": @@ -332,7 +337,7 @@ class ExtruderManager(QObject): return global_stack.getProperty("support_infill_extruder_nr", "value") # REALLY no adhesion? Use the first used extruder. - return self.getUsedExtruderStacks()[0].getProperty("extruder_nr", "value") + return self.getFirstUsedExtruderStack().getValue("extruder_nr") def removeMachineExtruders(self, machine_id: str) -> None: """Removes the container stack and user profile for the extruders for a specific machine. diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 986608cd49..3a2201449d 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -183,10 +183,14 @@ class MachineManager(QObject): self.setActiveMachine(active_machine_id) def _onOutputDevicesChanged(self) -> None: + for printer_output_device in self._printer_output_devices: + printer_output_device.activeChanged.disconnect(self.printerConnectedStatusChanged) + self._printer_output_devices = [] for printer_output_device in self._application.getOutputDeviceManager().getOutputDevices(): if isinstance(printer_output_device, PrinterOutputDevice): self._printer_output_devices.append(printer_output_device) + printer_output_device.activeChanged.connect(self.printerConnectedStatusChanged) self.outputDevicesChanged.emit() @@ -569,6 +573,13 @@ class MachineManager(QObject): def activeMachineIsUsingCloudConnection(self) -> bool: return self.activeMachineHasCloudConnection and not self.activeMachineHasNetworkConnection + @pyqtProperty(bool, notify = printerConnectedStatusChanged) + def activeMachineIsActive(self) -> bool: + if not self._printer_output_devices: + return True + + return self._printer_output_devices[0].active + def activeMachineNetworkKey(self) -> str: if self._global_container_stack: return self._global_container_stack.getMetaDataEntry("um_network_key", "") diff --git a/cura/Stages/CuraStage.py b/cura/Stages/CuraStage.py index 869ed309dc..8c207db8ad 100644 --- a/cura/Stages/CuraStage.py +++ b/cura/Stages/CuraStage.py @@ -1,6 +1,8 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from typing import Optional + from PyQt6.QtCore import pyqtProperty, QUrl from UM.Stage import Stage @@ -13,8 +15,8 @@ from UM.Stage import Stage # * The MainComponent is the component that will be drawn starting from the bottom of the stageBar and fills the rest # of the screen. class CuraStage(Stage): - def __init__(self, parent = None) -> None: - super().__init__(parent) + def __init__(self, parent = None, active_view: Optional[str] = "SolidView") -> None: + super().__init__(parent, active_view = active_view) @pyqtProperty(str, constant = True) def stageId(self) -> str: diff --git a/cura/XRayPass.py b/cura/XRayPass.py index 965294ba89..20fe38741e 100644 --- a/cura/XRayPass.py +++ b/cura/XRayPass.py @@ -16,7 +16,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator class XRayPass(RenderPass): def __init__(self, width, height): - super().__init__("xray", width, height) + super().__init__("xray", width, height, -100) self._shader = None self._gl = OpenGL.getInstance().getBindingsObject() diff --git a/packaging/NSIS/Ultimaker-Cura.nsi.jinja b/packaging/NSIS/Ultimaker-Cura.nsi.jinja index ac826af0d9..9f61e6950c 100644 --- a/packaging/NSIS/Ultimaker-Cura.nsi.jinja +++ b/packaging/NSIS/Ultimaker-Cura.nsi.jinja @@ -1,9 +1,8 @@ -# Copyright (c) 2022 UltiMaker B.V. +# Copyright (c) 2025 UltiMaker # Cura's build system is released under the terms of the AGPLv3 or higher. !define APP_NAME "{{ app_name }}" !define COMP_NAME "{{ company }}" -!define WEB_SITE "{{ web_site }}" !define VERSION "{{ version }}" !define VIVERSION "{{ version_major }}.{{ version_minor }}.{{ version_patch }}.0" !define COPYRIGHT "Copyright (c) {{ year }} {{ company }}" @@ -16,13 +15,11 @@ !define REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\${APP_NAME}-${VERSION}" !define UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}-${VERSION}" -!define REG_START_MENU "Start Menu Folder" +!define REG_START_MENU "Start Menu Shortcut" ;Require administrator access RequestExecutionLevel admin -var SM_Folder - ###################################################################### VIProductVersion "${VIVERSION}" @@ -64,11 +61,9 @@ InstallDir "$PROGRAMFILES64\${APP_NAME}" !ifdef REG_START_MENU !define MUI_STARTMENUPAGE_NODISABLE -!define MUI_STARTMENUPAGE_DEFAULTFOLDER "UltiMaker Cura" !define MUI_STARTMENUPAGE_REGISTRY_ROOT "${REG_ROOT}" !define MUI_STARTMENUPAGE_REGISTRY_KEY "${UNINSTALL_PATH}" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "${REG_START_MENU}" -!insertmacro MUI_PAGE_STARTMENU Application $SM_Folder !endif !insertmacro MUI_PAGE_INSTFILES @@ -107,27 +102,11 @@ SetOutPath "$INSTDIR" WriteUninstaller "$INSTDIR\uninstall.exe" !ifdef REG_START_MENU -!insertmacro MUI_STARTMENU_WRITE_BEGIN Application -CreateDirectory "$SMPROGRAMS\$SM_Folder" -CreateShortCut "$SMPROGRAMS\$SM_Folder\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" -CreateShortCut "$SMPROGRAMS\$SM_Folder\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe" - -!ifdef WEB_SITE -WriteIniStr "$INSTDIR\UltiMaker Cura website.url" "InternetShortcut" "URL" "${WEB_SITE}" -CreateShortCut "$SMPROGRAMS\$SM_Folder\UltiMaker Cura website.lnk" "$INSTDIR\UltiMaker Cura website.url" -!endif -!insertmacro MUI_STARTMENU_WRITE_END +CreateShortCut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" !endif !ifndef REG_START_MENU -CreateDirectory "$SMPROGRAMS\{{ app_name }}" -CreateShortCut "$SMPROGRAMS\{{ app_name }}\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" -CreateShortCut "$SMPROGRAMS\{{ app_name }}\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe" - -!ifdef WEB_SITE -WriteIniStr "$INSTDIR\UltiMaker Cura website.url" "InternetShortcut" "URL" "${WEB_SITE}" -CreateShortCut "$SMPROGRAMS\{{ app_name }}\UltiMaker Cura website.lnk" "$INSTDIR\UltiMaker Cura website.url" -!endif +CreateShortCut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" !endif WriteRegStr ${REG_ROOT} "${REG_APP_PATH}" "" "$INSTDIR\${MAIN_APP_EXE}" @@ -138,9 +117,6 @@ WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayIcon" "$INSTDIR\${MAIN_APP_ WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayVersion" "${VERSION}" WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "Publisher" "${COMP_NAME}" -!ifdef WEB_SITE -WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "URLInfoAbout" "${WEB_SITE}" -!endif SectionEnd ###################################################################### @@ -177,29 +153,17 @@ RmDir "$INSTDIR\share\uranium" RmDir "$INSTDIR\share" Delete "$INSTDIR\uninstall.exe" -!ifdef WEB_SITE -Delete "$INSTDIR\${APP_NAME} website.url" -!endif RmDir /r /REBOOTOK "$INSTDIR" !ifdef REG_START_MENU -!insertmacro MUI_STARTMENU_GETFOLDER "Application" $SM_Folder -Delete "$SMPROGRAMS\$SM_Folder\${APP_NAME}.lnk" -Delete "$SMPROGRAMS\$SM_Folder\Uninstall ${APP_NAME}.lnk" -!ifdef WEB_SITE -Delete "$SMPROGRAMS\$SM_Folder\UltiMaker Cura website.lnk" -!endif -RmDir "$SMPROGRAMS\$SM_Folder" +Delete "$SMPROGRAMS\${APP_NAME}.lnk" +Delete "$SMPROGRAMS\Uninstall ${APP_NAME}.lnk" !endif !ifndef REG_START_MENU -Delete "$SMPROGRAMS\{{ app_name }}\${APP_NAME}.lnk" -Delete "$SMPROGRAMS\{{ app_name }}\Uninstall ${APP_NAME}.lnk" -!ifdef WEB_SITE -Delete "$SMPROGRAMS\{{ app_name }}\UltiMaker Cura website.lnk" -!endif -RmDir "$SMPROGRAMS\{{ app_name }}" +Delete "$SMPROGRAMS\${APP_NAME}.lnk" +Delete "$SMPROGRAMS\Uninstall ${APP_NAME}.lnk" !endif !insertmacro APP_UNASSOCIATE "stl" "Cura.model" diff --git a/packaging/NSIS/create_windows_installer.py b/packaging/NSIS/create_windows_installer.py index d15d62b951..e01c757dbb 100644 --- a/packaging/NSIS/create_windows_installer.py +++ b/packaging/NSIS/create_windows_installer.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 UltiMaker +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. @@ -51,7 +51,6 @@ def generate_nsi(source_path: str, dist_path: str, filename: str, version: str): version_minor = str(parsed_version.minor), version_patch = str(parsed_version.patch), company = "UltiMaker", - web_site = "https://ultimaker.com", year = datetime.now().year, cura_license_file = str(source_loc.joinpath("packaging", "cura_license.txt")), compression_method = "LZMA", # ZLIB, BZIP2 or LZMA diff --git a/packaging/msi/create_windows_msi.py b/packaging/msi/create_windows_msi.py index e44a9a924b..12c64ed24f 100644 --- a/packaging/msi/create_windows_msi.py +++ b/packaging/msi/create_windows_msi.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 UltiMaker +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. @@ -40,7 +40,6 @@ def generate_wxs(source_path: Path, dist_path: Path, filename: Path, app_name: s version_minor=str(parsed_version.minor), version_patch=str(parsed_version.patch), company="UltiMaker", - web_site="https://ultimaker.com", year=datetime.now().year, upgrade_code=str(uuid.uuid5(uuid.NAMESPACE_DNS, app_name)), cura_license_file=str(source_loc.joinpath("packaging", "msi", "cura_license.rtf")), diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 9d4ace1698..09143dde64 100755 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -1,12 +1,14 @@ # Copyright (c) 2021-2022 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. - +import json import os.path import zipfile from typing import List, Optional, Union, TYPE_CHECKING, cast import pySavitar as Savitar import numpy +from PyQt6.QtCore import QBuffer +from PyQt6.QtGui import QImage, QImageReader from UM.Logger import Logger from UM.Math.Matrix import Matrix @@ -18,6 +20,8 @@ from UM.Scene.GroupDecorator import GroupDecorator from UM.Scene.SceneNode import SceneNode # For typing. from UM.Scene.SceneNodeSettings import SceneNodeSettings from UM.Util import parseBool +from UM.View.GL.OpenGL import OpenGL +from UM.View.GL.Texture import Texture from cura.CuraApplication import CuraApplication from cura.Machines.ContainerTree import ContainerTree from cura.Scene.BuildPlateDecorator import BuildPlateDecorator @@ -94,14 +98,14 @@ class ThreeMFReader(MeshReader): return temp_mat @staticmethod - def _convertSavitarNodeToUMNode(savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]: + def _convertSavitarNodeToUMNode(savitar_node: Savitar.SceneNode, file_name: str = "", archive: zipfile.ZipFile = None, scene: Savitar.Scene = None) -> Optional[SceneNode]: """Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node. :returns: Scene node. """ try: node_name = savitar_node.getName() - node_id = savitar_node.getId() + node_id = str(savitar_node.getId()) except AttributeError: Logger.log("e", "Outdated version of libSavitar detected! Please update to the newest version!") node_name = "" @@ -115,6 +119,10 @@ class ThreeMFReader(MeshReader): active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate + component_path = savitar_node.getComponentPath() + if component_path != "" and archive is not None: + savitar_node.parseComponentData(archive.open(component_path.lstrip("/")).read()) + um_node = CuraSceneNode() # This adds a SettingOverrideDecorator um_node.addDecorator(BuildPlateDecorator(active_build_plate)) try: @@ -127,23 +135,31 @@ class ThreeMFReader(MeshReader): um_node.setTransformation(transformation) mesh_builder = MeshBuilder() - data = numpy.fromstring(savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32) + mesh_data = savitar_node.getMeshData() + + vertices_data = numpy.fromstring(mesh_data.getFlatVerticesAsBytes(), dtype=numpy.float32) + vertices = numpy.resize(vertices_data, (int(vertices_data.size / 3), 3)) + + texture_path = mesh_data.getTexturePath(scene) + uv_data = numpy.fromstring(mesh_data.getUVCoordinatesPerVertexAsBytes(scene), dtype=numpy.float32) + uv_coordinates = numpy.resize(uv_data, (int(uv_data.size / 2), 2)) - vertices = numpy.resize(data, (int(data.size / 3), 3)) mesh_builder.setVertices(vertices) mesh_builder.calculateNormals(fast=True) mesh_builder.setMeshId(node_id) + mesh_builder.setUVCoordinates(uv_coordinates) if file_name: # The filename is used to give the user the option to reload the file if it is changed on disk # It is only set for the root node of the 3mf file mesh_builder.setFileName(file_name) + mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): um_node.setMeshData(mesh_data) for child in savitar_node.getChildren(): - child_node = ThreeMFReader._convertSavitarNodeToUMNode(child) + child_node = ThreeMFReader._convertSavitarNodeToUMNode(child, archive=archive, scene=scene) if child_node: um_node.addChild(child_node) @@ -215,6 +231,30 @@ class ThreeMFReader(MeshReader): # affects (auto) slicing sliceable_decorator = SliceableObjectDecorator() um_node.addDecorator(sliceable_decorator) + + if texture_path != "" and archive is not None: + texture_data = archive.open(texture_path).read() + texture_buffer = QBuffer() + texture_buffer.open(QBuffer.OpenModeFlag.ReadWrite) + texture_buffer.write(texture_data) + + image_reader = QImageReader(texture_buffer, b"png") + + texture_buffer.seek(0) + texture_image = image_reader.read() + texture = Texture(OpenGL.getInstance()) + texture.setImage(texture_image) + sliceable_decorator.setPaintTexture(texture) + + texture_buffer.seek(0) + data_mapping_desc = image_reader.text("Description") + if data_mapping_desc != "": + data_mapping = json.loads(data_mapping_desc) + for key, value in data_mapping.items(): + # Tuples are stored as lists in json, restore them back to tuples + data_mapping[key] = tuple(value) + sliceable_decorator.setTextureDataMapping(data_mapping) + return um_node def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]: @@ -232,7 +272,7 @@ class ThreeMFReader(MeshReader): CuraApplication.getInstance().getController().getScene().setMetaDataEntry(key, value) for node in scene_3mf.getSceneNodes(): - um_node = ThreeMFReader._convertSavitarNodeToUMNode(node, file_name) + um_node = ThreeMFReader._convertSavitarNodeToUMNode(node, file_name, archive, scene_3mf) if um_node is None: continue @@ -332,7 +372,7 @@ class ThreeMFReader(MeshReader): # Convert the scene to scene nodes nodes = [] for savitar_node in scene.getSceneNodes(): - scene_node = ThreeMFReader._convertSavitarNodeToUMNode(savitar_node, "file_name") + scene_node = ThreeMFReader._convertSavitarNodeToUMNode(savitar_node, "file_name", scene=scene) if scene_node is None: continue nodes.append(scene_node) diff --git a/plugins/3MFReader/__init__.py b/plugins/3MFReader/__init__.py index 5e2b68fce0..d468783a90 100644 --- a/plugins/3MFReader/__init__.py +++ b/plugins/3MFReader/__init__.py @@ -23,7 +23,7 @@ def getMetaData() -> Dict: if "3MFReader.ThreeMFReader" in sys.modules: metaData["mesh_reader"] = [ { - "extension": "3mf", + "extension": workspace_extension, "description": catalog.i18nc("@item:inlistbox", "3MF File") } ] diff --git a/plugins/3MFWriter/BambuLabVariant.py b/plugins/3MFWriter/BambuLabVariant.py new file mode 100644 index 0000000000..69814505ad --- /dev/null +++ b/plugins/3MFWriter/BambuLabVariant.py @@ -0,0 +1,176 @@ +# Copyright (c) 2025 UltiMaker +# Cura is released under the terms of the LGPLv3 or higher. + +import hashlib +import json +from io import StringIO +import xml.etree.ElementTree as ET +import zipfile + +from PyQt6.QtCore import Qt, QBuffer +from PyQt6.QtGui import QImage + +from UM.Application import Application +from UM.Logger import Logger +from UM.Mesh.MeshWriter import MeshWriter +from UM.PluginRegistry import PluginRegistry +from typing import cast + +from cura.CuraApplication import CuraApplication + +from .ThreeMFVariant import ThreeMFVariant +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + +# Path constants +METADATA_PATH = "Metadata" +THUMBNAIL_PATH_MULTIPLATE = f"{METADATA_PATH}/plate_1.png" +THUMBNAIL_PATH_MULTIPLATE_SMALL = f"{METADATA_PATH}/plate_1_small.png" +GCODE_PATH = f"{METADATA_PATH}/plate_1.gcode" +GCODE_MD5_PATH = f"{GCODE_PATH}.md5" +MODEL_SETTINGS_PATH = f"{METADATA_PATH}/model_settings.config" +PLATE_DESC_PATH = f"{METADATA_PATH}/plate_1.json" +SLICE_INFO_PATH = f"{METADATA_PATH}/slice_info.config" +PROJECT_SETTINGS_PATH = f"{METADATA_PATH}/project_settings.config" + +class BambuLabVariant(ThreeMFVariant): + """BambuLab specific implementation of the 3MF format.""" + + @property + def mime_type(self) -> str: + return "application/vnd.bambulab-package.3dmanufacturing-3dmodel+xml" + + def process_thumbnail(self, snapshot: QImage, thumbnail_buffer: QBuffer, + archive: zipfile.ZipFile, relations_element: ET.Element) -> None: + """Process the thumbnail for BambuLab variant.""" + # Write thumbnail + archive.writestr(zipfile.ZipInfo(THUMBNAIL_PATH_MULTIPLATE), thumbnail_buffer.data()) + + # Add relations elements for thumbnails + ET.SubElement(relations_element, "Relationship", + Target="/" + THUMBNAIL_PATH_MULTIPLATE, Id="rel-2", + pe="http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail") + + ET.SubElement(relations_element, "Relationship", + Target="/" + THUMBNAIL_PATH_MULTIPLATE, Id="rel-4", + Type="http://schemas.bambulab.com/package/2021/cover-thumbnail-middle") + + # Create and save small thumbnail + small_snapshot = snapshot.scaled(128, 128, transformMode=Qt.TransformationMode.SmoothTransformation) + small_thumbnail_buffer = QBuffer() + small_thumbnail_buffer.open(QBuffer.OpenModeFlag.ReadWrite) + small_snapshot.save(small_thumbnail_buffer, "PNG") + + # Write small thumbnail + archive.writestr(zipfile.ZipInfo(THUMBNAIL_PATH_MULTIPLATE_SMALL), small_thumbnail_buffer.data()) + + # Add relation for small thumbnail + ET.SubElement(relations_element, "Relationship", + Target="/" + THUMBNAIL_PATH_MULTIPLATE_SMALL, Id="rel-5", + Type="http://schemas.bambulab.com/package/2021/cover-thumbnail-small") + + def add_extra_files(self, archive: zipfile.ZipFile, metadata_relations_element: ET.Element) -> None: + """Add BambuLab specific files to the archive.""" + self._storeGCode(archive, metadata_relations_element) + self._storeModelSettings(archive) + self._storePlateDesc(archive) + self._storeSliceInfo(archive) + self._storeProjectSettings(archive) + + def _storeGCode(self, archive: zipfile.ZipFile, metadata_relations_element: ET.Element): + """Store GCode data in the archive.""" + gcode_textio = StringIO() + gcode_writer = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter")) + success = gcode_writer.write(gcode_textio, None) + + if not success: + error_msg = catalog.i18nc("@info:error", "Can't write GCode to 3MF file") + self._writer.setInformation(error_msg) + Logger.error(error_msg) + raise Exception(error_msg) + + gcode_data = gcode_textio.getvalue().encode("UTF-8") + archive.writestr(zipfile.ZipInfo(GCODE_PATH), gcode_data) + + gcode_relation_element = ET.SubElement(metadata_relations_element, "Relationship", + Target=f"/{GCODE_PATH}", Id="rel-1", + Type="http://schemas.bambulab.com/package/2021/gcode") + + # Calculate and store the MD5 sum of the gcode data + md5_hash = hashlib.md5(gcode_data).hexdigest() + archive.writestr(zipfile.ZipInfo(GCODE_MD5_PATH), md5_hash.encode("UTF-8")) + + def _storeModelSettings(self, archive: zipfile.ZipFile): + """Store model settings in the archive.""" + config = ET.Element("config") + plate = ET.SubElement(config, "plate") + ET.SubElement(plate, "metadata", key="plater_id", value="1") + ET.SubElement(plate, "metadata", key="plater_name", value="") + ET.SubElement(plate, "metadata", key="locked", value="false") + ET.SubElement(plate, "metadata", key="filament_map_mode", value="Auto For Flush") + extruders_count = len(CuraApplication.getInstance().getExtruderManager().extruderIds) + ET.SubElement(plate, "metadata", key="filament_maps", value=" ".join("1" for _ in range(extruders_count))) + ET.SubElement(plate, "metadata", key="gcode_file", value=GCODE_PATH) + ET.SubElement(plate, "metadata", key="thumbnail_file", value=THUMBNAIL_PATH_MULTIPLATE) + ET.SubElement(plate, "metadata", key="pattern_bbox_file", value=PLATE_DESC_PATH) + + self._writer._storeElementTree(archive, MODEL_SETTINGS_PATH, config) + + def _storePlateDesc(self, archive: zipfile.ZipFile): + """Store plate description in the archive.""" + plate_desc = {} + + filament_ids = [] + filament_colors = [] + + for extruder in CuraApplication.getInstance().getExtruderManager().getUsedExtruderStacks(): + filament_ids.append(extruder.getValue("extruder_nr")) + filament_colors.append(self._writer._getMaterialColor(extruder)) + + plate_desc["filament_ids"] = filament_ids + plate_desc["filament_colors"] = filament_colors + plate_desc["first_extruder"] = CuraApplication.getInstance().getExtruderManager().getInitialExtruderNr() + plate_desc["is_seq_print"] = Application.getInstance().getGlobalContainerStack().getValue("print_sequence") == "one_at_a_time" + plate_desc["nozzle_diameter"] = CuraApplication.getInstance().getExtruderManager().getActiveExtruderStack().getValue("machine_nozzle_size") + plate_desc["version"] = 2 + + file = zipfile.ZipInfo(PLATE_DESC_PATH) + file.compress_type = zipfile.ZIP_DEFLATED + archive.writestr(file, json.dumps(plate_desc).encode("UTF-8")) + + def _storeSliceInfo(self, archive: zipfile.ZipFile): + """Store slice information in the archive.""" + config = ET.Element("config") + + header = ET.SubElement(config, "header") + ET.SubElement(header, "header_item", key="X-BBL-Client-Type", value="slicer") + ET.SubElement(header, "header_item", key="X-BBL-Client-Version", value="02.00.01.50") + + plate = ET.SubElement(config, "plate") + ET.SubElement(plate, "metadata", key="index", value="1") + ET.SubElement(plate, + "metadata", + key="nozzle_diameters", + value=str(CuraApplication.getInstance().getExtruderManager().getActiveExtruderStack().getValue("machine_nozzle_size"))) + + print_information = CuraApplication.getInstance().getPrintInformation() + for index, extruder in enumerate(Application.getInstance().getGlobalContainerStack().extruderList): + used_m = print_information.materialLengths[index] + used_g = print_information.materialWeights[index] + if used_m > 0.0 and used_g > 0.0: + ET.SubElement(plate, + "filament", + id=str(extruder.getValue("extruder_nr") + 1), + tray_info_idx="GFA00", + type=extruder.material.getMetaDataEntry("material", ""), + color=self._writer._getMaterialColor(extruder), + used_m=str(used_m), + used_g=str(used_g)) + + self._writer._storeElementTree(archive, SLICE_INFO_PATH, config) + + def _storeProjectSettings(self, archive: zipfile.ZipFile): + api = CuraApplication.getInstance().getCuraAPI() + file = zipfile.ZipInfo(PROJECT_SETTINGS_PATH) + json_string = json.dumps(api.interface.settings.getAllGlobalSettings(), separators=(", ", ": "), indent=4) + archive.writestr(file, json_string.encode("UTF-8")) \ No newline at end of file diff --git a/plugins/3MFWriter/Cura3mfVariant.py b/plugins/3MFWriter/Cura3mfVariant.py new file mode 100644 index 0000000000..3ae766e651 --- /dev/null +++ b/plugins/3MFWriter/Cura3mfVariant.py @@ -0,0 +1,33 @@ +# Copyright (c) 2025 UltiMaker +# Cura is released under the terms of the LGPLv3 or higher. + +import xml.etree.ElementTree as ET +import zipfile + +from PyQt6.QtCore import QBuffer +from PyQt6.QtGui import QImage + +from .ThreeMFVariant import ThreeMFVariant + +# Standard 3MF paths +METADATA_PATH = "Metadata" +THUMBNAIL_PATH = f"{METADATA_PATH}/thumbnail.png" + +class Cura3mfVariant(ThreeMFVariant): + """Default implementation of the 3MF format.""" + + @property + def mime_type(self) -> str: + return "application/vnd.ms-package.3dmanufacturing-3dmodel+xml" + + def process_thumbnail(self, snapshot: QImage, thumbnail_buffer: QBuffer, + archive: zipfile.ZipFile, relations_element: ET.Element) -> None: + """Process the thumbnail for default 3MF variant.""" + thumbnail_file = zipfile.ZipInfo(THUMBNAIL_PATH) + # Don't try to compress snapshot file, because the PNG is pretty much as compact as it will get + archive.writestr(thumbnail_file, thumbnail_buffer.data()) + + # Add thumbnail relation to _rels/.rels file + ET.SubElement(relations_element, "Relationship", + Target="/" + THUMBNAIL_PATH, Id="rel1", + Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail") diff --git a/plugins/3MFWriter/ThreeMFVariant.py b/plugins/3MFWriter/ThreeMFVariant.py new file mode 100644 index 0000000000..d8f4e31770 --- /dev/null +++ b/plugins/3MFWriter/ThreeMFVariant.py @@ -0,0 +1,74 @@ +# Copyright (c) 2025 UltiMaker +# Cura is released under the terms of the LGPLv3 or higher. + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING +import xml.etree.ElementTree as ET +import zipfile + +from PyQt6.QtGui import QImage +from PyQt6.QtCore import QBuffer + +if TYPE_CHECKING: + from .ThreeMFWriter import ThreeMFWriter + +class ThreeMFVariant(ABC): + """Base class for 3MF format variants. + + Different vendors may have their own extensions to the 3MF format, + such as BambuLab's 3MF variant. This class provides an interface + for implementing these variants. + """ + + def __init__(self, writer: 'ThreeMFWriter'): + """ + :param writer: The ThreeMFWriter instance that will use this variant + """ + self._writer = writer + + @property + @abstractmethod + def mime_type(self) -> str: + """The MIME type for this 3MF variant.""" + pass + + def handles_mime_type(self, mime_type: str) -> bool: + """Check if this variant handles the given MIME type. + + :param mime_type: The MIME type to check + :return: True if this variant handles the MIME type, False otherwise + """ + return mime_type == self.mime_type + + def prepare_content_types(self, content_types: ET.Element) -> None: + """Prepare the content types XML element for this variant. + + :param content_types: The content types XML element + """ + pass + + def prepare_relations(self, relations_element: ET.Element) -> None: + """Prepare the relations XML element for this variant. + + :param relations_element: The relations XML element + """ + pass + + def process_thumbnail(self, snapshot: QImage, thumbnail_buffer: QBuffer, + archive: zipfile.ZipFile, relations_element: ET.Element) -> None: + """Process the thumbnail for this variant. + + :param snapshot: The snapshot image + :param thumbnail_buffer: Buffer containing the thumbnail data + :param archive: The zip archive to write to + :param relations_element: The relations XML element + """ + pass + + def add_extra_files(self, archive: zipfile.ZipFile, metadata_relations_element: ET.Element) -> None: + """Add any extra files required by this variant to the archive. + + :param archive: The zip archive to write to + :param metadata_relations_element: The metadata relations XML element + """ + pass diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index a3eb43ca32..37345b16b0 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -1,11 +1,13 @@ -# Copyright (c) 2015-2022 Ultimaker B.V. +# Copyright (c) 2015-2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. + import json import re import threading -from typing import Optional, cast, List, Dict, Pattern, Set +from typing import Optional, cast, List, Dict, Set +from UM.PluginRegistry import PluginRegistry from UM.Mesh.MeshWriter import MeshWriter from UM.Math.Vector import Vector from UM.Logger import Logger @@ -19,7 +21,9 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from cura.CuraApplication import CuraApplication from cura.CuraPackageManager import CuraPackageManager +from cura.Machines.Models.ExtrudersModel import ExtrudersModel from cura.Settings import CuraContainerStack +from cura.Settings.ExtruderStack import ExtruderStack from cura.Utils.Threading import call_on_qt_thread from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Snapshot import Snapshot @@ -45,13 +49,17 @@ import UM.Application from .SettingsExportModel import SettingsExportModel from .SettingsExportGroup import SettingsExportGroup +from .ThreeMFVariant import ThreeMFVariant +from .Cura3mfVariant import Cura3mfVariant +from .BambuLabVariant import BambuLabVariant from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") -THUMBNAIL_PATH = "Metadata/thumbnail.png" MODEL_PATH = "3D/3dmodel.model" PACKAGE_METADATA_PATH = "Cura/packages.json" +TEXTURES_PATH = "3D/Textures" +MODEL_RELATIONS_PATH = "3D/_rels/3dmodel.model.rels" class ThreeMFWriter(MeshWriter): def __init__(self): @@ -68,6 +76,12 @@ class ThreeMFWriter(MeshWriter): self._store_archive = False self._lock = threading.Lock() + # Register available variants + self._variants = { + Cura3mfVariant(self).mime_type: Cura3mfVariant, + BambuLabVariant(self).mime_type: BambuLabVariant + } + @staticmethod def _convertMatrixToString(matrix): result = "" @@ -97,7 +111,11 @@ class ThreeMFWriter(MeshWriter): def _convertUMNodeToSavitarNode(um_node, transformation = Matrix(), exported_settings: Optional[Dict[str, Set[str]]] = None, - center_mesh = False): + center_mesh = False, + scene: Savitar.Scene = None, + archive: zipfile.ZipFile = None, + model_relations_element: ET.Element = None, + content_types_element: ET.Element = None): """Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode :returns: Uranium Scene node. @@ -138,7 +156,28 @@ class ThreeMFWriter(MeshWriter): if indices_array is not None: savitar_node.getMeshData().setFacesFromBytes(indices_array) else: - savitar_node.getMeshData().setFacesFromBytes(numpy.arange(mesh_data.getVertices().size / 3, dtype=numpy.int32).tostring()) + savitar_node.getMeshData().setFacesFromBytes(numpy.arange(mesh_data.getVertices().size / 3, dtype=numpy.int32).tobytes()) + + packed_texture = um_node.callDecoration("packTexture") + uv_coordinates_array = mesh_data.getUVCoordinatesAsByteArray() + if packed_texture is not None and archive is not None and uv_coordinates_array is not None and len(uv_coordinates_array) > 0: + texture_path = f"{TEXTURES_PATH}/{id(um_node)}.png" + texture_file = zipfile.ZipInfo(texture_path) + # Don't try to compress texture file, because the PNG is pretty much as compact as it will get + archive.writestr(texture_file, packed_texture) + + savitar_node.getMeshData().setUVCoordinatesPerVertexAsBytes(uv_coordinates_array, texture_path, scene) + + # Add texture relation to model relations file + if model_relations_element is not None: + ET.SubElement(model_relations_element, "Relationship", + Target=texture_path, Id=f"rel{len(model_relations_element)+1}", + Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture") + + if content_types_element is not None: + ET.SubElement(content_types_element, "Override", PartName=texture_path, + ContentType="application/vnd.ms-package.3dmanufacturing-3dmodeltexture") + # Handle per object settings (if any) stack = um_node.callDecoration("getStack") @@ -175,7 +214,11 @@ class ThreeMFWriter(MeshWriter): if child_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr: continue savitar_child_node = ThreeMFWriter._convertUMNodeToSavitarNode(child_node, - exported_settings = exported_settings) + exported_settings = exported_settings, + scene = scene, + archive = archive, + model_relations_element = model_relations_element, + content_types_element = content_types_element) if savitar_child_node is not None: savitar_node.addChild(savitar_child_node) @@ -201,26 +244,51 @@ class ThreeMFWriter(MeshWriter): painter.end() - def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode, export_settings_model = None) -> bool: + def _getVariant(self, mime_type: str) -> ThreeMFVariant: + """Get the appropriate variant for the given MIME type. + + :param mime_type: The MIME type to get the variant for + :return: An instance of the variant for the given MIME type + """ + variant_class = self._variants.get(mime_type, Cura3mfVariant) + return variant_class(self) + + def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode, export_settings_model = None, **kwargs) -> bool: self._archive = None # Reset archive archive = zipfile.ZipFile(stream, "w", compression = zipfile.ZIP_DEFLATED) + + # Determine which variant to use based on mime type in kwargs + mime_type = kwargs.get("mime_type", Cura3mfVariant(self).mime_type) + variant = self._getVariant(mime_type) + try: model_file = zipfile.ZipInfo(MODEL_PATH) # Because zipfile is stupid and ignores archive-level compression settings when writing with ZipInfo. model_file.compress_type = zipfile.ZIP_DEFLATED # Create content types file - content_types_file = zipfile.ZipInfo("[Content_Types].xml") - content_types_file.compress_type = zipfile.ZIP_DEFLATED content_types = ET.Element("Types", xmlns = self._namespaces["content-types"]) rels_type = ET.SubElement(content_types, "Default", Extension = "rels", ContentType = "application/vnd.openxmlformats-package.relationships+xml") model_type = ET.SubElement(content_types, "Default", Extension = "model", ContentType = "application/vnd.ms-package.3dmanufacturing-3dmodel+xml") # Create _rels/.rels file - relations_file = zipfile.ZipInfo("_rels/.rels") - relations_file.compress_type = zipfile.ZIP_DEFLATED - relations_element = ET.Element("Relationships", xmlns = self._namespaces["relationships"]) - model_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/" + MODEL_PATH, Id = "rel0", Type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel") + relations_element = self._makeRelationsTree() + model_relation_element = ET.SubElement(relations_element, "Relationship", Target="/" + MODEL_PATH, + Id="rel0", + Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel") + + # Create Metadata/_rels/model_settings.config.rels + metadata_relations_element = self._makeRelationsTree() + + # Create model relations + model_relations_element = self._makeRelationsTree() + + # Let the variant add its specific files + variant.add_extra_files(archive, metadata_relations_element) + + # Let the variant prepare content types and relations + variant.prepare_content_types(content_types) + variant.prepare_relations(relations_element) # Attempt to add a thumbnail snapshot = self._createSnapshot() @@ -233,16 +301,11 @@ class ThreeMFWriter(MeshWriter): thumbnail_buffer.open(QBuffer.OpenModeFlag.ReadWrite) snapshot.save(thumbnail_buffer, "PNG") - thumbnail_file = zipfile.ZipInfo(THUMBNAIL_PATH) - # Don't try to compress snapshot file, because the PNG is pretty much as compact as it will get - archive.writestr(thumbnail_file, thumbnail_buffer.data()) - # Add PNG to content types file thumbnail_type = ET.SubElement(content_types, "Default", Extension="png", ContentType="image/png") - # Add thumbnail relation to _rels/.rels file - thumbnail_relation_element = ET.SubElement(relations_element, "Relationship", - Target="/" + THUMBNAIL_PATH, Id="rel1", - Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail") + + # Let the variant process the thumbnail + variant.process_thumbnail(snapshot, thumbnail_buffer, archive, relations_element) # Write material metadata packages_metadata = self._getMaterialPackageMetadata() + self._getPluginPackageMetadata() @@ -291,13 +354,21 @@ class ThreeMFWriter(MeshWriter): savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(root_child, transformation_matrix, exported_model_settings, - center_mesh = True) + center_mesh = True, + scene = savitar_scene, + archive = archive, + model_relations_element = model_relations_element, + content_types_element = content_types) if savitar_node: savitar_scene.addSceneNode(savitar_node) else: savitar_node = self._convertUMNodeToSavitarNode(node, transformation_matrix, - exported_model_settings) + exported_model_settings, + scene = savitar_scene, + archive = archive, + model_relations_element = model_relations_element, + content_types_element = content_types) if savitar_node: savitar_scene.addSceneNode(savitar_node) @@ -305,8 +376,12 @@ class ThreeMFWriter(MeshWriter): scene_string = parser.sceneToString(savitar_scene) archive.writestr(model_file, scene_string) - archive.writestr(content_types_file, b' \n' + ET.tostring(content_types)) - archive.writestr(relations_file, b' \n' + ET.tostring(relations_element)) + self._storeElementTree(archive, "[Content_Types].xml", content_types) + self._storeElementTree(archive, "_rels/.rels", relations_element) + if len(metadata_relations_element) > 0: + self._storeElementTree(archive, "Metadata/_rels/model_settings.config.rels", metadata_relations_element) + if len(model_relations_element) > 0: + self._storeElementTree(archive, MODEL_RELATIONS_PATH, model_relations_element) except Exception as error: Logger.logException("e", "Error writing zip file") self.setInformation(str(error)) @@ -319,6 +394,25 @@ class ThreeMFWriter(MeshWriter): return True + @staticmethod + def _storeElementTree(archive: zipfile.ZipFile, file_path: str, root_element: ET.Element): + file = zipfile.ZipInfo(file_path) + file.compress_type = zipfile.ZIP_DEFLATED + archive.writestr(file, b' \n' + ET.tostring(root_element)) + + def _makeRelationsTree(self): + return ET.Element("Relationships", xmlns=self._namespaces["relationships"]) + + @staticmethod + def _getMaterialColor(extruder: "ExtruderStack") -> str: + position = int(extruder.getMetaDataEntry("position", default="0")) + try: + default_color = ExtrudersModel.defaultColors[position] + except IndexError: + default_color = "#e0e000" + color_code = extruder.material.getMetaDataEntry("color_code", default=default_color) + return color_code.upper() + @staticmethod def _storeMetadataJson(metadata: Dict[str, List[Dict[str, str]]], archive: zipfile.ZipFile, path: str) -> None: """Stores metadata inside archive path as json file""" @@ -450,7 +544,7 @@ class ThreeMFWriter(MeshWriter): def sceneNodesToString(scene_nodes: [SceneNode]) -> str: savitar_scene = Savitar.Scene() for scene_node in scene_nodes: - savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(scene_node, center_mesh = True) + savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(scene_node, center_mesh = True, scene = savitar_scene) savitar_scene.addSceneNode(savitar_node) parser = Savitar.ThreeMFParser() scene_string = parser.sceneToString(savitar_scene) diff --git a/plugins/3MFWriter/__init__.py b/plugins/3MFWriter/__init__.py index 980aefdf85..5d8a2e4d20 100644 --- a/plugins/3MFWriter/__init__.py +++ b/plugins/3MFWriter/__init__.py @@ -28,11 +28,17 @@ def getMetaData(): metaData["mesh_writer"] = { "output": [ { - "extension": "3mf", + "extension": workspace_extension, "description": i18n_catalog.i18nc("@item:inlistbox", "3MF file"), "mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml", "mode": ThreeMFWriter.ThreeMFWriter.OutputMode.BinaryMode }, + { + "extension": f"gcode.{workspace_extension}", + "description": i18n_catalog.i18nc("@item:inlistbox", "BambuLab 3MF file"), + "mime_type": "application/vnd.bambulab-package.3dmanufacturing-3dmodel+xml", + "mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode + } ] } metaData["workspace_writer"] = { @@ -44,7 +50,7 @@ def getMetaData(): "mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode }, { - "extension": "3mf", + "extension": workspace_extension, "description": i18n_catalog.i18nc("@item:inlistbox", "Universal Cura Project"), "mime_type": "application/x-ucp", "mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode diff --git a/plugins/CuraDrive/src/CreateBackupJob.py b/plugins/CuraDrive/src/CreateBackupJob.py index 7d772769ed..4820f886ab 100644 --- a/plugins/CuraDrive/src/CreateBackupJob.py +++ b/plugins/CuraDrive/src/CreateBackupJob.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Ultimaker B.V. +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. import json import threading @@ -13,11 +13,14 @@ from UM.Message import Message from UM.TaskManagement.HttpRequestManager import HttpRequestManager from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope from UM.i18n import i18nCatalog +from cura.ApplicationMetadata import CuraSDKVersion from cura.CuraApplication import CuraApplication from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope +import cura.UltimakerCloud.UltimakerCloudConstants as UltimakerCloudConstants catalog = i18nCatalog("cura") +PACKAGES_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}/cura/v{CuraSDKVersion}/packages" class CreateBackupJob(Job): """Creates backup zip, requests upload url and uploads the backup file to cloud storage.""" @@ -40,23 +43,54 @@ class CreateBackupJob(Job): self._job_done = threading.Event() """Set when the job completes. Does not indicate success.""" self.backup_upload_error_message = "" - """After the job completes, an empty string indicates success. Othrerwise, the value is a translated message.""" + """After the job completes, an empty string indicates success. Otherwise, the value is a translated message.""" + + def _setPluginFetchErrorMessage(self, error_msg: str) -> None: + Logger.error(f"Fetching plugins for backup resulted in error: {error_msg}") + self.backup_upload_error_message = "Couldn't update currently available plugins, backup stopped." + self._upload_message.hide() + self._job_done.set() def run(self) -> None: - upload_message = Message(catalog.i18nc("@info:backup_status", "Creating your backup..."), + self._upload_message = Message(catalog.i18nc("@info:backup_status", "Fetch re-downloadable package-ids..."), title = self.MESSAGE_TITLE, progress = -1) - upload_message.show() + self._upload_message.show() + CuraApplication.getInstance().processEvents() + + if CuraApplication.getInstance().getCuraAPI().backups.shouldReinstallDownloadablePlugins(): + request_url = f"{PACKAGES_URL}?package_type=plugin" + scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) + HttpRequestManager.getInstance().get( + request_url, + scope=scope, + callback=self._continueRun, + error_callback=lambda reply, error: self._setPluginFetchErrorMessage(str(error)), + ) + else: + self._continueRun() + + def _continueRun(self, reply: "QNetworkReply" = None) -> None: + if reply is not None: + response_data = HttpRequestManager.readJSON(reply) + if "data" not in response_data: + self._setPluginFetchErrorMessage(f"Missing 'data' from response. Keys in response: {response_data.keys()}") + return + available_remote_plugins = frozenset({v["package_id"] for v in response_data["data"]}) + else: + available_remote_plugins = frozenset() + + self._upload_message.setText(catalog.i18nc("@info:backup_status", "Creating your backup...")) CuraApplication.getInstance().processEvents() cura_api = CuraApplication.getInstance().getCuraAPI() - self._backup_zip, backup_meta_data = cura_api.backups.createBackup() + self._backup_zip, backup_meta_data = cura_api.backups.createBackup(available_remote_plugins) if not self._backup_zip or not backup_meta_data: self.backup_upload_error_message = catalog.i18nc("@info:backup_status", "There was an error while creating your backup.") - upload_message.hide() + self._upload_message.hide() return - upload_message.setText(catalog.i18nc("@info:backup_status", "Uploading your backup...")) + self._upload_message.setText(catalog.i18nc("@info:backup_status", "Uploading your backup...")) CuraApplication.getInstance().processEvents() # Create an upload entry for the backup. @@ -64,13 +98,18 @@ class CreateBackupJob(Job): backup_meta_data["description"] = "{}.backup.{}.cura.zip".format(timestamp, backup_meta_data["cura_release"]) self._requestUploadSlot(backup_meta_data, len(self._backup_zip)) - self._job_done.wait() + # Note: One 'process events' call wasn't enough with the changed situation somehow. + for _ in range(5000): + CuraApplication.getInstance().processEvents() + if self._job_done.wait(0.02): + break + if self.backup_upload_error_message == "": - upload_message.setText(catalog.i18nc("@info:backup_status", "Your backup has finished uploading.")) - upload_message.setProgress(None) # Hide progress bar + self._upload_message.setText(catalog.i18nc("@info:backup_status", "Your backup has finished uploading.")) + self._upload_message.setProgress(None) # Hide progress bar else: # some error occurred. This error is presented to the user by DrivePluginExtension - upload_message.hide() + self._upload_message.hide() def _requestUploadSlot(self, backup_metadata: Dict[str, Any], backup_size: int) -> None: """Request a backup upload slot from the API. @@ -83,7 +122,6 @@ class CreateBackupJob(Job): "metadata": backup_metadata } }).encode() - HttpRequestManager.getInstance().put( self._api_backup_url, data = payload, diff --git a/plugins/CuraDrive/src/RestoreBackupJob.py b/plugins/CuraDrive/src/RestoreBackupJob.py index 54c94b389e..503b39547a 100644 --- a/plugins/CuraDrive/src/RestoreBackupJob.py +++ b/plugins/CuraDrive/src/RestoreBackupJob.py @@ -1,8 +1,9 @@ -# Copyright (c) 2021 Ultimaker B.V. +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. - import base64 import hashlib +import json +import os import threading from tempfile import NamedTemporaryFile from typing import Optional, Any, Dict @@ -12,9 +13,16 @@ from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest from UM.Job import Job from UM.Logger import Logger from UM.PackageManager import catalog +from UM.Resources import Resources from UM.TaskManagement.HttpRequestManager import HttpRequestManager -from cura.CuraApplication import CuraApplication +from UM.Version import Version +from cura.ApplicationMetadata import CuraSDKVersion +from cura.CuraApplication import CuraApplication +from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope +import cura.UltimakerCloud.UltimakerCloudConstants as UltimakerCloudConstants + +PACKAGES_URL_TEMPLATE = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}/cura/v{{0}}/packages/{{1}}/download" class RestoreBackupJob(Job): """Downloads a backup and overwrites local configuration with the backup. @@ -38,7 +46,6 @@ class RestoreBackupJob(Job): self.restore_backup_error_message = "" def run(self) -> None: - url = self._backup.get("download_url") assert url is not None @@ -48,7 +55,11 @@ class RestoreBackupJob(Job): error_callback = self._onRestoreRequestCompleted ) - self._job_done.wait() # A job is considered finished when the run function completes + # Note: Just to be sure, use the same structure here as in CreateBackupJob. + for _ in range(5000): + CuraApplication.getInstance().processEvents() + if self._job_done.wait(0.02): + break def _onRestoreRequestCompleted(self, reply: QNetworkReply, error: Optional["QNetworkReply.NetworkError"] = None) -> None: if not HttpRequestManager.replyIndicatesSuccess(reply, error): @@ -60,8 +71,8 @@ class RestoreBackupJob(Job): # We store the file in a temporary path fist to ensure integrity. try: - temporary_backup_file = NamedTemporaryFile(delete = False) - with open(temporary_backup_file.name, "wb") as write_backup: + self._temporary_backup_file = NamedTemporaryFile(delete_on_close = False) + with open(self._temporary_backup_file.name, "wb") as write_backup: app = CuraApplication.getInstance() bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) while bytes_read: @@ -69,23 +80,98 @@ class RestoreBackupJob(Job): bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) app.processEvents() except EnvironmentError as e: - Logger.log("e", f"Unable to save backed up files due to computer limitations: {str(e)}") + Logger.error(f"Unable to save backed up files due to computer limitations: {str(e)}") self.restore_backup_error_message = self.DEFAULT_ERROR_MESSAGE self._job_done.set() return - if not self._verifyMd5Hash(temporary_backup_file.name, self._backup.get("md5_hash", "")): + if not self._verifyMd5Hash(self._temporary_backup_file.name, self._backup.get("md5_hash", "")): # Don't restore the backup if the MD5 hashes do not match. # This can happen if the download was interrupted. - Logger.log("w", "Remote and local MD5 hashes do not match, not restoring backup.") + Logger.error("Remote and local MD5 hashes do not match, not restoring backup.") self.restore_backup_error_message = self.DEFAULT_ERROR_MESSAGE + self._job_done.set() + return # Tell Cura to place the backup back in the user data folder. - with open(temporary_backup_file.name, "rb") as read_backup: + metadata = self._backup.get("metadata", {}) + with open(self._temporary_backup_file.name, "rb") as read_backup: cura_api = CuraApplication.getInstance().getCuraAPI() - cura_api.backups.restoreBackup(read_backup.read(), self._backup.get("metadata", {})) + cura_api.backups.restoreBackup(read_backup.read(), metadata, auto_close=False) - self._job_done.set() + # Read packages data-file, to get the 'to_install' plugin-ids. + version_to_restore = Version(metadata.get("cura_release", "dev")) + version_str = f"{version_to_restore.getMajor()}.{version_to_restore.getMinor()}" + packages_path = os.path.abspath(os.path.join(os.path.abspath( + Resources.getConfigStoragePath()), "..", version_str, "packages.json")) + if not os.path.exists(packages_path): + Logger.error(f"Can't find path '{packages_path}' to tell what packages should be redownloaded.") + self.restore_backup_error_message = self.DEFAULT_ERROR_MESSAGE + self._job_done.set() + return + + to_install = {} + try: + with open(packages_path, "r") as packages_file: + packages_json = json.load(packages_file) + if "to_install" in packages_json: + for package_data in packages_json["to_install"].values(): + if "package_info" not in package_data: + continue + package_info = package_data["package_info"] + if "package_id" in package_info and "sdk_version_semver" in package_info: + to_install[package_info["package_id"]] = package_info["sdk_version_semver"] + except IOError as ex: + Logger.error(f"Couldn't open '{packages_path}' because '{str(ex)}' to get packages to re-install.") + self.restore_backup_error_message = self.DEFAULT_ERROR_MESSAGE + self._job_done.set() + return + + if len(to_install) < 1: + Logger.info("No packages to reinstall, early out.") + self._job_done.set() + return + + # Download all re-installable plugins packages, so they can be put back on start-up. + redownload_errors = [] + def packageDownloadCallback(package_id: str, msg: "QNetworkReply", err: "QNetworkReply.NetworkError" = None) -> None: + if err is not None or HttpRequestManager.safeHttpStatus(msg) != 200: + redownload_errors.append(err) + del to_install[package_id] + + try: + with NamedTemporaryFile(mode="wb", suffix=".curapackage", delete=False) as temp_file: + bytes_read = msg.read(self.DISK_WRITE_BUFFER_SIZE) + while bytes_read: + temp_file.write(bytes_read) + bytes_read = msg.read(self.DISK_WRITE_BUFFER_SIZE) + CuraApplication.getInstance().processEvents() + temp_file.close() + if not CuraApplication.getInstance().getPackageManager().installPackage(temp_file.name): + redownload_errors.append(f"Couldn't install package '{package_id}'.") + except IOError as ex: + redownload_errors.append(f"Couldn't process package '{package_id}' because '{ex}'.") + + if len(to_install) < 1: + if len(redownload_errors) == 0: + Logger.info("All packages redownloaded!") + self._job_done.set() + else: + msgs = "\n - ".join(redownload_errors) + Logger.error(f"Couldn't re-install at least one package(s) because: {msgs}") + self.restore_backup_error_message = self.DEFAULT_ERROR_MESSAGE + self._job_done.set() + + self._package_download_scope = UltimakerCloudScope(CuraApplication.getInstance()) + for package_id, package_api_version in to_install.items(): + def handlePackageId(package_id: str = package_id): + HttpRequestManager.getInstance().get( + PACKAGES_URL_TEMPLATE.format(package_api_version, package_id), + scope=self._package_download_scope, + callback=lambda msg: packageDownloadCallback(package_id, msg), + error_callback=lambda msg, err: packageDownloadCallback(package_id, msg, err) + ) + handlePackageId(package_id) @staticmethod def _verifyMd5Hash(file_path: str, known_hash: str) -> bool: diff --git a/plugins/CuraEngineBackend/Cura.proto b/plugins/CuraEngineBackend/Cura.proto index 238829ba64..1636c56c20 100644 --- a/plugins/CuraEngineBackend/Cura.proto +++ b/plugins/CuraEngineBackend/Cura.proto @@ -53,6 +53,8 @@ message Object bytes indices = 4; //An array of ints. repeated Setting settings = 5; // Setting override per object, overruling the global settings. string name = 6; //Mesh name + bytes uv_coordinates = 7; //An array of 2 floats. + bytes texture = 8; //PNG-encoded texture data } message Progress @@ -78,10 +80,14 @@ message Polygon { SkirtType = 5; InfillType = 6; SupportInfillType = 7; - MoveCombingType = 8; - MoveRetractionType = 9; + MoveUnretracted = 8; + MoveRetracted = 9; SupportInterfaceType = 10; PrimeTowerType = 11; + MoveWhileRetracting = 12; + MoveWhileUnretracting = 13; + StationaryRetractUnretract = 14; + NumPrintFeatureTypes = 15; } Type type = 1; // Type of move bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index e3e15f5381..f8736d69b8 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -2,6 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. import argparse #To run the engine in debug mode if the front-end is in debug mode. +from cmath import isnan from collections import defaultdict import os from PyQt6.QtCore import QObject, QTimer, QUrl, pyqtSlot @@ -158,6 +159,7 @@ class CuraEngineBackend(QObject, Backend): self._backend_log_max_lines: int = 20000 # Maximum number of lines to buffer self._error_message: Optional[Message] = None # Pop-up message that shows errors. + self._unused_extruders: list[int] = [] # Extruder numbers of found unused extruders # Count number of objects to see if there is something changed self._last_num_objects: Dict[int, int] = defaultdict(int) @@ -960,12 +962,44 @@ class CuraEngineBackend(QObject, Backend): """ material_amounts = [] + self._unused_extruders = [] for index in range(message.repeatedMessageCount("materialEstimates")): - material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount) + material_use_for_tool = message.getRepeatedMessage("materialEstimates", index).material_amount + if isnan(material_use_for_tool): + material_amounts.append(0.0) + if self._global_container_stack.extruderList[int(index)].isEnabled: + self._unused_extruders.append(index) + else: + material_amounts.append(material_use_for_tool) + + if self._unused_extruders: + extruder_names = [self._global_container_stack.extruderList[int(idx)].definition.getName() for idx in self._unused_extruders] + unused_extruders = [f"
  • {extruder_name}
  • " for extruder_name in extruder_names] + warning_message = Message( + text=catalog.i18nc("@message", "At least one extruder remains unused in this print:" + f"
    This can sometimes become a problem, " + "for example when the bed temperature is adjusted for the material present in the unused extruder. " + "It might be desirable to disable these unused extruders."), + title=catalog.i18nc("@message:title", "Unused Extruder(s)"), + message_type=Message.MessageType.WARNING + ) + warning_message.addAction("disable_extruders", + name=catalog.i18nc("@button", "Disable unused extruder(s)"), + icon="", + description=catalog.i18nc("@label", "Automatically disable the unused extruder(s)") + ) + warning_message.actionTriggered.connect(self._onMessageActionTriggered) + warning_message.show() times = self._parseMessagePrintTimes(message) self.printDurationMessage.emit(self._start_slice_job_build_plate, times, material_amounts) + def _onMessageActionTriggered(self, message: Message, message_action: str) -> None: + if message_action == "disable_extruders": + message.hide() + for unused_extruder in self._unused_extruders: + CuraApplication.getInstance().getMachineManager().setExtruderEnabled(unused_extruder, False) + def _parseMessagePrintTimes(self, message: Arcus.PythonMessage) -> Dict[str, float]: """Called for parsing message to retrieve estimated time per feature diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index b276469d09..8b27a0319a 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -509,6 +509,14 @@ class StartSliceJob(Job): obj.vertices = flat_verts + uv_coordinates = mesh_data.getUVCoordinates() + if uv_coordinates is not None: + obj.uv_coordinates = uv_coordinates.flatten() + + packed_texture = object.callDecoration("packTexture") + if packed_texture is not None: + obj.texture = packed_texture + self._handlePerObjectSettings(cast(CuraSceneNode, object), obj) Job.yieldThread() diff --git a/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml b/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml index ca836ee21d..931a4fe9f0 100644 --- a/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml +++ b/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml @@ -11,10 +11,10 @@ Cura.RoundedRectangle width: parent.width height: projectImage.height + 2 * UM.Theme.getSize("default_margin").width cornerSide: Cura.RoundedRectangle.Direction.All - border.color: UM.Theme.getColor("lining") + border.color: enabled ? UM.Theme.getColor("lining") : UM.Theme.getColor("action_button_disabled_border") border.width: UM.Theme.getSize("default_lining").width radius: UM.Theme.getSize("default_radius").width - color: UM.Theme.getColor("main_background") + color: getBackgroundColor() signal clicked() property alias imageSource: projectImage.source property alias projectNameText: displayNameLabel.text @@ -22,17 +22,18 @@ Cura.RoundedRectangle property alias projectLastUpdatedText: lastUpdatedLabel.text property alias cardMouseAreaEnabled: cardMouseArea.enabled - onVisibleChanged: color = UM.Theme.getColor("main_background") + onVisibleChanged: color = getBackgroundColor() MouseArea { id: cardMouseArea anchors.fill: parent - hoverEnabled: true - onEntered: base.color = UM.Theme.getColor("action_button_hovered") - onExited: base.color = UM.Theme.getColor("main_background") + hoverEnabled: base.enabled + onEntered: color = getBackgroundColor() + onExited: color = getBackgroundColor() onClicked: base.clicked() } + Row { id: projectInformationRow @@ -73,7 +74,7 @@ Cura.RoundedRectangle width: parent.width height: Math.round(parent.height / 3) elide: Text.ElideRight - color: UM.Theme.getColor("small_button_text") + color: base.enabled ? UM.Theme.getColor("small_button_text") : UM.Theme.getColor("text_disabled") } UM.Label @@ -82,8 +83,27 @@ Cura.RoundedRectangle width: parent.width height: Math.round(parent.height / 3) elide: Text.ElideRight - color: UM.Theme.getColor("small_button_text") + color: base.enabled ? UM.Theme.getColor("small_button_text") : UM.Theme.getColor("text_disabled") } } } -} \ No newline at end of file + + function getBackgroundColor() + { + if(enabled) + { + if(cardMouseArea.containsMouse) + { + return UM.Theme.getColor("action_button_hovered") + } + else + { + return UM.Theme.getColor("main_background") + } + } + else + { + return UM.Theme.getColor("action_button_disabled") + } + } +} diff --git a/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml b/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml index faceb4df23..2d0bd30f2b 100644 --- a/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml +++ b/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml @@ -159,17 +159,30 @@ Item Repeater { model: manager.digitalFactoryProjectModel - delegate: ProjectSummaryCard + delegate: Item { - id: projectSummaryCard - imageSource: model.thumbnailUrl || "../images/placeholder.svg" - projectNameText: model.displayName - projectUsernameText: model.username - projectLastUpdatedText: "Last updated: " + model.lastUpdated + width: parent.width + height: projectSummaryCard.height - onClicked: + UM.TooltipArea { - manager.selectedProjectIndex = index + anchors.fill: parent + text: "This project is inactive and cannot be used." + enabled: !model.active + } + + ProjectSummaryCard + { + id: projectSummaryCard + imageSource: model.thumbnailUrl || "../images/placeholder.svg" + projectNameText: model.displayName + projectUsernameText: model.username + projectLastUpdatedText: "Last updated: " + model.lastUpdated + enabled: model.active + + onClicked: { + manager.selectedProjectIndex = index + } } } } diff --git a/plugins/DigitalLibrary/src/DigitalFactoryProjectModel.py b/plugins/DigitalLibrary/src/DigitalFactoryProjectModel.py index bd12a4ca12..7140657508 100644 --- a/plugins/DigitalLibrary/src/DigitalFactoryProjectModel.py +++ b/plugins/DigitalLibrary/src/DigitalFactoryProjectModel.py @@ -17,6 +17,7 @@ class DigitalFactoryProjectModel(ListModel): ThumbnailUrlRole = Qt.ItemDataRole.UserRole + 5 UsernameRole = Qt.ItemDataRole.UserRole + 6 LastUpdatedRole = Qt.ItemDataRole.UserRole + 7 + ActiveRole = Qt.ItemDataRole.UserRole + 8 dfProjectModelChanged = pyqtSignal() @@ -28,6 +29,7 @@ class DigitalFactoryProjectModel(ListModel): self.addRoleName(self.ThumbnailUrlRole, "thumbnailUrl") self.addRoleName(self.UsernameRole, "username") self.addRoleName(self.LastUpdatedRole, "lastUpdated") + self.addRoleName(self.ActiveRole, "active") self._projects = [] # type: List[DigitalFactoryProjectResponse] def setProjects(self, df_projects: List[DigitalFactoryProjectResponse]) -> None: @@ -59,5 +61,6 @@ class DigitalFactoryProjectModel(ListModel): "thumbnailUrl": project.thumbnail_url, "username": project.username, "lastUpdated": project.last_updated.strftime(PROJECT_UPDATED_AT_DATETIME_FORMAT) if project.last_updated else "", + "active": project.active, }) self.dfProjectModelChanged.emit() diff --git a/plugins/DigitalLibrary/src/DigitalFactoryProjectResponse.py b/plugins/DigitalLibrary/src/DigitalFactoryProjectResponse.py index bef90e5125..303271f211 100644 --- a/plugins/DigitalLibrary/src/DigitalFactoryProjectResponse.py +++ b/plugins/DigitalLibrary/src/DigitalFactoryProjectResponse.py @@ -28,6 +28,7 @@ class DigitalFactoryProjectResponse(BaseModel): team_ids: Optional[List[str]] = None, status: Optional[str] = None, technical_requirements: Optional[Dict[str, Any]] = None, + is_inactive: bool = False, **kwargs) -> None: """ Creates a new digital factory project response object @@ -56,6 +57,7 @@ class DigitalFactoryProjectResponse(BaseModel): self.last_updated = datetime.strptime(last_updated, DIGITAL_FACTORY_RESPONSE_DATETIME_FORMAT) if last_updated else None self.status = status self.technical_requirements = technical_requirements + self.active = not is_inactive super().__init__(**kwargs) def __str__(self) -> str: diff --git a/plugins/GCodeGzWriter/GCodeGzWriter.py b/plugins/GCodeGzWriter/GCodeGzWriter.py index 2bbaaeb0a3..cb5b66fbdc 100644 --- a/plugins/GCodeGzWriter/GCodeGzWriter.py +++ b/plugins/GCodeGzWriter/GCodeGzWriter.py @@ -24,7 +24,7 @@ class GCodeGzWriter(MeshWriter): def __init__(self) -> None: super().__init__(add_to_recent_files = False) - def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode = MeshWriter.OutputMode.BinaryMode) -> bool: + def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode = MeshWriter.OutputMode.BinaryMode, **kwargs) -> bool: """Writes the gzipped g-code to a stream. Note that even though the function accepts a collection of nodes, the diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py index 74dbeadec0..f83a9bbb34 100644 --- a/plugins/GCodeReader/FlavorParser.py +++ b/plugins/GCodeReader/FlavorParser.py @@ -133,7 +133,10 @@ class FlavorParser: if i > 0: line_feedrates[i - 1] = point[3] line_types[i - 1] = point[5] - if point[5] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]: + if point[5] in [LayerPolygon.MoveUnretractedType, + LayerPolygon.MoveRetractedType, + LayerPolygon.MoveWhileRetractingType, + LayerPolygon.MoveWhileUnretractingType]: line_widths[i - 1] = 0.1 line_thicknesses[i - 1] = 0.0 # Travels are set as zero thickness lines else: @@ -196,7 +199,7 @@ class FlavorParser: path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion self._previous_extrusion_value = new_extrusion_value else: - path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction + path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractedType]) # retraction e[self._extruder_number] = new_extrusion_value # Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions @@ -205,9 +208,9 @@ class FlavorParser: self._current_layer_thickness = z - self._previous_z # allow a tiny overlap self._previous_z = z elif self._previous_extrusion_value > e[self._extruder_number]: - path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) + path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractedType]) else: - path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveCombingType]) + path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveUnretractedType]) return self._position(x, y, z, f, e) @@ -419,7 +422,7 @@ class FlavorParser: self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) current_path.clear() # Start the new layer at the end position of the last layer - current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType]) + current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveUnretractedType]) # When using a raft, the raft layers are stored as layers < 0, it mimics the same behavior # as in ProcessSlicedLayersJob @@ -461,9 +464,9 @@ class FlavorParser: # When changing tool, store the end point of the previous path, then process the code and finally # add another point with the new position of the head. - current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType]) + current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveUnretractedType]) current_position = self.processTCode(global_stack, T, line, current_position, current_path) - current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType]) + current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveUnretractedType]) if line.startswith("M"): M = self._getInt(line, "M") diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index 9fa4f88614..0eac653b56 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -56,7 +56,7 @@ class GCodeWriter(MeshWriter): self._application = Application.getInstance() - def write(self, stream, nodes, mode = MeshWriter.OutputMode.TextMode): + def write(self, stream, nodes, mode = MeshWriter.OutputMode.TextMode, **kwargs): """Writes the g-code for the entire scene to a stream. Note that even though the function accepts a collection of nodes, the diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index e4c2d2cbd1..30f37fe3fa 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -91,7 +91,7 @@ class MakerbotWriter(MeshWriter): return None - def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode=MeshWriter.OutputMode.BinaryMode) -> bool: + def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode=MeshWriter.OutputMode.BinaryMode, **kwargs) -> bool: metadata, file_format = self._getMeta(nodes) if mode != MeshWriter.OutputMode.BinaryMode: Logger.log("e", "MakerbotWriter does not support text mode.") diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 86910f8f4a..fc287b5877 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -21,7 +21,6 @@ class Marketplace(Extension, QObject): def __init__(self, parent: Optional[QObject] = None) -> None: QObject.__init__(self, parent) Extension.__init__(self) - self._window: Optional["QObject"] = None # If the window has been loaded yet, it'll be cached in here. self._package_manager = CuraApplication.getInstance().getPackageManager() self._material_package_list: Optional[RemotePackageList] = None @@ -79,20 +78,17 @@ class Marketplace(Extension, QObject): If the window hadn't been loaded yet into Qt, it will be created lazily. """ - if self._window is None: - plugin_registry = PluginRegistry.getInstance() - plugin_registry.pluginsEnabledOrDisabledChanged.connect(self.checkIfRestartNeeded) - plugin_path = plugin_registry.getPluginPath(self.getPluginId()) - if plugin_path is None: - plugin_path = os.path.dirname(__file__) - path = os.path.join(plugin_path, "resources", "qml", "Marketplace.qml") - self._window = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) - if self._window is None: # Still None? Failed to load the QML then. - return - if not self._window.isVisible(): - self.setTabShown(0) - self._window.show() - self._window.requestActivate() # Bring window into focus, if it was already open in the background. + + plugin_registry = PluginRegistry.getInstance() + plugin_registry.pluginsEnabledOrDisabledChanged.connect(self.checkIfRestartNeeded) + plugin_path = plugin_registry.getPluginPath(self.getPluginId()) + if plugin_path is None: + plugin_path = os.path.dirname(__file__) + path = os.path.join(plugin_path, "resources", "qml", "Marketplace.qml") + window = CuraApplication.getInstance().createQmlSubWindow(path, {"manager": self}) + + if window is not None: # Still None? Failed to load the QML then. + window.show() @pyqtSlot() def setVisibleTabToMaterials(self) -> None: @@ -103,9 +99,6 @@ class Marketplace(Extension, QObject): self.setTabShown(1) def checkIfRestartNeeded(self) -> None: - if self._window is None: - return - if self._package_manager.hasPackagesToRemoveOrInstall or \ PluginRegistry.getInstance().getCurrentSessionActivationChangedPlugins(): self._restart_needed = True diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 8028b89e02..c858297ac9 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -9,7 +9,7 @@ import QtQuick.Window 2.2 import UM 1.5 as UM import Cura 1.6 as Cura -Window +UM.Dialog { id: marketplaceDialog property variant catalog: UM.I18nCatalog { name: "cura" } @@ -25,293 +25,289 @@ Window width: minimumWidth height: minimumHeight - onVisibleChanged: - { - while(contextStack.depth > 1) - { - contextStack.pop(); //Do NOT use the StackView.Immediate transition here, since it causes the window to stay empty. Seemingly a Qt bug: https://bugreports.qt.io/browse/QTBUG-60670? - } - } - - Connections - { - target: Cura.API.account - function onLoginStateChanged() - { - close(); - } - } - title: "Marketplace" //Seen by Ultimaker as a brand name, so this doesn't get translated. - modality: Qt.NonModal // Background color Rectangle { anchors.fill: parent color: UM.Theme.getColor("main_background") - } - //The Marketplace can have a page in front of everything with package details. The stack view controls its visibility. - StackView - { - id: contextStack - anchors.fill: parent - initialItem: packageBrowse - - ColumnLayout + //The Marketplace can have a page in front of everything with package details. The stack view controls its visibility. + StackView { - id: packageBrowse + id: contextStack + anchors.fill: parent - spacing: UM.Theme.getSize("narrow_margin").height + initialItem: packageBrowse - // Page title. - Item + ColumnLayout { - Layout.preferredWidth: parent.width - Layout.preferredHeight: childrenRect.height + UM.Theme.getSize("default_margin").height + id: packageBrowse - UM.Label + spacing: UM.Theme.getSize("narrow_margin").height + + // Page title. + Item { - id: pageTitle - anchors - { - left: parent.left - leftMargin: UM.Theme.getSize("default_margin").width - right: parent.right - rightMargin: UM.Theme.getSize("default_margin").width - bottom: parent.bottom - } + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height + UM.Theme.getSize("default_margin").height - font: UM.Theme.getFont("large") - text: content.item ? content.item.pageTitle: catalog.i18nc("@title", "Loading...") + UM.Label + { + id: pageTitle + anchors + { + left: parent.left + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: UM.Theme.getSize("default_margin").width + bottom: parent.bottom + } + + font: UM.Theme.getFont("large") + text: content.item ? content.item.pageTitle : catalog.i18nc("@title", "Loading...") + } } - } - OnboardBanner - { - id: onBoardBanner - visible: content.item && content.item.bannerVisible - text: content.item && content.item.bannerText - icon: content.item && content.item.bannerIcon - onRemove: content.item && content.item.onRemoveBanner - readMoreUrl: content.item && content.item.bannerReadMoreUrl - - Layout.fillWidth: true - Layout.leftMargin: UM.Theme.getSize("default_margin").width - Layout.rightMargin: UM.Theme.getSize("default_margin").width - } - - // Search & Top-Level Tabs - Item - { - id: searchHeader - implicitHeight: childrenRect.height - implicitWidth: parent.width - 2 * UM.Theme.getSize("default_margin").width - Layout.alignment: Qt.AlignHCenter - RowLayout + OnboardBanner { - width: parent.width - height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("default_margin").height - spacing: UM.Theme.getSize("thin_margin").width + id: onBoardBanner + visible: content.item && content.item.bannerVisible + text: content.item && content.item.bannerText + icon: content.item && content.item.bannerIcon + onRemove: content.item && content.item.onRemoveBanner + readMoreUrl: content.item && content.item.bannerReadMoreUrl - Cura.SearchBar + Layout.fillWidth: true + Layout.leftMargin: UM.Theme.getSize("default_margin").width + Layout.rightMargin: UM.Theme.getSize("default_margin").width + } + + // Search & Top-Level Tabs + Item + { + id: searchHeader + implicitHeight: childrenRect.height + implicitWidth: parent.width - 2 * UM.Theme.getSize("default_margin").width + Layout.alignment: Qt.AlignHCenter + RowLayout { - id: searchBar - implicitHeight: UM.Theme.getSize("button_icon").height - Layout.fillWidth: true - onTextEdited: searchStringChanged(text) - } + width: parent.width + height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("default_margin").height + spacing: UM.Theme.getSize("thin_margin").width - // Page selection. - TabBar - { - id: pageSelectionTabBar - Layout.alignment: Qt.AlignRight - height: UM.Theme.getSize("button_icon").height - spacing: 0 - background: Rectangle { color: "transparent" } - currentIndex: manager.tabShown - - onCurrentIndexChanged: + Cura.SearchBar { - manager.tabShown = currentIndex - searchBar.text = ""; - searchBar.visible = currentItem.hasSearch; - content.source = currentItem.sourcePage; + id: searchBar + implicitHeight: UM.Theme.getSize("button_icon").height + Layout.fillWidth: true + onTextEdited: searchStringChanged(text) } - PackageTypeTab + // Page selection. + TabBar { - id: pluginTabText - width: implicitWidth - text: catalog.i18nc("@button", "Plugins") - property string sourcePage: "Plugins.qml" - property bool hasSearch: true - } - PackageTypeTab - { - id: materialsTabText - width: implicitWidth - text: catalog.i18nc("@button", "Materials") - property string sourcePage: "Materials.qml" - property bool hasSearch: true - } - ManagePackagesButton - { - property string sourcePage: "ManagedPackages.qml" - property bool hasSearch: false + id: pageSelectionTabBar + Layout.alignment: Qt.AlignRight + height: UM.Theme.getSize("button_icon").height + spacing: 0 + background: Rectangle { + color: "transparent" + } + currentIndex: manager.tabShown - Cura.NotificationIcon + onCurrentIndexChanged: { - anchors - { - horizontalCenter: parent.right - verticalCenter: parent.top - } - visible: CuraApplication.getPackageManager().packagesWithUpdate.length > 0 + manager.tabShown = currentIndex + searchBar.text = ""; + searchBar.visible = currentItem.hasSearch; + content.source = currentItem.sourcePage; + } - labelText: + PackageTypeTab + { + id: pluginTabText + width: implicitWidth + text: catalog.i18nc("@button", "Plugins") + property string sourcePage: "Plugins.qml" + property bool hasSearch: true + } + PackageTypeTab + { + id: materialsTabText + width: implicitWidth + text: catalog.i18nc("@button", "Materials") + property string sourcePage: "Materials.qml" + property bool hasSearch: true + } + ManagePackagesButton + { + property string sourcePage: "ManagedPackages.qml" + property bool hasSearch: false + + Cura.NotificationIcon { - const itemCount = CuraApplication.getPackageManager().packagesWithUpdate.length - return itemCount > 9 ? "9+" : itemCount + anchors + { + horizontalCenter: parent.right + verticalCenter: parent.top + } + visible: CuraApplication.getPackageManager().packagesWithUpdate.length > 0 + + labelText: + { + const itemCount = CuraApplication.getPackageManager().packagesWithUpdate.length + return itemCount > 9 ? "9+" : itemCount + } } } } } } - } - FontMetrics - { - id: fontMetrics - font: UM.Theme.getFont("default") - } + FontMetrics + { + id: fontMetrics + font: UM.Theme.getFont("default") + } - Cura.TertiaryButton - { - text: catalog.i18nc("@info", "Search in the browser") - iconSource: UM.Theme.getIcon("LinkExternal") - visible: pageSelectionTabBar.currentItem.hasSearch && searchHeader.visible - isIconOnRightSide: true - height: fontMetrics.height - textFont: fontMetrics.font - textColor: UM.Theme.getColor("text") + Cura.TertiaryButton + { + text: catalog.i18nc("@info", "Search in the browser") + iconSource: UM.Theme.getIcon("LinkExternal") + visible: pageSelectionTabBar.currentItem.hasSearch && searchHeader.visible + isIconOnRightSide: true + height: fontMetrics.height + textFont: fontMetrics.font + textColor: UM.Theme.getColor("text") - onClicked: content.item && Qt.openUrlExternally(content.item.searchInBrowserUrl) - } - - // Page contents. - Rectangle - { - Layout.preferredWidth: parent.width - Layout.fillHeight: true - color: UM.Theme.getColor("detail_background") + onClicked: content.item && Qt.openUrlExternally(content.item.searchInBrowserUrl) + } // Page contents. - Loader + Rectangle { - id: content - anchors.fill: parent - anchors.margins: UM.Theme.getSize("default_margin").width - source: "Plugins.qml" + Layout.preferredWidth: parent.width + Layout.fillHeight: true + color: UM.Theme.getColor("detail_background") - Connections + // Page contents. + Loader { - target: content - function onLoaded() + id: content + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width + source: "Plugins.qml" + + Connections { - pageTitle.text = content.item.pageTitle - searchStringChanged.connect(handleSearchStringChanged) - } - function handleSearchStringChanged(new_search) - { - content.item.model.searchString = new_search + target: content + + function onLoaded() + { + pageTitle.text = content.item.pageTitle + searchStringChanged.connect(handleSearchStringChanged) + } + + function handleSearchStringChanged(new_search) + { + content.item.model.searchString = new_search + } } } } } } - } - Rectangle - { - height: quitButton.height + 2 * UM.Theme.getSize("default_margin").width - color: UM.Theme.getColor("primary") - visible: manager.showRestartNotification - anchors - { - left: parent.left - right: parent.right - bottom: parent.bottom - } - - RowLayout + Rectangle { + height: quitButton.height + 2 * UM.Theme.getSize("default_margin").width + color: UM.Theme.getColor("primary") + visible: manager.showRestartNotification anchors { left: parent.left right: parent.right - verticalCenter: parent.verticalCenter - margins: UM.Theme.getSize("default_margin").width + bottom: parent.bottom } - spacing: UM.Theme.getSize("default_margin").width - UM.ColorImage - { - id: bannerIcon - source: UM.Theme.getIcon("Plugin") - color: UM.Theme.getColor("primary_button_text") - implicitWidth: UM.Theme.getSize("banner_icon_size").width - implicitHeight: UM.Theme.getSize("banner_icon_size").height - } - Text + RowLayout { - color: UM.Theme.getColor("primary_button_text") - text: catalog.i18nc("@button", "In order to use the package you will need to restart Cura") - font: UM.Theme.getFont("default") - renderType: Text.NativeRendering - Layout.fillWidth: true - } - Cura.SecondaryButton - { - id: quitButton - text: catalog.i18nc("@info:button, %1 is the application name", "Quit %1").arg(CuraApplication.applicationDisplayName) - onClicked: + anchors { - marketplaceDialog.hide(); - CuraApplication.checkAndExitApplication(); + left: parent.left + right: parent.right + verticalCenter: parent.verticalCenter + margins: UM.Theme.getSize("default_margin").width + } + spacing: UM.Theme.getSize("default_margin").width + UM.ColorImage + { + id: bannerIcon + source: UM.Theme.getIcon("Plugin") + + color: UM.Theme.getColor("primary_button_text") + implicitWidth: UM.Theme.getSize("banner_icon_size").width + implicitHeight: UM.Theme.getSize("banner_icon_size").height + } + Text + { + color: UM.Theme.getColor("primary_button_text") + text: catalog.i18nc("@button", "In order to use the package you will need to restart Cura") + font: UM.Theme.getFont("default") + renderType: Text.NativeRendering + Layout.fillWidth: true + } + Cura.SecondaryButton + { + id: quitButton + text: catalog.i18nc("@info:button, %1 is the application name", "Quit %1").arg(CuraApplication.applicationDisplayName) + onClicked: + { + marketplaceDialog.hide(); + CuraApplication.checkAndExitApplication(); + } } } } - } - Rectangle - { - color: UM.Theme.getColor("main_background") - anchors.fill: parent - visible: !Cura.API.account.isLoggedIn && CuraApplication.isEnterprise - - UM.Label + Rectangle { - id: signInLabel - anchors.centerIn: parent - width: Math.round(UM.Theme.getSize("modal_window_minimum").width / 2.5) - text: catalog.i18nc("@description","Please sign in to get verified plugins and materials for UltiMaker Cura Enterprise") - horizontalAlignment: Text.AlignHCenter + color: UM.Theme.getColor("main_background") + anchors.fill: parent + visible: !Cura.API.account.isLoggedIn && CuraApplication.isEnterprise + + UM.Label + { + id: signInLabel + anchors.centerIn: parent + width: Math.round(UM.Theme.getSize("modal_window_minimum").width / 2.5) + text: catalog.i18nc("@description", "Please sign in to get verified plugins and materials for UltiMaker Cura Enterprise") + horizontalAlignment: Text.AlignHCenter + } + + Cura.PrimaryButton + { + id: loginButton + width: UM.Theme.getSize("account_button").width + height: UM.Theme.getSize("account_button").height + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: signInLabel.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height * 2 + text: catalog.i18nc("@button", "Sign in") + fixedWidthMode: true + onClicked: Cura.API.account.login() + } } - Cura.PrimaryButton + Connections { - id: loginButton - width: UM.Theme.getSize("account_button").width - height: UM.Theme.getSize("account_button").height - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: signInLabel.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height * 2 - text: catalog.i18nc("@button", "Sign in") - fixedWidthMode: true - onClicked: Cura.API.account.login() + target: Cura.API.account + function onLoginStateChanged() + { + reject(); + } } } } diff --git a/plugins/PaintTool/BrushColorButton.qml b/plugins/PaintTool/BrushColorButton.qml new file mode 100644 index 0000000000..b62ab09e92 --- /dev/null +++ b/plugins/PaintTool/BrushColorButton.qml @@ -0,0 +1,18 @@ +// Copyright (c) 2025 UltiMaker +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick + +import UM 1.7 as UM +import Cura 1.0 as Cura + + +UM.ToolbarButton +{ + id: buttonBrushColor + + property string color + + checked: UM.Controller.properties.getValue("BrushColor") === buttonBrushColor.color + onClicked: UM.Controller.setProperty("BrushColor", buttonBrushColor.color) +} diff --git a/plugins/PaintTool/BrushShapeButton.qml b/plugins/PaintTool/BrushShapeButton.qml new file mode 100644 index 0000000000..e05cd206f3 --- /dev/null +++ b/plugins/PaintTool/BrushShapeButton.qml @@ -0,0 +1,18 @@ +// Copyright (c) 2025 UltiMaker +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick + +import UM 1.7 as UM +import Cura 1.0 as Cura + + +UM.ToolbarButton +{ + id: buttonBrushShape + + property int shape + + checked: UM.Controller.properties.getValue("BrushShape") === buttonBrushShape.shape + onClicked: UM.Controller.setProperty("BrushShape", buttonBrushShape.shape) +} diff --git a/plugins/PaintTool/PaintModeButton.qml b/plugins/PaintTool/PaintModeButton.qml new file mode 100644 index 0000000000..833a009551 --- /dev/null +++ b/plugins/PaintTool/PaintModeButton.qml @@ -0,0 +1,18 @@ +// Copyright (c) 2025 UltiMaker +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick + +import UM 1.7 as UM +import Cura 1.0 as Cura + + +Cura.ModeSelectorButton +{ + id: modeSelectorButton + + property string mode + + selected: UM.Controller.properties.getValue("PaintType") === modeSelectorButton.mode + onClicked: UM.Controller.setProperty("PaintType", modeSelectorButton.mode) +} diff --git a/plugins/PaintTool/PaintTool.py b/plugins/PaintTool/PaintTool.py new file mode 100644 index 0000000000..e67795301d --- /dev/null +++ b/plugins/PaintTool/PaintTool.py @@ -0,0 +1,424 @@ +# Copyright (c) 2025 UltiMaker +# Cura is released under the terms of the LGPLv3 or higher. + +from enum import IntEnum +import numpy +from PyQt6.QtCore import Qt, QObject, pyqtEnum +from PyQt6.QtGui import QImage, QPainter, QColor, QPen +from PyQt6 import QtWidgets +from typing import cast, Dict, List, Optional, Tuple + +from numpy import ndarray + +from UM.Application import Application +from UM.Event import Event, MouseEvent, KeyEvent +from UM.Job import Job +from UM.Logger import Logger +from UM.Scene.SceneNode import SceneNode +from UM.Scene.Selection import Selection +from UM.Tool import Tool +from UM.View.GL.OpenGL import OpenGL + +from cura.CuraApplication import CuraApplication +from cura.PickingPass import PickingPass +from UM.View.SelectionPass import SelectionPass +from .PaintView import PaintView +from .PrepareTextureJob import PrepareTextureJob + + +class PaintTool(Tool): + """Provides the tool to paint meshes.""" + + class Brush(QObject): + @pyqtEnum + class Shape(IntEnum): + SQUARE = 0 + CIRCLE = 1 + + class Paint(QObject): + @pyqtEnum + class State(IntEnum): + MULTIPLE_SELECTION = 0 # Multiple objects are selected, wait until there is only one + PREPARING_MODEL = 1 # Model is being prepared (UV-unwrapping, texture generation) + READY = 2 # Ready to paint ! + + def __init__(self, view: PaintView) -> None: + super().__init__() + + self._view: PaintView = view + self._view.canUndoChanged.connect(self._onCanUndoChanged) + self._view.canRedoChanged.connect(self._onCanRedoChanged) + + self._picking_pass: Optional[PickingPass] = None + self._faces_selection_pass: Optional[SelectionPass] = None + + self._shortcut_key: Qt.Key = Qt.Key.Key_P + + self._node_cache: Optional[SceneNode] = None + self._mesh_transformed_cache = None + self._cache_dirty: bool = True + + self._brush_size: int = 200 + self._brush_color: str = "preferred" + self._brush_shape: PaintTool.Brush.Shape = PaintTool.Brush.Shape.CIRCLE + self._brush_pen: QPen = self._createBrushPen() + + self._mouse_held: bool = False + + self._last_text_coords: Optional[numpy.ndarray] = None + self._last_mouse_coords: Optional[Tuple[int, int]] = None + self._last_face_id: Optional[int] = None + + self._state: PaintTool.Paint.State = PaintTool.Paint.State.MULTIPLE_SELECTION + self._prepare_texture_job: Optional[PrepareTextureJob] = None + + self.setExposedProperties("PaintType", "BrushSize", "BrushColor", "BrushShape", "State", "CanUndo", "CanRedo") + + self._controller.activeViewChanged.connect(self._updateIgnoreUnselectedObjects) + self._controller.activeToolChanged.connect(self._updateState) + + def _createBrushPen(self) -> QPen: + pen = QPen() + pen.setWidth(self._brush_size) + pen.setColor(Qt.GlobalColor.white) + + match self._brush_shape: + case PaintTool.Brush.Shape.SQUARE: + pen.setCapStyle(Qt.PenCapStyle.SquareCap) + case PaintTool.Brush.Shape.CIRCLE: + pen.setCapStyle(Qt.PenCapStyle.RoundCap) + return pen + + def _createStrokeImage(self, x0: float, y0: float, x1: float, y1: float) -> Tuple[QImage, Tuple[int, int]]: + xdiff = int(x1 - x0) + ydiff = int(y1 - y0) + + half_brush_size = self._brush_size // 2 + start_x = int(min(x0, x1) - half_brush_size) + start_y = int(min(y0, y1) - half_brush_size) + + stroke_image = QImage(abs(xdiff) + self._brush_size, abs(ydiff) + self._brush_size, QImage.Format.Format_RGB32) + stroke_image.fill(0) + + painter = QPainter(stroke_image) + painter.setRenderHint(QPainter.RenderHint.Antialiasing, False) + painter.setPen(self._brush_pen) + if xdiff == 0 and ydiff == 0: + painter.drawPoint(int(x0 - start_x), int(y0 - start_y)) + else: + painter.drawLine(int(x0 - start_x), int(y0 - start_y), int(x1 - start_x), int(y1 - start_y)) + painter.end() + + return stroke_image, (start_x, start_y) + + def getPaintType(self) -> str: + return self._view.getPaintType() + + def setPaintType(self, paint_type: str) -> None: + if paint_type != self.getPaintType(): + self._view.setPaintType(paint_type) + + self._brush_pen = self._createBrushPen() + self._updateScene() + self.propertyChanged.emit() + + def getBrushSize(self) -> int: + return self._brush_size + + def setBrushSize(self, brush_size: float) -> None: + brush_size_int = int(brush_size) + if brush_size_int != self._brush_size: + self._brush_size = brush_size_int + self._brush_pen = self._createBrushPen() + self.propertyChanged.emit() + + def getBrushColor(self) -> str: + return self._brush_color + + def setBrushColor(self, brush_color: str) -> None: + if brush_color != self._brush_color: + self._brush_color = brush_color + self.propertyChanged.emit() + + def getBrushShape(self) -> int: + return self._brush_shape + + def setBrushShape(self, brush_shape: int) -> None: + if brush_shape != self._brush_shape: + self._brush_shape = brush_shape + self._brush_pen = self._createBrushPen() + self.propertyChanged.emit() + + def getCanUndo(self) -> bool: + return self._view.canUndo() + + def getState(self) -> int: + return self._state + + def _onCanUndoChanged(self): + self.propertyChanged.emit() + + def getCanRedo(self) -> bool: + return self._view.canRedo() + + def _onCanRedoChanged(self): + self.propertyChanged.emit() + + def undoStackAction(self) -> None: + self._view.undoStroke() + self._updateScene() + + def redoStackAction(self) -> None: + self._view.redoStroke() + self._updateScene() + + def clear(self) -> None: + width, height = self._view.getUvTexDimensions() + clear_image = QImage(width, height, QImage.Format.Format_RGB32) + clear_image.fill(Qt.GlobalColor.white) + self._view.addStroke(clear_image, 0, 0, "none", False) + + self._updateScene() + + @staticmethod + def _get_intersect_ratio_via_pt(a: numpy.ndarray, pt: numpy.ndarray, b: numpy.ndarray, c: numpy.ndarray) -> float: + # compute the intersection of (param) A - pt with (param) B - (param) C + if all(a == pt) or all(b == c) or all(a == c) or all(a == b): + return 1.0 + + # compute unit vectors of directions of lines A and B + udir_a = a - pt + udir_a /= numpy.linalg.norm(udir_a) + udir_b = b - c + udir_b /= numpy.linalg.norm(udir_b) + + # find unit direction vector for line C, which is perpendicular to lines A and B + udir_res = numpy.cross(udir_b, udir_a) + udir_res_len = numpy.linalg.norm(udir_res) + if udir_res_len == 0: + return 1.0 + udir_res /= udir_res_len + + # solve system of equations + rhs = b - a + lhs = numpy.array([udir_a, -udir_b, udir_res]).T + try: + solved = numpy.linalg.solve(lhs, rhs) + except numpy.linalg.LinAlgError: + return 1.0 + + # get the ratio + intersect = ((a + solved[0] * udir_a) + (b + solved[1] * udir_b)) * 0.5 + a_intersect_dist = numpy.linalg.norm(a - intersect) + if a_intersect_dist == 0: + return 1.0 + return numpy.linalg.norm(pt - intersect) / a_intersect_dist + + def _nodeTransformChanged(self, *args) -> None: + self._cache_dirty = True + + def _getTexCoordsFromClick(self, node: SceneNode, x: float, y: float) -> Tuple[int, Optional[numpy.ndarray]]: + face_id = self._faces_selection_pass.getFaceIdAtPosition(x, y) + if face_id < 0 or face_id >= node.getMeshData().getFaceCount(): + return face_id, None + + pt = self._picking_pass.getPickedPosition(x, y).getData() + + va, vb, vc = self._mesh_transformed_cache.getFaceNodes(face_id) + + face_uv_coordinates = node.getMeshData().getFaceUvCoords(face_id) + if face_uv_coordinates is None: + return face_id, None + ta, tb, tc = face_uv_coordinates + + # 'Weight' of each vertex that would produce point pt, so we can generate the texture coordinates from the uv ones of the vertices. + # See (also) https://mathworld.wolfram.com/BarycentricCoordinates.html + wa = PaintTool._get_intersect_ratio_via_pt(va, pt, vb, vc) + wb = PaintTool._get_intersect_ratio_via_pt(vb, pt, vc, va) + wc = PaintTool._get_intersect_ratio_via_pt(vc, pt, va, vb) + wt = wa + wb + wc + if wt == 0: + return face_id, None + wa /= wt + wb /= wt + wc /= wt + texcoords = wa * ta + wb * tb + wc * tc + return face_id, texcoords + + def _iteratateSplitSubstroke(self, node, substrokes, + info_a: Tuple[Tuple[float, float], Tuple[int, Optional[numpy.ndarray]]], + info_b: Tuple[Tuple[float, float], Tuple[int, Optional[numpy.ndarray]]]) -> None: + click_a, (face_a, texcoords_a) = info_a + click_b, (face_b, texcoords_b) = info_b + + if (abs(click_a[0] - click_b[0]) < 0.0001 and abs(click_a[1] - click_b[1]) < 0.0001) or (face_a < 0 and face_b < 0): + return + if face_b < 0 or face_a == face_b: + substrokes.append((self._last_text_coords, texcoords_a)) + return + if face_a < 0: + substrokes.append((self._last_text_coords, texcoords_b)) + return + + mouse_mid = (click_a[0] + click_b[0]) / 2.0, (click_a[1] + click_b[1]) / 2.0 + face_mid, texcoords_mid = self._getTexCoordsFromClick(node, mouse_mid[0], mouse_mid[1]) + mid_struct = (mouse_mid, (face_mid, texcoords_mid)) + if face_mid == face_a: + substrokes.append((texcoords_a, texcoords_mid)) + self._iteratateSplitSubstroke(node, substrokes, mid_struct, info_b) + elif face_mid == face_b: + substrokes.append((texcoords_mid, texcoords_b)) + self._iteratateSplitSubstroke(node, substrokes, info_a, mid_struct) + else: + self._iteratateSplitSubstroke(node, substrokes, mid_struct, info_b) + self._iteratateSplitSubstroke(node, substrokes, info_a, mid_struct) + + def event(self, event: Event) -> bool: + """Handle mouse and keyboard events. + + :param event: The event to handle. + :return: Whether this event has been caught by this tool (True) or should + be passed on (False). + """ + super().event(event) + + controller = Application.getInstance().getController() + node = Selection.getSelectedObject(0) + if node is None: + return False + + # Make sure the displayed values are updated if the bounding box of the selected mesh(es) changes + if event.type == Event.ToolActivateEvent: + return True + + if event.type == Event.ToolDeactivateEvent: + return True + + if self._state != PaintTool.Paint.State.READY: + return False + + if event.type == Event.MouseReleaseEvent and self._controller.getToolsEnabled(): + if MouseEvent.LeftButton not in cast(MouseEvent, event).buttons: + return False + self._mouse_held = False + self._last_text_coords = None + self._last_mouse_coords = None + self._last_face_id = None + return True + + is_moved = event.type == Event.MouseMoveEvent + is_pressed = event.type == Event.MousePressEvent + if (is_moved or is_pressed) and self._controller.getToolsEnabled(): + if is_moved and not self._mouse_held: + return False + + mouse_evt = cast(MouseEvent, event) + if is_pressed: + if MouseEvent.LeftButton not in mouse_evt.buttons: + return False + else: + self._mouse_held = True + + if not self._faces_selection_pass: + self._faces_selection_pass = CuraApplication.getInstance().getRenderer().getRenderPass("selection_faces") + if not self._faces_selection_pass: + return False + + if not self._picking_pass: + self._picking_pass = CuraApplication.getInstance().getRenderer().getRenderPass("picking_selected") + if not self._picking_pass: + return False + + camera = self._controller.getScene().getActiveCamera() + if not camera: + return False + + if node != self._node_cache: + if self._node_cache is not None: + self._node_cache.transformationChanged.disconnect(self._nodeTransformChanged) + self._node_cache = node + self._node_cache.transformationChanged.connect(self._nodeTransformChanged) + self._cache_dirty = True + if self._cache_dirty: + self._cache_dirty = False + self._mesh_transformed_cache = self._node_cache.getMeshDataTransformed() + if not self._mesh_transformed_cache: + return False + + face_id, texcoords = self._getTexCoordsFromClick(node, mouse_evt.x, mouse_evt.y) + if texcoords is None: + return False + if self._last_text_coords is None: + self._last_text_coords = texcoords + self._last_mouse_coords = (mouse_evt.x, mouse_evt.y) + self._last_face_id = face_id + + substrokes = [] + if face_id == self._last_face_id: + substrokes.append((self._last_text_coords, texcoords)) + else: + self._iteratateSplitSubstroke(node, substrokes, + (self._last_mouse_coords, (self._last_face_id, self._last_text_coords)), + ((mouse_evt.x, mouse_evt.y), (face_id, texcoords))) + + w, h = self._view.getUvTexDimensions() + for start_coords, end_coords in substrokes: + sub_image, (start_x, start_y) = self._createStrokeImage( + start_coords[0] * w, + start_coords[1] * h, + end_coords[0] * w, + end_coords[1] * h + ) + self._view.addStroke(sub_image, start_x, start_y, self._brush_color, is_moved) + + self._last_text_coords = texcoords + self._last_mouse_coords = (mouse_evt.x, mouse_evt.y) + self._last_face_id = face_id + self._updateScene(node) + return True + + return False + + def getRequiredExtraRenderingPasses(self) -> list[str]: + return ["selection_faces", "picking_selected"] + + @staticmethod + def _updateScene(node: SceneNode = None): + if node is None: + node = Selection.getSelectedObject(0) + if node is not None: + Application.getInstance().getController().getScene().sceneChanged.emit(node) + + def _onSelectionChanged(self): + super()._onSelectionChanged() + + self.setActiveView("PaintTool" if len(Selection.getAllSelectedObjects()) == 1 else None) + self._updateState() + + def _updateState(self): + if len(Selection.getAllSelectedObjects()) == 1 and self._controller.getActiveTool() == self: + selected_object = Selection.getSelectedObject(0) + if selected_object.callDecoration("getPaintTexture") is not None: + new_state = PaintTool.Paint.State.READY + else: + new_state = PaintTool.Paint.State.PREPARING_MODEL + self._prepare_texture_job = PrepareTextureJob(selected_object) + self._prepare_texture_job.finished.connect(self._onPrepareTextureFinished) + self._prepare_texture_job.start() + else: + new_state = PaintTool.Paint.State.MULTIPLE_SELECTION + + if new_state != self._state: + self._state = new_state + self.propertyChanged.emit() + + def _onPrepareTextureFinished(self, job: Job): + if job == self._prepare_texture_job: + self._prepare_texture_job = None + self._state = PaintTool.Paint.State.READY + self.propertyChanged.emit() + + def _updateIgnoreUnselectedObjects(self): + ignore_unselected_objects = self._controller.getActiveView().name == "PaintTool" + CuraApplication.getInstance().getRenderer().getRenderPass("selection").setIgnoreUnselectedObjects(ignore_unselected_objects) + CuraApplication.getInstance().getRenderer().getRenderPass("selection_faces").setIgnoreUnselectedObjects(ignore_unselected_objects) \ No newline at end of file diff --git a/plugins/PaintTool/PaintTool.qml b/plugins/PaintTool/PaintTool.qml new file mode 100644 index 0000000000..548b6b047e --- /dev/null +++ b/plugins/PaintTool/PaintTool.qml @@ -0,0 +1,301 @@ +// Copyright (c) 2025 UltiMaker +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import UM 1.7 as UM +import Cura 1.0 as Cura + +Item +{ + id: base + width: childrenRect.width + height: childrenRect.height + UM.I18nCatalog { id: catalog; name: "cura"} + + Action + { + id: undoAction + shortcut: "Ctrl+L" + enabled: UM.Controller.properties.getValue("CanUndo") + onTriggered: UM.Controller.triggerAction("undoStackAction") + } + + Action + { + id: redoAction + shortcut: "Ctrl+Shift+L" + enabled: UM.Controller.properties.getValue("CanRedo") + onTriggered: UM.Controller.triggerAction("redoStackAction") + } + + Column + { + id: mainColumn + spacing: UM.Theme.getSize("default_margin").height + + RowLayout + { + id: rowPaintMode + width: parent.width + + PaintModeButton + { + text: catalog.i18nc("@action:button", "Seam") + icon: "Seam" + tooltipText: catalog.i18nc("@tooltip", "Refine seam placement by defining preferred/avoidance areas") + mode: "seam" + } + + PaintModeButton + { + text: catalog.i18nc("@action:button", "Support") + icon: "Support" + tooltipText: catalog.i18nc("@tooltip", "Refine support placement by defining preferred/avoidance areas") + mode: "support" + visible: false + } + } + + //Line between the sections. + Rectangle + { + width: parent.width + height: UM.Theme.getSize("default_lining").height + color: UM.Theme.getColor("lining") + } + + RowLayout + { + id: rowBrushColor + + UM.Label + { + text: catalog.i18nc("@label", "Mark as") + } + + BrushColorButton + { + id: buttonPreferredArea + color: "preferred" + + text: catalog.i18nc("@action:button", "Preferred") + toolItem: UM.ColorImage + { + source: UM.Theme.getIcon("CheckBadge", "low") + color: UM.Theme.getColor("paint_preferred_area") + } + } + + BrushColorButton + { + id: buttonAvoidArea + color: "avoid" + + text: catalog.i18nc("@action:button", "Avoid") + toolItem: UM.ColorImage + { + source: UM.Theme.getIcon("CancelBadge", "low") + color: UM.Theme.getColor("paint_avoid_area") + } + } + + BrushColorButton + { + id: buttonEraseArea + color: "none" + + text: catalog.i18nc("@action:button", "Erase") + toolItem: UM.ColorImage + { + source: UM.Theme.getIcon("Eraser") + color: UM.Theme.getColor("icon") + } + } + } + + RowLayout + { + id: rowBrushShape + + UM.Label + { + text: catalog.i18nc("@label", "Brush Shape") + } + + BrushShapeButton + { + id: buttonBrushCircle + shape: Cura.PaintToolBrush.CIRCLE + + text: catalog.i18nc("@action:button", "Circle") + toolItem: UM.ColorImage + { + source: UM.Theme.getIcon("Circle") + color: UM.Theme.getColor("icon") + } + } + + BrushShapeButton + { + id: buttonBrushSquare + shape: Cura.PaintToolBrush.SQUARE + + text: catalog.i18nc("@action:button", "Square") + toolItem: UM.ColorImage + { + source: UM.Theme.getIcon("MeshTypeNormal") + color: UM.Theme.getColor("icon") + } + } + } + + UM.Label + { + text: catalog.i18nc("@label", "Brush Size") + } + + UM.Slider + { + id: shapeSizeSlider + width: parent.width + indicatorVisible: false + + from: 10 + to: 1000 + value: UM.Controller.properties.getValue("BrushSize") + + onPressedChanged: function(pressed) + { + if(! pressed) + { + UM.Controller.setProperty("BrushSize", shapeSizeSlider.value); + } + } + } + + //Line between the sections. + Rectangle + { + width: parent.width + height: UM.Theme.getSize("default_lining").height + color: UM.Theme.getColor("lining") + } + + RowLayout + { + UM.ToolbarButton + { + id: undoButton + + enabled: undoAction.enabled + text: catalog.i18nc("@action:button", "Undo Stroke") + toolItem: UM.ColorImage + { + source: UM.Theme.getIcon("ArrowReset") + color: UM.Theme.getColor("icon") + } + + onClicked: undoAction.trigger() + } + + UM.ToolbarButton + { + id: redoButton + + enabled: redoAction.enabled + text: catalog.i18nc("@action:button", "Redo Stroke") + toolItem: UM.ColorImage + { + source: UM.Theme.getIcon("ArrowReset") + color: UM.Theme.getColor("icon") + transform: [ + Scale { xScale: -1; origin.x: width/2 } + ] + } + + onClicked: redoAction.trigger() + } + + Cura.SecondaryButton + { + id: clearButton + text: catalog.i18nc("@button", "Clear all") + onClicked: UM.Controller.triggerAction("clear") + } + } + } + + Rectangle + { + id: waitPrepareItem + anchors.fill: parent + color: UM.Theme.getColor("main_background") + visible: UM.Controller.properties.getValue("State") === Cura.PaintToolState.PREPARING_MODEL + + ColumnLayout + { + anchors.fill: parent + + UM.Label + { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.verticalStretchFactor: 2 + + text: catalog.i18nc("@label", "Preparing model for painting...") + verticalAlignment: Text.AlignBottom + horizontalAlignment: Text.AlignHCenter + } + + Item + { + Layout.preferredWidth: loadingIndicator.width + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: true + Layout.verticalStretchFactor: 1 + + UM.ColorImage + { + id: loadingIndicator + + anchors.top: parent.top + anchors.left: parent.left + width: UM.Theme.getSize("card_icon").width + height: UM.Theme.getSize("card_icon").height + source: UM.Theme.getIcon("ArrowDoubleCircleRight") + color: UM.Theme.getColor("text_default") + + RotationAnimator + { + target: loadingIndicator + from: 0 + to: 360 + duration: 2000 + loops: Animation.Infinite + running: true + alwaysRunToEnd: true + } + } + } + } + } + + Rectangle + { + id: selectSingleMessageItem + anchors.fill: parent + color: UM.Theme.getColor("main_background") + visible: UM.Controller.properties.getValue("State") === Cura.PaintToolState.MULTIPLE_SELECTION + + UM.Label + { + anchors.fill: parent + text: catalog.i18nc("@label", "Select a single model to start painting") + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } + } +} diff --git a/plugins/PaintTool/PaintUndoCommand.py b/plugins/PaintTool/PaintUndoCommand.py new file mode 100644 index 0000000000..50bfb787b7 --- /dev/null +++ b/plugins/PaintTool/PaintUndoCommand.py @@ -0,0 +1,104 @@ +# Copyright (c) 2025 UltiMaker +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import cast, Optional + +from PyQt6.QtCore import QRect, QPoint +from PyQt6.QtGui import QUndoCommand, QImage, QPainter + +from UM.View.GL.Texture import Texture + + +class PaintUndoCommand(QUndoCommand): + """Provides the command that does the actual painting on objects with undo/redo mechanisms""" + + def __init__(self, + texture: Texture, + stroke_mask: QImage, + x: int, + y: int, + set_value: int, + bit_range: tuple[int, int], + mergeable: bool) -> None: + super().__init__() + + self._original_texture_image: Optional[QImage] = texture.getImage().copy() if not mergeable else None + self._texture: Texture = texture + self._stroke_mask: QImage = stroke_mask + self._x: int = x + self._y: int = y + self._set_value: int = set_value + self._bit_range: tuple[int, int] = bit_range + self._mergeable: bool = mergeable + + def id(self) -> int: + # Since the undo stack will contain only commands of this type, we can use a fixed ID + return 0 + + def redo(self) -> None: + actual_image = self._texture.getImage() + + bit_range_start, bit_range_end = self._bit_range + full_int32 = 0xffffffff + clear_texture_bit_mask = full_int32 ^ (((full_int32 << (32 - 1 - (bit_range_end - bit_range_start))) & full_int32) >> ( + 32 - 1 - bit_range_end)) + image_rect = QRect(0, 0, self._stroke_mask.width(), self._stroke_mask.height()) + + clear_bits_image = self._stroke_mask.copy() + clear_bits_image.invertPixels() + painter = QPainter(clear_bits_image) + painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Lighten) + painter.fillRect(image_rect, clear_texture_bit_mask) + painter.end() + + set_value_image = self._stroke_mask.copy() + painter = QPainter(set_value_image) + painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Multiply) + painter.fillRect(image_rect, self._set_value) + painter.end() + + stroked_image = actual_image.copy(self._x, self._y, self._stroke_mask.width(), self._stroke_mask.height()) + painter = QPainter(stroked_image) + painter.setCompositionMode(QPainter.CompositionMode.RasterOp_SourceAndDestination) + painter.drawImage(0, 0, clear_bits_image) + painter.setCompositionMode(QPainter.CompositionMode.RasterOp_SourceOrDestination) + painter.drawImage(0, 0, set_value_image) + painter.end() + + self._texture.setSubImage(stroked_image, self._x, self._y) + + def undo(self) -> None: + if self._original_texture_image is not None: + self._texture.setSubImage(self._original_texture_image.copy(self._x, + self._y, + self._stroke_mask.width(), + self._stroke_mask.height()), + self._x, + self._y) + + def mergeWith(self, command: QUndoCommand) -> bool: + if not isinstance(command, PaintUndoCommand): + return False + paint_undo_command = cast(PaintUndoCommand, command) + + if not paint_undo_command._mergeable: + return False + + self_rect = QRect(QPoint(self._x, self._y), self._stroke_mask.size()) + command_rect = QRect(QPoint(paint_undo_command._x, paint_undo_command._y), paint_undo_command._stroke_mask.size()) + bounding_rect = self_rect.united(command_rect) + + merged_mask = QImage(bounding_rect.width(), bounding_rect.height(), self._stroke_mask.format()) + merged_mask.fill(0) + + painter = QPainter(merged_mask) + painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Lighten) + painter.drawImage(self._x - bounding_rect.x(), self._y - bounding_rect.y(), self._stroke_mask) + painter.drawImage(paint_undo_command._x - bounding_rect.x(), paint_undo_command._y - bounding_rect.y(), paint_undo_command._stroke_mask) + painter.end() + + self._x = bounding_rect.x() + self._y = bounding_rect.y() + self._stroke_mask = merged_mask + + return True diff --git a/plugins/PaintTool/PaintView.py b/plugins/PaintTool/PaintView.py new file mode 100644 index 0000000000..b3bc9867c3 --- /dev/null +++ b/plugins/PaintTool/PaintView.py @@ -0,0 +1,173 @@ +# Copyright (c) 2025 UltiMaker +# Cura is released under the terms of the LGPLv3 or higher. + +import os +from PyQt6.QtCore import QRect, pyqtSignal +from typing import Optional, Dict + +from PyQt6.QtGui import QImage, QUndoStack + +from cura.CuraApplication import CuraApplication +from cura.BuildVolume import BuildVolume +from cura.CuraView import CuraView +from UM.PluginRegistry import PluginRegistry +from UM.View.GL.ShaderProgram import ShaderProgram +from UM.View.GL.Texture import Texture +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.Scene.Selection import Selection +from UM.View.GL.OpenGL import OpenGL +from UM.i18n import i18nCatalog +from UM.Math.Color import Color + +from .PaintUndoCommand import PaintUndoCommand + +catalog = i18nCatalog("cura") + + +class PaintView(CuraView): + """View for model-painting.""" + + class PaintType: + def __init__(self, display_color: Color, value: int): + self.display_color: Color = display_color + self.value: int = value + + def __init__(self) -> None: + super().__init__(use_empty_menu_placeholder = True) + self._paint_shader: Optional[ShaderProgram] = None + self._current_paint_texture: Optional[Texture] = None + self._current_bits_ranges: tuple[int, int] = (0, 0) + self._current_paint_type = "" + self._paint_modes: Dict[str, Dict[str, "PaintView.PaintType"]] = {} + + self._paint_undo_stack: QUndoStack = QUndoStack() + self._paint_undo_stack.setUndoLimit(32) # Set a quite low amount since every command copies the full texture + self._paint_undo_stack.canUndoChanged.connect(self.canUndoChanged) + self._paint_undo_stack.canRedoChanged.connect(self.canRedoChanged) + + application = CuraApplication.getInstance() + application.engineCreatedSignal.connect(self._makePaintModes) + self._scene = application.getController().getScene() + + canUndoChanged = pyqtSignal(bool) + canRedoChanged = pyqtSignal(bool) + + def canUndo(self): + return self._paint_undo_stack.canUndo() + + def canRedo(self): + return self._paint_undo_stack.canRedo() + + def _makePaintModes(self): + theme = CuraApplication.getInstance().getTheme() + usual_types = {"none": self.PaintType(Color(*theme.getColor("paint_normal_area").getRgb()), 0), + "preferred": self.PaintType(Color(*theme.getColor("paint_preferred_area").getRgb()), 1), + "avoid": self.PaintType(Color(*theme.getColor("paint_avoid_area").getRgb()), 2)} + self._paint_modes = { + "seam": usual_types, + "support": usual_types, + } + + self._current_paint_type = "seam" + + def _checkSetup(self): + if not self._paint_shader: + shader_filename = os.path.join(PluginRegistry.getInstance().getPluginPath("PaintTool"), "paint.shader") + self._paint_shader = OpenGL.getInstance().createShaderProgram(shader_filename) + + def addStroke(self, stroke_mask: QImage, start_x: int, start_y: int, brush_color: str, merge_with_previous: bool) -> None: + if self._current_paint_texture is None or self._current_paint_texture.getImage() is None: + return + + self._prepareDataMapping() + + current_image = self._current_paint_texture.getImage() + texture_rect = QRect(0, 0, current_image.width(), current_image.height()) + stroke_rect = QRect(start_x, start_y, stroke_mask.width(), stroke_mask.height()) + intersect_rect = texture_rect.intersected(stroke_rect) + if intersect_rect != stroke_rect: + # Stroke doesn't fully fit into the image, we have to crop it + stroke_mask = stroke_mask.copy(intersect_rect.x() - start_x, + intersect_rect.y() - start_y, + intersect_rect.width(), + intersect_rect.height()) + start_x = intersect_rect.x() + start_y = intersect_rect.y() + + bit_range_start, bit_range_end = self._current_bits_ranges + set_value = self._paint_modes[self._current_paint_type][brush_color].value << bit_range_start + + self._paint_undo_stack.push(PaintUndoCommand(self._current_paint_texture, + stroke_mask, + start_x, + start_y, + set_value, + (bit_range_start, bit_range_end), + merge_with_previous)) + + def undoStroke(self) -> None: + self._paint_undo_stack.undo() + + def redoStroke(self) -> None: + self._paint_undo_stack.redo() + + def getUvTexDimensions(self): + if self._current_paint_texture is not None: + return self._current_paint_texture.getWidth(), self._current_paint_texture.getHeight() + return 0, 0 + + def getPaintType(self) -> str: + return self._current_paint_type + + def setPaintType(self, paint_type: str) -> None: + self._current_paint_type = paint_type + + def _prepareDataMapping(self): + node = Selection.getAllSelectedObjects()[0] + if node is None: + return + + paint_data_mapping = node.callDecoration("getTextureDataMapping") + + if self._current_paint_type not in paint_data_mapping: + new_mapping = self._add_mapping(paint_data_mapping, len(self._paint_modes[self._current_paint_type])) + paint_data_mapping[self._current_paint_type] = new_mapping + node.callDecoration("setTextureDataMapping", paint_data_mapping) + + self._current_bits_ranges = paint_data_mapping[self._current_paint_type] + + @staticmethod + def _add_mapping(actual_mapping: Dict[str, tuple[int, int]], nb_storable_values: int) -> tuple[int, int]: + start_index = 0 + if actual_mapping: + start_index = max(end_index for _, end_index in actual_mapping.values()) + 1 + + end_index = start_index + int.bit_length(nb_storable_values - 1) - 1 + + return start_index, end_index + + def beginRendering(self) -> None: + if self._current_paint_type not in self._paint_modes: + return + + self._checkSetup() + renderer = self.getRenderer() + + for node in DepthFirstIterator(self._scene.getRoot()): + if isinstance(node, BuildVolume): + node.render(renderer) + + paint_batch = renderer.createRenderBatch(shader=self._paint_shader) + renderer.addRenderBatch(paint_batch) + + for node in Selection.getAllSelectedObjects(): + paint_batch.addItem(node.getWorldTransformation(copy=False), node.getMeshData(), normal_transformation=node.getCachedNormalMatrix()) + self._current_paint_texture = node.callDecoration("getPaintTexture") + self._paint_shader.setTexture(0, self._current_paint_texture) + + self._paint_shader.setUniformValue("u_bitsRangesStart", self._current_bits_ranges[0]) + self._paint_shader.setUniformValue("u_bitsRangesEnd", self._current_bits_ranges[1]) + + colors = [paint_type_obj.display_color for paint_type_obj in self._paint_modes[self._current_paint_type].values()] + colors_values = [[int(color_part * 255) for color_part in [color.r, color.g, color.b]] for color in colors] + self._paint_shader.setUniformValueArray("u_renderColors", colors_values) diff --git a/plugins/PaintTool/PrepareTextureJob.py b/plugins/PaintTool/PrepareTextureJob.py new file mode 100644 index 0000000000..6c5e61c009 --- /dev/null +++ b/plugins/PaintTool/PrepareTextureJob.py @@ -0,0 +1,33 @@ +# Copyright (c) 2025 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from UM.Job import Job +from UM.Scene.SceneNode import SceneNode +from UM.View.GL.OpenGL import OpenGL + + +class PrepareTextureJob(Job): + """ + Background job to prepare a model for painting, i.e. do the UV-unwrapping and create the appropriate texture image, + which can last a few seconds + """ + + def __init__(self, node: SceneNode): + super().__init__() + self._node: SceneNode = node + + def run(self) -> None: + # If the model has already-provided UV coordinates, we can only assume that the associated texture + # should be a square + texture_width = texture_height = 4096 + + mesh = self._node.getMeshData() + if not mesh.hasUVCoordinates(): + texture_width, texture_height = mesh.calculateUnwrappedUVCoordinates() + + self._node.callDecoration("prepareTexture", texture_width, texture_height) + + if hasattr(mesh, OpenGL.VertexBufferProperty): + # Force clear OpenGL buffer so that new UV coordinates will be sent + delattr(mesh, OpenGL.VertexBufferProperty) + diff --git a/plugins/PaintTool/__init__.py b/plugins/PaintTool/__init__.py new file mode 100644 index 0000000000..a95559ff0f --- /dev/null +++ b/plugins/PaintTool/__init__.py @@ -0,0 +1,35 @@ +# Copyright (c) 2025 UltiMaker +# Cura is released under the terms of the LGPLv3 or higher. + +from . import PaintTool +from . import PaintView + +from PyQt6.QtQml import qmlRegisterUncreatableType + +from UM.i18n import i18nCatalog +i18n_catalog = i18nCatalog("cura") + +def getMetaData(): + return { + "tool": { + "name": i18n_catalog.i18nc("@action:button", "Paint"), + "description": i18n_catalog.i18nc("@info:tooltip", "Paint Model"), + "icon": "Visual", + "tool_panel": "PaintTool.qml", + "weight": 0 + }, + "view": { + "name": i18n_catalog.i18nc("@item:inmenu", "Paint view"), + "weight": 0, + "visible": False + } + } + +def register(app): + qmlRegisterUncreatableType(PaintTool.PaintTool.Brush, "Cura", 1, 0, "This is an enumeration class", "PaintToolBrush") + qmlRegisterUncreatableType(PaintTool.PaintTool.Paint, "Cura", 1, 0, "This is an enumeration class", "PaintToolState") + view = PaintView.PaintView() + return { + "tool": PaintTool.PaintTool(view), + "view": view + } diff --git a/plugins/PaintTool/paint.shader b/plugins/PaintTool/paint.shader new file mode 100644 index 0000000000..1982724910 --- /dev/null +++ b/plugins/PaintTool/paint.shader @@ -0,0 +1,146 @@ +[shaders] +vertex = + uniform highp mat4 u_modelMatrix; + uniform highp mat4 u_viewMatrix; + uniform highp mat4 u_projectionMatrix; + + uniform highp mat4 u_normalMatrix; + + attribute highp vec4 a_vertex; + attribute highp vec4 a_normal; + attribute highp vec2 a_uvs; + + varying highp vec3 v_vertex; + varying highp vec3 v_normal; + varying highp vec2 v_uvs; + + void main() + { + vec4 world_space_vert = u_modelMatrix * a_vertex; + gl_Position = u_projectionMatrix * u_viewMatrix * world_space_vert; + + v_vertex = world_space_vert.xyz; + v_normal = (u_normalMatrix * normalize(a_normal)).xyz; + + v_uvs = a_uvs; + } + +fragment = + uniform mediump vec4 u_ambientColor; + uniform highp vec3 u_lightPosition; + uniform highp vec3 u_viewPosition; + uniform sampler2D u_texture; + uniform mediump int u_bitsRangesStart; + uniform mediump int u_bitsRangesEnd; + uniform mediump vec3 u_renderColors[16]; + + varying highp vec3 v_vertex; + varying highp vec3 v_normal; + varying highp vec2 v_uvs; + + void main() + { + mediump vec4 final_color = vec4(0.0); + + /* Ambient Component */ + final_color += u_ambientColor; + + highp vec3 normal = normalize(v_normal); + highp vec3 light_dir = normalize(u_lightPosition - v_vertex); + + /* Diffuse Component */ + ivec4 texture = ivec4(texture(u_texture, v_uvs) * 255.0); + uint color_index = (texture.r << 16) | (texture.g << 8) | texture.b; + color_index = (color_index << (32 - 1 - u_bitsRangesEnd)) >> 32 - 1 - (u_bitsRangesEnd - u_bitsRangesStart); + + vec4 diffuse_color = vec4(u_renderColors[color_index] / 255.0, 1.0); + highp float n_dot_l = mix(0.3, 0.7, dot(normal, light_dir)); + final_color += (n_dot_l * diffuse_color); + + final_color.a = 1.0; + + frag_color = final_color; + } + +vertex41core = + #version 410 + uniform highp mat4 u_modelMatrix; + uniform highp mat4 u_viewMatrix; + uniform highp mat4 u_projectionMatrix; + + uniform highp mat4 u_normalMatrix; + + in highp vec4 a_vertex; + in highp vec4 a_normal; + in highp vec2 a_uvs; + + out highp vec3 v_vertex; + out highp vec3 v_normal; + out highp vec2 v_uvs; + + void main() + { + vec4 world_space_vert = u_modelMatrix * a_vertex; + gl_Position = u_projectionMatrix * u_viewMatrix * world_space_vert; + + v_vertex = world_space_vert.xyz; + v_normal = (u_normalMatrix * normalize(a_normal)).xyz; + + v_uvs = a_uvs; + } + +fragment41core = + #version 410 + uniform mediump vec4 u_ambientColor; + uniform highp vec3 u_lightPosition; + uniform highp vec3 u_viewPosition; + uniform sampler2D u_texture; + uniform mediump int u_bitsRangesStart; + uniform mediump int u_bitsRangesEnd; + uniform mediump vec3 u_renderColors[16]; + + in highp vec3 v_vertex; + in highp vec3 v_normal; + in highp vec2 v_uvs; + out vec4 frag_color; + + void main() + { + mediump vec4 final_color = vec4(0.0); + + /* Ambient Component */ + final_color += u_ambientColor; + + highp vec3 normal = normalize(v_normal); + highp vec3 light_dir = normalize(u_lightPosition - v_vertex); + + /* Diffuse Component */ + ivec4 texture = ivec4(texture(u_texture, v_uvs) * 255.0); + uint color_index = (texture.r << 16) | (texture.g << 8) | texture.b; + color_index = (color_index << (32 - 1 - u_bitsRangesEnd)) >> 32 - 1 - (u_bitsRangesEnd - u_bitsRangesStart); + + vec4 diffuse_color = vec4(u_renderColors[color_index] / 255.0, 1.0); + highp float n_dot_l = mix(0.3, 0.7, dot(normal, light_dir)); + final_color += (n_dot_l * diffuse_color); + + final_color.a = 1.0; + + frag_color = final_color; + } + +[defaults] +u_ambientColor = [0.3, 0.3, 0.3, 1.0] +u_texture = 0 + +[bindings] +u_modelMatrix = model_matrix +u_viewMatrix = view_matrix +u_projectionMatrix = projection_matrix +u_normalMatrix = normal_matrix +u_lightPosition = light_0_position +u_viewPosition = camera_position + +[attributes] +a_vertex = vertex +a_normal = normal +a_uvs = uv0 diff --git a/plugins/PaintTool/plugin.json b/plugins/PaintTool/plugin.json new file mode 100644 index 0000000000..2a55d677d2 --- /dev/null +++ b/plugins/PaintTool/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "Paint Tools", + "author": "UltiMaker", + "version": "1.0.0", + "description": "Provides the paint tools.", + "api": 8, + "i18n-catalog": "cura" +} diff --git a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py index 44709afd24..b046a77c2f 100644 --- a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py +++ b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py @@ -1,16 +1,21 @@ -# Designed in January 2023 by GregValiant (Greg Foresi) -## My design intent was to make this as full featured and "industrial strength" as I could. People printing exotic materials on large custom printers may want to turn the fans off for certain layers, and then back on again later in the print. This script allows that. -# Functions: -## Remove all fan speed lines from the file (optional). This should be enabled for the first instance of the script. It is disabled by default in any following instances. -## "By Layer" allows the user to adjust the fan speed up, or down, or off, within the print. "By Feature" allows different fan speeds for different features (;TYPE:WALL-OUTER, etc.). -## If 'By Feature' then a Start Layer and/or an End Layer can be defined. -## Fan speeds are scaled PWM (0 - 255) or RepRap (0.0 - 1.0) depending on {machine_scale_fan_speed_zero_to_one}. -## A minimum fan speed of 12% is enforced. It is the slowest speed that my cooling fan will turn on so that's what I used. 'M106 S14' (as Cura might insert) was pretty useless. -## If multiple extruders have separate fan circuits the speeds are set at tool changes and conform to the layer or feature setting. There is support for up to 4 layer cooling fan circuits. -## My thanks to @5axes(@CUQ), @fieldOfView(@AHoeben), @Ghostkeeper, and @Torgeir. A special thanks to @RBurema for his patience in reviewing my 'non-pythonic' script. -## 9/14/23 (Greg Foresi) Added support for One-at-a-Time print sequence. -## 12/15/23 (Greg Foresi) Split off 'Single Fan By Layer', 'Multi-fan By Layer', 'Single Fan By Feature', and 'Multi-fan By Feature' from the main 'execute' script. -## 1/5/24 (Greg Foresi) Revised the regex replacements. +""" +Designed in January 2023 by GregValiant (Greg Foresi) + My design intent was to make this as full featured and "industrial strength" as I could. People printing exotic materials on large custom printers may want to turn the fans off for certain layers, and then back on again later in the print. This script allows that. + Functions: + Remove all fan speed lines from the file (optional). This should be enabled for the first instance of the script. It is disabled by default in any following instances. + "By Layer" allows the user to adjust the fan speed up, or down, or off, within the print. "By Feature" allows different fan speeds for different features (;TYPE:WALL-OUTER, etc.). + If 'By Feature' then a Start Layer and/or an End Layer can be defined. + Fan speeds are scaled PWM (0 - 255) or RepRap (0.0 - 1.0) depending on {machine_scale_fan_speed_zero_to_one}. + A minimum fan speed of 12% is enforced. It is the slowest speed that my cooling fan will turn on so that's what I used. 'M106 S14' (as Cura might insert) was pretty useless. + If multiple extruders have separate fan circuits the speeds are set at tool changes and conform to the layer or feature setting. There is support for up to 4 layer cooling fan circuits. + My thanks to @5axes(@CUQ), @fieldOfView(@AHoeben), @Ghostkeeper, and @Torgeir. A special thanks to @RBurema for his patience in reviewing my 'non-pythonic' script. + Changes: + 09/14/23 (GV) Added support for One-at-a-Time print sequence. + 12/15/23 (GV) Split off 'Single Fan By Layer', 'Multi-fan By Layer', 'Single Fan By Feature', and 'Multi-fan By Feature' from the main 'execute' script. + 01/05/24 (GV) Revised the regex replacements. + 12/11/24 (GV) Added 'off_fan_speed' for the idle nozzle layer cooling fan. It does not have to go to 0%. + 03/22/25 (GV) Added 'Chamber Cooling Fan / Auxiliary Fan' control. +""" from ..Script import Script from UM.Application import Application @@ -43,7 +48,8 @@ class AddCoolingProfile(Script): "type": "bool", "enabled": true, "value": true, - "default_value": true + "default_value": true, + "read_only": true }, "feature_fan_start_layer": { @@ -273,67 +279,180 @@ class AddCoolingProfile(Script): "maximum_value": 100, "unit": "% ", "enabled": "fan_enable_raft" + }, + "enable_off_fan_speed": + { + "label": "Enable 'Off speed' of the idle fan", + "description": "For machines with independent layer cooling fans. Leaving a fan running while the other nozzle is printing can help with oozing. You can pick the speed % for the idle nozzle layer cooling fan to hold at.", + "type": "bool", + "default_value": false, + "enabled": "enable_off_fan_speed_enable and self.extruder_count > 1" + }, + "off_fan_speed": + { + "label": " 'Off' speed of idle nozzle fan", + "description": "This is the speed that the 'idle nozzle' layer cooling fan will maintain rather than being turned off completely.", + "type": "int", + "default_value": 35, + "minimum_value": 0, + "maximum_value": 100, + "unit": "% ", + "enabled": "enable_off_fan_speed_enable and enable_off_fan_speed and self.extruder_count > 1" + }, + "enable_off_fan_speed_enable": + { + "label": "Hidden setting", + "description": "For dual extruder printers, this enables 'enable_off_fan_speed'.", + "type": "bool", + "default_value": false, + "enabled": false + }, + "bv_fan_speed_control_enable": + { + "label": "Enable 'Chamber/Aux Fan' control", + "description": "Controls the 'Build Volume Fan' or an 'Auxiliary Fan' on printers with that hardware. Provides: 'On' layer, 'Off' layer, and PWM speed control of a secondary fan.", + "type": "bool", + "default_value": false, + "enabled": "enable_bv_fan" + }, + "bv_fan_nr": + { + "label": " Chamber/Aux Fan Number", + "description": "The mainboard circuit number of the Chamber or Auxiliary Fan.", + "type": "int", + "unit": "# ", + "default_value": 0, + "minimum_value": 0, + "enabled": "enable_bv_fan and bv_fan_speed_control_enable" + }, + "bv_fan_speed": + { + "label": " Chamber/Aux Fan Speed %", + "description": "The speed of the Chamber or Auxiliary Fan. This will be converted to PWM Duty Cycle (0-255) or (RepRap 0-1 if that is enabled in Cura). If your specified fan does not operate on variable speeds then set this to '100'.", + "type": "int", + "unit": "% ", + "default_value": 50, + "maximum_value": 100, + "minimum_value": 0, + "enabled": "enable_bv_fan and bv_fan_speed_control_enable" + }, + "bv_fan_start_layer": + { + "label": " Chamber/Aux Fan Start Layer", + "description": "The layer to start the Chamber or Auxiliary Fan. Use the Cura preview layer number and the fan will start at the beginning of the layer.", + "type": "int", + "unit": "Layer# ", + "default_value": 1, + "minimum_value": 1, + "enabled": "enable_bv_fan and bv_fan_speed_control_enable" + }, + "bv_fan_end_layer": + { + "label": " Chamber/Aux Fan End Layer", + "description": "The layer number for Chamber or Auxiliary Fan to turn off. Use the Cura preview layer number or '-1' to indicate the end of the print. The fan will run until the end of the layer", + "type": "int", + "unit": "Layer# ", + "default_value": -1, + "minimum_value": -1, + "enabled": "enable_bv_fan and bv_fan_speed_control_enable" + }, + "enable_bv_fan": + { + "label": "Hidden setting", + "description": "This is enabled when machine_heated_bed is true, and in turn this enables 'bv_fan_speed_control_enable'.", + "type": "bool", + "default_value": false, + "enabled": false } } }""" def initialize(self) -> None: super().initialize() - scripts = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("post_processing_scripts") + self.global_stack = Application.getInstance().getGlobalContainerStack() + self.extruder_list = self.global_stack.extruderList + self.extruder_count = self.global_stack.getProperty("machine_extruder_count", "value") + scripts = self.global_stack.getMetaDataEntry("post_processing_scripts") if scripts != None: script_count = scripts.count("AddCoolingProfile") if script_count > 0: - ## Set 'Remove M106 lines' to "false" if there is already an instance of this script running. + # Set 'Remove M106 lines' to "false" if there is already an instance of this script running. self._instance.setProperty("delete_existing_m106", "value", False) + self._instance.setProperty("enable_off_fan_speed_enable", "value", False) + if self.extruder_count > 1: + if self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value") != self.extruder_list[1].getProperty("machine_extruder_cooling_fan_number", "value"): + self._instance.setProperty("enable_off_fan_speed_enable", "value", True) + if bool(self.global_stack.getProperty("machine_heated_bed", "value")): + self._instance.setProperty("enable_bv_fan", "value", True) def execute(self, data): - #Initialize variables that are buried in if statements. - mycura = Application.getInstance().getGlobalContainerStack() + """ + Collect the settings from Cura and from this script + params: + t0_fan thru t3_fan: The fan numbers for up to 4 layer cooling circuits + fan_mode: Whether the fan scale will be 0-255 PWM (when true) or 0-1 RepRap (when false) + bed_adhesion: Is only important if a raft is enabled + print_seuence: Options are slightly different if in One-at-a-Time mode + is_multi-fan: Used to distinguish between a multi-extruder with a single fan for each nozzle, or one fan for both nozzles. + is_multi_extr_print: For the slight difference in handling a multi-extruder printer and a print that only uses one of the extruders. + fan_list: A list of fan speeds (even numbered items) and layer numbers (odd numbered items) + feature_speed_list: A list of the speeds for each ';TYPE:' in the gcode + feature_name_list: The list of each 'TYPE' in the gcode + off_fan_speed: The speed that will be maintained by the fan for the inactive extruder (for an anti-oozing effect) + init_fan: The fan number of the first extruder used in a print + delete_existing_m106: The first instance of the script in the post processing list should remove the CUra M106 lines. Following instances should not delete the changes made by the first instance. + feature_fan_combing: Whether or not to shut the cooling fan off during travel moves. + the_start_layer: When in By Feature this is the user selected start of the fan changes. + the_end_layer: When in By Feature this is the user selected end of the fan changes + the_end_is_enabled: When in By Feature, if the fan control ends before the print ends, then this will enable the Final Fan Speed to carry through to the print end. + + """ + # Exit if the gcode has been previously post-processed. + if ";POSTPROCESSED" in data[0]: + return data + # Initialize variables that are buried in if statements. t0_fan = " P0"; t1_fan = " P0"; t2_fan = " P0"; t3_fan = " P0"; is_multi_extr_print = True - #Get some information from Cura----------------------------------- - extruder = mycura.extruderList - - #This will be true when fan scale is 0-255pwm and false when it's RepRap 0-1 (Cura 5.x) + # This will be true when fan scale is 0-255pwm and false when it's RepRap 0-1 (Cura 5.x) fan_mode = True - ##For 4.x versions that don't have the 0-1 option + # For 4.x versions that don't have the 0-1 option try: - fan_mode = not bool(extruder[0].getProperty("machine_scale_fan_speed_zero_to_one", "value")) - except: + fan_mode = not bool(self.extruder_list[0].getProperty("machine_scale_fan_speed_zero_to_one", "value")) + except AttributeError: pass - bed_adhesion = (extruder[0].getProperty("adhesion_type", "value")) - extruder_count = mycura.getProperty("machine_extruder_count", "value") - print_sequence = str(mycura.getProperty("print_sequence", "value")) - #Assign the fan numbers to the tools------------------------------ - if extruder_count == 1: + bed_adhesion = (self.extruder_list[0].getProperty("adhesion_type", "value")) + print_sequence = str(self.global_stack.getProperty("print_sequence", "value")) + + # Assign the fan numbers to the tools + if self.extruder_count == 1: is_multi_fan = False is_multi_extr_print = False - if int((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value"))) > 0: - t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value"))) + if int((self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value"))) > 0: + t0_fan = " P" + str((self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value"))) else: - #No P parameter if there is a single fan circuit------------------ + # No P parameter if there is a single fan circuit t0_fan = "" - #Get the cooling fan numbers for each extruder if the printer has multiple extruders - elif extruder_count > 1: + # Get the cooling fan numbers for each extruder if the printer has multiple extruders + elif self.extruder_count > 1: is_multi_fan = True - t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value"))) + t0_fan = " P" + str((self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value"))) if is_multi_fan: - if extruder_count > 1: t1_fan = " P" + str((extruder[1].getProperty("machine_extruder_cooling_fan_number", "value"))) - if extruder_count > 2: t2_fan = " P" + str((extruder[2].getProperty("machine_extruder_cooling_fan_number", "value"))) - if extruder_count > 3: t3_fan = " P" + str((extruder[3].getProperty("machine_extruder_cooling_fan_number", "value"))) + if self.extruder_count > 1: t1_fan = " P" + str((self.extruder_list[1].getProperty("machine_extruder_cooling_fan_number", "value"))) + if self.extruder_count > 2: t2_fan = " P" + str((self.extruder_list[2].getProperty("machine_extruder_cooling_fan_number", "value"))) + if self.extruder_count > 3: t3_fan = " P" + str((self.extruder_list[3].getProperty("machine_extruder_cooling_fan_number", "value"))) - #Initialize the fan_list with defaults---------------------------- + # Initialize the fan_list with defaults fan_list = ["z"] * 16 for num in range(0,15,2): fan_list[num] = len(data) fan_list[num + 1] = "M106 S0" - #Assign the variable values if "By Layer"------------------------- + # Assign the variable values if "By Layer" by_layer_or_feature = self.getSettingValueByKey("fan_layer_or_feature") if by_layer_or_feature == "by_layer": - ## By layer doesn't do any feature search so there is no need to look for combing moves + # By layer doesn't do any feature search so there is no need to look for combing moves feature_fan_combing = False fan_list[0] = self.getSettingValueByKey("layer_fan_1") fan_list[2] = self.getSettingValueByKey("layer_fan_2") @@ -343,25 +462,25 @@ class AddCoolingProfile(Script): fan_list[10] = self.getSettingValueByKey("layer_fan_6") fan_list[12] = self.getSettingValueByKey("layer_fan_7") fan_list[14] = self.getSettingValueByKey("layer_fan_8") - ## If there is no '/' delimiter then ignore the line else put the settings in a list + # If there is no '/' delimiter then ignore the line else put the settings in a list for num in range(0,15,2): if "/" in fan_list[num]: fan_list[num + 1] = self._layer_checker(fan_list[num], "p", fan_mode) fan_list[num] = self._layer_checker(fan_list[num], "l", fan_mode) - ## Assign the variable values if "By Feature" + # Assign the variable values if "By Feature" elif by_layer_or_feature == "by_feature": the_start_layer = self.getSettingValueByKey("feature_fan_start_layer") - 1 the_end_layer = self.getSettingValueByKey("feature_fan_end_layer") try: if int(the_end_layer) != -1: - ## Catch a possible input error. + # Catch a possible input error. if the_end_layer < the_start_layer: the_end_layer = the_start_layer - except: - the_end_layer = -1 ## If there is an input error default to the entire gcode file. + except ValueError: + the_end_layer = -1 # If there is an input error then default to the entire gcode file. - ## Get the speed for each feature + # Get the speed for each feature feature_name_list = [] feature_speed_list = [] feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_skirt"), fan_mode)); feature_name_list.append(";TYPE:SKIRT") @@ -376,20 +495,29 @@ class AddCoolingProfile(Script): feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_feature_final"), fan_mode)); feature_name_list.append("FINAL_FAN") feature_fan_combing = self.getSettingValueByKey("feature_fan_combing") if the_end_layer > -1 and by_layer_or_feature == "by_feature": - ## Required so the final speed input can be determined + # Required so the final speed input can be determined the_end_is_enabled = True else: - ## There is no ending layer so do the whole file + # There is no ending layer so do the whole file the_end_is_enabled = False if the_end_layer == -1 or the_end_is_enabled == False: the_end_layer = len(data) + 2 - ## Find the Layer0Index and the RaftIndex + # For multi-extruder printers with separate cooling fans the 'idle' nozzle fan can be left on for ooze control + off_fan_speed = 0 + if self.extruder_count > 1: + if self.getSettingValueByKey("enable_off_fan_speed"): + if fan_mode: + off_fan_speed = round(int(self.getSettingValueByKey("off_fan_speed")) * 2.55) + else: + off_fan_speed = round(int(self.getSettingValueByKey("off_fan_speed")) * .01, 2) + + # Find the Layer0Index and the RaftIndex raft_start_index = 0 number_of_raft_layers = 0 layer_0_index = 0 - ## Catch the number of raft layers. - for l_num in range(1,10,1): + # Catch the number of raft layers. + for l_num in range(1,len(data) - 1): layer = data[l_num] if ";LAYER:-" in layer: number_of_raft_layers += 1 @@ -399,14 +527,14 @@ class AddCoolingProfile(Script): layer_0_index = l_num break - ## Is this a single extruder print on a multi-extruder printer? - get the correct fan number for the extruder being used. + # Is this a single extruder print on a multi-extruder printer? - get the correct fan number for the extruder being used. if is_multi_fan: T0_used = False T1_used = False T2_used = False T3_used = False - ## Bypass the file header and ending gcode. - for num in range(1,len(data)-1,1): + # Bypass the file header and ending gcode. + for num in range(1,len(data)-1): lines = data[num] if "T0" in lines: T0_used = True @@ -418,7 +546,7 @@ class AddCoolingProfile(Script): T3_used = True is_multi_extr_print = True if sum([T0_used, T1_used, T2_used, T3_used]) > 1 else False - ## On a multi-extruder printer and single extruder print find out which extruder starts the file. + # On a multi-extruder printer and single extruder print find out which extruder starts the file. init_fan = t0_fan if not is_multi_extr_print: startup = data[1] @@ -431,7 +559,7 @@ class AddCoolingProfile(Script): elif line == "T3": t0_fan = t3_fan elif is_multi_extr_print: - ## On a multi-extruder printer and multi extruder print find out which extruder starts the file. + # On a multi-extruder printer and multi extruder print find out which extruder starts the file. startup = data[1] lines = startup.split("\n") for line in lines: @@ -445,23 +573,23 @@ class AddCoolingProfile(Script): init_fan = t3_fan else: init_fan = "" - ## Assign the variable values if "Raft Enabled" + # Assign the variable values if "Raft Enabled" raft_enabled = self.getSettingValueByKey("fan_enable_raft") if raft_enabled and bed_adhesion == "raft": fan_sp_raft = self._feature_checker(self.getSettingValueByKey("fan_raft_percent"), fan_mode) else: fan_sp_raft = "M106 S0" - # Start to alter the data----------------------------------------- - ## Strip the existing M106 lines from the file up to the end of the last layer. If a user wants to use more than one instance of this plugin then they won't want to erase the M106 lines that the preceding plugins inserted so 'delete_existing_m106' is an option. + # Start to alter the data + # Strip the existing M106 lines from the file up to the end of the last layer. If a user wants to use more than one instance of this plugin then they won't want to erase the M106 lines that the preceding plugins inserted so 'delete_existing_m106' is an option. delete_existing_m106 = self.getSettingValueByKey("delete_existing_m106") if delete_existing_m106: - ## Start deleting from the beginning + # Start deleting from the beginning start_from = int(raft_start_index) else: if by_layer_or_feature == "by_layer": altered_start_layer = str(len(data)) - ## The fan list layers don't need to be in ascending order. Get the lowest. + # The fan list layers don't need to be in ascending order. Get the lowest. for num in range(0,15,2): try: if int(fan_list[num]) < int(altered_start_layer): @@ -471,12 +599,12 @@ class AddCoolingProfile(Script): elif by_layer_or_feature == "by_feature": altered_start_layer = int(the_start_layer) - 1 start_from = int(layer_0_index) + int(altered_start_layer) - ## Strip the M106 and M107 lines from the file + # Strip the M106 and M107 lines from the file for l_index in range(int(start_from), len(data) - 1, 1): data[l_index] = re.sub(re.compile("M106(.*)\n"), "", data[l_index]) data[l_index] = re.sub(re.compile("M107(.*)\n"), "", data[l_index]) - ## Deal with a raft and with One-At-A-Time print sequence + # Deal with a raft and with One-At-A-Time print sequence if raft_enabled and bed_adhesion == "raft": if print_sequence == "one_at_a_time": for r_index in range(2,len(data)-2,1): @@ -486,9 +614,9 @@ class AddCoolingProfile(Script): lines.insert(1, "M106 S0" + str(t0_fan)) if raft_enabled and bed_adhesion == "raft": if ";LAYER:-" in data[r_index]: - ## Turn the raft fan on + # Turn the raft fan on lines.insert(1, fan_sp_raft + str(t0_fan)) - ## Shut the raft fan off at layer 0 + # Shut the raft fan off at layer 0 if ";LAYER:0" in data[r_index]: lines.insert(1,"M106 S0" + str(t0_fan)) data[r_index] = "\n".join(lines) @@ -496,13 +624,13 @@ class AddCoolingProfile(Script): layer = data[raft_start_index] lines = layer.split("\n") if ";LAYER:-" in layer: - ## Turn the raft fan on + # Turn the raft fan on lines.insert(1, fan_sp_raft + str(init_fan)) layer = "\n".join(lines) data[raft_start_index] = layer layer = data[layer_0_index] lines = layer.split("\n") - ## Shut the raft fan off + # Shut the raft fan off lines.insert(1, "M106 S0" + str(init_fan)) data[layer_0_index] = "\n".join(lines) else: @@ -513,41 +641,44 @@ class AddCoolingProfile(Script): lines.insert(1, "M106 S0" + str(t0_fan)) data[r_index] = "\n".join(lines) - ## Turn off all fans at the end of data[1]. If more than one instance of this script is running then this will result in multiple M106 lines. + # Turn off all fans at the end of data[1]. If more than one instance of this script is running then this will result in multiple M106 lines. temp_startup = data[1].split("\n") temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t0_fan)) - ## If there are multiple cooling fans shut them all off + # If there are multiple cooling fans shut them all off if is_multi_fan: - if extruder_count > 1 and t1_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t1_fan)) - if extruder_count > 2 and t2_fan != t1_fan and t2_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t2_fan)) - if extruder_count > 3 and t3_fan != t2_fan and t3_fan != t1_fan and t3_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t3_fan)) + if self.extruder_count > 1 and t1_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t1_fan)) + if self.extruder_count > 2 and t2_fan != t1_fan and t2_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t2_fan)) + if self.extruder_count > 3 and t3_fan != t2_fan and t3_fan != t1_fan and t3_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t3_fan)) data[1] = "\n".join(temp_startup) - ## If 'feature_fan_combing' is True then add additional 'MESH:NONMESH' lines for travel moves over 5 lines long - ## For compatibility with 5.3.0 change any MESH:NOMESH to MESH:NONMESH. + # If 'feature_fan_combing' is True then add additional 'MESH:NONMESH' lines for travel moves over 5 lines long + # For compatibility with 5.3.0 change any MESH:NOMESH to MESH:NONMESH. if feature_fan_combing: for layer_num in range(2,len(data)): layer = data[layer_num] data[layer_num] = re.sub(";MESH:NOMESH", ";MESH:NONMESH", layer) data = self._add_travel_comment(data, layer_0_index) - # Single Fan "By Layer"-------------------------------------------- + if bool(self.getSettingValueByKey("bv_fan_speed_control_enable")): + data = self._control_bv_fan(data) + + # Single Fan "By Layer" if by_layer_or_feature == "by_layer" and not is_multi_fan: return self._single_fan_by_layer(data, layer_0_index, fan_list, t0_fan) - # Multi-Fan "By Layer"--------------------------------------------- + # Multi-Fan "By Layer" if by_layer_or_feature == "by_layer" and is_multi_fan: - return self._multi_fan_by_layer(data, layer_0_index, fan_list, t0_fan, t1_fan, t2_fan, t3_fan) + return self._multi_fan_by_layer(data, layer_0_index, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, fan_mode, off_fan_speed) - #Single Fan "By Feature"------------------------------------------ + # Single Fan "By Feature" if by_layer_or_feature == "by_feature" and (not is_multi_fan or not is_multi_extr_print): return self._single_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, feature_speed_list, feature_name_list, feature_fan_combing) - #Multi Fan "By Feature"------------------------------------------- + # Multi Fan "By Feature" if by_layer_or_feature == "by_feature" and is_multi_fan: - return self._multi_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, feature_speed_list, feature_name_list, feature_fan_combing) + return self._multi_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, feature_speed_list, feature_name_list, feature_fan_combing, fan_mode, off_fan_speed) - # The Single Fan "By Layer"---------------------------------------- + # The Single Fan "By Layer" def _single_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str)->str: layer_number = "0" single_fan_data = data @@ -557,15 +688,15 @@ class AddCoolingProfile(Script): for fan_line in fan_lines: if ";LAYER:" in fan_line: layer_number = str(fan_line.split(":")[1]) - ## If there is a match for the current layer number make the insertion + # If there is a match for the current layer number make the insertion for num in range(0,15,2): if layer_number == str(fan_list[num]): layer = layer.replace(fan_lines[0],fan_lines[0] + "\n" + fan_list[num + 1] + str(t0_fan)) single_fan_data[l_index] = layer return single_fan_data - # Multi-Fan "By Layer"----------------------------------------- - def _multi_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str)->str: + # Multi-Fan "By Layer" + def _multi_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, fan_mode: bool, off_fan_speed: str)->str: multi_fan_data = data layer_number = "0" current_fan_speed = "0" @@ -573,15 +704,16 @@ class AddCoolingProfile(Script): this_fan = str(t0_fan) start_index = str(len(multi_fan_data)) for num in range(0,15,2): - ## The fan_list may not be in ascending order. Get the lowest layer number + + # The fan_list may not be in ascending order. Get the lowest layer number try: if int(fan_list[num]) < int(start_index): start_index = str(fan_list[num]) - except: + except ValueError: pass - ## Move the start point if delete_existing_m106 is false + # Move the start point if delete_existing_m106 is false start_index = int(start_index) + int(layer_0_index) - ## Track the tool number + # Track the tool number for num in range(1,int(start_index),1): layer = multi_fan_data[num] lines = layer.split("\n") @@ -598,18 +730,19 @@ class AddCoolingProfile(Script): elif line == "T3": prev_fan = this_fan this_fan = t3_fan + # With Active Tool tracked - now the body of changes can start for l_index in range(int(start_index),len(multi_fan_data)-1,1): modified_data = "" layer = multi_fan_data[l_index] fan_lines = layer.split("\n") for fan_line in fan_lines: - ## Prepare to shut down the previous fan and start the next one. + # Prepare to turn off the previous fan and start the next one. if fan_line.startswith("T"): if fan_line == "T0": this_fan = str(t0_fan) if fan_line == "T1": this_fan = str(t1_fan) if fan_line == "T2": this_fan = str(t2_fan) if fan_line == "T3": this_fan = str(t3_fan) - modified_data += "M106 S0" + prev_fan + "\n" + modified_data += f"M106 S{off_fan_speed}" + prev_fan + "\n" modified_data += fan_line + "\n" modified_data += "M106 S" + str(current_fan_speed) + this_fan + "\n" prev_fan = this_fan @@ -620,19 +753,22 @@ class AddCoolingProfile(Script): if layer_number == str(fan_list[num]): modified_data += fan_list[num + 1] + this_fan + "\n" current_fan_speed = str(fan_list[num + 1].split("S")[1]) - current_fan_speed = str(current_fan_speed.split(" ")[0]) ## Just in case + current_fan_speed = str(current_fan_speed.split(" ")[0]) # Just in case else: modified_data += fan_line + "\n" if modified_data.endswith("\n"): modified_data = modified_data[0:-1] multi_fan_data[l_index] = modified_data + # Insure the fans get shut off if 'off_fan_speed' was enabled + if self.extruder_count > 1 and self.getSettingValueByKey("enable_off_fan_speed"): + multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n" return multi_fan_data - # Single fan by feature----------------------------------------------- + # Single fan by feature def _single_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool)->str: single_fan_data = data layer_number = "0" index = 1 - ## Start with layer:0 + # Start with layer:0 for l_index in range(layer_0_index,len(single_fan_data)-1,1): modified_data = "" layer = single_fan_data[l_index] @@ -652,15 +788,16 @@ class AddCoolingProfile(Script): if feature_fan_combing == True: modified_data += "M106 S0" + t0_fan + "\n" modified_data += line + "\n" - ## If an End Layer is defined and is less than the last layer then insert the Final Speed + + # If an End Layer is defined and is less than the last layer then insert the Final Speed if line == ";LAYER:" + str(the_end_layer) and the_end_is_enabled == True: modified_data += feature_speed_list[len(feature_speed_list) - 1] + t0_fan + "\n" if modified_data.endswith("\n"): modified_data = modified_data[0: - 1] single_fan_data[l_index] = modified_data return single_fan_data - # Multi-fan by feature------------------------------------------------ - def _multi_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool)->str: + # Multi-fan by feature + def _multi_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool, fan_mode: bool, off_fan_speed: str)->str: multi_fan_data = data layer_number = "0" start_index = 1 @@ -673,7 +810,7 @@ class AddCoolingProfile(Script): if ";LAYER:" + str(the_start_layer) + "\n" in layer: start_index = int(my_index) - 1 break - ## Track the previous tool changes + # Track the previous tool changes for num in range(1,start_index,1): layer = multi_fan_data[num] lines = layer.split("\n") @@ -690,7 +827,7 @@ class AddCoolingProfile(Script): elif line == "T3": prev_fan = this_fan this_fan = t3_fan - ## Get the current tool. + # Get the current tool. for l_index in range(start_index,start_index + 1,1): layer = multi_fan_data[l_index] lines = layer.split("\n") @@ -702,7 +839,7 @@ class AddCoolingProfile(Script): if line == "T3": this_fan = t3_fan prev_fan = this_fan - ## Start to make insertions------------------------------------- + # Start to make insertions for l_index in range(start_index+1,len(multi_fan_data)-1,1): layer = multi_fan_data[l_index] lines = layer.split("\n") @@ -712,10 +849,10 @@ class AddCoolingProfile(Script): if line == "T1": this_fan = t1_fan if line == "T2": this_fan = t2_fan if line == "T3": this_fan = t3_fan - ## Turn off the prev fan - modified_data += "M106 S0" + prev_fan + "\n" + # Turn off the prev fan + modified_data += f"M106 S{off_fan_speed}" + prev_fan + "\n" modified_data += line + "\n" - ## Turn on the current fan + # Turn on the current fan modified_data += "M106 S" + str(current_fan_speed) + this_fan + "\n" prev_fan = this_fan if ";LAYER:" in line: @@ -725,34 +862,39 @@ class AddCoolingProfile(Script): temp = line.split(" ")[0] try: name_index = feature_name_list.index(temp) - except: + except IndexError: name_index = -1 + if name_index != -1: modified_data += line + "\n" + feature_speed_list[name_index] + this_fan + "\n" - #modified_data += feature_speed_list[name_index] + this_fan + "\n" current_fan_speed = str(feature_speed_list[name_index].split("S")[1]) elif ";MESH:NONMESH" in line: if feature_fan_combing == True: modified_data += line + "\n" - modified_data += "M106 S0" + this_fan + "\n" + modified_data += f"M106 S{off_fan_speed}" + this_fan + "\n" current_fan_speed = "0" else: modified_data += line + "\n" - ## If an end layer is defined - Insert the final speed and set the other variables to Final Speed to finish the file - ## There cannot be a break here because if there are multiple fan numbers they still need to be shut off and turned on. + + # If an end layer is defined - Insert the final speed and set the other variables to Final Speed to finish the file + # There cannot be a 'break' here because if there are multiple fan numbers they still need to be shut off and turned on. elif line == ";LAYER:" + str(the_end_layer): modified_data += feature_speed_list[len(feature_speed_list) - 1] + this_fan + "\n" for set_speed in range(0, len(feature_speed_list) - 2): feature_speed_list[set_speed] = feature_speed_list[len(feature_speed_list) - 1] else: - ## Layer and Tool get inserted into modified_data above. All other lines go into modified_data here + # Layer and Tool get inserted into modified_data above. All other lines go into modified_data here if not line.startswith("T") and not line.startswith(";LAYER:"): modified_data += line + "\n" + if modified_data.endswith("\n"): modified_data = modified_data[0: - 1] multi_fan_data[l_index] = modified_data modified_data = "" + # Insure the fans get shut off if 'off_fan_speed' was enabled + if self.extruder_count > 1 and self.getSettingValueByKey("enable_off_fan_speed"): + multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n" return multi_fan_data - #Try to catch layer input errors, set the minimum speed to 12%, and put the strings together + # Try to catch layer input errors, set the minimum speed to 12%, and put the strings together def _layer_checker(self, fan_string: str, ty_pe: str, fan_mode: bool) -> str: fan_string_l = str(fan_string.split("/")[0]) try: @@ -768,7 +910,7 @@ class AddCoolingProfile(Script): if int(fan_string_p) > 100: fan_string_p = "100" except ValueError: fan_string_p = "0" - ## Set the minimum fan speed to 12% + # Set the minimum fan speed to 12% if int(fan_string_p) < 12 and int(fan_string_p) != 0: fan_string_p = "12" fan_layer_line = str(fan_string_l) @@ -784,7 +926,7 @@ class AddCoolingProfile(Script): #Try to catch feature input errors, set the minimum speed to 12%, and put the strings together when 'By Feature' def _feature_checker(self, fan_feat_string: int, fan_mode: bool) -> str: if fan_feat_string < 0: fan_feat_string = 0 - ## Set the minimum fan speed to 12% + # Set the minimum fan speed to 12% if fan_feat_string > 0 and fan_feat_string < 12: fan_feat_string = 12 if fan_feat_string > 100: fan_feat_string = 100 if fan_mode: @@ -798,7 +940,7 @@ class AddCoolingProfile(Script): for lay_num in range(int(lay_0_index), len(comment_data)-1,1): layer = comment_data[lay_num] lines = layer.split("\n") - ## Copy the data to new_data and make the insertions there + # Copy the data to new_data and make the insertions there new_data = lines g0_count = 0 g0_index = -1 @@ -818,12 +960,12 @@ class AddCoolingProfile(Script): if g0_index == -1: g0_index = lines.index(line) elif not line.startswith("G0 ") and not is_travel: - ## Add additional 'NONMESH' lines to shut the fan off during long combing moves-------- + # Add additional 'NONMESH' lines to shut the fan off during long combing moves if g0_count > 5: if not is_travel: new_data.insert(g0_index + insert_index, ";MESH:NONMESH") insert_index += 1 - ## Add the feature_type at the end of the combing move to turn the fan back on + # Add the feature_type at the end of the combing move to turn the fan back on new_data.insert(g0_index + g0_count + 1, feature_type) insert_index += 1 g0_count = 0 @@ -834,4 +976,35 @@ class AddCoolingProfile(Script): g0_index = -1 is_travel = False comment_data[lay_num] = "\n".join(new_data) - return comment_data \ No newline at end of file + return comment_data + + def _control_bv_fan(self, bv_data: str) -> str: + # Control any secondary fan. Can be used for an Auxilliary/Chamber fan + bv_start_layer = self.getSettingValueByKey("bv_fan_start_layer") - 1 + bv_end_layer = self.getSettingValueByKey("bv_fan_end_layer") + bv_fan_nr = self.getSettingValueByKey("bv_fan_nr") + if bv_end_layer != -1: + bv_end_layer -= 1 + # Get the PWM speed or if RepRap then the 0-1 speed + if self.extruder_list[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"): + bv_fan_speed = round(self.getSettingValueByKey("bv_fan_speed") * .01, 1) + else: + bv_fan_speed = int(self.getSettingValueByKey("bv_fan_speed") * 2.55) + # Turn the chamber fan on + for index, layer in enumerate(bv_data): + if f";LAYER:{bv_start_layer}\n" in layer: + bv_data[index] = re.sub(f";LAYER:{bv_start_layer}", f";LAYER:{bv_start_layer}\nM106 S{bv_fan_speed} P{bv_fan_nr}",layer) + break + # Turn the chamber fan off + if bv_end_layer == -1: + bv_data[len(bv_data)-2] += f"M106 S0 P{bv_fan_nr}\n" + else: + for index, layer in enumerate(bv_data): + if f";LAYER:{bv_end_layer}\n" in layer: + lines = layer.split("\n") + for fdex, line in enumerate(lines): + if ";TIME_ELAPSED:" in line: + lines[fdex] = f"M106 S0 P{bv_fan_nr}\n" + line + bv_data[index] = "\n".join(lines) + break + return bv_data \ No newline at end of file diff --git a/plugins/PostProcessingPlugin/scripts/AnnealingOrDrying.py b/plugins/PostProcessingPlugin/scripts/AnnealingOrDrying.py new file mode 100644 index 0000000000..1841a8b62b --- /dev/null +++ b/plugins/PostProcessingPlugin/scripts/AnnealingOrDrying.py @@ -0,0 +1,571 @@ +""" +Copyright (c) 2025 GregValiant (Greg Foresi) + + When Annealing: + The user may elect to hold the build plate at a temperature for a period of time. When the hold expires, the 'Timed Cooldown' will begin. + If there is no 'Hold Time' then the 'Annealing' cooldown will begin when the print ends. In 'Annealing' the bed temperature drops in 3° increments across the time span. + G4 commands are used for the cooldown steps. + If there is a 'Heated Chamber' then the chamber will start to cool when the bed temperature reaches the chamber temperature. + + When drying filament: + The bed must be empty because the printer will auto-home before raising the Z to 'machine_height minus 20mm' and then park the head in the XY. + The bed will heat up to the set point. + G4 commands are used to keep the machine from turning the bed off until the Drying Time has expired. + If you happen to have an enclosure with a fan, the fan can be set up to run during the drying or annealing. + + NOTE: This script uses the G4 Dwell command as a timer. It cannot be canceled from the LCD. If you wish to 'escape' from G4 you might have to cancel the print from the LCD or cycle the printer on and off to reset. +""" + +from UM.Application import Application +from ..Script import Script +from UM.Message import Message + +class AnnealingOrDrying(Script): + + def initialize(self) -> None: + super().initialize() + # Get the Bed Temperature from Cura + self.global_stack = Application.getInstance().getGlobalContainerStack() + bed_temp_during_print = str(self.global_stack.getProperty("material_bed_temperature", "value")) + self._instance.setProperty("startout_temp", "value", bed_temp_during_print) + # Get the Build Volume temperature if there is one + heated_build_volume = bool(self.global_stack.getProperty("machine_heated_build_volume", "value")) + chamber_fan_nr = self.global_stack.getProperty("build_volume_fan_nr", "value") + extruder_count = self.global_stack.getProperty("machine_extruder_count", "value") + if heated_build_volume: + chamber_temp = self.global_stack.getProperty("build_volume_temperature", "value") + self._instance.setProperty("has_build_volume_heater", "value", heated_build_volume) + self._instance.setProperty("build_volume_temp", "value", chamber_temp) + try: + if chamber_fan_nr > 0: + self._instance.setProperty("enable_chamber_fan_setting", "value", True) + except: + pass + + def getSettingDataString(self): + return """{ + "name": "Annealing CoolDown or Filament Drying", + "key": "AnnealingOrDrying", + "metadata": {}, + "version": 2, + "settings": + { + "enable_script": + { + "label": "Enable the Script", + "description": "If it isn't enabled it doesn't run.", + "type": "bool", + "default_value": true, + "enabled": true + }, + "cycle_type": + { + "label": "Anneal Print or Dry Filament", + "description": "Whether to Anneal the Print (by keeping the bed hot for a period of time), or to use the bed as a Filament Dryer. If drying; you will still need to slice a model, but it will not print. The gcode will consist only of a short script to heat the bed, wait for a while, then turn the bed off. The 'Z' will move to the max height and XY park position so the filament can be covered. The 'Hold Time', 'Bed Start Temp' and (if applicable) the 'Chamber Temp' come from these settings rather than from the Cura settings. When annealing; the Timed Cooldown will commence when the print ends.", + "type": "enum", + "options": + { + "anneal_cycle": "Anneal Print", + "dry_cycle": "Dry Filament" + }, + "default_value": "anneal_cycle", + "enabled": true, + "enabled": "enable_script" + }, + "heating_zone_selection": + { + "label": "Hold the Temp for the:", + "description": "Select the 'Bed' for just the bed, or 'Bed and Chamber' if you want to include your 'Heated Build Volume'.", + "type": "enum", + "options": + { + "bed_only": "Bed", + "bed_chamber": "Bed and Chamber" + }, + "default_value": "bed_only", + "enabled": "enable_script" + }, + "wait_time": + { + "label": "Hold Time at Temp(s)", + "description": "Hold the bed temp at the 'Bed Start Out Temperature' for this amount of time (in decimal hours). When this time expires then the Annealing cool down will start. This is also the 'Drying Time' used when 'Drying Filament'.", + "type": "float", + "default_value": 0.0, + "unit": "Decimal Hrs ", + "enabled": "enable_script and cycle_type == 'anneal_cycle'" + }, + "dry_time": + { + "label": "Drying Time", + "description": "Hold the bed temp at the 'Bed Start Out Temperature' for this amount of time (in decimal hours). When this time expires the bed will shut off.", + "type": "float", + "default_value": 4.0, + "unit": "Decimal Hrs ", + "enabled": "enable_script and cycle_type == 'dry_cycle'" + }, + "pause_cmd": + { + "label": "Pause Cmd for Auto-Home", + "description": "Not required when you are paying attention and the bed is empty; ELSE; Enter the pause command to use prior to the Auto-Home command. The pause insures that the user IS paying attention and clears the build plate for Auto-Home. If you leave the box empty then there won't be a pause.", + "type": "str", + "default_value": "", + "enabled": "enable_script and cycle_type == 'dry_cycle'" + }, + "startout_temp": + { + "label": "Bed Start Out Temperature:", + "description": "Enter the temperature to start at. This is typically the bed temperature during the print but can be changed here. This is also the temperature used when drying filament.", + "type": "int", + "value": 30, + "unit": "Degrees ", + "minimum_value": 30, + "maximum_value": 110, + "maximum_value_warning": 100, + "enabled": "enable_script" + }, + "lowest_temp": + { + "label": "Shut-Off Temp:", + "description": "Enter the lowest temperature to control the cool down. This is the shut-off temperature for the build plate and (when applicable) the Heated Chamber. The minimum value is 30", + "type": "int", + "default_value": 30, + "unit": "Degrees ", + "minimum_value": 30, + "enabled": "enable_script and cycle_type == 'anneal_cycle'" + }, + "build_volume_temp": + { + "label": "Build Volume Temperature:", + "description": "Enter the temperature for the Build Volume (Heated Chamber). This is typically the temperature during the print but can be changed here.", + "type": "int", + "value": 24, + "unit": "Degrees ", + "minimum_value": 0, + "maximum_value": 90, + "maximum_value_warning": 75, + "enabled": "enable_script and has_build_volume_heater and heating_zone_selection == 'bed_chamber'" + }, + "enable_chamber_fan_setting": + { + "label": "Hidden Setting", + "description": "Enables chamber fan and speed.", + "type": "bool", + "default_value": false, + "enabled": false + }, + "chamber_fan_speed": + { + "label": "Chamber Fan Speed", + "description": "Set to % fan speed. Set to 0 to turn it off.", + "type": "int", + "default_value": 0, + "minimum_value": 0, + "maximum_value": 100, + "unit": "% ", + "enabled": "enable_script and enable_chamber_fan_setting" + }, + "time_span": + { + "label": "Cool Down Time Span:", + "description": "The total amount of time (in decimal hours) to control the cool down. The build plate temperature will be dropped in 3° increments across this time span. 'Cool Down Time' starts at the end of the 'Hold Time' if you entered one.", + "type": "float", + "default_value": 1.0, + "unit": "Decimal Hrs ", + "minimum_value_warning": 0.25, + "enabled": "enable_script and cycle_type == 'anneal_cycle'" + }, + "park_head": + { + "label": "Park at MaxX and MaxY", + "description": "When unchecked, the park position is X0 Y0. Enable this setting to move the nozzle to the Max X and Max Y to allow access to the print.", + "type": "bool", + "default_value": false, + "enabled": "enable_script and cycle_type == 'anneal_cycle'" + }, + "park_max_z": + { + "label": "Move to MaxZ", + "description": "Enable this setting to move the nozzle to 'Machine_Height - 20' to allow the print to be covered.", + "type": "bool", + "default_value": false, + "enabled": "enable_script and cycle_type == 'anneal_cycle'" + }, + "beep_when_done": + { + "label": "Beep when done", + "description": "Add an annoying noise when the Cool Down completes.", + "type": "bool", + "default_value": true, + "enabled": "enable_script" + }, + "beep_duration": + { + "label": "Beep Duration", + "description": "The length of the buzzer sound. Units are in milliseconds so 1000ms = 1 second.", + "type": "int", + "unit": "milliseconds ", + "default_value": 1000, + "enabled": "beep_when_done and enable_script" + }, + "add_messages": + { + "label": "Include M117 and M118 messages", + "description": "Add messages to the LCD and any print server.", + "type": "bool", + "default_value": false, + "enabled": "enable_script" + }, + "has_build_volume_heater": + { + "label": "Hidden setting", + "description": "Hidden. This setting enables the build volume settings.", + "type": "bool", + "default_value": false, + "enabled": false + } + } + }""" + + def execute(self, data): + # Exit if there is no heated bed. + if not bool(self.global_stack.getProperty("machine_heated_bed", "value")): + Message(title = "[Anneal or Dry Filament]", text = "The script did not run because Heated Bed is disabled in Machine Settings.").show() + return data + # Enter a message in the gcode if the script is not enabled. + if not bool(self.getSettingValueByKey("enable_script")): + data[0] += "; [Anneal or Dry Filament] was not enabled\n" + return data + lowest_temp = int(self.getSettingValueByKey("lowest_temp")) + + # If the shutoff temp is under 30° then exit as a safety precaution so the bed doesn't stay on. + if lowest_temp < 30: + data[0] += "; Anneal or Dry Filament did not run. Shutoff Temp < 30\n" + Message(title = "[Anneal or Dry Filament]", text = "The script did not run because the Shutoff Temp is less than 30°.").show() + return data + extruders = self.global_stack.extruderList + bed_temperature = int(self.getSettingValueByKey("startout_temp")) + heated_chamber = bool(self.global_stack.getProperty("machine_heated_build_volume", "value")) + heating_zone = self.getSettingValueByKey("heating_zone_selection") + + # Get the heated chamber temperature or set to 0 if no chamber + if heated_chamber: + chamber_temp = str(self.getSettingValueByKey("build_volume_temp")) + else: + heating_zone = "bed_only" + chamber_temp = "0" + + # Beep line + if bool(self.getSettingValueByKey("beep_when_done")): + beep_duration = self.getSettingValueByKey("beep_duration") + self.beep_string = f"M300 S440 P{beep_duration} ; Beep\n" + else: + self.beep_string = "" + + # For compatibility with earlier Cura versions + if self.global_stack.getProperty("build_volume_fan_nr", "value") is not None: + has_bv_fan = bool(self.global_stack.getProperty("build_volume_fan_nr", "value")) + bv_fan_nr = int(self.global_stack.getProperty("build_volume_fan_nr", "value")) + if bv_fan_nr > 0: + speed_bv_fan = int(self.getSettingValueByKey("chamber_fan_speed")) + else: + speed_bv_fan = 0 + + if bool(extruders[0].getProperty("machine_scale_fan_speed_zero_to_one", "value")) and has_bv_fan: + speed_bv_fan = round(speed_bv_fan * 0.01) + else: + speed_bv_fan = round(speed_bv_fan * 2.55) + + if has_bv_fan and speed_bv_fan > 0: + self.bv_fan_on_str = f"M106 S{speed_bv_fan} P{bv_fan_nr} ; Build Chamber Fan On\n" + self.bv_fan_off_str = f"M106 S0 P{bv_fan_nr} ; Build Chamber Fan Off\n" + else: + self.bv_fan_on_str = "" + self.bv_fan_off_str = "" + else: + has_bv_fan = False + bv_fan_nr = 0 + speed_bv_fan = 0 + self.bv_fan_on_str = "" + self.bv_fan_off_str = "" + + # Park Head + max_y = str(self.global_stack.getProperty("machine_depth", "value")) + max_x = str(self.global_stack.getProperty("machine_width", "value")) + + # Max_z is limited to 'machine_height - 20' just so the print head doesn't smack into anything. + max_z = str(int(self.global_stack.getProperty("machine_height", "value")) - 20) + speed_travel = str(round(extruders[0].getProperty("speed_travel", "value")*60)) + park_xy = bool(self.getSettingValueByKey("park_head")) + park_z = bool(self.getSettingValueByKey("park_max_z")) + cycle_type = self.getSettingValueByKey("cycle_type") + add_messages = bool(self.getSettingValueByKey("add_messages")) + + if cycle_type == "anneal_cycle": + data = self._anneal_print(add_messages, data, bed_temperature, chamber_temp, heated_chamber, heating_zone, lowest_temp, max_x, max_y, max_z, park_xy, park_z, speed_travel) + elif cycle_type == "dry_cycle": + data = self._dry_filament_only(data, bed_temperature, chamber_temp, heated_chamber, heating_zone, max_y, max_z, speed_travel) + + return data + + def _anneal_print( + self, + add_messages: bool, + anneal_data: str, + bed_temperature: int, + chamber_temp: str, + heated_chamber: bool, + heating_zone: str, + lowest_temp: int, + max_x: str, + max_y: str, + max_z: str, + park_xy: bool, + park_z: bool, + speed_travel: str) -> str: + """ + The procedure disables the M140 (and M141) lines at the end of the print, and adds additional bed (and chamber) temperature commands to the end of the G-Code file. + The bed is allowed to cool down over a period of time. + + :param add_messages: Whether to include M117 and M118 messages for LCD and print server + :param anneal_data: The G-code data to be modified with annealing commands + :param bed_temperature: Starting bed temperature in degrees Celsius + :param chamber_temp: Chamber/build volume temperature in degrees Celsius as string + :param heated_chamber: Whether the printer has a heated build volume/chamber + :param heating_zone: Zone selection - "bed_only" or "bed_chamber" + :param lowest_temp: Final shutdown temperature in degrees Celsius + :param max_x: Maximum X axis position for parking as string + :param max_y: Maximum Y axis position for parking as string + :param max_z: Maximum Z axis position (machine height - 20mm) as string + :param park_xy: Whether to park the print head at max X and Y positions + :param park_z: Whether to raise Z to maximum safe height + :param speed_travel: Travel speed for positioning moves in mm/min as string + :return: Modified G-code data with annealing cooldown sequence + """ + # Put the head parking string together + bed_temp_during_print = int(self.global_stack.getProperty("material_bed_temperature", "value")) + time_minutes = 1 + time_span = int(float(self.getSettingValueByKey("time_span")) * 3600) + park_string = "" + if park_xy: + park_string += f"G0 F{speed_travel} X{max_x} Y{max_y} ; Park XY\n" + if park_z: + park_string += f"G0 Z{max_z} ; Raise Z to 'ZMax - 20'\n" + if not park_xy and not park_z: + park_string += f"G91 ; Relative movement\nG0 F{speed_travel} Z5 ; Raise Z\nG90 ; Absolute movement\nG0 X0 Y0 ; Park\n" + park_string += "M84 X Y E ; Disable steppers except Z\n" + + # Calculate the temperature differential + hysteresis = bed_temperature - lowest_temp + + # Exit if the bed temp is below the shutoff temp + if hysteresis <= 0: + anneal_data[0] += "; Anneal or Dry Filament did not run. Bed Temp < Shutoff Temp\n" + Message(title = "Anneal or Dry Filament", text = "Did not run because the Bed Temp < Shutoff Temp.").show() + return anneal_data + + # Drop the bed temperature in 3° increments. + num_steps = int(hysteresis / 3) + step_index = 2 + deg_per_step = int(hysteresis / num_steps) + time_per_step = int(time_span / num_steps) + step_down = bed_temperature + wait_time = int(float(self.getSettingValueByKey("wait_time")) * 3600) + + # Put the first lines of the anneal string together + anneal_string = ";\n;TYPE:CUSTOM ---------------- Anneal Print\n" + if bed_temperature == bed_temp_during_print: + anneal_string += self.beep_string + if add_messages: + anneal_string += "M117 Cool Down for " + str(round((wait_time + time_span)/3600,2)) + "hr\n" + anneal_string += "M118 Cool Down for " + str(round((wait_time + time_span)/3600,2)) + "hr\n" + anneal_string += self.bv_fan_on_str + if wait_time > 0: + # Add the parking string BEFORE the M190 + anneal_string += park_string + if heating_zone == "bed_only": + anneal_string += f"M190 S{bed_temperature} ; Set the bed temp\n{self.beep_string}" + if heating_zone == "bed_chamber": + anneal_string += f"M190 S{bed_temperature} ; Set the bed temp\nM141 S{chamber_temp} ; Set the chamber temp\n{self.beep_string}" + anneal_string += f"G4 S{wait_time} ; Hold for {round(wait_time / 3600,2)} hrs\n" + else: + # Add the parking string AFTER the M140 + anneal_string += f"M140 S{step_down} ; Set bed temp\n" + anneal_string += park_string + anneal_string += f"G4 S{time_per_step} ; wait time in seconds\n" + + step_down -= deg_per_step + time_remaining = round(time_span/3600,2) + + # Step the bed/chamber temps down and add each step to the anneal string. The chamber remains at it's temperature until the bed gets down to that temperature. + for num in range(bed_temperature, lowest_temp, -3): + anneal_string += f"M140 S{step_down} ; Step down bed\n" + if heating_zone == "bed_chamber" and int(step_down) < int(chamber_temp): + anneal_string += f"M141 S{step_down} ; Step down chamber\n" + anneal_string += f"G4 S{time_per_step} ; Wait\n" + if time_remaining >= 1.00: + if add_messages: + anneal_string += f"M117 CoolDown - {round(time_remaining,1)}hr\n" + anneal_string += f"M118 CoolDown - {round(time_remaining,1)}hr\n" + elif time_minutes > 0: + time_minutes = round(time_remaining * 60,1) + if add_messages: + anneal_string += f"M117 CoolDown - {time_minutes}min\n" + anneal_string += f"M118 CoolDown - {time_minutes}min\n" + time_remaining = round((time_span-(step_index*time_per_step))/3600,2) + step_down -= deg_per_step + step_index += 1 + if step_down <= lowest_temp: + break + + # Close out the anneal string + anneal_string += "M140 S0 ; Shut off the bed heater" + "\n" + if heating_zone == "bed_chamber": + anneal_string += "M141 S0 ; Shut off the chamber heater\n" + anneal_string += self.bv_fan_off_str + anneal_string += self.beep_string + if add_messages: + anneal_string += "M117 CoolDown Complete\n" + anneal_string += "M118 CoolDown Complete\n" + anneal_string += ";TYPE:CUSTOM ---------------- End of Anneal\n;" + + # Format the inserted lines. + anneal_lines = anneal_string.split("\n") + for index, line in enumerate(anneal_lines): + if not line.startswith(";") and ";" in line: + front_txt = anneal_lines[index].split(";")[0] + back_txt = anneal_lines[index].split(";")[1] + anneal_lines[index] = front_txt + str(" " * (30 - len(front_txt))) +";" + back_txt + anneal_string = "\n".join(anneal_lines) + "\n" + + end_gcode = anneal_data[-1] + end_lines = end_gcode.split("\n") + + # Comment out the existing M140 S0 lines in the ending gcode. + for num in range(len(end_lines)-1,-1,-1): + if end_lines[num].startswith("M140 S0"): + end_lines[num] = ";M140 S0 ; Shutoff Overide - Anneal or Dry Filament" + anneal_data[-1] = "\n".join(end_lines) + + # If there is a Heated Chamber and it's included then comment out the M141 S0 line + if heating_zone == "bed_chamber" and heated_chamber: + for num in range(0,len(end_lines)-1): + if end_lines[num].startswith("M141 S0"): + end_lines[num] = ";M141 S0 ; Shutoff Overide - Anneal or Dry Filament" + anneal_data[-1] = "\n".join(end_lines) + + # If park head is enabled then dont let the steppers disable until the head is parked + disable_string = "" + for num in range(0,len(end_lines)-1): + if end_lines[num][:3] in ("M84", "M18"): + disable_string = end_lines[num] + "\n" + stepper_timeout = int(wait_time + time_span) + if stepper_timeout > 14400: stepper_timeout = 14400 + end_lines[num] = ";" + end_lines[num] + " ; Overide - Anneal or Dry Filament" + end_lines.insert(num, "M84 S" + str(stepper_timeout) + " ; Increase stepper timeout - Anneal or Dry Filament") + anneal_data[-1] = "\n".join(end_lines) + break + + # The Anneal string is the new end of the gcode so move the 'End of Gcode' comment line in case there are other scripts running + anneal_data[-1] = anneal_data[-1].replace(";End of Gcode", anneal_string + disable_string + ";End of Gcode") + return anneal_data + + def _dry_filament_only( + self, + bed_temperature: int, + chamber_temp: int, + drydata: str, + heated_chamber: bool, + heating_zone: str, + max_y: str, + max_z: str, + speed_travel: str) -> str: + """ + This procedure turns the bed on, homes the printer, parks the head. After the time period the bed is turned off. + There is no actual print in the generated gcode, just a couple of moves to get the nozzle out of the way, and the bed heat (and possibly chamber heat) control. + It allows a user to use the bed to warm up and hopefully dry a filament roll. + + :param bed_temperature: Bed temperature for drying in degrees Celsius + :param chamber_temp: Chamber/build volume temperature for drying in degrees Celsius + :param drydata: The G-code data to be replaced with filament drying commands + :param heated_chamber: Whether the printer has a heated build volume/chamber + :param heating_zone: Zone selection - "bed_only" or "bed_chamber" + :param max_y: Maximum Y axis position for parking as string + :param max_z: Maximum Z axis position (machine height - 20mm) as string + :param speed_travel: Travel speed for positioning moves in mm/min as string + :return: Modified G-code data containing only filament drying sequence + """ + for num in range(2, len(drydata)): + drydata[num] = "" + drydata[0] = drydata[0].split("\n")[0] + "\n" + add_messages = bool(self.getSettingValueByKey("add_messages")) + pause_cmd = self.getSettingValueByKey("pause_cmd") + if pause_cmd != "": + pause_cmd = self.beep_string + pause_cmd + dry_time = self.getSettingValueByKey("dry_time") * 3600 + lines = drydata[1].split("\n") + drying_string = lines[0] + f"\n;............TYPE:CUSTOM: Dry Filament\n{self.beep_string}" + if add_messages: + drying_string += f"M117 Cool Down for {round(dry_time/3600,2)} hr ; Message\n" + drying_string += f"M118 Cool Down for {round(dry_time/3600,2)} hr ; Message\n" + + # M113 sends messages to a print server as a 'Keep Alive' and can generate a lot of traffic over the USB + drying_string += "M113 S0 ; No echo\n" + drying_string += f"M84 S{round(dry_time)} ; Set stepper timeout\n" + drying_string += f"M140 S{bed_temperature} ; Heat bed\n" + drying_string += self.bv_fan_on_str + if heated_chamber and heating_zone == "bed_chamber": + drying_string += f"M141 S{chamber_temp} ; Chamber temp\n" + if pause_cmd == "M0": + pause_cmd = "M0 Clear bed and click...; Pause" + if pause_cmd != "": + drying_string += pause_cmd + " ; Pause\n" + drying_string += "G28 ; Auto-Home\n" + drying_string += f"G0 F{speed_travel} Z{max_z} ; Raise Z to 'ZMax - 20'\n" + drying_string += f"G0 F{speed_travel} X0 Y{max_y} ; Park print head\n" + if dry_time <= 3600: + if add_messages: + drying_string += f"M117 {dry_time/3600} hr remaining ; Message\n" + drying_string += f"M118 {dry_time/3600} hr remaining ; Message\n" + drying_string += f"G4 S{dry_time} ; Dry time\n" + elif dry_time > 3600: + temp_time = dry_time + while temp_time > 3600: + if add_messages: + drying_string += f"M117 {temp_time/3600} hr remaining ; Message\n" + drying_string += f"M118 {temp_time/3600} hr remaining ; Message\n" + drying_string += f"G4 S3600 ; Dry time split\n" + if temp_time > 3600: + temp_time -= 3600 + if temp_time > 0: + if add_messages: + drying_string += f"M117 {temp_time/3600} hr remaining ; Message\n" + drying_string += f"M118 {temp_time/3600} hr remaining ; Message\n" + drying_string += f"G4 S{temp_time} ; Dry time\n" + if heated_chamber and heating_zone == "bed_chamber": + drying_string += f"M141 S0 ; Shut off chamber\n" + drying_string += "M140 S0 ; Shut off bed\n" + drying_string += self.bv_fan_off_str + if self.getSettingValueByKey("beep_when_done"): + beep_duration = self.getSettingValueByKey("beep_duration") + drying_string += self.beep_string + if add_messages: + drying_string += "M117 End of drying cycle ; Message\n" + drying_string += "M118 End of drying cycle ; Message\n" + drying_string += "M84 X Y E ; Disable steppers except Z\n" + drying_string += ";End of Gcode" + + # Format the lines + lines = drying_string.split("\n") + for index, line in enumerate(lines): + if not line.startswith(";") and ";" in line: + front_txt = lines[index].split(";")[0] + back_txt = lines[index].split(";")[1] + lines[index] = front_txt + str(" " * (30 - len(front_txt))) +";" + back_txt + drydata[1] = "\n".join(lines) + "\n" + dry_txt = "; Drying time ...................... " + str(self.getSettingValueByKey("dry_time")) + " hrs\n" + dry_txt += "; Drying temperature ........ " + str(bed_temperature) + "°\n" + if heated_chamber and heating_zone == "bed_chamber": + dry_txt += "; Chamber temperature ... " + str(chamber_temp) + "°\n" + Message(title = "[Dry Filament]", text = dry_txt).show() + drydata[0] = "; <<< This is a filament drying file only. There is no actual print. >>>\n;\n" + dry_txt + ";\n" + return drydata diff --git a/plugins/PostProcessingPlugin/scripts/CreateThumbnail.py b/plugins/PostProcessingPlugin/scripts/CreateThumbnail.py index 7d6094ade3..1ee85bdc0b 100644 --- a/plugins/PostProcessingPlugin/scripts/CreateThumbnail.py +++ b/plugins/PostProcessingPlugin/scripts/CreateThumbnail.py @@ -35,16 +35,21 @@ class CreateThumbnail(Script): def _convertSnapshotToGcode(self, encoded_snapshot, width, height, chunk_size=78): gcode = [] + use_thumbnail = self.getSettingValueByKey("use_thumbnail") + use_star = self.getSettingValueByKey("use_star") + encoded_snapshot_length = len(encoded_snapshot) + image_type = "thumbnail" if use_thumbnail else "png" + resolution_symbol = '*' if use_star else 'x' gcode.append(";") - gcode.append("; thumbnail begin {}x{} {}".format( - width, height, encoded_snapshot_length)) + gcode.append("; {} begin {}{}{} {}".format( + image_type, width, resolution_symbol, height, encoded_snapshot_length)) chunks = ["; {}".format(encoded_snapshot[i:i+chunk_size]) for i in range(0, len(encoded_snapshot), chunk_size)] gcode.extend(chunks) - gcode.append("; thumbnail end") + gcode.append("; {} end".format(image_type)) gcode.append(";") gcode.append("") @@ -79,6 +84,20 @@ class CreateThumbnail(Script): "minimum_value": "0", "minimum_value_warning": "12", "maximum_value_warning": "600" + }, + "use_thumbnail": + { + "label": "Thumbnail Begin/End", + "description": "Use Thumbnail Begin/End rather than PNG", + "type": "bool", + "default_value": true + }, + "use_star": + { + "label": "Use '*' for size of image", + "description": "Use '*' instead of 'x' for size of image as Width '*' Height", + "type": "bool", + "default_value": false } } }""" diff --git a/plugins/PostProcessingPlugin/scripts/DisplayInfoOnLCD.py b/plugins/PostProcessingPlugin/scripts/DisplayInfoOnLCD.py index 63c1c8c788..edfa9d8632 100644 --- a/plugins/PostProcessingPlugin/scripts/DisplayInfoOnLCD.py +++ b/plugins/PostProcessingPlugin/scripts/DisplayInfoOnLCD.py @@ -1,31 +1,36 @@ -# Display Filename and Layer on the LCD by Amanda de Castilho on August 28, 2018 -# Modified: Joshua Pope-Lewis on November 16, 2018 -# Display Progress on LCD by Mathias Lyngklip Kjeldgaard, Alexander Gee, Kimmo Toivanen, Inigo Martinez on July 31, 2019 -# Show Progress was adapted from Display Progress by Louis Wooters on January 6, 2020. His changes are included here. -#--------------------------------------------------------------- -# DisplayNameOrProgressOnLCD.py -# Cura Post-Process plugin -# Combines 'Display Filename and Layer on the LCD' with 'Display Progress' -# Combined and with additions by: GregValiant (Greg Foresi) -# Date: September 8, 2023 -# NOTE: This combined post processor will make 'Display Filename and Layer on the LCD' and 'Display Progress' obsolete -# Description: Display Filename and Layer options: -# Status messages sent to the printer... -# - Scrolling (SCROLL_LONG_FILENAMES) if enabled in Marlin and you aren't printing a small item select this option. -# - Name: By default it will use the name generated by Cura (EG: TT_Test_Cube) - You may enter a custom name here -# - Start Num: Choose which number you prefer for the initial layer, 0 or 1 -# - Max Layer: Enabling this will show how many layers are in the entire print (EG: Layer 1 of 265!) -# - Add prefix 'Printing': Enabling this will add the prefix 'Printing' -# - Example Line on LCD: Printing Layer 0 of 395 3DBenchy -# Display Progress options: -# - Display Total Layer Count -# - Disply Time Remaining for the print -# - Time Fudge Factor % - Divide the Actual Print Time by the Cura Estimate. Enter as a percentage and the displayed time will be adjusted. This allows you to bring the displayed time closer to reality (Ex: Entering 87.5 would indicate an adjustment to 87.5% of the Cura estimate). -# - Example line on LCD: 1/479 | ET 2h13m -# - Time to Pauses changes the M117/M118 lines to countdown to the next pause as 1/479 | TP 2h36m -# - 'Add M118 Line' is available with either option. M118 will bounce the message back to a remote print server through the USB connection. -# - 'Add M73 Line' is used by 'Display Progress' only. There are options to incluse M73 P(percent) and M73 R(time remaining) -# - Enable 'Finish-Time' Message - when enabled, takes the Print Time and calculates when the print will end. It takes into account the Time Fudge Factor. The user may enter a print start time. This is also available for Display Filename. +""" +Display Filename and Layer on the LCD by Amanda de Castilho on August 28, 2018 + Modified: Joshua Pope-Lewis on November 16, 2018 + Display Progress on LCD by Mathias Lyngklip Kjeldgaard, Alexander Gee, Kimmo Toivanen, Inigo Martinez on July 31, 2019 + Show Progress was adapted from Display Progress by Louis Wooters on January 6, 2020. His changes are included here. +--------------------------------------------------------------- + DisplayNameOrProgressOnLCD.py + Cura Post-Process plugin + Combines 'Display Filename and Layer on the LCD' with 'Display Progress' + Combined and with additions by: GregValiant (Greg Foresi) + Date: September 8, 2023 + Date: March 31, 2024 - Bug fix for problem with adding M118 lines if 'Remaining Time' was not checked. + NOTE: This combined post processor will make 'Display Filename and Layer on the LCD' and 'Display Progress' obsolete + Description: Display Filename and Layer options: + Status messages sent to the printer... + - Scrolling (SCROLL_LONG_FILENAMES) if enabled in Marlin and you aren't printing a small item select this option. + - Name: By default it will use the name generated by Cura (EG: TT_Test_Cube) - You may enter a custom name here + - Start Num: Choose which number you prefer for the initial layer, 0 or 1 + - Max Layer: Enabling this will show how many layers are in the entire print (EG: Layer 1 of 265!) + - Add prefix 'Printing': Enabling this will add the prefix 'Printing' + - Example Line on LCD: Printing Layer 0 of 395 3DBenchy + Display Progress options: + - Display Total Layer Count + - Disply Time Remaining for the print + - Time Fudge Factor % - Divide the Actual Print Time by the Cura Estimate. Enter as a percentage and the displayed time will be adjusted. + This allows you to bring the displayed time closer to reality (Ex: Entering 87.5 would indicate an adjustment to 87.5% of the Cura estimate). + - Example line on LCD: 1/479 | ET 2h13m + - Time to Pauses changes the M117/M118 lines to countdown to the next pause as 1/479 | TP 2h36m + - 'Add M118 Line' is available with either option. M118 will bounce the message back to a remote print server through the USB connection. + - 'Add M73 Line' is used by 'Display Progress' only. There are options to incluse M73 P(percent) and M73 R(time remaining) + - Enable 'Finish-Time' Message - when enabled, takes the Print Time and calculates when the print will end. It uses the Time Fudge Factor. The user may enter a print start time. +Date: June 30, 2025 Cost of electricity added to the other print statistics in '_add_stats'. +""" from ..Script import Script from UM.Application import Application @@ -37,6 +42,19 @@ from UM.Message import Message class DisplayInfoOnLCD(Script): + def initialize(self) -> None: + super().initialize() + try: + if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "all_at_once": + enable_countdown = True + self._instance.setProperty("enable_countdown", "value", enable_countdown) + except AttributeError: + # Handle cases where the global container stack or its properties are not accessible + pass + except KeyError: + # Handle cases where the "print_sequence" property is missing + pass + def getSettingDataString(self): return """{ "name": "Display Info on LCD", @@ -77,7 +95,7 @@ class DisplayInfoOnLCD(Script): "label": "Initial layer number:", "description": "Choose which number you prefer for the initial layer, 0 or 1", "type": "int", - "default_value": 0, + "default_value": 1, "minimum_value": 0, "maximum_value": 1, "enabled": "display_option == 'filename_layer'" @@ -114,17 +132,40 @@ class DisplayInfoOnLCD(Script): "default_value": true, "enabled": "display_option == 'display_progress'" }, + "add_m117_line": + { + "label": "Add M117 Line", + "description": "M117 sends a message to the LCD screen. Some screen firmware will not accept or display messages.", + "type": "bool", + "default_value": true + }, "add_m118_line": { "label": "Add M118 Line", "description": "Adds M118 in addition to the M117. It will bounce the message back through the USB port to a computer print server (if a printer server like Octoprint or Pronterface is in use).", "type": "bool", - "default_value": false + "default_value": true + }, + "add_m118_a1": + { + "label": " Add A1 to M118 Line", + "description": "Adds A1 parameter. A1 adds a double foreslash '//' to the response. Octoprint may require this.", + "type": "bool", + "default_value": false, + "enabled": "add_m118_line" + }, + "add_m118_p0": + { + "label": " Add P0 to M118 Line", + "description": "Adds P0 parameter. P0 has the printer send the response out through all it's ports. Octoprint may require this.", + "type": "bool", + "default_value": false, + "enabled": "add_m118_line" }, "add_m73_line": { "label": "Add M73 Line(s)", - "description": "Adds M73 in addition to the M117. For some firmware this will set the printers time and or percentage.", + "description": "Adds M73 in addition to the M117. For some firmware this will set the printers time and or percentage. M75 is added to the beginning of the file and M77 is added to the end of the file. M73 will be added if one or both of the following options is chosen.", "type": "bool", "default_value": false, "enabled": "display_option == 'display_progress'" @@ -132,7 +173,7 @@ class DisplayInfoOnLCD(Script): "add_m73_percent": { "label": " Add M73 Percentage", - "description": "Adds M73 with the P parameter. For some firmware this will set the printers 'percentage' of layers completed and it will count upward.", + "description": "Adds M73 with the P parameter to the start of each layer. For some firmware this will set the printers 'percentage' of layers completed and it will count upward.", "type": "bool", "default_value": false, "enabled": "add_m73_line and display_option == 'display_progress'" @@ -140,10 +181,10 @@ class DisplayInfoOnLCD(Script): "add_m73_time": { "label": " Add M73 Time", - "description": "Adds M73 with the R parameter. For some firmware this will set the printers 'print time' and it will count downward.", + "description": "Adds M73 with the R parameter to the start of each layer. For some firmware this will set the printers 'print time' and it will count downward.", "type": "bool", "default_value": false, - "enabled": "add_m73_line and display_option == 'display_progress'" + "enabled": "add_m73_line and display_option == 'display_progress' and display_remaining_time" }, "speed_factor": { @@ -154,13 +195,29 @@ class DisplayInfoOnLCD(Script): "default_value": 100, "enabled": "enable_end_message or display_option == 'display_progress'" }, + "enable_countdown": + { + "label": "Enable Countdown to Pauses", + "description": "If print sequence is 'one_at_a_time' this is false. This setting is always hidden.", + "type": "bool", + "value": false, + "enabled": false + }, "countdown_to_pause": { "label": "Countdown to Pauses", - "description": "Instead of the remaining print time the LCD will show the estimated time to pause (TP).", + "description": "This must run AFTER any script that adds a pause. Instead of the remaining print time the LCD will show the estimated time to the next layer that has a pause (TP). Countdown to Pause is not available when in One-at-a-Time' mode.", "type": "bool", "default_value": false, - "enabled": "display_option == 'display_progress'" + "enabled": "display_option == 'display_progress' and enable_countdown and display_remaining_time" + }, + "pause_cmd": + { + "label": " What pause command(s) are used?", + "description": "This might be M0, or M25 or M600 if Filament Change is used. If you have mixed commands then delimit them with a comma ',' (Ex: M0,M600). Spaces are not allowed.", + "type": "str", + "default_value": "M0", + "enabled": "display_option == 'display_progress' and countdown_to_pause and enable_countdown and display_remaining_time" }, "enable_end_message": { @@ -173,11 +230,29 @@ class DisplayInfoOnLCD(Script): "print_start_time": { "label": "Print Start Time (Ex 16:45)", - "description": "Use 'Military' time. 16:45 would be 4:45PM. 09:30 would be 9:30AM. If you leave this blank it will be assumed that the print will start Now. If you enter a guesstimate of your printer start time and that time is before 'Now' the guesstimate will consider that the print will start tomorrow at the entered time. ", + "description": "Use 'Military' time. 16:45 would be 4:45PM. 09:30 would be 9:30AM. If you leave this blank it will be assumed that the print will start Now. If you enter a guesstimate of your printer start time and that time is before 'Now' then the guesstimate will consider that the print will start tomorrow at the entered time. ", "type": "str", "default_value": "", "unit": "hrs ", "enabled": "enable_end_message" + }, + "electricity_cost": + { + "label": "Electricity Cost per kWh", + "description": "Cost of electricity per kilowatt-hour. This should be on your electric utility bill.", + "type": "float", + "default_value": 0.151, + "minimum_value": 0, + "unit": "€/kWh " + }, + "printer_power_usage": + { + "label": "Printer Power Usage", + "description": "Average power usage of the 3D printer in Watts. The actual wattage has many variables. 50% of the power supply rating would be a ballpark figure.", + "type": "float", + "default_value": 175, + "minimum_value": 0, + "unit": "Watts " } } @@ -185,239 +260,303 @@ class DisplayInfoOnLCD(Script): def execute(self, data): display_option = self.getSettingValueByKey("display_option") - add_m118_line = self.getSettingValueByKey("add_m118_line") - add_m73_line = self.getSettingValueByKey("add_m73_line") - add_m73_time = self.getSettingValueByKey("add_m73_time") - add_m73_percent = self.getSettingValueByKey("add_m73_percent") - - # This is Display Filename and Layer on LCD--------------------------------------------------------- + self.add_m117_line = self.getSettingValueByKey("add_m117_line") + self.add_m118_line = self.getSettingValueByKey("add_m118_line") + self.add_m118_a1 = self.getSettingValueByKey("add_m118_a1") + self.add_m118_p0 = self.getSettingValueByKey("add_m118_p0") + self.m118_text = "M118 " + self.add_m73_line = self.getSettingValueByKey("add_m73_line") + self.add_m73_time = self.getSettingValueByKey("add_m73_time") + self.add_m73_percent = self.getSettingValueByKey("add_m73_percent") + self.m73_str = "" + para_1 = data[0].split("\n") + for line in para_1: + if line.startswith(";TIME:") or line.startswith(";PRINT.TIME:"): + self.time_total = int(line.split(":")[1]) + break if display_option == "filename_layer": - max_layer = 0 - lcd_text = "M117 " - if self.getSettingValueByKey("file_name") != "": - file_name = self.getSettingValueByKey("file_name") - else: - file_name = Application.getInstance().getPrintInformation().jobName - if self.getSettingValueByKey("addPrefixPrinting"): - lcd_text += "Printing " - if not self.getSettingValueByKey("scroll"): - lcd_text += "Layer " - else: - lcd_text += file_name + " - Layer " - i = self.getSettingValueByKey("startNum") - for layer in data: - display_text = lcd_text + str(i) - layer_index = data.index(layer) - lines = layer.split("\n") - for line in lines: - if line.startswith(";LAYER_COUNT:"): - max_layer = line - max_layer = max_layer.split(":")[1] - if self.getSettingValueByKey("startNum") == 0: - max_layer = str(int(max_layer) - 1) - if line.startswith(";LAYER:"): - if self.getSettingValueByKey("maxlayer"): - display_text = display_text + " of " + max_layer - if not self.getSettingValueByKey("scroll"): - display_text = display_text + " " + file_name - else: - if not self.getSettingValueByKey("scroll"): - display_text = display_text + " " + file_name + "!" - else: - display_text = display_text + "!" - line_index = lines.index(line) - lines.insert(line_index + 1, display_text) - if add_m118_line: - lines.insert(line_index + 2, str(display_text.replace("M117", "M118", 1))) - i += 1 - final_lines = "\n".join(lines) - data[layer_index] = final_lines - if bool(self.getSettingValueByKey("enable_end_message")): - message_str = self.message_to_user(self.getSettingValueByKey("speed_factor") / 100) - Message(title = "Display Info on LCD - Estimated Finish Time", text = message_str[0] + "\n\n" + message_str[1] + "\n" + message_str[2] + "\n" + message_str[3]).show() - return data + data = self._display_filename_layer(data) + else: + data = self._display_progress(data) + return data - # Display Progress (from 'Show Progress' and 'Display Progress on LCD')--------------------------------------- - elif display_option == "display_progress": - # get settings - display_total_layers = self.getSettingValueByKey("display_total_layers") - display_remaining_time = self.getSettingValueByKey("display_remaining_time") - speed_factor = self.getSettingValueByKey("speed_factor") / 100 - m73_time = False - m73_percent = False - if add_m73_line and add_m73_time: - m73_time = True - if add_m73_line and add_m73_percent: - m73_percent = True - # initialize global variables - first_layer_index = 0 - time_total = 0 - number_of_layers = 0 - time_elapsed = 0 - # if at least one of the settings is disabled, there is enough room on the display to display "layer" - first_section = data[0] - lines = first_section.split("\n") + # This is from the original 'Display Filename and Layer on LCD' + def _display_filename_layer(self, data: str) -> str: + data[0] = self._add_stats(data) + max_layer = 0 + format_option = self.getSettingValueByKey("format_option") + lcd_text = "M117 " + octo_text = "M118 " + if self.getSettingValueByKey("file_name") != "": + file_name = self.getSettingValueByKey("file_name") + else: + file_name = Application.getInstance().getPrintInformation().jobName + if self.getSettingValueByKey("addPrefixPrinting"): + lcd_text += "Printing " + octo_text += "Printing " + if not format_option: + lcd_text += "Lay " + octo_text += "Layer " + else: + lcd_text += file_name + " - Layer " + octo_text += file_name + " - Layer " + i = self.getSettingValueByKey("startNum") + for layer in data: + display_text = lcd_text + str(i) + self.m118_text = octo_text + str(i) + layer_index = data.index(layer) + lines = layer.split("\n") for line in lines: - if line.startswith(";TIME:"): - tindex = lines.index(line) - cura_time = int(line.split(":")[1]) - print_time = cura_time * speed_factor - hhh = print_time/3600 - hr = round(hhh // 1) - mmm = round((hhh % 1) * 60) - orig_hhh = cura_time/3600 - orig_hr = round(orig_hhh // 1) - orig_mmm = math.floor((orig_hhh % 1) * 60) - orig_sec = round((((orig_hhh % 1) * 60) % 1) * 60) - if add_m118_line: lines.insert(tindex + 3,"M118 Adjusted Print Time " + str(hr) + "hr " + str(mmm) + "min") - lines.insert(tindex + 3,"M117 ET " + str(hr) + "hr " + str(mmm) + "min") - # add M73 line at beginning - mins = int(60 * hr + mmm) - if m73_time: - lines.insert(tindex + 3, "M73 R{}".format(mins)) - if m73_percent: - lines.insert(tindex + 3, "M73 P0") - # If Countdonw to pause is enabled then count the pauses - pause_str = "" - if bool(self.getSettingValueByKey("countdown_to_pause")): - pause_count = 0 - for num in range(2,len(data) - 1, 1): - if "PauseAtHeight.py" in data[num]: - pause_count += 1 - pause_str = f" with {pause_count} pause(s)" - # This line goes in to convert seconds to hours and minutes - lines.insert(tindex + 3, f";Cura Time Estimate: {cura_time}sec = {orig_hr}hr {orig_mmm}min {orig_sec}sec {pause_str}") - data[0] = "\n".join(lines) - data[len(data)-1] += "M117 Orig Cura Est " + str(orig_hr) + "hr " + str(orig_mmm) + "min\n" - if add_m118_line: data[len(data)-1] += "M118 Est w/FudgeFactor " + str(speed_factor * 100) + "% was " + str(hr) + "hr " + str(mmm) + "min\n" - if not display_total_layers or not display_remaining_time: - base_display_text = "layer " - else: - base_display_text = "" - layer = data[len(data)-1] - data[len(data)-1] = layer.replace(";End of Gcode" + "\n", "") - data[len(data)-1] += ";End of Gcode" + "\n" - # Search for the number of layers and the total time from the start code - for index in range(len(data)): - data_section = data[index] - # We have everything we need, save the index of the first layer and exit the loop - if ";LAYER:" in data_section: - first_layer_index = index - break - else: - for line in data_section.split("\n"): - if line.startswith(";LAYER_COUNT:"): - number_of_layers = int(line.split(":")[1]) - elif line.startswith(";TIME:"): - time_total = int(line.split(":")[1]) - # for all layers... - current_layer = 0 - for layer_counter in range(len(data)-2): - current_layer += 1 - layer_index = first_layer_index + layer_counter - display_text = base_display_text - display_text += str(current_layer) - # create a list where each element is a single line of code within the layer - lines = data[layer_index].split("\n") - if not ";LAYER:" in data[layer_index]: - current_layer -= 1 - continue - # add the total number of layers if this option is checked - if display_total_layers: - display_text += "/" + str(number_of_layers) - # if display_remaining_time is checked, it is calculated in this loop - if display_remaining_time: - time_remaining_display = " | ET " # initialize the time display - m = (time_total - time_elapsed) // 60 # estimated time in minutes - m *= speed_factor # correct for printing time - m = int(m) - h, m = divmod(m, 60) # convert to hours and minutes - # add the time remaining to the display_text - if h > 0: # if it's more than 1 hour left, display format = xhxxm - time_remaining_display += str(h) + "h" - if m < 10: # add trailing zero if necessary - time_remaining_display += "0" - time_remaining_display += str(m) + "m" + if line.startswith(";LAYER_COUNT:"): + max_layer = line + max_layer = max_layer.split(":")[1] + if self.getSettingValueByKey("startNum") == 0: + max_layer = str(int(max_layer) - 1) + if line.startswith(";LAYER:"): + if self.getSettingValueByKey("maxlayer"): + display_text += "/" + max_layer + self.m118_text += "/" + max_layer + if not format_option: + display_text += "|" + file_name + self.m118_text += " | " + file_name else: - time_remaining_display += str(m) + "m" - display_text += time_remaining_display - # find time_elapsed at the end of the layer (used to calculate the remaining time of the next layer) - if not current_layer == number_of_layers: - for line_index in range(len(lines) - 1, -1, -1): - line = lines[line_index] - if line.startswith(";TIME_ELAPSED:"): - # update time_elapsed for the NEXT layer and exit the loop - time_elapsed = int(float(line.split(":")[1])) - break - # insert the text AFTER the first line of the layer (in case other scripts use ";LAYER:") - for l_index, line in enumerate(lines): - if line.startswith(";LAYER:"): + if not format_option: + display_text += "|" + file_name + "!" + self.m118_text += " | " + file_name + "!" + else: + display_text += "!" + self.m118_text += "!" + line_index = lines.index(line) + if self.add_m117_line: + lines.insert(line_index + 1, display_text) + if self.add_m118_line: + if self.add_m118_a1: + self.m118_text = self.m118_text.replace("M118 ","M118 A1 ") + if self.add_m118_p0: + self.m118_text = self.m118_text.replace("M118 ","M118 P0 ") + lines.insert(line_index + 2, self.m118_text) + i += 1 + final_lines = "\n".join(lines) + data[layer_index] = final_lines + if bool(self.getSettingValueByKey("enable_end_message")): + message_str = self._message_to_user(self.getSettingValueByKey("speed_factor") / 100) + Message(title = "Display Info on LCD - Estimated Finish Time", text = message_str[0] + "\n\n" + message_str[1] + "\n" + message_str[2] + "\n" + message_str[3]).show() + return data + + # This is from 'Show Progress on LCD' + def _display_progress(self, data: str) -> str: + # Add some common print settings to the start of the gcode + data[0] = self._add_stats(data) + # Get settings + print_sequence = Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") + display_total_layers = self.getSettingValueByKey("display_total_layers") + display_remaining_time = self.getSettingValueByKey("display_remaining_time") + speed_factor = self.getSettingValueByKey("speed_factor") / 100 + m73_time = False + m73_percent = False + if self.add_m73_line and self.add_m73_time: + m73_time = True + if self.add_m73_line and self.add_m73_percent: + m73_percent = True + if self.add_m73_line: + data[1] = "M75\n" + data[1] + data[len(data)-1] += "M77\n" + # Initialize some variables + first_layer_index = 0 + number_of_layers = 0 + time_elapsed = 0 + + # If at least one of the settings is disabled, there is enough room on the display to display "layer" + first_section = data[0] + lines = first_section.split("\n") + pause_cmd = [] + for line in lines: + if line.startswith(";TIME:"): + tindex = lines.index(line) + cura_time = int(line.split(":")[1]) + print_time = cura_time * speed_factor + hhh = print_time/3600 + hr = round(hhh // 1) + mmm = round((hhh % 1) * 60) + orig_hhh = cura_time/3600 + orig_hr = round(orig_hhh // 1) + orig_mmm = math.floor((orig_hhh % 1) * 60) + if self.add_m118_line: + lines.insert(len(lines) - 2, f"M118 Adjusted Print Time is {hr} hr {mmm} min") + if self.add_m117_line: + lines.insert(len(lines) - 2, f"M117 ET {hr} hr {mmm} min") + # Add M73 line at beginning + mins = int(60 * hr + mmm) + if self.add_m73_line and (self.add_m73_time or self.add_m73_percent): + if m73_time: + self.m73_str += " R{}".format(mins) + if m73_percent: + self.m73_str += " P0" + lines.insert(tindex + 4, "M73" + self.m73_str) + # If Countdown to pause is enabled then count the pauses + pause_str = "" + if bool(self.getSettingValueByKey("countdown_to_pause")): + pause_count = 0 + pause_setting = self.getSettingValueByKey("pause_cmd").upper() + if pause_setting != "": + pause_cmd = [] + if "," in pause_setting: + pause_cmd = pause_setting.split(",") + else: + pause_cmd.append(pause_setting) + for q in range(0, len(pause_cmd)): + pause_cmd[q] = "\n" + pause_cmd[q] + for num in range(2,len(data) - 2, 1): + for q in range(0,len(pause_cmd)): + if pause_cmd[q] in data[num]: + pause_count += data[num].count(pause_cmd[q], 0, len(data[num])) + pause_str = f"with {pause_count} pause" + ("s" if pause_count > 1 else "") + else: + pause_str = "" + # This line goes in to convert seconds to hours and minutes + lines.insert(tindex + 1, f";Cura Time Estimate: {orig_hr}hr {orig_mmm}min {pause_str}") + data[0] = "\n".join(lines) + if self.add_m117_line: + data[len(data)-1] += "M117 Orig Cura Est " + str(orig_hr) + "hr " + str(orig_mmm) + "min\n" + if self.add_m118_line: + data[len(data)-1] += "M118 Est w/FudgeFactor " + str(speed_factor * 100) + "% was " + str(hr) + "hr " + str(mmm) + "min\n" + if not display_total_layers or not display_remaining_time: + base_display_text = "layer " + else: + base_display_text = "" + layer = data[len(data)-1] + data[len(data)-1] = layer.replace(";End of Gcode" + "\n", "") + data[len(data)-1] += ";End of Gcode" + "\n" + # Search for the number of layers and the total time from the start code + for index in range(len(data)): + data_section = data[index] + # We have everything we need, save the index of the first layer and exit the loop + if ";LAYER:" in data_section: + first_layer_index = index + break + else: + for line in data_section.split("\n"): + if line.startswith(";LAYER_COUNT:"): + number_of_layers = int(line.split(":")[1]) + if print_sequence == "one_at_a_time": + number_of_layers = 1 + for lay in range(2,len(data)-1,1): + if ";LAYER:" in data[lay]: + number_of_layers += 1 + # for all layers... + current_layer = 0 + for layer_counter in range(len(data)-2): + current_layer += 1 + layer_index = first_layer_index + layer_counter + display_text = base_display_text + display_text += str(current_layer) + # create a list where each element is a single line of code within the layer + lines = data[layer_index].split("\n") + if not ";LAYER:" in data[layer_index]: + current_layer -= 1 + continue + # add the total number of layers if this option is checked + if display_total_layers: + display_text += "/" + str(number_of_layers) + # if display_remaining_time is checked, it is calculated in this loop + if display_remaining_time: + time_remaining_display = " | ET " # initialize the time display + m = (self.time_total - time_elapsed) // 60 # estimated time in minutes + m *= speed_factor # correct for printing time + m = int(m) + h, m = divmod(m, 60) # convert to hours and minutes + # add the time remaining to the display_text + if h > 0: # if it's more than 1 hour left, display format = xhxxm + time_remaining_display += str(h) + "h" + if m < 10: # add trailing zero if necessary + time_remaining_display += "0" + time_remaining_display += str(m) + "m" + else: + time_remaining_display += str(m) + "m" + display_text += time_remaining_display + # find time_elapsed at the end of the layer (used to calculate the remaining time of the next layer) + if not current_layer == number_of_layers: + for line_index in range(len(lines) - 1, -1, -1): + line = lines[line_index] + if line.startswith(";TIME_ELAPSED:"): + # update time_elapsed for the NEXT layer and exit the loop + time_elapsed = int(float(line.split(":")[1])) + break + # insert the text AFTER the first line of the layer (in case other scripts use ";LAYER:") + for l_index, line in enumerate(lines): + if line.startswith(";LAYER:"): + if self.add_m117_line: lines[l_index] += "\nM117 " + display_text - # add M73 line + if self.add_m118_line: + m118_text = "\nM118 " + if self.add_m118_a1: + m118_text += "A1 " + if self.add_m118_p0: + m118_text += "P0 " + lines[l_index] += m118_text + display_text + # add M73 line + if display_remaining_time: mins = int(60 * h + m) - if m73_time: - lines[l_index] += "\nM73 R{}".format(mins) + if self.add_m73_line and (self.add_m73_time or self.add_m73_percent): + self.m73_str = "" + if m73_time and display_remaining_time: + self.m73_str += " R{}".format(mins) if m73_percent: - lines[l_index] += "\nM73 P" + str(round(int(current_layer) / int(number_of_layers) * 100)) - if add_m118_line: - lines[l_index] += "\nM118 " + display_text - break - # overwrite the layer with the modified layer - data[layer_index] = "\n".join(lines) + self.m73_str += " P" + str(round(int(current_layer) / int(number_of_layers) * 100)) + lines[l_index] += "\nM73" + self.m73_str + break + # overwrite the layer with the modified layer + data[layer_index] = "\n".join(lines) # If enabled then change the ET to TP for 'Time To Pause' - if bool(self.getSettingValueByKey("countdown_to_pause")): - time_list = [] - time_list.append("0") - time_list.append("0") - this_time = 0 - pause_index = 1 + time_list = [] + if bool(self.getSettingValueByKey("countdown_to_pause")): + time_list.append("0") + time_list.append("0") + this_time = 0 + pause_index = 1 - # Get the layer times - for num in range(2,len(data) - 1): - layer = data[num] - lines = layer.split("\n") - for line in lines: - if line.startswith(";TIME_ELAPSED:"): - this_time = (float(line.split(":")[1]))*speed_factor - time_list.append(str(this_time)) - if "PauseAtHeight.py" in layer: + # Get the layer times + for num in range(2,len(data) - 1): + layer = data[num] + lines = layer.split("\n") + for line in lines: + if line.startswith(";TIME_ELAPSED:"): + this_time = (float(line.split(":")[1]))*speed_factor + time_list.append(str(this_time)) + for p_cmd in pause_cmd: + if p_cmd in layer: for qnum in range(num - 1, pause_index, -1): time_list[qnum] = str(float(this_time) - float(time_list[qnum])) + "P" pause_index = num-1 + break - # Make the adjustments to the M117 (and M118) lines that are prior to a pause - for num in range (2, len(data) - 1,1): - layer = data[num] - lines = layer.split("\n") - for line in lines: + # Make the adjustments to the M117 (and M118) lines that are prior to a pause + for num in range (2, len(data) - 1,1): + layer = data[num] + lines = layer.split("\n") + for line in lines: + try: if line.startswith("M117") and "|" in line and "P" in time_list[num]: - M117_line = line.split("|")[0] + "| TP " - alt_time = time_list[num][:-1] - hhh = int(float(alt_time) / 3600) - if hhh > 0: - hhr = str(hhh) + "h" - else: - hhr = "" - mmm = ((float(alt_time) / 3600) - (int(float(alt_time) / 3600))) * 60 - sss = int((mmm - int(mmm)) * 60) - mmm = str(round(mmm)) + "m" - time_to_go = str(hhr) + str(mmm) - if hhr == "": time_to_go = time_to_go + str(sss) + "s" - M117_line = M117_line + time_to_go + time_to_go = self._get_time_to_go(time_list[num]) + M117_line = line.split("|")[0] + "| TP " + time_to_go layer = layer.replace(line, M117_line) if line.startswith("M118") and "|" in line and "P" in time_list[num]: + time_to_go = self._get_time_to_go(time_list[num]) M118_line = line.split("|")[0] + "| TP " + time_to_go layer = layer.replace(line, M118_line) - data[num] = layer - setting_data = "" - if bool(self.getSettingValueByKey("enable_end_message")): - message_str = self.message_to_user(speed_factor) - Message(title = "[Display Info on LCD] - Estimated Finish Time", text = message_str[0] + "\n\n" + message_str[1] + "\n" + message_str[2] + "\n" + message_str[3]).show() + except: + continue + data[num] = layer + if bool(self.getSettingValueByKey("enable_end_message")): + message_str = self._message_to_user(data, speed_factor, pause_cmd) + Message(title = "[Display Info on LCD] - Estimated Finish Time", text = message_str[0] + "\n\n" + message_str[1] + "\n" + message_str[2] + "\n" + message_str[3]).show() return data - def message_to_user(self, speed_factor: float): - # Message the user of the projected finish time of the print + def _message_to_user(self, data: str, speed_factor: float, pause_cmd: str) -> str: + """ + Message the user of the projected finish time of the print and when any pauses might occur + """ print_time = Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601) print_start_time = self.getSettingValueByKey("print_start_time") # If the user entered a print start time make sure it is in the correct format or ignore it. @@ -476,8 +615,97 @@ class DisplayInfoOnLCD(Script): if print_start_time != "": print_start_str = "Print Start Time................." + str(print_start_time) + "hrs" else: - print_start_str = "Print Start Time.................Now." + print_start_str = "Print Start Time.................Now" estimate_str = "Cura Time Estimate.........." + str(print_time) adjusted_str = "Adjusted Time Estimate..." + str(time_change) - finish_str = week_day + " " + str(mo_str) + " " + str(new_time.strftime("%d")) + ", " + str(new_time.strftime("%Y")) + " at " + str(show_hr) + str(new_time.strftime("%M")) + str(show_ampm) - return finish_str, estimate_str, adjusted_str, print_start_str \ No newline at end of file + finish_str = f"{week_day} {mo_str} {new_time.strftime('%d')}, {new_time.strftime('%Y')} at {show_hr}{new_time.strftime('%M')}{show_ampm}" + + # If there are pauses and if countdown is enabled, then add the time-to-pause to the message. + if bool(self.getSettingValueByKey("countdown_to_pause")): + num = 1 + for layer in data: + for p_cmd in pause_cmd: + if p_cmd in layer or "Do the actual pause" in layer: + adjusted_str += "\n" + self._get_time_to_go(layer.split("TIME_ELAPSED:")[1].split("\n")[0]) + " ET from start to pause #" + str(num) + num += 1 + return finish_str, estimate_str, adjusted_str, print_start_str + + def _get_time_to_go(self, time_str: str): + """ + Converts a time string in seconds to a human-readable format (e.g., "2h30m"). + :param time_str: The time string in seconds. + :return: A formatted string representing the time. + """ + alt_time = time_str[:-1] + total_seconds = float(alt_time) + hours = int(total_seconds // 3600) + minutes = int((total_seconds % 3600) // 60) + seconds = int(total_seconds % 60) + time_to_go = f"{hours}h" if hours > 0 else "" + time_to_go += f"{minutes}m" + if hours == 0: + time_to_go += f"{seconds}s" + return time_to_go + + def _add_stats(self, data: str) -> str: + global_stack = Application.getInstance().getGlobalContainerStack() + """ + Make a list of the models in the file. + Add some of the filament stats to the first section of the gcode. + """ + model_list = [] + for mdex, layer in enumerate(data): + layer = data[mdex].split("\n") + for line in layer: + if line.startswith(";MESH:") and "NONMESH" not in line: + model_name = line.split(":")[1] + if not model_name in model_list: + model_list.append(model_name) + # Filament stats + extruder_count = global_stack.getProperty("machine_extruder_count", "value") + layheight_0 = global_stack.getProperty("layer_height_0", "value") + init_layer_hgt_line = ";Initial Layer Height: " + f"{layheight_0:.2f}".format(layheight_0) + filament_line_t0 = ";Extruder 1 (T0)\n" + filament_amount = Application.getInstance().getPrintInformation().materialLengths + filament_line_t0 += f"; Filament used: {filament_amount[0]}m\n" + filament_line_t0 += f"; Filament Type: {global_stack.extruderList[0].material.getMetaDataEntry("material", "")}\n" + filament_line_t0 += f"; Filament Dia.: {global_stack.extruderList[0].getProperty("material_diameter", "value")}mm\n" + filament_line_t0 += f"; Nozzle Size : {global_stack.extruderList[0].getProperty("machine_nozzle_size", "value")}mm\n" + filament_line_t0 += f"; Print Temp. : {global_stack.extruderList[0].getProperty("material_print_temperature", "value")}°\n" + filament_line_t0 += f"; Bed Temp. : {global_stack.extruderList[0].getProperty("material_bed_temperature", "value")}°" + + # if there is more than one extruder then get the stats for the second one. + filament_line_t1 = "" + if extruder_count > 1: + filament_line_t1 = "\n;Extruder 2 (T1)\n" + filament_line_t1 += f"; Filament used: {filament_amount[1]}m\n" + filament_line_t1 += f"; Filament Type: {global_stack.extruderList[1].material.getMetaDataEntry("material", "")}\n" + filament_line_t1 += f"; Filament Dia.: {global_stack.extruderList[1].getProperty("material_diameter", "value")}mm\n" + filament_line_t1 += f"; Nozzle Size : {global_stack.extruderList[1].getProperty("machine_nozzle_size", "value")}mm\n" + filament_line_t1 += f"; Print Temp. : {global_stack.extruderList[1].getProperty("material_print_temperature", "value")}°" + + # Calculate the cost of electricity for the print + electricity_cost = self.getSettingValueByKey("electricity_cost") + printer_power_usage = self.getSettingValueByKey("printer_power_usage") + currency_unit = Application.getInstance().getPreferences().getValue("cura/currency") + total_cost_electricity = (printer_power_usage / 1000) * (self.time_total / 3600) * electricity_cost + + # Add the stats to the gcode file + lines = data[0].split("\n") + for index, line in enumerate(lines): + if line.startswith(";Layer height:") or line.startswith(";TARGET_MACHINE.NAME:"): + lines[index] = ";Layer height: " + f"{global_stack.getProperty("layer_height", "value")}" + lines[index] += f"\n{init_layer_hgt_line}" + lines[index] += f"\n;Base Quality Name : '{global_stack.quality.getMetaDataEntry("name", "")}'" + lines[index] += f"\n;Custom Quality Name: '{global_stack.qualityChanges.getMetaDataEntry("name")}'" + if line.startswith(";Filament used"): + lines[index] = filament_line_t0 + filament_line_t1 + f"\n;Electric Cost: {currency_unit}{total_cost_electricity:.2f}".format(total_cost_electricity) + # The target machine "machine_name" is actually the printer model. This adds the user defined printer name to the "TARGET_MACHINE" line. + if line.startswith(";TARGET_MACHINE"): + machine_model = str(global_stack.getProperty("machine_name", "value")) + machine_name = str(global_stack.getName()) + lines[index] += f" / {machine_name}" + if "MINX" in line or "MIN.X" in line: + # Add the Object List + lines[index - 1] += f"\n;Model List: {str(model_list)}" + return "\n".join(lines) \ No newline at end of file diff --git a/plugins/PostProcessingPlugin/scripts/FilamentChange.py b/plugins/PostProcessingPlugin/scripts/FilamentChange.py index 6fe28ef2f2..f51ba73ffb 100644 --- a/plugins/PostProcessingPlugin/scripts/FilamentChange.py +++ b/plugins/PostProcessingPlugin/scripts/FilamentChange.py @@ -92,7 +92,7 @@ class FilamentChange(Script): "type": "float", "default_value": 0, "minimum_value": 0, - "enabled": "enabled" + "enabled": "enabled and not firmware_config" }, "retract_method": { diff --git a/plugins/PostProcessingPlugin/scripts/InsertAtLayerChange.py b/plugins/PostProcessingPlugin/scripts/InsertAtLayerChange.py index ea783b08d8..d2a51a28fa 100644 --- a/plugins/PostProcessingPlugin/scripts/InsertAtLayerChange.py +++ b/plugins/PostProcessingPlugin/scripts/InsertAtLayerChange.py @@ -1,6 +1,7 @@ # Copyright (c) 2020 Ultimaker B.V. # Created by Wayne Porter # Re-write in April of 2024 by GregValiant (Greg Foresi) +# Made convert inserted text to upper-case optional March 2025 by HellAholic # Changes: # Added an 'Enable' setting # Added support for multi-line insertions (comma delimited) @@ -82,6 +83,14 @@ class InsertAtLayerChange(Script): "type": "str", "default_value": "", "enabled": "enabled" + }, + "convert_to_upper": + { + "label": "Convert to upper-case", + "description": "Convert all inserted text to upper-case as some firmwares don't understand lower-case.", + "type": "bool", + "default_value": true, + "enabled": "enabled" } } }""" @@ -91,7 +100,7 @@ class InsertAtLayerChange(Script): if not bool(self.getSettingValueByKey("enabled")): return data #Initialize variables - mycode = self.getSettingValueByKey("gcode_to_add").upper() + mycode = self.getSettingValueByKey("gcode_to_add").upper() if self.getSettingValueByKey("convert_to_upper") else self.getSettingValueByKey("gcode_to_add") start_layer = int(self.getSettingValueByKey("start_layer")) end_layer = int(self.getSettingValueByKey("end_layer")) when_to_insert = self.getSettingValueByKey("insert_frequency") diff --git a/plugins/PostProcessingPlugin/scripts/PurgeLinesAndUnload.py b/plugins/PostProcessingPlugin/scripts/PurgeLinesAndUnload.py index a356fec379..44c2b50f9e 100644 --- a/plugins/PostProcessingPlugin/scripts/PurgeLinesAndUnload.py +++ b/plugins/PostProcessingPlugin/scripts/PurgeLinesAndUnload.py @@ -35,8 +35,9 @@ class Position(tuple, Enum): class PurgeLinesAndUnload(Script): - def __init__(self): - super().__init__() + def initialize(self) -> None: + super().initialize() + # Get required values from the global stack and set default values for the script self.global_stack = Application.getInstance().getGlobalContainerStack() self.extruder = self.global_stack.extruderList self.end_purge_location = None @@ -56,9 +57,6 @@ class PurgeLinesAndUnload(Script): self.machine_back = self.machine_depth - 1.0 self.start_x = None self.start_y = None - - def initialize(self) -> None: - super().initialize() # Get the StartUp Gcode from Cura and attempt to catch if it contains purge lines. Message the user if an extrusion is in the startup. startup_gcode = self.global_stack.getProperty("machine_start_gcode", "value") start_lines = startup_gcode.splitlines() @@ -496,7 +494,7 @@ class PurgeLinesAndUnload(Script): """Generates G-code lines for prime blob adjustment.""" gcode_lines = [ f"G1 F{retract_speed} E{retract_distance} ; Unretract", - "G92 E0 ; Reset extruder" + "G92 E0 ; Reset extruder\n" ] return "\n".join(gcode_lines) diff --git a/plugins/PreviewStage/PreviewStage.py b/plugins/PreviewStage/PreviewStage.py index 88f432ef9b..3f1a4423b2 100644 --- a/plugins/PreviewStage/PreviewStage.py +++ b/plugins/PreviewStage/PreviewStage.py @@ -24,25 +24,6 @@ class PreviewStage(CuraStage): super().__init__(parent) self._application = application self._application.engineCreatedSignal.connect(self._engineCreated) - self._previously_active_view = None # type: Optional[View] - - def onStageSelected(self) -> None: - """When selecting the stage, remember which was the previous view so that - - we can revert to that view when we go out of the stage later. - """ - self._previously_active_view = self._application.getController().getActiveView() - - def onStageDeselected(self) -> None: - """Called when going to a different stage (away from the Preview Stage). - - When going to a different stage, the view should be reverted to what it - was before. Normally, that just reverts it to solid view. - """ - - if self._previously_active_view is not None: - self._application.getController().setActiveView(self._previously_active_view.getPluginId()) - self._previously_active_view = None def _engineCreated(self) -> None: """Delayed load of the QML files. diff --git a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py index a9a0666d9c..2ddf8aa334 100644 --- a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py +++ b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py @@ -101,7 +101,8 @@ class RemovableDriveOutputDevice(OutputDevice): self._stream = open(file_name, "wt", buffering = 1, encoding = "utf-8") else: #Binary mode. self._stream = open(file_name, "wb", buffering = 1) - job = WriteFileJob(writer, self._stream, nodes, preferred_format["mode"]) + writer_args = {"mime_type": preferred_format["mime_type"]} + job = WriteFileJob(writer, self._stream, nodes, preferred_format["mode"], writer_args) job.setFileName(file_name) job.progress.connect(self._onProgress) job.finished.connect(self._onFinished) diff --git a/plugins/SimulationView/SimulationPass.py b/plugins/SimulationView/SimulationPass.py index 436d5b8723..a7411a2ed0 100644 --- a/plugins/SimulationView/SimulationPass.py +++ b/plugins/SimulationView/SimulationPass.py @@ -203,9 +203,9 @@ class SimulationPass(RenderPass): self._layer_shader.setUniformValue("u_next_vertex", not_a_vector) self._layer_shader.setUniformValue("u_last_line_ratio", 1.0) - # The first line does not have a previous line: add a MoveCombingType in front for start detection + # The first line does not have a previous line: add a MoveUnretractedType in front for start detection # this way the first start of the layer can also be drawn - prev_line_types = numpy.concatenate([numpy.asarray([LayerPolygon.MoveCombingType], dtype = numpy.float32), layer_data._attributes["line_types"]["value"]]) + prev_line_types = numpy.concatenate([numpy.asarray([LayerPolygon.MoveUnretractedType], dtype = numpy.float32), layer_data._attributes["line_types"]["value"]]) # Remove the last element prev_line_types = prev_line_types[0:layer_data._attributes["line_types"]["value"].size] layer_data._attributes["prev_line_types"] = {'opengl_type': 'float', 'value': prev_line_types, 'opengl_name': 'a_prev_line_type'} diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index 10861acfd0..5d339e7f74 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -172,13 +172,20 @@ class SimulationView(CuraView): self._updateSliceWarningVisibility() self.activityChanged.emit() - def getSimulationPass(self) -> SimulationPass: + def getSimulationPass(self) -> Optional[SimulationPass]: if not self._layer_pass: + renderer = self.getRenderer() + if renderer is None: + return None + # Currently the RenderPass constructor requires a size > 0 # This should be fixed in RenderPass's constructor. self._layer_pass = SimulationPass(1, 1) self._compatibility_mode = self._evaluateCompatibilityMode() self._layer_pass.setSimulationView(self) + self._layer_pass.setEnabled(False) + renderer.addRenderPass(self._layer_pass) + return self._layer_pass def getCurrentLayer(self) -> int: @@ -608,8 +615,10 @@ class SimulationView(CuraView): visible_line_types.append(LayerPolygon.SupportInterfaceType) visible_line_types_with_extrusion = visible_line_types.copy() # Copy before travel moves are added if self.getShowTravelMoves(): - visible_line_types.append(LayerPolygon.MoveCombingType) - visible_line_types.append(LayerPolygon.MoveRetractionType) + visible_line_types.append(LayerPolygon.MoveUnretractedType) + visible_line_types.append(LayerPolygon.MoveRetractedType) + visible_line_types.append(LayerPolygon.MoveWhileRetractingType) + visible_line_types.append(LayerPolygon.MoveWhileUnretractingType) for node in DepthFirstIterator(self.getController().getScene().getRoot()): layer_data = node.callDecoration("getLayerData") @@ -732,11 +741,14 @@ class SimulationView(CuraView): # Make sure the SimulationPass is created layer_pass = self.getSimulationPass() + if layer_pass is None: + return False + renderer = self.getRenderer() if renderer is None: return False - renderer.addRenderPass(layer_pass) + layer_pass.setEnabled(True) # Make sure the NozzleNode is add to the root nozzle = self.getNozzleNode() @@ -776,7 +788,7 @@ class SimulationView(CuraView): return False if self._layer_pass is not None: - renderer.removeRenderPass(self._layer_pass) + self._layer_pass.setEnabled(False) if self._composite_pass: self._composite_pass.setLayerBindings(cast(List[str], self._old_layer_bindings)) self._composite_pass.setCompositeShader(cast(ShaderProgram, self._old_composite_shader)) diff --git a/plugins/SimulationView/SimulationViewMenuComponent.qml b/plugins/SimulationView/SimulationViewMenuComponent.qml index d434d883eb..78b0b2b74f 100644 --- a/plugins/SimulationView/SimulationViewMenuComponent.qml +++ b/plugins/SimulationView/SimulationViewMenuComponent.qml @@ -227,29 +227,52 @@ Cura.ExpandableComponent id: typesLegendModel Component.onCompleted: { + const travelsTypesModel = [ + { + label: catalog.i18nc("@label", "Not retracted"), + colorId: "layerview_move_combing" + }, + { + label: catalog.i18nc("@label", "Retracted"), + colorId: "layerview_move_retraction" + }, + { + label: catalog.i18nc("@label", "Retracting"), + colorId: "layerview_move_while_retracting" + }, + { + label: catalog.i18nc("@label", "Priming"), + colorId: "layerview_move_while_unretracting" + } + ]; + typesLegendModel.append({ label: catalog.i18nc("@label", "Travels"), initialValue: viewSettings.show_travel_moves, preference: "layerview/show_travel_moves", - colorId: "layerview_move_combing" + colorId: "layerview_move_combing", + subTypesModel: travelsTypesModel }); typesLegendModel.append({ label: catalog.i18nc("@label", "Helpers"), initialValue: viewSettings.show_helpers, preference: "layerview/show_helpers", - colorId: "layerview_support" + colorId: "layerview_support", + subTypesModel: [] }); typesLegendModel.append({ label: catalog.i18nc("@label", "Shell"), initialValue: viewSettings.show_skin, preference: "layerview/show_skin", - colorId: "layerview_inset_0" + colorId: "layerview_inset_0", + subTypesModel: [] }); typesLegendModel.append({ label: catalog.i18nc("@label", "Infill"), initialValue: viewSettings.show_infill, preference: "layerview/show_infill", - colorId: "layerview_infill" + colorId: "layerview_infill", + subTypesModel: [] }); if (! UM.SimulationView.compatibilityMode) { @@ -257,7 +280,8 @@ Cura.ExpandableComponent label: catalog.i18nc("@label", "Starts"), initialValue: viewSettings.show_starts, preference: "layerview/show_starts", - colorId: "layerview_starts" + colorId: "layerview_starts", + subTypesModel: [] }); } } @@ -273,6 +297,7 @@ Cura.ExpandableComponent Rectangle { + id: rectangleColor anchors.verticalCenter: parent.verticalCenter anchors.right: legendModelCheckBox.right width: UM.Theme.getSize("layerview_legend_size").width @@ -281,6 +306,58 @@ Cura.ExpandableComponent border.width: UM.Theme.getSize("default_lining").width border.color: UM.Theme.getColor("lining") visible: viewSettings.show_legend + + MouseArea + { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + enabled: subTypesModel.count > 0 + + onEntered: tooltip.show() + onExited: tooltip.hide() + + UM.ToolTip + { + id: tooltip + delay: 0 + width: subTypesColumn.implicitWidth + 2 * UM.Theme.getSize("thin_margin").width + height: subTypesColumn.implicitHeight + 2 * UM.Theme.getSize("thin_margin").width + + contentItem: Column + { + id: subTypesColumn + padding: 0 + spacing: UM.Theme.getSize("layerview_row_spacing").height + + Repeater + { + model: subTypesModel + UM.Label + { + text: label + + height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height + width: UM.Theme.getSize("layerview_menu_size").width + color: UM.Theme.getColor("tooltip_text") + Rectangle + { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + + width: UM.Theme.getSize("layerview_legend_size").width + height: UM.Theme.getSize("layerview_legend_size").height + + color: UM.Theme.getColor(model.colorId) + + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("lining") + } + } + } + } + } + } } UM.Label diff --git a/plugins/SimulationView/layers.shader b/plugins/SimulationView/layers.shader index e6210c2b65..d5079fd82b 100644 --- a/plugins/SimulationView/layers.shader +++ b/plugins/SimulationView/layers.shader @@ -22,8 +22,8 @@ vertex = gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; // shade the color depending on the extruder index v_color = a_color; - // 8 and 9 are travel moves - if ((a_line_type != 8.0) && (a_line_type != 9.0)) { + // 8, 9, 12 and 13 are travel moves + if ((a_line_type != 8.0) && (a_line_type != 9.0) && (a_line_type != 12.0) && (a_line_type != 13.0)) { v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a); } @@ -48,7 +48,9 @@ fragment = void main() { - if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9 + // travel moves: 8, 9, 12, 13 + if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) || + ((v_line_type >= 11.5) && (v_line_type <= 13.5)))) { // discard movements discard; } @@ -100,7 +102,7 @@ vertex41core = { gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; v_color = a_color; - if ((a_line_type != 8) && (a_line_type != 9)) { + if ((a_line_type != 8) && (a_line_type != 9) && (a_line_type != 12) && (a_line_type != 13)) { v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a); } @@ -120,7 +122,9 @@ fragment41core = void main() { - if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9 + // travel moves: 8, 9, 12, 13 + if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) || + ((v_line_type >= 11.5) && (v_line_type <= 13.5)))) { // discard movements discard; } diff --git a/plugins/SimulationView/layers3d.shader b/plugins/SimulationView/layers3d.shader index 494a07083d..e2f57823f3 100644 --- a/plugins/SimulationView/layers3d.shader +++ b/plugins/SimulationView/layers3d.shader @@ -228,22 +228,26 @@ geometry41core = { highp mat4 viewProjectionMatrix = u_projectionMatrix * u_viewMatrix; - vec4 g_vertex_delta; - vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers - vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position + // Vertices are declared as vec4 so that they can be used for calculations with gl_in[x].gl_Position + vec3 g_vertex_delta; + vec3 g_vertex_normal_horz; + vec4 g_vertex_offset_horz; vec3 g_vertex_normal_vert; vec4 g_vertex_offset_vert; vec3 g_vertex_normal_horz_head; vec4 g_vertex_offset_horz_head; + vec3 g_axial_plan_vector; + vec3 g_radial_plan_vector; float size_x; float size_y; - if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) { + if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) && + (v_line_type[0] != 8) && (v_line_type[0] != 9) && (v_line_type[0] != 12) && (v_line_type[0] != 13)) { return; } - // See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType - if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) { + // See LayerPolygon; 8 is MoveUnretractedType, 9 is RetractionType, 12 is MoveWhileRetractingType, 13 is MoveWhileUnretractingType + if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13))) { return; } if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10) || v_line_type[0] == 11)) { @@ -256,7 +260,7 @@ geometry41core = return; } - if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { + if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) { // fixed size for movements size_x = 0.05; } else { @@ -264,26 +268,47 @@ geometry41core = } size_y = v_line_dim[1].y / 2 + 0.01; - g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position; //Actual movement exhibited by the line. - g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z)); //Lengthwise normal vector pointing backwards. - g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector pointing backwards. + g_vertex_delta = (gl_in[1].gl_Position - gl_in[0].gl_Position).xyz; //Actual movement exhibited by the line. - g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x)); //Normal vector pointing right. + if (g_vertex_delta == vec3(0.0)) { + return; + } + + if (g_vertex_delta.y == 0.0) + { + // vector is in the horizontal plan, radial vector is a simple rotation around Y axis + g_radial_plan_vector = vec3(g_vertex_delta.z, 0.0, -g_vertex_delta.x); + } + else if(g_vertex_delta.x == 0.0 && g_vertex_delta.z == 0.0) + { + // delta vector is purely vertical, display the line rotated vertically so that it is visible in front and side views + g_radial_plan_vector = vec3(1.0, 0.0, -1.0); + } + else + { + // delta vector is completely 3D + g_axial_plan_vector = vec3(g_vertex_delta.x, 0.0, g_vertex_delta.z); // Vector projected in the horizontal plan + g_radial_plan_vector = cross(g_vertex_delta, g_axial_plan_vector); // Radial vector in the horizontal plan, pointing right. + } + + g_vertex_normal_horz_head = normalize(g_vertex_delta); //Lengthwise normal vector + g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector + + g_vertex_normal_horz = normalize(g_radial_plan_vector); //Normal vector pointing right. g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //Offset vector pointing right. g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); //Upwards normal vector. g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0); //Upwards offset vector. Goes up by half the layer thickness. - if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { //Travel or retraction moves. - vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert); + if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) { //Travel or retraction moves. + vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert); vec4 va_up = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert); vec4 va_down = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert); - vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert); + vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert); vec4 vb_down = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert); vec4 vb_up = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert); // Travels: flat plane with pointy ends - myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up); myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_head); myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_down); myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up); @@ -308,8 +333,8 @@ geometry41core = vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz); //Line end, right vertex. vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert); //Line start, bottom vertex. vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert); //Line end, bottom vertex. - vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head); //Line start, tip. - vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head); //Line end, tip. + vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head); //Line start, tip. + vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head); //Line end, tip. // All normal lines are rendered as 3d tubes. myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); @@ -328,14 +353,14 @@ geometry41core = // left side myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_p_vert); - myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz_head, va_head); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head); myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz); EndPrimitive(); myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz); myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_vert, va_m_vert); - myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz_head, va_head); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head); myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); EndPrimitive(); @@ -343,14 +368,14 @@ geometry41core = // right side myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert); - myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz); EndPrimitive(); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert); - myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz); EndPrimitive(); diff --git a/plugins/SimulationView/layers3d_shadow.shader b/plugins/SimulationView/layers3d_shadow.shader index 88268938c9..0cf3e4f75a 100644 --- a/plugins/SimulationView/layers3d_shadow.shader +++ b/plugins/SimulationView/layers3d_shadow.shader @@ -95,22 +95,26 @@ geometry41core = { highp mat4 viewProjectionMatrix = u_projectionMatrix * u_viewMatrix; - vec4 g_vertex_delta; - vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers - vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position + // Vertices are declared as vec4 so that they can be used for calculations with gl_in[x].gl_Position + vec3 g_vertex_delta; + vec3 g_vertex_normal_horz; + vec4 g_vertex_offset_horz; vec3 g_vertex_normal_vert; vec4 g_vertex_offset_vert; vec3 g_vertex_normal_horz_head; vec4 g_vertex_offset_horz_head; + vec3 g_axial_plane_vector; + vec3 g_radial_plane_vector; float size_x; float size_y; - if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) { + if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) && + (v_line_type[0] != 8) && (v_line_type[0] != 9) && (v_line_type[0] != 12) && (v_line_type[0] != 13)) { return; } - // See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType - if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) { + // See LayerPolygon; 8 is MoveUnretractedType, 9 is RetractionType, 12 is MoveWhileRetractingType, 13 is MoveWhileUnretractingType + if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13))) { return; } if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10))) { @@ -123,7 +127,7 @@ geometry41core = return; } - if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { + if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) { // fixed size for movements size_x = 0.05; } else { @@ -131,93 +135,114 @@ geometry41core = } size_y = v_line_dim[1].y / 2 + 0.01; - g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position; - g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z)); - g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); + g_vertex_delta = (gl_in[1].gl_Position - gl_in[0].gl_Position).xyz; //Actual movement exhibited by the line. - g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x)); + if (g_vertex_delta == vec3(0.0)) { + return; + } - g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //size * g_vertex_normal_horz; - g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); - g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0); + if (g_vertex_delta.y == 0.0) + { + // vector is in the horizontal plane, radial vector is a simple rotation around Y axis + g_radial_plane_vector = vec3(g_vertex_delta.z, 0.0, -g_vertex_delta.x); + } + else if(g_vertex_delta.x == 0.0 && g_vertex_delta.z == 0.0) + { + // delta vector is purely vertical, display the line rotated vertically so that it is visible in front and side views + g_radial_plane_vector = vec3(1.0, 0.0, -1.0); + } + else + { + // delta vector is completely 3D + g_axial_plane_vector = vec3(g_vertex_delta.x, 0.0, g_vertex_delta.z); // Vector projected in the horizontal plane + g_radial_plane_vector = cross(g_vertex_delta, g_axial_plane_vector); // Radial vector in the horizontal plane, pointing right. + } - if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { - vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert); + g_vertex_normal_horz_head = normalize(g_vertex_delta); //Lengthwise normal vector + g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector + + g_vertex_normal_horz = normalize(g_radial_plane_vector); //Normal vector pointing right. + g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //Offset vector pointing right. + + g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); //Upwards normal vector. + g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0); //Upwards offset vector. Goes up by half the layer thickness. + + if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) { + vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert); vec4 va_up = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert); vec4 va_down = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert); - vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert); + vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert); vec4 vb_down = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert); vec4 vb_up = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert); // Travels: flat plane with pointy ends - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_head); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_down); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_head); //And reverse so that the line is also visible from the back side. myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_down); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_head); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up); EndPrimitive(); } else { - vec4 va_m_horz = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz); - vec4 vb_m_horz = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz); - vec4 va_p_vert = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert); - vec4 vb_p_vert = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert); - vec4 va_p_horz = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz); - vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz); - vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert); - vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert); - vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head); - vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head); + vec4 va_m_horz = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz); //Line start, left vertex. + vec4 vb_m_horz = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz); //Line end, left vertex. + vec4 va_p_vert = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert); //Line start, top vertex. + vec4 vb_p_vert = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert); //Line end, top vertex. + vec4 va_p_horz = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz); //Line start, right vertex. + vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz); //Line end, right vertex. + vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert); //Line start, bottom vertex. + vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert); //Line end, bottom vertex. + vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head); //Line start, tip. + vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head); //Line end, tip. // All normal lines are rendered as 3d tubes. - myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_p_vert); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz); - myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_vert, va_m_vert); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert); - myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz); EndPrimitive(); // left side - myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_p_vert); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz); EndPrimitive(); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz); - myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head); - myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_vert, va_m_vert); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); EndPrimitive(); // right side myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert); - myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz); EndPrimitive(); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert); - myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz); EndPrimitive(); diff --git a/plugins/SimulationView/layers_shadow.shader b/plugins/SimulationView/layers_shadow.shader index 4bc2de3d0b..73278914b7 100644 --- a/plugins/SimulationView/layers_shadow.shader +++ b/plugins/SimulationView/layers_shadow.shader @@ -48,8 +48,10 @@ fragment = void main() { - if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) - { // actually, 8 and 9 + // travel moves: 8, 9, 12, 13 + if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) || + ((v_line_type >= 11.5) && (v_line_type <= 13.5)))) { + { // discard movements discard; } @@ -124,7 +126,9 @@ fragment41core = void main() { - if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9 + // travel moves: 8, 9, 12, 13 + if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) || + ((v_line_type >= 11.5) && (v_line_type <= 13.5)))) { // discard movements discard; } diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 7f32b0df7f..e25273cb13 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -1,13 +1,12 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import os.path from UM.View.View import View from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Selection import Selection from UM.Resources import Resources -from PyQt6.QtGui import QOpenGLContext, QDesktopServices, QImage -from PyQt6.QtCore import QSize, QUrl +from PyQt6.QtGui import QDesktopServices, QImage +from PyQt6.QtCore import QUrl import numpy as np import time @@ -36,11 +35,12 @@ class SolidView(View): """Standard view for mesh models.""" _show_xray_warning_preference = "view/show_xray_warning" + _show_overhang_preference = "view/show_overhang" def __init__(self): super().__init__() application = Application.getInstance() - application.getPreferences().addPreference("view/show_overhang", True) + application.getPreferences().addPreference(self._show_overhang_preference, True) application.globalContainerStackChanged.connect(self._onGlobalContainerChanged) self._enabled_shader = None self._disabled_shader = None @@ -212,7 +212,7 @@ class SolidView(View): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: - if Application.getInstance().getPreferences().getValue("view/show_overhang"): + if Application.getInstance().getPreferences().getValue(self._show_overhang_preference): # Make sure the overhang angle is valid before passing it to the shader if self._support_angle >= 0 and self._support_angle <= 90: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - self._support_angle))) @@ -289,8 +289,9 @@ class SolidView(View): def endRendering(self): # check whether the xray overlay is showing badness - if time.time() > self._next_xray_checking_time\ - and Application.getInstance().getPreferences().getValue(self._show_xray_warning_preference): + if (time.time() > self._next_xray_checking_time + and Application.getInstance().getPreferences().getValue(self._show_xray_warning_preference) + and self._xray_pass is not None): self._next_xray_checking_time = time.time() + self._xray_checking_update_time xray_img = self._xray_pass.getOutput() diff --git a/plugins/SupportEraser/SupportEraser.py b/plugins/SupportEraser/SupportEraser.py index 0a714396aa..afdad6a4d0 100644 --- a/plugins/SupportEraser/SupportEraser.py +++ b/plugins/SupportEraser/SupportEraser.py @@ -1,6 +1,8 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from typing import Optional + from PyQt6.QtCore import Qt, QTimer from PyQt6.QtWidgets import QApplication @@ -35,6 +37,7 @@ class SupportEraser(Tool): self._controller = self.getController() self._selection_pass = None + self._picking_pass: Optional[PickingPass] = None CuraApplication.getInstance().globalContainerStackChanged.connect(self._updateEnabled) # Note: if the selection is cleared with this tool active, there is no way to switch to @@ -84,12 +87,13 @@ class SupportEraser(Tool): # Only "normal" meshes can have anti_overhang_meshes added to them return - # Create a pass for picking a world-space location from the mouse location - active_camera = self._controller.getScene().getActiveCamera() - picking_pass = PickingPass(active_camera.getViewportWidth(), active_camera.getViewportHeight()) - picking_pass.render() + # Get the pass for picking a world-space location from the mouse location + if self._picking_pass is None: + self._picking_pass = Application.getInstance().getRenderer().getRenderPass("picking_selected") + if not self._picking_pass: + return - picked_position = picking_pass.getPickedPosition(event.x, event.y) + picked_position = self._picking_pass.getPickedPosition(event.x, event.y) # Add the anti_overhang_mesh cube at the picked location self._createEraserMesh(picked_node, picked_position) @@ -189,3 +193,6 @@ class SupportEraser(Tool): mesh.calculateNormals() return mesh + + def getRequiredExtraRenderingPasses(self) -> list[str]: + return ["picking_selected"] \ No newline at end of file diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py index 0cf756b6a4..c5558c1140 100644 --- a/plugins/UFPWriter/UFPWriter.py +++ b/plugins/UFPWriter/UFPWriter.py @@ -51,7 +51,7 @@ class UFPWriter(MeshWriter): # Qt thread. The File read/write operations right now are executed on separated threads because they are scheduled # by the Job class. @call_on_qt_thread - def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode): + def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode, **kwargs): archive = VirtualFile() archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py index 3c8e53b2e9..0831ceebd3 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py @@ -163,7 +163,7 @@ class CloudApiClient: scope=self._scope, data=b"", callback=self._parseCallback(on_finished, CloudPrintResponse), - error_callback=on_error, + error_callback=self._parseError(on_error), timeout=self.DEFAULT_REQUEST_TIMEOUT) def doPrintJobAction(self, cluster_id: str, cluster_job_id: str, action: str, @@ -256,7 +256,6 @@ class CloudApiClient: """Creates a callback function so that it includes the parsing of the response into the correct model. The callback is added to the 'finished' signal of the reply. - :param reply: The reply that should be listened to. :param on_finished: The callback in case the response is successful. Depending on the endpoint it will be either a list or a single item. :param model: The type of the model to convert the response to. @@ -281,6 +280,25 @@ class CloudApiClient: self._anti_gc_callbacks.append(parse) return parse + def _parseError(self, + on_error: Callable[[CloudError, "QNetworkReply.NetworkError", int], None]) -> Callable[[QNetworkReply, "QNetworkReply.NetworkError"], None]: + + """Creates a callback function so that it includes the parsing of an explicit error response into the correct model. + + :param on_error: The callback in case the response gives an explicit error + """ + + def parse(reply: QNetworkReply, error: "QNetworkReply.NetworkError") -> None: + + self._anti_gc_callbacks.remove(parse) + + http_code, response = self._parseReply(reply) + result = CloudError(**response["errors"][0]) + on_error(result, error, http_code) + + self._anti_gc_callbacks.append(parse) + return parse + @classmethod def getMachineIDMap(cls) -> Dict[str, str]: if cls._machine_id_to_name is None: diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py index 090355a3c0..010ef93fbd 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py @@ -27,9 +27,11 @@ from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOut from ..Messages.PrintJobUploadBlockedMessage import PrintJobUploadBlockedMessage from ..Messages.PrintJobUploadErrorMessage import PrintJobUploadErrorMessage from ..Messages.PrintJobUploadQueueFullMessage import PrintJobUploadQueueFullMessage +from ..Messages.PrintJobUploadPrinterInactiveMessage import PrintJobUploadPrinterInactiveMessage from ..Messages.PrintJobUploadSuccessMessage import PrintJobUploadSuccessMessage from ..Models.Http.CloudClusterResponse import CloudClusterResponse from ..Models.Http.CloudClusterStatus import CloudClusterStatus +from ..Models.Http.CloudError import CloudError from ..Models.Http.CloudPrintJobUploadRequest import CloudPrintJobUploadRequest from ..Models.Http.CloudPrintResponse import CloudPrintResponse from ..Models.Http.CloudPrintJobResponse import CloudPrintJobResponse @@ -87,7 +89,8 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): address="", connection_type=ConnectionType.CloudConnection, properties=properties, - parent=parent + parent=parent, + active=cluster.display_status != "inactive" ) self._api = api_client @@ -190,6 +193,8 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): self._received_print_jobs = status.print_jobs self._updatePrintJobs(status.print_jobs) + self._setActive(status.active) + def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, filter_by_machine: bool = False, **kwargs) -> None: @@ -291,19 +296,21 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): self.writeFinished.emit() - def _onPrintUploadSpecificError(self, reply: "QNetworkReply", _: "QNetworkReply.NetworkError"): + def _onPrintUploadSpecificError(self, error: CloudError, _: "QNetworkReply.NetworkError", http_error: int): """ Displays a message when an error occurs specific to uploading print job (i.e. queue is full). """ - error_code = reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute) - if error_code == 409: - PrintJobUploadQueueFullMessage().show() + if http_error == 409: + if error.code == "printerInactive": + PrintJobUploadPrinterInactiveMessage().show() + else: + PrintJobUploadQueueFullMessage().show() else: PrintJobUploadErrorMessage(I18N_CATALOG.i18nc("@error:send", "Unknown error code when uploading print job: {0}", - error_code)).show() + http_error)).show() - Logger.log("w", "Upload of print job failed specifically with error code {}".format(error_code)) + Logger.log("w", "Upload of print job failed specifically with error code {}".format(http_error)) self._progress.hide() self._pre_upload_print_job = None diff --git a/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadPrinterInactiveMessage.py b/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadPrinterInactiveMessage.py new file mode 100644 index 0000000000..324259eea4 --- /dev/null +++ b/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadPrinterInactiveMessage.py @@ -0,0 +1,20 @@ +# Copyright (c) 2020 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from UM import i18nCatalog +from UM.Message import Message + + +I18N_CATALOG = i18nCatalog("cura") + + +class PrintJobUploadPrinterInactiveMessage(Message): + """Message shown when uploading a print job to a cluster and the printer is inactive.""" + + def __init__(self) -> None: + super().__init__( + text = I18N_CATALOG.i18nc("@info:status", "The printer is inactive and cannot accept a new print job."), + title = I18N_CATALOG.i18nc("@info:title", "Printer inactive"), + lifetime = 10, + message_type=Message.MessageType.ERROR + ) diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py index 713582b8ad..a1f22f7b36 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py @@ -10,7 +10,7 @@ class CloudClusterResponse(BaseModel): """Class representing a cloud connected cluster.""" def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str, - host_internal_ip: Optional[str] = None, host_version: Optional[str] = None, + display_status: str, host_internal_ip: Optional[str] = None, host_version: Optional[str] = None, friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", printer_count: int = 1, capabilities: Optional[List[str]] = None, **kwargs) -> None: """Creates a new cluster response object. @@ -20,6 +20,7 @@ class CloudClusterResponse(BaseModel): :param host_name: The name of the printer as configured during the Wi-Fi setup. Used as identifier for end users. :param is_online: Whether this cluster is currently connected to the cloud. :param status: The status of the cluster authentication (active or inactive). + :param display_status: The display status of the cluster. :param host_version: The firmware version of the cluster host. This is where the Stardust client is running on. :param host_internal_ip: The internal IP address of the host printer. :param friendly_name: The human readable name of the host printer. @@ -31,6 +32,7 @@ class CloudClusterResponse(BaseModel): self.host_guid = host_guid self.host_name = host_name self.status = status + self.display_status = display_status self.is_online = is_online self.host_version = host_version self.host_internal_ip = host_internal_ip @@ -51,5 +53,5 @@ class CloudClusterResponse(BaseModel): Convenience function for printing when debugging. :return: A human-readable representation of the data in this object. """ - return str({k: v for k, v in self.__dict__.items() if k in {"cluster_id", "host_guid", "host_name", "status", "is_online", "host_version", "host_internal_ip", "friendly_name", "printer_type", "printer_count", "capabilities"}}) + return str({k: v for k, v in self.__dict__.items() if k in {"cluster_id", "host_guid", "host_name", "status", "display_status", "is_online", "host_version", "host_internal_ip", "friendly_name", "printer_type", "printer_count", "capabilities"}}) diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterStatus.py b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterStatus.py index 5cd151d8ef..34249dc67a 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterStatus.py @@ -14,6 +14,7 @@ class CloudClusterStatus(BaseModel): def __init__(self, printers: List[Union[ClusterPrinterStatus, Dict[str, Any]]], print_jobs: List[Union[ClusterPrintJobStatus, Dict[str, Any]]], generated_time: Union[str, datetime], + unavailable: bool = False, **kwargs) -> None: """Creates a new cluster status model object. @@ -23,6 +24,7 @@ class CloudClusterStatus(BaseModel): """ self.generated_time = self.parseDate(generated_time) + self.active = not unavailable self.printers = self.parseModels(ClusterPrinterStatus, printers) self.print_jobs = self.parseModels(ClusterPrintJobStatus, print_jobs) super().__init__(**kwargs) diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py index 925b4844c1..260d276427 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py @@ -20,13 +20,23 @@ from ..BaseModel import BaseModel class ClusterPrinterStatus(BaseModel): """Class representing a cluster printer""" - def __init__(self, enabled: bool, firmware_version: str, friendly_name: str, ip_address: str, machine_variant: str, - status: str, unique_name: str, uuid: str, - configuration: List[Union[Dict[str, Any], ClusterPrintCoreConfiguration]], - reserved_by: Optional[str] = None, maintenance_required: Optional[bool] = None, - firmware_update_status: Optional[str] = None, latest_available_firmware: Optional[str] = None, - build_plate: Union[Dict[str, Any], ClusterBuildPlate] = None, - material_station: Union[Dict[str, Any], ClusterPrinterMaterialStation] = None, **kwargs) -> None: + def __init__(self, + enabled: Optional[bool] = True, + friendly_name: Optional[str] = "", + machine_variant: Optional[str] = "", + status: Optional[str] = "unknown", + unique_name: Optional[str] = "", + uuid: Optional[str] = "", + configuration: Optional[List[Union[Dict[str, Any], ClusterPrintCoreConfiguration]]] = None, + firmware_version: Optional[str] = None, + ip_address: Optional[str] = None, + reserved_by: Optional[str] = "", + maintenance_required: Optional[bool] = False, + firmware_update_status: Optional[str] = "", + latest_available_firmware: Optional[str] = "", + build_plate: Optional[Union[Dict[str, Any], ClusterBuildPlate]] = None, + material_station: Optional[Union[Dict[str, Any], ClusterPrinterMaterialStation]] = None, + **kwargs) -> None: """ Creates a new cluster printer status :param enabled: A printer can be disabled if it should not receive new jobs. By default, every printer is enabled. @@ -47,7 +57,7 @@ class ClusterPrinterStatus(BaseModel): :param material_station: The material station that is on the printer. """ - self.configuration = self.parseModels(ClusterPrintCoreConfiguration, configuration) + self.configuration = self.parseModels(ClusterPrintCoreConfiguration, configuration) if configuration else [] self.enabled = enabled self.firmware_version = firmware_version self.friendly_name = friendly_name @@ -70,7 +80,7 @@ class ClusterPrinterStatus(BaseModel): :param controller: - The controller of the model. """ - model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version) + model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version or "") self.updateOutputModel(model) return model @@ -86,7 +96,8 @@ class ClusterPrinterStatus(BaseModel): model.updateType(self.machine_variant) model.updateState(self.status if self.enabled else "disabled") model.updateBuildplate(self.build_plate.type if self.build_plate else "glass") - model.setCameraUrl(QUrl("http://{}:8080/?action=stream".format(self.ip_address))) + if self.ip_address: + model.setCameraUrl(QUrl("http://{}:8080/?action=stream".format(self.ip_address))) if not model.printerConfiguration: # Prevent accessing printer configuration when not available. diff --git a/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py index 8f25df37db..3ac5ccc7e7 100644 --- a/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py @@ -46,10 +46,10 @@ class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice): QUEUED_PRINT_JOBS_STATES = {"queued", "error"} def __init__(self, device_id: str, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType, - parent=None) -> None: + parent=None, active: bool = True) -> None: super().__init__(device_id=device_id, address=address, properties=properties, connection_type=connection_type, - parent=parent) + parent=parent, active=active) # Trigger the printersChanged signal when the private signal is triggered. self.printersChanged.connect(self._clusterPrintersChanged) diff --git a/resources/conandata.yml b/resources/conandata.yml index c4418d4f57..0644c1a42e 100644 --- a/resources/conandata.yml +++ b/resources/conandata.yml @@ -1 +1 @@ -version: "5.10.2" +version: "5.11.0-alpha.0" diff --git a/resources/definitions/anycubic_kobra3v2.def.json b/resources/definitions/anycubic_kobra3v2.def.json new file mode 100644 index 0000000000..6b8df0cc4b --- /dev/null +++ b/resources/definitions/anycubic_kobra3v2.def.json @@ -0,0 +1,52 @@ +{ + "version": 2, + "name": "Anycubic Kobra 3 v2", + "inherits": "fdmprinter", + "metadata": + { + "visible": true, + "author": "Sam Bonnekamp", + "manufacturer": "Anycubic", + "file_formats": "text/x-gcode", + "platform": "anycubic_kobra3v2_buildplate.stl", + "has_textured_buildplate": true, + "machine_extruder_trains": { "0": "anycubic_kobra3v2_extruder_0" } + }, + "overrides": + { + "adhesion_type": { "value": "'skirt'" }, + "layer_height": { "default_value": 0.2 }, + "machine_buildplate_type": { "default_value": "PEI Spring Steel" }, + "machine_center_is_zero": { "default_value": false }, + "machine_depth": { "default_value": 250 }, + "machine_end_gcode": { "default_value": "G1 X5 Y{machine_depth*0.95} F{speed_travel*60} ; present print\nM140 S0 ; turn off heat bed\nM104 S0 ; turn off temperature\nM84; disable motors ; disable stepper motors" }, + "machine_heated_bed": { "default_value": true }, + "machine_height": { "default_value": 260 }, + "machine_name": + { + "default_value": "Anycubic Kobra 3 v2", + "description": "Anycubic Kobra 3 v2" + }, + "machine_start_gcode": { "default_value": "; thumbnail begin 32x32\n; iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAX\n; NSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARWSURBVHgB1Vc7bx1FFD5n/cC+YOQ4RBTXlg0STfIH\n; gM78h0SAhGgQjyqARO8eFEQFBaKhCHIoKagcOkJJQUWBI19XJLaJgp+5M8x5zZzdO+sg0ZCR1zM7u3\n; e+73znMbMAT0KLMVqPdKVhvuze+o2NjUafget7Gz4GeO3s7Ow29YiYiXSJ2TPft0BwEmZ2dpYnp+Ec\n; 8NPT09tpyOAhhF4CXSBPwhOvEZk+B/ynNFzd2QvwxlfHMEq9PqR//mVaWcegmqKCIf9FKGS2Px30Ey\n; A/Hh8fE/gWgRPomww+VkxVgYFoWcz3xXpUJo2QsXvEFu8qAQJvmobA13b3I1u+Q+AhQuRfB1kTBDS6\n; FUVmVBx5iwmqNIgNv9ONkUzg4ODgBQNn2b80yyNbCzFQqMsC4MCNg/eveSQGdQM6glhXYDAYbJHvRw\n; q+ux90dQ8eeBxacaD+5T5ZSdLbfIcT61hTgCZTuq1R/zqDjxMOva6SKzBdMQRoO1OsovcLZrpPkmOO\n; ytibCdPejOQCGJHl5l+zlEGNxBiuvjxI1zNwZTgDzw4a6LbV6/fTT8eqSGD/G3hVgTQRT05OOv6Nyl\n; zBwxgW5iN8/e4leOWlOTivESjAlNhvYJoEvTEA6gq7wIqMyZ76zevPw5XlWdi5/wi+2foLNu88hAeH\n; QQKQJG/ScjjFY1cUFD3mYKxmgeef+1z9AstO4KMEfu3zXdi594gXlHIj3hYFY6su5fVQU7OjgAvZNg\n; kpaDFn8jvrC9zf+GEvgZ+16HqX5QRVctl6FmAyXhq3TmHnROBhWuBysp7aj7/+rS+IWybKO8oPWTns\n; zFX2kxalEKTe+6pZXR9kx0X3olhXdmqJ+GIF6lyfC8ArUCnZ8PPvJ9xfXn4KLMCkuBlo306payJMWD\n; 9BoGspE9LrVop4ajfevgQrz82U5xkQyg7onyEWfpUzxSQBlUnjWRZM8t66cwi/jU5h5eIMbH40hKuv\n; LqS6UFwh7mhv1RL4AYojY9VQbqkQRWL34icPtQ5o6dXqR9fwQlLgrYuPLUSrH96TvZDigsg1UwxFc3\n; c/e5rfsRNRI/JFNNns8MBy8QKNvJYKzGgf4NoXf8LH3+5xTDw4jP0szH16ZvBlybdcitMhpJzrwKUB\n; jcmCiDkDvv/liK92qRWLeTdsZYSsQaPhhXOOZD44uLJhqXAIxYKIQUIaSqBGg+Di09CuBiUYxQ0E/t\n; 0Hc2bk9gQB26kiYJEK7a6kJyqdvL/bKSiKOmx9dGumfpnA35/jnsDH4/H6BAH7Qba3dobTTQYrOY0N\n; usOnbr9JieEiCPgSzzH4/Pz8dpVASw3T2OY0Y2V7FkBrdjDOarErkLPmpgNPkb/u5e8l4CqHG1IqhU\n; xEoQuZztlwuIRw8705WFma8uB3a0jcUhb05pQ/zdp+Ufso8R8hdLqygDs6OnptcXHxD+gx9V8R6H6a\n; dQ8WrSwq81XZ/1NToLztuY9T8D3U687/r/0D2siIlZoKRzIAAAAASUVORK5CYII=\n; thumbnail end\n; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, + "machine_start_gcode_first": { "default_value": true }, + "machine_width": { "default_value": 250 }, + "material_bed_temperature": + { + "maximum_value": "110", + "maximum_value_warning": "90" + }, + "material_diameter": { "default_value": 1.75 }, + "material_initial_print_temperature": + { + "maximum_value_warning": 295, + "value": "material_print_temperature + 5" + }, + "material_print_temperature": { "maximum_value_warning": 250 }, + "material_print_temperature_layer_0": + { + "maximum_value_warning": 295, + "value": "material_print_temperature + 5" + }, + "relative_extrusion": { "value": true } + } +} \ No newline at end of file diff --git a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json new file mode 100644 index 0000000000..fc464c9eee --- /dev/null +++ b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json @@ -0,0 +1,61 @@ +{ + "version": 2, + "name": "Anycubic Kobra 3 v2 ACE PRO", + "inherits": "fdmprinter", + "metadata": + { + "visible": true, + "author": "Sam Bonnekamp", + "manufacturer": "Anycubic", + "file_formats": "text/x-gcode", + "platform": "anycubic_kobra3v2_buildplate.stl", + "has_textured_buildplate": true, + "machine_extruder_trains": + { + "0": "anycubic_kobra3v2_ACEPRO_extruder_0", + "1": "anycubic_kobra3v2_ACEPRO_extruder_1", + "2": "anycubic_kobra3v2_ACEPRO_extruder_2", + "3": "anycubic_kobra3v2_ACEPRO_extruder_3" + } + }, + "overrides": + { + "adhesion_type": { "value": "'skirt'" }, + "layer_height": { "default_value": 0.2 }, + "machine_buildplate_type": { "default_value": "PEI Spring Steel" }, + "machine_center_is_zero": { "default_value": false }, + "machine_depth": { "default_value": 250 }, + "machine_end_gcode": { "default_value": "G1 X5 Y{machine_depth*0.95} F{speed_travel*60} ; present print\nM140 S0 ; turn off heat bed\nM104 S0 ; turn off temperature\nM84; disable motors ; disable stepper motors" }, + "machine_extruder_count": { "default_value": 4 }, + "machine_heated_bed": { "default_value": true }, + "machine_height": { "default_value": 260 }, + "machine_name": + { + "default_value": "Anycubic Kobra 3 v2", + "description": "Anycubic Kobra 3 v2" + }, + "machine_start_gcode": { "default_value": "; thumbnail begin 32x32\n; iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAX\n; NSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARWSURBVHgB1Vc7bx1FFD5n/cC+YOQ4RBTXlg0STfIH\n; gM78h0SAhGgQjyqARO8eFEQFBaKhCHIoKagcOkJJQUWBI19XJLaJgp+5M8x5zZzdO+sg0ZCR1zM7u3\n; e+73znMbMAT0KLMVqPdKVhvuze+o2NjUafget7Gz4GeO3s7Ow29YiYiXSJ2TPft0BwEmZ2dpYnp+Ec\n; 8NPT09tpyOAhhF4CXSBPwhOvEZk+B/ynNFzd2QvwxlfHMEq9PqR//mVaWcegmqKCIf9FKGS2Px30Ey\n; A/Hh8fE/gWgRPomww+VkxVgYFoWcz3xXpUJo2QsXvEFu8qAQJvmobA13b3I1u+Q+AhQuRfB1kTBDS6\n; FUVmVBx5iwmqNIgNv9ONkUzg4ODgBQNn2b80yyNbCzFQqMsC4MCNg/eveSQGdQM6glhXYDAYbJHvRw\n; q+ux90dQ8eeBxacaD+5T5ZSdLbfIcT61hTgCZTuq1R/zqDjxMOva6SKzBdMQRoO1OsovcLZrpPkmOO\n; ytibCdPejOQCGJHl5l+zlEGNxBiuvjxI1zNwZTgDzw4a6LbV6/fTT8eqSGD/G3hVgTQRT05OOv6Nyl\n; zBwxgW5iN8/e4leOWlOTivESjAlNhvYJoEvTEA6gq7wIqMyZ76zevPw5XlWdi5/wi+2foLNu88hAeH\n; QQKQJG/ScjjFY1cUFD3mYKxmgeef+1z9AstO4KMEfu3zXdi594gXlHIj3hYFY6su5fVQU7OjgAvZNg\n; kpaDFn8jvrC9zf+GEvgZ+16HqX5QRVctl6FmAyXhq3TmHnROBhWuBysp7aj7/+rS+IWybKO8oPWTns\n; zFX2kxalEKTe+6pZXR9kx0X3olhXdmqJ+GIF6lyfC8ArUCnZ8PPvJ9xfXn4KLMCkuBlo306payJMWD\n; 9BoGspE9LrVop4ajfevgQrz82U5xkQyg7onyEWfpUzxSQBlUnjWRZM8t66cwi/jU5h5eIMbH40hKuv\n; LqS6UFwh7mhv1RL4AYojY9VQbqkQRWL34icPtQ5o6dXqR9fwQlLgrYuPLUSrH96TvZDigsg1UwxFc3\n; c/e5rfsRNRI/JFNNns8MBy8QKNvJYKzGgf4NoXf8LH3+5xTDw4jP0szH16ZvBlybdcitMhpJzrwKUB\n; jcmCiDkDvv/liK92qRWLeTdsZYSsQaPhhXOOZD44uLJhqXAIxYKIQUIaSqBGg+Di09CuBiUYxQ0E/t\n; 0Hc2bk9gQB26kiYJEK7a6kJyqdvL/bKSiKOmx9dGumfpnA35/jnsDH4/H6BAH7Qba3dobTTQYrOY0N\n; usOnbr9JieEiCPgSzzH4/Pz8dpVASw3T2OY0Y2V7FkBrdjDOarErkLPmpgNPkb/u5e8l4CqHG1IqhU\n; xEoQuZztlwuIRw8705WFma8uB3a0jcUhb05pQ/zdp+Ufso8R8hdLqygDs6OnptcXHxD+gx9V8R6H6a\n; dQ8WrSwq81XZ/1NToLztuY9T8D3U687/r/0D2siIlZoKRzIAAAAASUVORK5CYII=\n; thumbnail end\n; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, + "machine_start_gcode_first": { "default_value": true }, + "machine_width": { "default_value": 250 }, + "material_bed_temperature": + { + "maximum_value": "110", + "maximum_value_warning": "90" + }, + "material_diameter": { "default_value": 1.75 }, + "material_initial_print_temperature": + { + "maximum_value_warning": 295, + "value": "material_print_temperature + 5" + }, + "material_print_temp_wait": { "value": true }, + "material_print_temperature": { "maximum_value": 300 }, + "material_print_temperature_layer_0": + { + "maximum_value_warning": 295, + "value": "material_print_temperature + 5" + }, + "material_standby_temperature": { "default_value": "material_print_temperature" }, + "relative_extrusion": { "value": true } + } +} \ No newline at end of file diff --git a/resources/definitions/bambulab_a1.def.json b/resources/definitions/bambulab_a1.def.json new file mode 100644 index 0000000000..28737d9d8d --- /dev/null +++ b/resources/definitions/bambulab_a1.def.json @@ -0,0 +1,42 @@ +{ + "version": 2, + "name": "BambuLab A1", + "inherits": "bambulab_base", + "metadata": + { + "visible": true, + "platform": "bambulab_x1.obj", + "has_machine_quality": true, + "has_material": true, + "has_textured_buildplate": true, + "has_variant_buildplates": false, + "has_variants": true, + "machine_extruder_trains": + { + "0": "bambulab_a1_extruder_0", + "1": "bambulab_a1_extruder_1", + "2": "bambulab_a1_extruder_2", + "3": "bambulab_a1_extruder_3" + }, + "platform_offset": [ + -130, + 0, + 130 + ], + "platform_texture": "bambulab-buildplate.png", + "preferred_variant_name": "0.4mm", + "weight": 3 + }, + "overrides": + { + "machine_depth": { "value": 256 }, + "machine_end_gcode": { "default_value": ";===== date: 20231229 =====================\n;turn off nozzle clog detect\nG392 S0\n\nM400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-0.8 F1800 ; retract\nG1 Z{machine_height + 0.5} F900 ; lower z a little\nG1 X0 Y{machine_depth} F18000 ; move to safe pos\nG1 X-13.0 F3000 ; move to safe pos\n{if !magic_spiralize && print_sequence != 'one_at_a_time'}\nM1002 judge_flag timelapse_record_flag\nM622 J1\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM991 S0 P-1 ;end timelapse at safe pos\nM623\n{endif}\n\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nM106 P2 S0 ; turn off remote part cooling fan\nM106 P3 S0 ; turn off chamber cooling fan\n\n;G1 X27 F15000 ; wipe\n\n; pull back filament to AMS\nM620 S255\nG1 X181 F12000\nT255\nG1 X0 F18000\nG1 X-13.0 F3000\nG1 X0 F18000 ; wipe\nM621 S255\n\nM104 S0 ; turn off hotend\n\nM400 ; wait all motion done\nM17 S\nM17 Z0.4 ; lower z motor current to reduce impact if there is something in the bottom\nG1 Z180 F600\nG1 Z180\nM400 P100\nM17 R ; restore z current\n\nG90\nG1 X-13 Y180 F3600\n\nG91\nG1 Z-1 F600\nG90\nM83\n\nM220 S100 ; Reset feedrate magnitude\nM201.2 K1.0 ; Reset acc magnitude\nM73.2 R1.0 ;Reset left time magnitude\nM1002 set_gcode_claim_speed_level : 0\n\n;=====printer finish sound=========\nM17\nM400 S1\nM1006 S1\nM1006 A0 B20 L100 C37 D20 M100 E42 F20 N100\nM1006 A0 B10 L100 C44 D10 M100 E44 F10 N100\nM1006 A0 B10 L100 C46 D10 M100 E46 F10 N100\nM1006 A44 B20 L100 C39 D20 M100 E48 F20 N100\nM1006 A0 B10 L100 C44 D10 M100 E44 F10 N100\nM1006 A0 B10 L100 C0 D10 M100 E0 F10 N100\nM1006 A0 B10 L100 C39 D10 M100 E39 F10 N100\nM1006 A0 B10 L100 C0 D10 M100 E0 F10 N100\nM1006 A0 B10 L100 C44 D10 M100 E44 F10 N100\nM1006 A0 B10 L100 C0 D10 M100 E0 F10 N100\nM1006 A0 B10 L100 C39 D10 M100 E39 F10 N100\nM1006 A0 B10 L100 C0 D10 M100 E0 F10 N100\nM1006 A44 B10 L100 C0 D10 M100 E48 F10 N100\nM1006 A0 B10 L100 C0 D10 M100 E0 F10 N100\nM1006 A44 B20 L100 C41 D20 M100 E49 F20 N100\nM1006 A0 B20 L100 C0 D20 M100 E0 F20 N100\nM1006 A0 B20 L100 C37 D20 M100 E37 F20 N100\nM1006 W\n;=====printer finish sound=========\nM400 S1\nM18 X Y Z\n" }, + "machine_extruder_count": { "value": 4 }, + "machine_height": { "value": 251 }, + "machine_name": { "default_value": "BambuLab Bambu A1" }, + "machine_start_gcode": { "default_value": ";===== machine: A1 =========================\n;===== date: 20240620 =====================\nG392 S0\nM9833.2\n;M400\n;M73 P1.717\n\n;===== start to heat heatbead&hotend==========\nM1002 gcode_claim_action : 2\nM1002 set_filament_type:{material_type, initial_extruder_nr}\nM104 S140\nM140 S{material_bed_temperature_layer_0}\n\n;=====start printer sound ===================\n; 'The entertainer' by Scott Joplin\nM17\nM400 S1\nM1006 S1\n\nM1006 A0 B10 C39 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C40 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C41 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C49 D30 L30 M60 E0 F10 N60\nM1006 A0 B10 C41 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C49 D30 L30 M60 E0 F10 N60\nM1006 A0 B10 C41 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C49 D30 L30 M60 E0 F10 N60\n\nM1006 A0 B10 C0 D30 L30 M60 E0 F10 N60\n\nM1006 A0 B10 C49 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C50 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C51 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C53 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C50 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C51 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C53 D30 L30 M60 E0 F10 N60\nM1006 A0 B10 C47 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C51 D30 L30 M60 E0 F10 N60\nM1006 A0 B10 C49 D30 L30 M60 E0 F10 N60\n\nM1006 W\nM18\n;=====start printer sound ===================\n\n;=====avoid end stop =================\nG91\nG380 S2 Z40 F1200\nG380 S3 Z-15 F1200\nG90\n\n;===== reset machine status =================\n;M290 X39 Y39 Z8\nM204 S6000\n\nM630 S0 P0\nG91\nM17 Z0.3 ; lower the z-motor current\n\nG90\nM17 X0.65 Y1.2 Z0.6 ; reset motor current to default\nM960 S5 P1 ; turn on logo lamp\nG90\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nM73.2 R1.0 ;Reset left time magnitude\n;M211 X0 Y0 Z0 ; turn off soft endstop to prevent protential logic problem\n\n;====== cog noise reduction=================\nM982.2 S1 ; turn on cog noise reduction\n\nM1002 gcode_claim_action : 13\n\nG28 X\nG91\nG1 Z5 F1200\nG90\nG0 X128 F30000\nG0 Y254 F3000\nG91\nG1 Z-5 F1200\n\nM109 S25 H140\n\nM17 E0.3\nM83\nG1 E10 F1200\nG1 E-0.5 F30\nM17 D\n\nG28 Z P0 T140; home z with low precision,permit 300deg temperature\nM104 S{material_print_temperature_layer_0, initial_extruder_nr}\n\nM1002 judge_flag build_plate_detect_flag\nM622 S1\n G39.4\n G90\n G1 Z5 F1200\nM623\n\n;M400\n;M73 P1.717\n\n;===== prepare print temperature and material ==========\nM1002 gcode_claim_action : 24\n\nM400\n;G392 S1\nM211 X0 Y0 Z0 ;turn off soft endstop\nM975 S1 ; turn on\n\nG90\nG1 X-28.5 F30000\nG1 X-48.2 F3000\n\nM620 M ;enable remap\nM620 S{initial_extruder_nr}A ; switch material if AMS exist\n M1002 gcode_claim_action : 4\n M400\n M1002 set_filament_type:UNKNOWN\n M109 S{material_print_temperature_layer_0, initial_extruder_nr}\n M104 S250\n M400\n T{initial_extruder_nr}\n G1 X-48.2 F3000\n M400\n\n M620.1 E F{material_max_flowrate/2.4053*60, initial_extruder_nr} T{material_print_temperature, initial_extruder_nr}\n M109 S250 ;set nozzle to common flush temp\n M106 P1 S0\n G92 E0\n G1 E50 F200\n M400\n M1002 set_filament_type:{material_type, initial_extruder_nr}\nM621 S{initial_extruder_nr}A\n\nM109 S{material_print_temperature, initial_extruder_nr} H300\nG92 E0\nG1 E50 F200 ; lower extrusion speed to avoid clog\nM400\nM106 P1 S178\nG92 E0\nG1 E5 F200\nM104 S{material_print_temperature_layer_0, initial_extruder_nr}\nG92 E0\nG1 E-0.5 F300\n\nG1 X-28.5 F30000\nG1 X-48.2 F3000\nG1 X-28.5 F30000 ;wipe and shake\nG1 X-48.2 F3000\nG1 X-28.5 F30000 ;wipe and shake\nG1 X-48.2 F3000\n\n;G392 S0\n\nM400\nM106 P1 S0\n;===== prepare print temperature and material end =====\n\n;M400\n;M73 P1.717\n\n;===== auto extrude cali start =========================\nM975 S1\n;G392 S1\n\nG90\nM83\nT1000\nG1 X-48.2 Y0 Z10 F10000\nM400\nM1002 set_filament_type:UNKNOWN\n\nM412 S1 ; ===turn on filament runout detection===\nM400 P10\nM620.3 W1; === turn on filament tangle detection===\nM400 S2\n\nM1002 set_filament_type:{material_type, initial_extruder_nr}\n\n;M1002 set_flag extrude_cali_flag=1\nM1002 judge_flag extrude_cali_flag\n\nM622 J1\n M1002 gcode_claim_action : 8\n\n M109 S{material_print_temperature, initial_extruder_nr}\n G1 E10 F{speed_wall_0*wall_line_width_0*layer_height/2.4*60, initial_extruder_nr}\n M983 F{speed_wall_0*wall_line_width_0*layer_height/2.4, initial_extruder_nr} A0.3 H{machine_nozzle_size}; cali dynamic extrusion compensation\n\n M106 P1 S255\n M400 S5\n G1 X-28.5 F18000\n G1 X-48.2 F3000\n G1 X-28.5 F18000 ;wipe and shake\n G1 X-48.2 F3000\n G1 X-28.5 F12000 ;wipe and shake\n G1 X-48.2 F3000\n M400\n M106 P1 S0\n\n M1002 judge_last_extrude_cali_success\n M622 J0\n M983 F{speed_wall_0*wall_line_width_0*layer_height/2.4, initial_extruder_nr} A0.3 H{machine_nozzle_size}; cali dynamic extrusion compensation\n M106 P1 S255\n M400 S5\n G1 X-28.5 F18000\n G1 X-48.2 F3000\n G1 X-28.5 F18000 ;wipe and shake\n G1 X-48.2 F3000\n G1 X-28.5 F12000 ;wipe and shake\n M400\n M106 P1 S0\n M623\n\n G1 X-48.2 F3000\n M400\n M984 A0.1 E1 S1 F{speed_wall_0*wall_line_width_0*layer_height/2.4, initial_extruder_nr} H{machine_nozzle_size}\n M106 P1 S178\n M400 S7\n G1 X-28.5 F18000\n G1 X-48.2 F3000\n G1 X-28.5 F18000 ;wipe and shake\n G1 X-48.2 F3000\n G1 X-28.5 F12000 ;wipe and shake\n G1 X-48.2 F3000\n M400\n M106 P1 S0\nM623 ; end of 'draw extrinsic para cali paint'\n\n;G392 S0\n;===== auto extrude cali end ========================\n\n;M400\n;M73 P1.717\n\nM104 S170 ; prepare to wipe nozzle\nM106 S255 ; turn on fan\n\n;===== mech mode fast check start =====================\nM1002 gcode_claim_action : 3\n\nG1 X128 Y128 F20000\nG1 Z5 F1200\nM400 P200\nM970.3 Q1 A5 K0 O3\nM974 Q1 S2 P0\n\nM970.2 Q1 K1 W58 Z0.1\nM974 S2\n\nG1 X128 Y128 F20000\nG1 Z5 F1200\nM400 P200\nM970.3 Q0 A10 K0 O1\nM974 Q0 S2 P0\n\nM970.2 Q0 K1 W78 Z0.1\nM974 S2\n\nM975 S1\nG1 F30000\nG1 X0 Y5\nG28 X ; re-home XY\n\nG1 Z4 F1200\n\n;===== mech mode fast check end =======================\n\n;M400\n;M73 P1.717\n\n;===== wipe nozzle ===============================\nM1002 gcode_claim_action : 14\n\nM975 S1\nM106 S255 ; turn on fan (G28 has turn off fan)\nM211 S; push soft endstop status\nM211 X0 Y0 Z0 ;turn off Z axis endstop\n\n;===== remove waste by touching start =====\n\nM104 S170 ; set temp down to heatbed acceptable\n\nM83\nG1 E-1 F500\nG90\nM83\n\nM109 S170\nG0 X108 Y-0.5 F30000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X110 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X112 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X114 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X116 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X118 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X120 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X122 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X124 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X126 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X128 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X130 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X132 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X134 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X136 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X138 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X140 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X142 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X144 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X146 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X148 F10000\nG380 S3 Z-5 F1200\n\nG1 Z5 F30000\n;===== remove waste by touching end =====\n\nG1 Z10 F1200\nG0 X118 Y261 F30000\nG1 Z5 F1200\nM109 S{material_print_temperature_layer_0-50, initial_extruder_nr}\n\nG28 Z P0 T300; home z with low precision,permit 300deg temperature\nG29.2 S0 ; turn off ABL\nM104 S140 ; prepare to abl\nG0 Z5 F20000\n\nG0 X128 Y261 F20000 ; move to exposed steel surface\nG0 Z-1.01 F1200 ; stop the nozzle\n\nG91\nG2 I1 J0 X2 Y0 F2000.1\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\n\nG90\nG1 Z10 F1200\n\n;===== brush material wipe nozzle =====\n\nG90\nG1 Y250 F30000\nG1 X55\nG1 Z1.300 F1200\nG1 Y262.5 F6000\nG91\nG1 X-35 F30000\nG1 Y-0.5\nG1 X45\nG1 Y-0.5\nG1 X-45\nG1 Y-0.5\nG1 X45\nG1 Y-0.5\nG1 X-45\nG1 Y-0.5\nG1 X45\nG1 Z5.000 F1200\n\nG90\nG1 X30 Y250.000 F30000\nG1 Z1.300 F1200\nG1 Y262.5 F6000\nG91\nG1 X35 F30000\nG1 Y-0.5\nG1 X-45\nG1 Y-0.5\nG1 X45\nG1 Y-0.5\nG1 X-45\nG1 Y-0.5\nG1 X45\nG1 Y-0.5\nG1 X-45\nG1 Z10.000 F1200\n\n;===== brush material wipe nozzle end =====\n\nG90\n;G0 X128 Y261 F20000 ; move to exposed steel surface\nG1 Y250 F30000\nG1 X138\nG1 Y261\nG0 Z-1.01 F1200 ; stop the nozzle\n\nG91\nG2 I1 J0 X2 Y0 F2000.1\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\n\nM109 S140\nM106 S255 ; turn on fan (G28 has turn off fan)\n\nM211 R; pop softend status\n\n;===== wipe nozzle end ================================\n\n;M400\n;M73 P1.717\n\n;===== bed leveling ==================================\nM1002 judge_flag g29_before_print_flag\n\nG90\nG1 Z5 F1200\nG1 X0 Y0 F30000\nG29.2 S1 ; turn on ABL\n\nM190 S{material_bed_temperature_layer_0}; ensure bed temp\nM109 S140\nM106 S0 ; turn off fan , too noisy\n\nM622 J1\n M1002 gcode_claim_action : 1\n G29 A1 X{-machine_width/2 if machine_center_is_zero else 0} Y{-machine_depth/2 if machine_center_is_zero else 0} I{machine_width} J{machine_depth}\n M400\n M500 ; save cali data\nM623\n;===== bed leveling end ================================\n\n;===== home after wipe mouth============================\nM1002 judge_flag g29_before_print_flag\nM622 J0\n\n M1002 gcode_claim_action : 13\n G28\n\nM623\n\n;===== home after wipe mouth end =======================\n\n;M400\n;M73 P1.717\n\nG1 X108.000 Y-0.500 F30000\nG1 Z0.300 F1200\nM400\nG2814 Z0.32\n\nM104 S{material_print_temperature_layer_0, initial_extruder_nr} ; prepare to print\n\n;===== extrude cali test ===============================\n\nM400\n M900 S\n M900 C\n G90\n M83\n\n M109 S{material_print_temperature_layer_0, initial_extruder_nr}\n G0 X128 E8 F{speed_wall_0*wall_line_width_0*layer_height/(24/20) * 60, initial_extruder_nr}\n G0 X133 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4 * 60, initial_extruder_nr}\n G0 X138 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G0 X143 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4 * 60, initial_extruder_nr}\n G0 X148 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G0 X153 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4 * 60, initial_extruder_nr}\n G91\n G1 X1 Z-0.300\n G1 X4\n G1 Z1 F1200\n G90\n M400\n\nM900 R\n\nM1002 judge_flag extrude_cali_flag\nM622 J1\n G90\n G1 X108.000 Y1.000 F30000\n G91\n G1 Z-0.700 F1200\n G90\n M83\n G0 X128 E10 F{speed_wall_0*wall_line_width_0*layer_height/(24/20) * 60, initial_extruder_nr}\n G0 X133 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4 * 60, initial_extruder_nr}\n G0 X138 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G0 X143 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4 * 60, initial_extruder_nr}\n G0 X148 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G0 X153 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4 * 60, initial_extruder_nr}\n G91\n G1 X1 Z-0.300\n G1 X4\n G1 Z1 F1200\n G90\n M400\nM623\n\nG1 Z0.2\n\n;M400\n;M73 P1.717\n\n;========turn off light and wait extrude temperature =============\nM1002 gcode_claim_action : 0\nM400\n\n;===== for Textured PEI Plate , lower the nozzle as the nozzle was touching topmost of the texture when homing ==\n;curr_bed_type={curr_bed_type}\n{if machine_buildplate_type=='textured_pei_plate'}\nG29.1 Z{-0.02} ; for Textured PEI Plate\n{endif}\n\nM960 S1 P0 ; turn off laser\nM960 S2 P0 ; turn off laser\nM106 S0 ; turn off fan\nM106 P2 S0 ; turn off big fan\nM106 P3 S0 ; turn off chamber fan\n\nM975 S1 ; turn on mech mode supression\nG90\nM83\nT1000\n\nM211 X0 Y0 Z0 ;turn off soft endstop\n;G392 S1 ; turn on clog detection\nM1007 S1 ; turn on mass estimation\nG29.4\n" }, + "machine_width": { "value": 256 }, + "prime_tower_position_x": { "value": "resolveOrValue('prime_tower_size') + (resolveOrValue('prime_tower_base_size') if (resolveOrValue('adhesion_type') == 'raft' or resolveOrValue('prime_tower_brim_enable')) else 0) + max(max(extruderValues('travel_avoid_distance')) + max(extruderValues('machine_nozzle_offset_y')) + max(extruderValues('support_offset')) + (extruderValue(skirt_brim_extruder_nr, 'skirt_brim_line_width') * extruderValue(skirt_brim_extruder_nr, 'skirt_line_count') * extruderValue(skirt_brim_extruder_nr, 'initial_layer_line_width_factor') / 100 + extruderValue(skirt_brim_extruder_nr, 'skirt_gap') if resolveOrValue('adhesion_type') == 'skirt' else 0) + (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0), max(map(abs, extruderValues('machine_nozzle_offset_y'))), 1) - (resolveOrValue('machine_depth') / 2 if resolveOrValue('machine_center_is_zero') else 0)" }, + "prime_tower_position_y": { "value": "(resolveOrValue('prime_tower_base_size') if (resolveOrValue('adhesion_type') == 'raft' or resolveOrValue('prime_tower_brim_enable')) else 0) + max(max(extruderValues('travel_avoid_distance')) + max(extruderValues('machine_nozzle_offset_y')) + max(extruderValues('support_offset')) + (extruderValue(skirt_brim_extruder_nr, 'skirt_brim_line_width') * extruderValue(skirt_brim_extruder_nr, 'skirt_line_count') * extruderValue(skirt_brim_extruder_nr, 'initial_layer_line_width_factor') / 100 + extruderValue(skirt_brim_extruder_nr, 'skirt_gap') if resolveOrValue('adhesion_type') == 'skirt' else 0) + (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0), max(map(abs, extruderValues('machine_nozzle_offset_y'))), 1) - (resolveOrValue('machine_depth') / 2 if resolveOrValue('machine_center_is_zero') else 0)" } + } +} \ No newline at end of file diff --git a/resources/definitions/bambulab_a1mini.def.json b/resources/definitions/bambulab_a1mini.def.json new file mode 100644 index 0000000000..490556e4f0 --- /dev/null +++ b/resources/definitions/bambulab_a1mini.def.json @@ -0,0 +1,42 @@ +{ + "version": 2, + "name": "BambuLab A1 mini", + "inherits": "bambulab_base", + "metadata": + { + "visible": true, + "platform": "bambulab_a1mini.obj", + "has_machine_quality": true, + "has_material": true, + "has_textured_buildplate": true, + "has_variant_buildplates": false, + "has_variants": true, + "machine_extruder_trains": + { + "0": "bambulab_a1mini_extruder_0", + "1": "bambulab_a1mini_extruder_1", + "2": "bambulab_a1mini_extruder_2", + "3": "bambulab_a1mini_extruder_3" + }, + "platform_offset": [ + -90, + 0, + 90 + ], + "platform_texture": "bambulab-buildplate.png", + "preferred_variant_name": "0.4mm", + "weight": 3 + }, + "overrides": + { + "machine_depth": { "value": 180 }, + "machine_end_gcode": { "default_value": ";===== date: 20231229 =====================\n;turn off nozzle clog detect\nG392 S0\n\nM400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-0.8 F1800 ; retract\nG1 Z{machine_height + 0.5} F900 ; lower z a little\nG1 X0 Y{machine_depth} F18000 ; move to safe pos\nG1 X-13.0 F3000 ; move to safe pos\n{if !magic_spiralize && print_sequence != 'one_at_a_time'}\nM1002 judge_flag timelapse_record_flag\nM622 J1\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM400 P100\nM971 S11 C11 O0\nM991 S0 P-1 ;end timelapse at safe pos\nM623\n{endif}\n\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nM106 P2 S0 ; turn off remote part cooling fan\nM106 P3 S0 ; turn off chamber cooling fan\n\n;G1 X27 F15000 ; wipe\n\n; pull back filament to AMS\nM620 S255\nG1 X181 F12000\nT255\nG1 X0 F18000\nG1 X-13.0 F3000\nG1 X0 F18000 ; wipe\nM621 S255\n\nM104 S0 ; turn off hotend\n\nM400 ; wait all motion done\nM17 S\nM17 Z0.4 ; lower z motor current to reduce impact if there is something in the bottom\nG1 Z180 F600\nG1 Z180\nM400 P100\nM17 R ; restore z current\n\nG90\nG1 X-13 Y180 F3600\n\nG91\nG1 Z-1 F600\nG90\nM83\n\nM220 S100 ; Reset feedrate magnitude\nM201.2 K1.0 ; Reset acc magnitude\nM73.2 R1.0 ;Reset left time magnitude\nM1002 set_gcode_claim_speed_level : 0\n\n;=====printer finish sound=========\nM17\nM400 S1\nM1006 S1\nM1006 A0 B20 L100 C37 D20 M100 E42 F20 N100\nM1006 A0 B10 L100 C44 D10 M100 E44 F10 N100\nM1006 A0 B10 L100 C46 D10 M100 E46 F10 N100\nM1006 A44 B20 L100 C39 D20 M100 E48 F20 N100\nM1006 A0 B10 L100 C44 D10 M100 E44 F10 N100\nM1006 A0 B10 L100 C0 D10 M100 E0 F10 N100\nM1006 A0 B10 L100 C39 D10 M100 E39 F10 N100\nM1006 A0 B10 L100 C0 D10 M100 E0 F10 N100\nM1006 A0 B10 L100 C44 D10 M100 E44 F10 N100\nM1006 A0 B10 L100 C0 D10 M100 E0 F10 N100\nM1006 A0 B10 L100 C39 D10 M100 E39 F10 N100\nM1006 A0 B10 L100 C0 D10 M100 E0 F10 N100\nM1006 A44 B10 L100 C0 D10 M100 E48 F10 N100\nM1006 A0 B10 L100 C0 D10 M100 E0 F10 N100\nM1006 A44 B20 L100 C41 D20 M100 E49 F20 N100\nM1006 A0 B20 L100 C0 D20 M100 E0 F20 N100\nM1006 A0 B20 L100 C37 D20 M100 E37 F20 N100\nM1006 W\n;=====printer finish sound=========\nM400 S1\nM18 X Y Z\n" }, + "machine_extruder_count": { "value": 4 }, + "machine_height": { "value": 175 }, + "machine_name": { "default_value": "BambuLab Bambu A1 mini" }, + "machine_start_gcode": { "default_value": ";===== machine: A1 mini =========================\n\n;===== start to heat heatbead&hotend==========\nM1002 gcode_claim_action : 2\nM1002 set_filament_type:{material_type, initial_extruder_nr}\nM104 S170\nM140 S{material_bed_temperature_layer_0}\nG392 S0 ;turn off clog detect\nM9833.2\n;=====start printer sound ===================\n; 'The entertainer' by Scott Joplin\nM17\nM400 S1\nM1006 S1\n\nM1006 A0 B10 C39 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C40 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C41 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C49 D30 L30 M60 E0 F10 N60\nM1006 A0 B10 C41 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C49 D30 L30 M60 E0 F10 N60\nM1006 A0 B10 C41 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C49 D30 L30 M60 E0 F10 N60\n\nM1006 A0 B10 C0 D30 L30 M60 E0 F10 N60\n\nM1006 A0 B10 C49 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C50 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C51 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C53 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C50 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C51 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C53 D30 L30 M60 E0 F10 N60\nM1006 A0 B10 C47 D15 L30 M60 E0 F10 N60\nM1006 A0 B10 C51 D30 L30 M60 E0 F10 N60\nM1006 A0 B10 C49 D30 L30 M60 E0 F10 N60\n\nM1006 W\nM18\n;=====avoid end stop =================\nG91\nG380 S2 Z30 F1200\nG380 S3 Z-20 F1200\nG1 Z5 F1200\nG90\n\n;===== reset machine status =================\nM204 S6000\n\nM630 S0 P0\nG91\nM17 Z0.3 ; lower the z-motor current\n\nG90\nM17 X0.7 Y0.9 Z0.5 ; reset motor current to default\nM960 S5 P1 ; turn on logo lamp\nG90\nM83\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nM73.2 R1.0 ;Reset left time magnitude\n;====== cog noise reduction=================\nM982.2 S1 ; turn on cog noise reduction\n\n;===== prepare print temperature and material ==========\nM400\nM18\nM109 S100 H170\nM104 S170\nM400\nM17\nM400\nG28 X\n\nM211 X0 Y0 Z0 ;turn off soft endstop ; turn off soft endstop to prevent protential logic problem\n\nM975 S1 ; turn on\n\nG1 X0.0 F30000\nG1 X-13.5 F3000\n\nM620 M ;enable remap\nM620 S{initial_extruder_nr}A ; switch material if AMS exist\n G392 S0 ;turn on clog detect\n M1002 gcode_claim_action : 4\n M400\n M1002 set_filament_type:UNKNOWN\n M109 S{material_print_temperature_layer_0, initial_extruder_nr}\n M104 S250\n M400\n T{initial_extruder_nr}\n G1 X-13.5 F3000\n M400\n M620.1 E F{material_max_flowrate/2.4053*60, initial_extruder_nr} T{material_print_temperature, initial_extruder_nr}\n M109 S250 ;set nozzle to common flush temp\n M106 P1 S0\n G92 E0\n G1 E50 F200\n M400\n M1002 set_filament_type:{material_type, initial_extruder_nr}\n M104 S{material_print_temperature, initial_extruder_nr}\n G92 E0\n G1 E50 F{material_max_flowrate/2.4053*60, initial_extruder_nr}\n M400\n M106 P1 S178\n G92 E0\n G1 E5 F{material_max_flowrate/2.4053*60, initial_extruder_nr}\n M109 S{material_print_temperature_layer_0-20, initial_extruder_nr} ; drop nozzle temp, make filament shink a bit\n M104 S{material_print_temperature_layer_0-40, material_print_temperature_layer_0}\n G92 E0\n G1 E-0.5 F300\n\n G1 X0 F30000\n G1 X-13.5 F3000\n G1 X0 F30000 ;wipe and shake\n G1 X-13.5 F3000\n G1 X0 F12000 ;wipe and shake\n G1 X0 F30000\n G1 X-13.5 F3000\n M109 S{material_print_temperature_layer_0-40, initial_extruder_nr}\n G392 S0 ;turn off clog detect\nM621 S{initial_extruder_nr}A\n\nM400\nM106 P1 S0\n;===== prepare print temperature and material end =====\n\n\n;===== mech mode fast check============================\n{if material_print_temperature > 2000}\nM1002 gcode_claim_action : 3\n{endif}\nG0 X25 Y175 F20000 ; find a soft place to home\n;M104 S0\nG28 Z P0 T300; home z with low precision,permit 300deg temperature\nG29.2 S0 ; turn off ABL\nM104 S170\n\n; build plate detect\nM1002 judge_flag build_plate_detect_flag\nM622 S1\n G39.4\n M400\nM623\n\nG1 Z5 F3000\nG1 X90 Y-1 F30000\nM400 P200\nM970.3 Q1 A7 K0 O2\nM974 Q1 S2 P0\n\nG1 X90 Y0 Z5 F30000\nM400 P200\nM970 Q0 A10 B50 C90 H15 K0 M20 O3\nM974 Q0 S2 P0\n\nM975 S1\nG1 F30000\nG1 X-1 Y10\nG28 X ; re-home XY\n\n;===== wipe nozzle ===============================\n{if material_print_temperature > 2000}\nM1002 gcode_claim_action : 14\nM975 S1\n\nM104 S170 ; set temp down to heatbed acceptable\nM106 S255 ; turn on fan (G28 has turn off fan)\nM211 S; push soft endstop status\nM211 X0 Y0 Z0 ;turn off Z axis endstop\n\nM83\nG1 E-1 F500\nG90\nM83\n\nM109 S170\nM104 S140\nG0 X90 Y-4 F30000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X91 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X92 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X93 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X94 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X95 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X96 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X97 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X98 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X99 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X99 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X99 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X99 F10000\nG380 S3 Z-5 F1200\nG1 Z2 F1200\nG1 X99 F10000\nG380 S3 Z-5 F1200\n\nG1 Z5 F30000\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\nG1 X25 Y175 F30000.1 ;Brush material\nG1 Z0.2 F30000.1\nG1 Y185\nG91\nG1 X-30 F30000\nG1 Y-2\nG1 X27\nG1 Y1.5\nG1 X-28\nG1 Y-2\nG1 X30\nG1 Y1.5\nG1 X-30\nG90\nM83\n\nG1 Z5 F3000\nG0 X50 Y175 F20000 ; find a soft place to home\nG28 Z P0 T300; home z with low precision, permit 300deg temperature\nG29.2 S0 ; turn off ABL\n\nG0 X85 Y185 F10000 ;move to exposed steel surface and stop the nozzle\nG0 Z-1.01 F10000\nG91\n\nG2 I1 J0 X2 Y0 F2000.1\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\nG2 I1 J0 X2\nG2 I-0.75 J0 X-1.5\n\nG90\nG1 Z5 F30000\nG1 X25 Y175 F30000.1 ;Brush material\nG1 Z0.2 F30000.1\nG1 Y185\nG91\nG1 X-30 F30000\nG1 Y-2\nG1 X27\nG1 Y1.5\nG1 X-28\nG1 Y-2\nG1 X30\nG1 Y1.5\nG1 X-30\nG90\nM83\n\nG1 Z5\nG0 X55 Y175 F20000 ; find a soft place to home\nG28 Z P0 T300; home z with low precision, permit 300deg temperature\nG29.2 S0 ; turn off ABL\n\nG1 Z10\nG1 X85 Y185\nG1 Z-1.01\nG1 X95\nG1 X90\n\nM211 R; pop softend status\n\nM106 S0 ; turn off fan , too noisy\n{endif}\n;===== wipe nozzle end ================================\n\n\n;===== wait heatbed ====================\nM1002 gcode_claim_action : 2\nM104 S0\nM190 S{material_bed_temperature_layer_0};set bed temp\nM109 S140\n\nG1 Z5 F3000\nG29.2 S1\nG1 X10 Y10 F20000\n\n;===== bed leveling ==================================\n;M1002 set_flag g29_before_print_flag=1\nM1002 judge_flag g29_before_print_flag\nM622 J1\n M1002 gcode_claim_action : 1\n G29 A1 X{-machine_width/2 if machine_center_is_zero else 0} Y{-machine_depth/2 if machine_center_is_zero else 0} I{machine_width} J{machine_depth}\n M400\n M500 ; save cali data\nM623\n;===== bed leveling end ================================\n\n;===== home after wipe mouth============================\nM1002 judge_flag g29_before_print_flag\nM622 J0\n\n M1002 gcode_claim_action : 13\n G28 T145\n\nM623\n\n;===== home after wipe mouth end =======================\n\nM975 S1 ; turn on vibration supression\n;===== nozzle load line ===============================\nM975 S1\nG90\nM83\nT1000\n\nG1 X-13.5 Y0 Z10 F10000\nG1 E1.2 F500\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature_layer_0, initial_extruder_nr}\nM400\n\nM412 S1 ; ===turn on filament runout detection===\nM400 P10\n\nG392 S0 ;turn on clog detect\n\nM620.3 W1; === turn on filament tangle detection===\nM400 S2\n\nM1002 set_filament_type:{material_type, initial_extruder_nr}\n;M1002 set_flag extrude_cali_flag=1\nM1002 judge_flag extrude_cali_flag\nM622 J1\n M1002 gcode_claim_action : 8\n\n M400\n M900 K0.0 L1000.0 M1.0\n G90\n M83\n G0 X68 Y-4 F30000\n G0 Z0.3 F18000 ;Move to start position\n M400\n G0 X88 E10 F{speed_wall_0*wall_line_width_0*layer_height/(24/20) * 60, initial_extruder_nr}\n G0 X93 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4 * 60, initial_extruder_nr}\n G0 X98 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G0 X103 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4 * 60, initial_extruder_nr}\n G0 X108 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G0 X113 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4 * 60, initial_extruder_nr}\n G0 Y0 Z0 F20000\n M400\n\n G1 X-13.5 Y0 Z10 F10000\n M400\n\n G1 E10 F{speed_wall_0*wall_line_width_0*layer_height/2.4*60, initial_extruder_nr}\n M983 F{speed_wall_0*wall_line_width_0*layer_height/2.4, initial_extruder_nr} A0.3 H{machine_nozzle_size}; cali dynamic extrusion compensation\n M106 P1 S178\n M400 S7\n G1 X0 F18000\n G1 X-13.5 F3000\n G1 X0 F18000 ;wipe and shake\n G1 X-13.5 F3000\n G1 X0 F12000 ;wipe and shake\n G1 X-13.5 F3000\n M400\n M106 P1 S0\n\n M1002 judge_last_extrude_cali_success\n M622 J0\n M983 F{speed_wall_0*wall_line_width_0*layer_height/2.4, initial_extruder_nr} A0.3 H{machine_nozzle_size}; cali dynamic extrusion compensation\n M106 P1 S178\n M400 S7\n G1 X0 F18000\n G1 X-13.5 F3000\n G1 X0 F18000 ;wipe and shake\n G1 X-13.5 F3000\n G1 X0 F12000 ;wipe and shake\n M400\n M106 P1 S0\n M623\n\n G1 X-13.5 F3000\n M400\n M984 A0.1 E1 S1 F{speed_wall_0*wall_line_width_0*layer_height/2.4, initial_extruder_nr} H{machine_nozzle_size}\n M106 P1 S178\n M400 S7\n G1 X0 F18000\n G1 X-13.5 F3000\n G1 X0 F18000 ;wipe and shake\n G1 X-13.5 F3000\n G1 X0 F12000 ;wipe and shake\n G1 X-13.5 F3000\n M400\n M106 P1 S0\n\nM623 ; end of 'draw extrinsic para cali paint'\n\n;===== extrude cali test ===============================\nM104 S{material_print_temperature_layer_0, initial_extruder_nr}\nG90\nM83\nG0 X68 Y-2.5 F30000\nG0 Z0.3 F18000 ;Move to start position\nG0 X88 E10 F{speed_wall_0*wall_line_width_0*layer_height/(24/20) * 60, initial_extruder_nr}\nG0 X93 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4 * 60, initial_extruder_nr}\nG0 X98 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\nG0 X103 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4 * 60, initial_extruder_nr}\nG0 X108 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\nG0 X113 E.3742 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4 * 60, initial_extruder_nr}\nG0 X115 Z0 F20000\nG0 Z5\nM400\n\n;========turn off light and wait extrude temperature =============\nM1002 gcode_claim_action : 0\n\nM400 ; wait all motion done before implement the emprical L parameters\n\n;===== for Textured PEI Plate , lower the nozzle as the nozzle was touching topmost of the texture when homing ==\n;curr_bed_type={curr_bed_type}\n{if machine_buildplate_type=='textured_pei_plate'}\nG29.1 Z{-0.02} ; for Textured PEI Plate\n{endif}\n\nM960 S1 P0 ; turn off laser\nM960 S2 P0 ; turn off laser\nM106 S0 ; turn off fan\nM106 P2 S0 ; turn off big fan\nM106 P3 S0 ; turn off chamber fan\n\nM975 S1 ; turn on mech mode supression\nG90\nM83\nT1000\n\nM211 X0 Y0 Z0 ;turn off soft endstop\nM1007 S1\n\n\n\n" }, + "machine_width": { "value": 180 }, + "prime_tower_position_x": { "value": "resolveOrValue('prime_tower_size') + (resolveOrValue('prime_tower_base_size') if (resolveOrValue('adhesion_type') == 'raft' or resolveOrValue('prime_tower_brim_enable')) else 0) + max(max(extruderValues('travel_avoid_distance')) + max(extruderValues('machine_nozzle_offset_y')) + max(extruderValues('support_offset')) + (extruderValue(skirt_brim_extruder_nr, 'skirt_brim_line_width') * extruderValue(skirt_brim_extruder_nr, 'skirt_line_count') * extruderValue(skirt_brim_extruder_nr, 'initial_layer_line_width_factor') / 100 + extruderValue(skirt_brim_extruder_nr, 'skirt_gap') if resolveOrValue('adhesion_type') == 'skirt' else 0) + (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0), max(map(abs, extruderValues('machine_nozzle_offset_y'))), 1) - (resolveOrValue('machine_depth') / 2 if resolveOrValue('machine_center_is_zero') else 0)" }, + "prime_tower_position_y": { "value": "(resolveOrValue('prime_tower_base_size') if (resolveOrValue('adhesion_type') == 'raft' or resolveOrValue('prime_tower_brim_enable')) else 0) + max(max(extruderValues('travel_avoid_distance')) + max(extruderValues('machine_nozzle_offset_y')) + max(extruderValues('support_offset')) + (extruderValue(skirt_brim_extruder_nr, 'skirt_brim_line_width') * extruderValue(skirt_brim_extruder_nr, 'skirt_line_count') * extruderValue(skirt_brim_extruder_nr, 'initial_layer_line_width_factor') / 100 + extruderValue(skirt_brim_extruder_nr, 'skirt_gap') if resolveOrValue('adhesion_type') == 'skirt' else 0) + (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0), max(map(abs, extruderValues('machine_nozzle_offset_y'))), 1) - (resolveOrValue('machine_depth') / 2 if resolveOrValue('machine_center_is_zero') else 0)" } + } +} \ No newline at end of file diff --git a/resources/definitions/bambulab_base.def.json b/resources/definitions/bambulab_base.def.json new file mode 100644 index 0000000000..a28e606a38 --- /dev/null +++ b/resources/definitions/bambulab_base.def.json @@ -0,0 +1,267 @@ +{ + "version": 2, + "name": "BambuLab base definition", + "inherits": "fdmprinter", + "metadata": + { + "visible": false, + "author": "UltiMaker", + "manufacturer": "BambuLab", + "file_formats": "application/vnd.bambulab-package.3dmanufacturing-3dmodel+xml" + }, + "overrides": + { + "acceleration_infill": { "value": "acceleration_print" }, + "acceleration_layer_0": { "value": 2000 }, + "acceleration_prime_tower": { "value": "acceleration_print" }, + "acceleration_print": { "value": 20000 }, + "acceleration_print_layer_0": { "value": "acceleration_layer_0" }, + "acceleration_roofing": { "value": "acceleration_wall_0" }, + "acceleration_skirt_brim": { "value": "acceleration_layer_0" }, + "acceleration_support": { "value": "acceleration_print" }, + "acceleration_support_bottom": { "value": "acceleration_support_interface" }, + "acceleration_support_infill": { "value": "acceleration_support" }, + "acceleration_support_interface": { "value": "acceleration_support" }, + "acceleration_support_roof": { "value": "acceleration_support_interface" }, + "acceleration_topbottom": { "value": "acceleration_print" }, + "acceleration_travel": { "value": 20000 }, + "acceleration_travel_enabled": { "value": true }, + "acceleration_travel_layer_0": { "value": "acceleration_layer_0" }, + "acceleration_wall": { "value": "acceleration_print/8" }, + "acceleration_wall_0": { "value": "acceleration_wall" }, + "acceleration_wall_0_roofing": { "value": "acceleration_wall_0" }, + "acceleration_wall_x": { "value": "acceleration_print" }, + "acceleration_wall_x_roofing": { "value": "acceleration_wall" }, + "adhesion_type": { "value": "'skirt'" }, + "bottom_thickness": { "value": 0.6 }, + "bridge_skin_speed": + { + "unit": "mm/s", + "value": "bridge_wall_speed" + }, + "bridge_sparse_infill_max_density": { "value": 50 }, + "bridge_wall_min_length": { "value": 10 }, + "bridge_wall_speed": + { + "unit": "mm/s", + "value": 50 + }, + "cool_min_layer_time": { "value": 6 }, + "cool_min_speed": { "value": 6 }, + "cool_min_temperature": { "value": "material_print_temperature-15" }, + "default_material_print_temperature": { "maximum_value_warning": 320 }, + "extra_infill_lines_to_support_skins": { "value": "'walls_and_lines'" }, + "gradual_flow_enabled": { "value": false }, + "hole_xy_offset": { "value": 0.075 }, + "infill_overlap": { "value": 10 }, + "infill_pattern": { "value": "'zigzag' if infill_sparse_density > 80 else 'gyroid'" }, + "infill_sparse_density": { "value": 15 }, + "infill_wall_line_count": { "value": "1 if infill_sparse_density > 80 else 0" }, + "jerk_infill": { "value": "jerk_print" }, + "jerk_layer_0": { "value": "jerk_print/2" }, + "jerk_prime_tower": { "value": "jerk_print" }, + "jerk_print": { "value": "50" }, + "jerk_print_layer_0": { "value": "jerk_layer_0" }, + "jerk_roofing": { "value": "jerk_wall_0" }, + "jerk_skirt_brim": { "value": "jerk_layer_0" }, + "jerk_support": { "value": "jerk_print" }, + "jerk_support_bottom": { "value": "jerk_support_interface" }, + "jerk_support_infill": { "value": "jerk_support" }, + "jerk_support_interface": { "value": "jerk_support" }, + "jerk_support_roof": { "value": "jerk_support_interface" }, + "jerk_topbottom": { "value": "jerk_print" }, + "jerk_travel": { "value": 50 }, + "jerk_travel_enabled": { "value": true }, + "jerk_travel_layer_0": { "value": "jerk_travel" }, + "jerk_wall": { "value": "jerk_print/5" }, + "jerk_wall_0": { "value": "jerk_wall" }, + "jerk_wall_0_roofing": { "value": "jerk_wall_0" }, + "jerk_wall_x": { "value": "jerk_print" }, + "jerk_wall_x_roofing": { "value": "jerk_wall_0" }, + "line_width": { "value": 0.42 }, + "machine_acceleration": { "value": 10000 }, + "machine_buildplate_type": + { + "default_value": "textured_pei_plate", + "options": + { + "cool_plate": "Cool Plate", + "engineering_plate": "Engineering Plate", + "high_temp_plate": "High Temp Plate", + "textured_pei_plate": "Textured PEI Plate" + } + }, + "machine_center_is_zero": { "default_value": false }, + "machine_gcode_flavor": { "default_value": "BambuLab" }, + "machine_heated_bed": { "default_value": true }, + "machine_max_feedrate_e": { "value": 150 }, + "machine_max_feedrate_x": { "value": 500 }, + "machine_max_feedrate_y": { "value": 500 }, + "machine_max_feedrate_z": { "value": 15 }, + "machine_max_jerk_e": { "default_value": 100 }, + "machine_max_jerk_xy": { "default_value": 5000 }, + "machine_max_jerk_z": { "default_value": 100 }, + "machine_nozzle_cool_down_speed": { "default_value": 1.3 }, + "machine_nozzle_heat_up_speed": { "default_value": 1.9 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "machine_show_variants": { "value": true }, + "machine_use_extruder_offset_to_offset_coords": { "value": false }, + "material_diameter": { "default_value": 1.75 }, + "material_flush_purge_length": + { + "default_value": 80, + "enabled": "not prime_tower_enable" + }, + "material_flush_purge_speed": + { + "default_value": 500, + "enabled": "not prime_tower_enable" + }, + "material_max_flowrate": { "enabled": true }, + "max_skin_angle_for_expansion": { "value": 45 }, + "meshfix_maximum_resolution": { "value": 0.4 }, + "min_infill_area": { "default_value": 10 }, + "optimize_wall_printing_order": { "value": false }, + "prime_tower_enable": { "default_value": true }, + "prime_tower_line_width": { "value": "1.5 * line_width" }, + "prime_tower_min_volume": { "default_value": 250 }, + "prime_tower_size": { "default_value": 40 }, + "relative_extrusion": { "value": true }, + "retraction_amount": { "value": 0.5 }, + "retraction_combing_max_distance": { "value": 100 }, + "retraction_extra_prime_amount": { "value": 0.12 }, + "retraction_hop": { "value": 0.2 }, + "retraction_hop_after_extruder_switch_height": { "value": 2 }, + "retraction_hop_enabled": { "value": true }, + "retraction_min_travel": { "value": "5 if support_enable and support_structure=='tree' else line_width * 2" }, + "retraction_prime_speed": { "value": 15 }, + "retraction_speed": { "value": 30 }, + "skin_edge_support_thickness": { "value": 0 }, + "skin_material_flow": { "value": 95 }, + "skin_overlap": { "value": 0 }, + "skin_preshrink": { "value": 0 }, + "skirt_brim_speed": { "maximum_value_warning": 500 }, + "skirt_line_count": { "value": 5 }, + "small_skin_on_surface": { "value": false }, + "small_skin_width": { "value": 4 }, + "speed_infill": + { + "maximum_value_warning": 500, + "value": "speed_print" + }, + "speed_ironing": + { + "maximum_value_warning": 500, + "value": 20 + }, + "speed_layer_0": + { + "maximum_value_warning": 500, + "value": "speed_print/6" + }, + "speed_prime_tower": + { + "maximum_value_warning": 500, + "value": "speed_wall" + }, + "speed_print": + { + "maximum_value_warning": 500, + "value": 300 + }, + "speed_print_layer_0": + { + "maximum_value_warning": 500, + "value": "speed_layer_0" + }, + "speed_roofing": + { + "maximum_value_warning": 500, + "value": "speed_wall" + }, + "speed_support": + { + "maximum_value_warning": 500, + "value": "speed_wall_0" + }, + "speed_support_bottom": + { + "maximum_value_warning": 500, + "value": "speed_support_interface" + }, + "speed_support_infill": + { + "maximum_value_warning": 500, + "value": "speed_support" + }, + "speed_support_interface": + { + "maximum_value_warning": 500, + "value": 50 + }, + "speed_support_roof": + { + "maximum_value_warning": 500, + "value": "speed_support_interface" + }, + "speed_topbottom": + { + "maximum_value_warning": 500, + "value": "speed_print" + }, + "speed_travel": + { + "maximum_value": 500, + "value": 500 + }, + "speed_travel_layer_0": + { + "maximum_value": 500, + "value": 150 + }, + "speed_wall": + { + "maximum_value_warning": 500, + "value": "speed_print*2/3" + }, + "speed_wall_0": + { + "maximum_value_warning": 500, + "value": "speed_wall" + }, + "speed_wall_0_roofing": + { + "maximum_value_warning": 500, + "value": "speed_wall" + }, + "speed_wall_x": + { + "maximum_value_warning": 500, + "value": "speed_print" + }, + "speed_wall_x_roofing": + { + "maximum_value_warning": 500, + "value": "speed_wall" + }, + "support_brim_line_count": { "value": 5 }, + "support_infill_rate": { "value": "80 if gradual_support_infill_steps != 0 else 15" }, + "support_pattern": { "value": "'gyroid'" }, + "support_structure": { "value": "'tree'" }, + "switch_extruder_retraction_amount": { "value": 5 }, + "travel_avoid_other_parts": { "value": false }, + "wall_0_acceleration": { "value": 1000 }, + "wall_0_deceleration": { "value": 1000 }, + "wall_0_end_speed_ratio": { "value": 100 }, + "wall_0_speed_split_distance": { "value": 0.2 }, + "wall_0_start_speed_ratio": { "value": 100 }, + "wall_0_wipe_dist": { "value": 0 }, + "wall_material_flow": { "value": 95 }, + "wall_overhang_angle": { "value": 10 }, + "wall_overhang_speed_factors": { "default_value": "[25,15,5,5]" }, + "wall_x_material_flow": { "value": 100 }, + "z_seam_corner": { "value": "'z_seam_corner_weighted'" }, + "z_seam_position": { "value": "'backright'" }, + "z_seam_type": { "value": "'sharpest_corner'" } + } +} \ No newline at end of file diff --git a/resources/definitions/bambulab_x1.def.json b/resources/definitions/bambulab_x1.def.json new file mode 100644 index 0000000000..0c6d223e1a --- /dev/null +++ b/resources/definitions/bambulab_x1.def.json @@ -0,0 +1,58 @@ +{ + "version": 2, + "name": "BambuLab X1", + "inherits": "bambulab_base", + "metadata": + { + "visible": true, + "platform": "bambulab_x1.obj", + "has_machine_quality": true, + "has_material": true, + "has_textured_buildplate": true, + "has_variant_buildplates": false, + "has_variants": true, + "machine_extruder_trains": + { + "0": "bambulab_x1_extruder_0", + "1": "bambulab_x1_extruder_1", + "2": "bambulab_x1_extruder_2", + "3": "bambulab_x1_extruder_3" + }, + "platform_offset": [ + -130, + 0, + 130 + ], + "platform_texture": "bambulab-buildplate.png", + "preferred_variant_name": "X1 0.4mm", + "weight": 3 + }, + "overrides": + { + "machine_depth": { "value": 256 }, + "machine_disallowed_areas": + { + "default_value": [ + [ + [-128, 100], + [-110, 100], + [-110, 128], + [-128, 128] + ] + ] + }, + "machine_end_gcode": { "default_value": ";===== date: 20240528 =====================\nM400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-0.8 F1800 ; retract\nG1 Z{machine_height + 0.5} F900 ; lower z a little\nG1 X65 Y245 F12000 ; move to safe pos\nG1 Y265 F3000\n\nG1 X65 Y245 F12000\nG1 Y265 F3000\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nM106 P2 S0 ; turn off remote part cooling fan\nM106 P3 S0 ; turn off chamber cooling fan\n\nG1 X100 F12000 ; wipe\n; pull back filament to AMS\nM620 S255\nG1 X20 Y50 F12000\nG1 Y-3\nT255\nG1 X65 F12000\nG1 Y265\nG1 X100 F12000 ; wipe\nM621 S255\nM104 S0 ; turn off hotend\n\nM622.1 S1 ; for prev firware, default turned on\nM1002 judge_flag timelapse_record_flag\nM622 J1\n M400 ; wait all motion done\n M991 S0 P-1 ;end smooth timelapse at safe pos\n M400 S3 ;wait for last picture to be taken\nM623; end of 'timelapse_record_flag'\n\nM400 ; wait all motion done\nM17 S\nM17 Z0.4 ; lower z motor current to reduce impact if there is something in the bottom\nG1 Z250 F600\nG1 Z248\nM400 P100\nM17 R ; restore z current\n\nM220 S100 ; Reset feedrate magnitude\nM201.2 K1.0 ; Reset acc magnitude\nM73.2 R1.0 ;Reset left time magnitude\nM1002 set_gcode_claim_speed_level : 0\n\nM17 X0.8 Y0.8 Z0.5 ; lower motor current to 45% power\nM960 S5 P0 ; turn off logo lamp\n" }, + "machine_extruder_count": { "value": 4 }, + "machine_height": { "value": 251 }, + "machine_name": { "default_value": "BambuLab Bambu X1" }, + "machine_scan_first_layer": + { + "enabled": true, + "value": "machine_buildplate_type!='textured_pei_plate'" + }, + "machine_start_gcode": { "default_value": ";===== machine: X1 ====================\n;===== date: 20241023 ==================\n;===== turn on the HB fan =================\nM104 S75 ;set extruder temp to turn on the HB fan and prevent filament oozing from nozzle\n;===== reset machine status =================\nM290 X40 Y40 Z2.6666666\nG91\nM17 Z0.4 ; lower the z-motor current\nG380 S2 Z30 F300 ; G380 is same as G38; lower the hotbed , to prevent the nozzle is below the hotbed\nG380 S2 Z-25 F300 ;\nG1 Z5 F300;\nG90\nM17 X1.2 Y1.2 Z0.75 ; reset motor current to default\nM960 S5 P1 ; turn on logo lamp\nG90\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nM73.2 R1.0 ;Reset left time magnitude\nM1002 set_gcode_claim_speed_level : 5\nM221 X0 Y0 Z0 ; turn off soft endstop to prevent protential logic problem\nG29.1 Z0 ; clear z-trim value first\nM204 S10000 ; init ACC set to 10m/s^2\n\n;===== heatbed preheat ====================\nM1002 gcode_claim_action : 2\nM140 S{material_bed_temperature_layer_0} ;set bed temp\nM190 S{material_bed_temperature_layer_0} ;wait for bed temp\n\n{if machine_scan_first_layer}\n;=========register first layer scan=====\nM977 S1 P60\n{endif}\n\n;=============turn on fans to prevent PLA jamming=================\n{if material_type=='PLA' and (material_bed_temperature_layer_0>45 or material_bed_temperature>45), initial_extruder_nr}\n M106 P3 S180\n M142 P1 R35 S40\n{endif}\n{if material_type=='PLA' and (material_bed_temperature_layer_0<=45 and material_bed_temperature<=45) , initial_extruder_nr}\n M142 P1 R35 S40\n{endif}\nM106 P2 S100 ; turn on big fan ,to cool down toolhead\n\n;===== prepare print temperature and material ==========\nM104 S{material_print_temperature_layer_0, initial_extruder_nr} ;set extruder temp\nG91\nG0 Z10 F1200\nG90\nG28 X\nM975 S1 ; turn on\nG1 X60 F12000\nG1 Y245\nG1 Y265 F3000\nM620 M\nM620 S{initial_extruder_nr}A ; switch material if AMS exist\n M109 S{material_print_temperature_layer_0, initial_extruder_nr}\n G1 X120 F12000\n\n G1 X20 Y50 F12000\n G1 Y-3\n T{initial_extruder_nr}\n G1 X54 F12000\n G1 Y265\n M400\nM621 S{initial_extruder_nr}A\nM620.1 E F{material_max_flowrate/2.4053*60, initial_extruder_nr} T{material_print_temperature, initial_extruder_nr}\n\n\nM412 S1 ; ===turn on filament runout detection===\n\nM109 S250 ;set nozzle to common flush temp\nM106 P1 S0\nG92 E0\nG1 E50 F200\nM400\nM104 S{material_print_temperature_layer_0, initial_extruder_nr}\nG92 E0\nG1 E50 F200\nM400\nM106 P1 S255\nG92 E0\nG1 E5 F300\nM109 S{material_print_temperature_layer_0-20, initial_extruder_nr} ; drop nozzle temp, make filament shink a bit\nG92 E0\nG1 E-0.5 F300\n\nG1 X70 F9000\nG1 X76 F15000\nG1 X65 F15000\nG1 X76 F15000\nG1 X65 F15000; shake to put down garbage\nG1 X80 F6000\nG1 X95 F15000\nG1 X80 F15000\nG1 X165 F15000; wipe and shake\nM400\nM106 P1 S0\n;===== prepare print temperature and material end =====\n\n\n;===== wipe nozzle ===============================\nM1002 gcode_claim_action : 14\nM975 S1\nM106 S255\nG1 X65 Y230 F18000\nG1 Y264 F6000\nM109 S{material_print_temperature_layer_0-20, initial_extruder_nr}\nG1 X100 F18000 ; first wipe mouth\n\nG0 X135 Y253 F20000 ; move to exposed steel surface edge\nG28 Z P0 T300; home z with low precision,permit 300deg temperature\nG29.2 S0 ; turn off ABL\nG0 Z5 F20000\n\nG1 X60 Y265\nG92 E0\nG1 E-0.5 F300 ; retrack more\nG1 X100 F5000; second wipe mouth\nG1 X70 F15000\nG1 X100 F5000\nG1 X70 F15000\nG1 X100 F5000\nG1 X70 F15000\nG1 X100 F5000\nG1 X70 F15000\nG1 X90 F5000\nG0 X128 Y261 Z-1.5 F20000 ; move to exposed steel surface and stop the nozzle\nM104 S140 ; set temp down to heatbed acceptable\nM106 S255 ; turn on fan (G28 has turn off fan)\n\nM221 S; push soft endstop status\nM221 Z0 ;turn off Z axis endstop\nG0 Z0.5 F20000\nG0 X125 Y259.5 Z-1.01\nG0 X131 F211\nG0 X124\nG0 Z0.5 F20000\nG0 X125 Y262.5\nG0 Z-1.01\nG0 X131 F211\nG0 X124\nG0 Z0.5 F20000\nG0 X125 Y260.0\nG0 Z-1.01\nG0 X131 F211\nG0 X124\nG0 Z0.5 F20000\nG0 X125 Y262.0\nG0 Z-1.01\nG0 X131 F211\nG0 X124\nG0 Z0.5 F20000\nG0 X125 Y260.5\nG0 Z-1.01\nG0 X131 F211\nG0 X124\nG0 Z0.5 F20000\nG0 X125 Y261.5\nG0 Z-1.01\nG0 X131 F211\nG0 X124\nG0 Z0.5 F20000\nG0 X125 Y261.0\nG0 Z-1.01\nG0 X131 F211\nG0 X124\nG0 X128\nG2 I0.5 J0 F300\nG2 I0.5 J0 F300\nG2 I0.5 J0 F300\nG2 I0.5 J0 F300\n\nM109 S140 ; wait nozzle temp down to heatbed acceptable\nG2 I0.5 J0 F3000\nG2 I0.5 J0 F3000\nG2 I0.5 J0 F3000\nG2 I0.5 J0 F3000\n\nM221 R; pop softend status\nG1 Z10 F1200\nM400\nG1 Z10\nG1 F30000\nG1 X128 Y128\nG29.2 S1 ; turn on ABL\n;G28 ; home again after hard wipe mouth\nM106 S0 ; turn off fan , too noisy\n;===== wipe nozzle end ================================\n\n;===== check scanner clarity ===========================\nG1 X128 Y128 F24000\nG28 Z P0\nM972 S5 P0\nG1 X230 Y15 F24000\n;===== check scanner clarity end =======================\n\n;===== bed leveling ==================================\nM1002 judge_flag g29_before_print_flag\nM622 J1\n\n M1002 gcode_claim_action : 1\n G29 A X{-machine_width/2 if machine_center_is_zero else 0} Y{-machine_depth/2 if machine_center_is_zero else 0} I{machine_width} J{machine_depth}\n M400\n M500 ; save cali data\n\nM623\n;===== bed leveling end ================================\n\n;===== home after wipe mouth============================\nM1002 judge_flag g29_before_print_flag\nM622 J0\n\n M1002 gcode_claim_action : 13\n G28\n\nM623\n;===== home after wipe mouth end =======================\n\nM975 S1 ; turn on vibration supression\n\n;=============turn on fans to prevent PLA jamming=================\n{if material_type=='PLA' and (material_bed_temperature_layer_0>45 or material_bed_temperature>45), initial_extruder_nr}\n M106 P3 S180\n M142 P1 R35 S40\n{endif}\n{if material_type=='PLA' and (material_bed_temperature_layer_0<=45 and material_bed_temperature<=45) , initial_extruder_nr}\n M142 P1 R35 S40\n{endif}\nM106 P2 S100 ; turn on big fan ,to cool down toolhead\n\nM104 S{material_print_temperature_layer_0, initial_extruder_nr} ; set extrude temp earlier, to reduce wait time\n\n;===== mech mode fast check============================\nG1 X128 Y128 Z10 F20000\nM400 P200\nM970.3 Q1 A7 B30 C80 H15 K0\nM974 Q1 S2 P0\n\nG1 X128 Y128 Z10 F20000\nM400 P200\nM970.3 Q0 A7 B30 C90 Q0 H15 K0\nM974 Q0 S2 P0\n\nM975 S1\nG1 F30000\nG1 X230 Y15\nG28 X ; re-home XY\n;===== mech mode fast check============================\n\n{if machine_scan_first_layer}\n;start heatbed scan====================================\nM976 S2 P1\nG90\nG1 X128 Y128 F20000\nM976 S3 P2 ;register void printing detection\n{endif}\n\n;===== nozzle load line ===============================\nM975 S1\nG90\nM83\nT1000\nG1 X18.0 Y1.0 Z0.8 F18000;Move to start position\nM109 S{material_print_temperature, initial_extruder_nr}\nG1 Z0.2\nG0 E2 F300\nG0 X240 E15 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\nG0 Y11 E0.700 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\nG0 X239.5\nG0 E0.2\nG0 Y1.5 E0.700\nG0 X231 E0.700 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\nM400\n\n;===== for Textured PEI Plate , lower the nozzle as the nozzle was touching topmost of the texture when homing ==\n;curr_bed_type={curr_bed_type}\n{if machine_buildplate_type=='textured_pei_plate'}\nG29.1 Z{-0.04} ; for Textured PEI Plate\n{endif}\n\n;===== draw extrinsic para cali paint =================\nM1002 judge_flag extrude_cali_flag\nM622 J1\n\n M1002 gcode_claim_action : 8\n\n T1000\n\n G0 F1200.0 X231 Y15 Z0.2 E0.741\n G0 F1200.0 X226 Y15 Z0.2 E0.275\n G0 F1200.0 X226 Y8 Z0.2 E0.384\n G0 F1200.0 X216 Y8 Z0.2 E0.549\n G0 F1200.0 X216 Y1.5 Z0.2 E0.357\n\n G0 X48.0 E12.0 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G0 X48.0 Y14 E0.92 F1200.0\n G0 X35.0 Y6.0 E1.03 F1200.0\n\n ;=========== extruder cali extrusion ==================\n T1000\n M83\n {if acceleration_print > 0 and acceleration_wall_0 > 0, initial_extruder_nr}\n M204 S{acceleration_wall_0, initial_extruder_nr}\n {endif}\n {if acceleration_print > 0 and acceleration_wall_0 <= 0, initial_extruder_nr}\n M204 S{acceleration_print, initial_extruder_nr}\n {endif}\n\n G0 X35.000 Y6.000 Z0.300 F30000 E0\n G1 F1500.000 E0.800\n M106 S0 ; turn off fan\n G0 X185.000 E9.35441 F4800\n G0 X187 Z0\n G1 F1500.000 E-0.800\n G0 Z1\n G0 X180 Z0.3 F18000\n\n M900 L1000.0 M1.0\n M900 K0.160\n G0 X45.000 F30000\n G0 Y8.000 F30000\n G1 F1500.000 E0.800\n G1 X65.000 E1.24726 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X70.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X75.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X80.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X85.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X90.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X95.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X100.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X105.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X110.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X115.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X120.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X125.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X130.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X135.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X140.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X145.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X150.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X155.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X160.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X165.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X170.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X175.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X180.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 F1500.000 E-0.800\n G1 X183 Z0.15 F30000\n G1 X185\n G1 Z1.0\n G0 Y6.000 F30000 ; move y to clear pos\n G1 Z0.3\n M400\n\n G0 X45.000 F30000\n M900 K0.080\n G0 X45.000 F30000\n G0 Y10.000 F30000\n G1 F1500.000 E0.800\n G1 X65.000 E1.24726 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X70.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X75.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X80.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X85.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X90.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X95.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X100.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X105.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X110.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X115.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X120.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X125.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X130.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X135.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X140.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X145.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X150.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X155.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X160.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X165.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X170.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X175.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X180.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 F1500.000 E-0.800\n G1 X183 Z0.15 F30000\n G1 X185\n G1 Z1.0\n G0 Y6.000 F30000 ; move y to clear pos\n G1 Z0.3\n M400\n\n G0 X45.000 F30000\n M900 K0.000\n G0 X45.000 F30000\n G0 Y12.000 F30000\n G1 F1500.000 E0.800\n G1 X65.000 E1.24726 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X70.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X75.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X80.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X85.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X90.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X95.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X100.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X105.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X110.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X115.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X120.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X125.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X130.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X135.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X140.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X145.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X150.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X155.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X160.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X165.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X170.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X175.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X180.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 F1500.000 E-0.800\n G1 X183 Z0.15 F30000\n G1 X185\n G1 Z1.0\n G0 Y6.000 F30000 ; move y to clear pos\n G1 Z0.3\n\n G0 X45.000 F30000 ; move to start point\n\nM623 ; end of 'draw extrinsic para cali paint'\n\nM1002 judge_flag extrude_cali_flag\nM622 J0\n G0 X231 Y1.5 F30000\n G0 X18 E14.3 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\nM623\n\nM104 S140\n\n\n;=========== laser and rgb calibration ===========\nM400\nM18 E\nM500 R\n\nM973 S3 P14\n\nG1 X120 Y1.0 Z0.3 F18000.0;Move to first extrude line pos\nT1100\nG1 X235.0 Y1.0 Z0.3 F18000.0;Move to first extrude line pos\nM400 P100\nM960 S1 P1\nM400 P100\nM973 S6 P0; use auto exposure for horizontal laser by xcam\nM960 S0 P0\n\nG1 X240.0 Y6.0 Z0.3 F18000.0;Move to vertical extrude line pos\nM960 S2 P1\nM400 P100\nM973 S6 P1; use auto exposure for vertical laser by xcam\nM960 S0 P0\n\n;=========== handeye calibration ======================\nM1002 judge_flag extrude_cali_flag\nM622 J1\n\n M973 S3 P1 ; camera start stream\n M400 P500\n M973 S1\n G0 F6000 X228.500 Y4.500 Z0.000\n M960 S0 P1\n M973 S1\n M400 P800\n M971 S6 P0\n M973 S2 P0\n M400 P500\n G0 Z0.000 F12000\n M960 S0 P0\n M960 S1 P1\n G0 X221.00 Y4.50\n M400 P200\n M971 S5 P1\n M973 S2 P1\n M400 P500\n M960 S0 P0\n M960 S2 P1\n G0 X228.5 Y11.0\n M400 P200\n M971 S5 P3\n G0 Z0.500 F12000\n M960 S0 P0\n M960 S2 P1\n G0 X228.5 Y11.0\n M400 P200\n M971 S5 P4\n M973 S2 P0\n M400 P500\n M960 S0 P0\n M960 S1 P1\n G0 X221.00 Y4.50\n M400 P500\n M971 S5 P2\n M963 S1\n M400 P1500\n M964\n T1100\n G0 F6000 X228.500 Y4.500 Z0.000\n M960 S0 P1\n M973 S1\n M400 P800\n M971 S6 P0\n M973 S2 P0\n M400 P500\n G0 Z0.000 F12000\n M960 S0 P0\n M960 S1 P1\n G0 X221.00 Y4.50\n M400 P200\n M971 S5 P1\n M973 S2 P1\n M400 P500\n M960 S0 P0\n M960 S2 P1\n G0 X228.5 Y11.0\n M400 P200\n M971 S5 P3\n G0 Z0.500 F12000\n M960 S0 P0\n M960 S2 P1\n G0 X228.5 Y11.0\n M400 P200\n M971 S5 P4\n M973 S2 P0\n M400 P500\n M960 S0 P0\n M960 S1 P1\n G0 X221.00 Y4.50\n M400 P500\n M971 S5 P2\n M963 S1\n M400 P1500\n M964\n T1100\n G1 Z3 F3000\n\n M400\n M500 ; save cali data\n\n M104 S{material_print_temperature, initial_extruder_nr} ; rise nozzle temp now ,to reduce temp waiting time.\n\n T1100\n M400 P400\n M960 S0 P0\n G0 F30000.000 Y10.000 X65.000 Z0.000\n M400 P400\n M960 S1 P1\n M400 P50\n\n M969 S1 N3 A2000\n G0 F360.000 X181.000 Z0.000\n M980.3 A70.000 B{speed_wall_0*wall_line_width_0*layer_height/(1.75*1.75/4*3.14)*60/4, initial_extruder_nr} C5.000 D{speed_wall_0*wall_line_width_0*layer_height/(1.75*1.75/4*3.14)*60, initial_extruder_nr} E5.000 F175.000 H1.000 I0.000 J0.080 K0.160\n M400 P100\n G0 F20000\n G0 Z1 ; rise nozzle up\n T1000 ; change to nozzle space\n G0 X45.000 Y4.000 F30000 ; move to test line pos\n M969 S0 ; turn off scanning\n M960 S0 P0\n\n\n G1 Z2 F20000\n T1000\n G0 X45.000 Y4.000 F30000 E0\n M109 S{material_print_temperature, initial_extruder_nr}\n G0 Z0.3\n G1 F1500.000 E3.600\n G1 X65.000 E1.24726 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X70.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X75.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X80.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X85.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X90.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X95.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X100.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X105.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X110.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X115.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X120.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X125.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X130.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X135.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n\n ; see if extrude cali success, if not ,use default value\n M1002 judge_last_extrude_cali_success\n M622 J0\n M400\n M900 K0.08 M{speed_wall_0*wall_line_width_0*layer_height/(1.75*1.75/4*3.14)*0.08, initial_extruder_nr}\n M623\n\n G1 X140.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X145.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X150.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X155.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X160.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X165.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X170.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X175.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X180.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X185.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X190.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X195.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X200.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X205.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X210.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X215.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n G1 X220.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/ 4 * 60, initial_extruder_nr}\n G1 X225.000 E0.31181 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\n M973 S4\n\nM623\n\n;========turn off light and wait extrude temperature =============\nM1002 gcode_claim_action : 0\nM973 S4 ; turn off scanner\nM400 ; wait all motion done before implement the emprical L parameters\n;M900 L500.0 ; Empirical parameters\nM109 S{material_print_temperature_layer_0, initial_extruder_nr}\nM960 S1 P0 ; turn off laser\nM960 S2 P0 ; turn off laser\nM106 S0 ; turn off fan\nM106 P2 S0 ; turn off big fan\nM106 P3 S0 ; turn off chamber fan\n\nM975 S1 ; turn on mech mode supression\nG90\nM83\nT1000\n;===== purge line to wipe the nozzle ============================\nG1 E{-retraction_amount, initial_extruder_nr} F1800\nG1 X18.0 Y2.5 Z0.8 F18000.0;Move to start position\nG1 E{retraction_amount, initial_extruder_nr} F1800\nM109 S{material_print_temperature_layer_0, initial_extruder_nr}\nG1 Z0.2\nG0 X239 E15 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5) * 60, initial_extruder_nr}\nG0 Y12 E0.7 F{speed_wall_0*wall_line_width_0*layer_height/(0.3*0.5)/4* 60, initial_extruder_nr}\n" }, + "machine_width": { "value": 256 }, + "prime_tower_position_x": { "value": "resolveOrValue('prime_tower_size') + (resolveOrValue('prime_tower_base_size') if (resolveOrValue('adhesion_type') == 'raft' or resolveOrValue('prime_tower_brim_enable')) else 0) + max(max(extruderValues('travel_avoid_distance')) + max(extruderValues('machine_nozzle_offset_y')) + max(extruderValues('support_offset')) + (extruderValue(skirt_brim_extruder_nr, 'skirt_brim_line_width') * extruderValue(skirt_brim_extruder_nr, 'skirt_line_count') * extruderValue(skirt_brim_extruder_nr, 'initial_layer_line_width_factor') / 100 + extruderValue(skirt_brim_extruder_nr, 'skirt_gap') if resolveOrValue('adhesion_type') == 'skirt' else 0) + (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0), max(map(abs, extruderValues('machine_nozzle_offset_y'))), 1) - (resolveOrValue('machine_depth') / 2 if resolveOrValue('machine_center_is_zero') else 0)" }, + "travel_avoid_distance": { "value": "3" } + } +} \ No newline at end of file diff --git a/resources/definitions/biqu_b2.def.json b/resources/definitions/biqu_b2.def.json new file mode 100644 index 0000000000..85139b82c1 --- /dev/null +++ b/resources/definitions/biqu_b2.def.json @@ -0,0 +1,39 @@ +{ + "version": 2, + "name": "Biqu B2", + "inherits": "biqu_b1", + "metadata": + { + "visible": true, + "author": "Boris Juraga", + "has_textured_buildplate": true, + "machine_extruder_trains": + { + "0": "biqu_b2_extruder_0", + "1": "biqu_b2_extruder_1" + }, + "quality_definition": "biqu_b2" + }, + "overrides": + { + "gantry_height": { "value": 27.5 }, + "machine_end_gcode": { "default_value": ";BEGIN OF CUSTOM END GCODE\nM104 S0\nM140 S0\n;Retract the filament\nG91\nG1 E-30 F300\nG1 Z5\nG90\nG28 X0 Y{machine_depth}\n;END OF CUSTOM END GCODE" }, + "machine_extruder_count": { "default_value": 2 }, + "machine_extruders_share_heater": { "default_value": true }, + "machine_extruders_share_nozzle": { "default_value": true }, + "machine_extruders_shared_nozzle_initial_retraction": { "default_value": 30 }, + "machine_head_with_fans_polygon": + { + "default_value": [ + [-33, 35], + [-33, -23], + [33, -23], + [33, 35] + ] + }, + "machine_name": { "default_value": "BIQU B2" }, + "machine_start_gcode": { "default_value": ";BEGIN OF CUSTOM START GCODE\nG28 ;Home\nG1 Z15.0 F6000 ;Move the platform down 15mm\n;Prime the extruder\nM109 S{material_print_temperature_layer_0} ; Wait for Extruder temperature\nT0\nG92 E0\nG1 F1200 E-30\nG92 E0\nM109 S{material_print_temperature_layer_0} ; Wait for Extruder temperature\nT1\nG92 E0\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X6.1 Y20 Z0.3 F5000.0 ; Move to start position\nM117 Purging\nG1 X6.1 Y200.0 Z0.3 F1500.0 E10 ; Draw the first line\nG1 X6.4 Y200.0 Z0.3 F5000.0 ; Move to side a little\nG1 X6.4 Y20 Z0.3 F1500.0 E20 ; Draw the second line\nG1 X6.7 Y20 Z0.3 F5000.0 ; Move to side a little\nG1 X6.7 Y200.0 Z0.3 F1500.0 E30 ; Draw the three line\nG1 X7.0 Y200.0 Z0.3 F5000.0 ; Move to side a little\nG1 X7.0 Y20 Z0.3 F1500.0 E40 ; Draw the four line\nG1 X7.3 Y20 Z0.3 F5000.0 ; Move to side a little\nG1 X7.3 Y200.0 Z0.3 F1500.0 E50 ; Draw the four line\nG92 E0 \nT1\nG92 E0\nG1 F1200 E-30\nG92 E0\nT0\nG92 E0\nG1 F1200 E30\nG92 E0\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X9.1 Y20 Z0.3 F5000.0 ; Move to start position\nM117 Purging\nG1 X9.1 Y200.0 Z0.3 F1500.0 E10 ; Draw the first line\nG1 X9.4 Y200.0 Z0.3 F5000.0 ; Move to side a little\nG1 X9.4 Y20 Z0.3 F1500.0 E20 ; Draw the second line\nG1 X9.7 Y20 Z0.3 F5000.0 ; Move to side a little\nG1 X9.7 Y200.0 Z0.3 F1500.0 E30 ; Draw the three line\nG1 X10.0 Y200.0 Z0.3 F5000.0 ; Move to side a little\nG1 X10.0 Y20 Z0.3 F1500.0 E40 ; Draw the four line\nG1 X10.3 Y20 Z0.3 F5000.0 ; Move to side a little\nG1 X10.3 Y200.0 Z0.3 F1500.0 E50 ; Draw the four line\nT0\nG92 E0\nG1 F1200 E-30\nG92 E0\nG92 E0\nT{initial_extruder_nr} ; RESET EXTRUDER TO INITIAL\n; start print\n;END OF CUSTOM START GCODE" }, + "prime_tower_enable": { "default_value": true }, + "prime_tower_mode": { "default_value": "interleaved" } + } +} \ No newline at end of file diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index b42b5c417a..68852d805f 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -214,8 +214,8 @@ }, "machine_buildplate_type": { - "label": "Build Plate Material", - "description": "The material of the build plate installed on the printer.", + "label": "Build Plate Type", + "description": "The type of build plate installed on the printer.", "default_value": "glass", "type": "enum", "options": @@ -388,7 +388,8 @@ "Makerbot": "Makerbot", "BFB": "Bits from Bytes", "MACH3": "Mach3", - "Repetier": "Repetier" + "Repetier": "Repetier", + "BambuLab": "BambuLab" }, "default_value": "RepRap (Marlin/Sprinter)", "settable_per_mesh": false, @@ -1680,7 +1681,7 @@ "maximum_value": "999999", "type": "int", "minimum_value_warning": "2", - "value": "0 if infill_sparse_density == 100 else math.ceil(round(top_thickness / resolveOrValue('layer_height'), 4))", + "value": "math.ceil(round(top_thickness / resolveOrValue('layer_height'), 4))", "limit_to_extruder": "top_bottom_extruder_nr", "settable_per_mesh": true } @@ -1710,7 +1711,7 @@ "default_value": 6, "maximum_value": "999999", "type": "int", - "value": "999999 if infill_sparse_density == 100 and not magic_spiralize else math.ceil(round(bottom_thickness / resolveOrValue('layer_height'), 4))", + "value": "math.ceil(round(bottom_thickness / resolveOrValue('layer_height'), 4))", "limit_to_extruder": "top_bottom_extruder_nr", "settable_per_mesh": true }, @@ -3376,6 +3377,20 @@ "settable_per_mesh": false, "settable_per_extruder": true, "settable_per_meshgroup": false + }, + "material_max_flowrate": + { + "default_value": 16, + "description": "Maximum flow rate that the printer can extrude for the material", + "enabled": false, + "label": "Material Maximum Flow Rate", + "maximum_value": "machine_max_feedrate_e * (material_diameter/2)**2 * math.pi", + "minimum_value": "0", + "settable_per_extruder": true, + "settable_per_mesh": false, + "type": "float", + "unit": "mm\u00b3/s", + "value": "16" } } }, @@ -4553,7 +4568,7 @@ "minimum_value_warning": "-0.0001", "maximum_value_warning": "10.0", "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"", - "settable_per_mesh": false, + "settable_per_mesh": true, "settable_per_extruder": true }, "retraction_speed": @@ -4643,7 +4658,7 @@ "maximum_value": 999999999, "type": "int", "enabled": "retraction_enable", - "settable_per_mesh": false, + "settable_per_mesh": true, "settable_per_extruder": true }, "retraction_extrusion_window": @@ -4657,7 +4672,7 @@ "maximum_value_warning": "retraction_amount * 2", "value": "retraction_amount", "enabled": "retraction_enable", - "settable_per_mesh": false, + "settable_per_mesh": true, "settable_per_extruder": true }, "retraction_combing": @@ -4855,7 +4870,7 @@ }, "build_fan_full_at_height": { - "label": "Build Fan Speed at Height", + "label": "Build Volume Fan Speed at Height", "description": "The height at which the fans spin on regular fan speed. At the layers below the fan speed gradually increases from Initial Fan Speed to Regular Fan Speed.", "unit": "mm", "type": "float", @@ -4870,8 +4885,8 @@ { "build_fan_full_layer": { - "label": "Build Fan Speed at Layer", - "description": "The layer at which the build fans spin on full fan speed. This value is calculated and rounded to a whole number.", + "label": "Build Volume Fan Speed at Layer", + "description": "The layer at which the build-volume fans spin on full fan speed. This value is calculated and rounded to a whole number.", "type": "int", "default_value": 0, "minimum_value": "0", @@ -4884,6 +4899,34 @@ } } }, + "build_volume_fan_speed_0": + { + "label": "Initial Layers Build Volume Fan Speed", + "description": "The fan speed (as a percentage) for the auxiliary or build-volume fan, that is set until the layer specified at 'Build Volume Fan Speed at Layer' is reached. After that, the speed is set by 'Build Volume Fan Speed' instead (so not this 'Initial Layers' one).", + "unit": "%", + "type": "float", + "minimum_value": "0", + "maximum_value": "100", + "default_value": 0, + "enabled": "build_volume_fan_nr != 0", + "settable_per_mesh": false, + "settable_per_extruder": false, + "settable_per_meshgroup": false + }, + "build_volume_fan_speed": + { + "label": "Build Volume Fan Speed", + "description": "The fan speed (as a percentage) for the auxiliary or build-volume fan, that is set from the moment that the layer specified at 'Build Volume Fan Speed at Layer' is reached and onwards. Before that, the speed is set by 'Initial Layers Build Volume Fan Speed' instead.", + "unit": "%", + "type": "float", + "minimum_value": "0", + "maximum_value": "100", + "default_value": 100, + "enabled": "build_volume_fan_nr != 0", + "settable_per_mesh": false, + "settable_per_extruder": false, + "settable_per_meshgroup": false + }, "cool_fan_speed": { "label": "Fan Speed", @@ -7465,6 +7508,17 @@ "limit_to_extruder": "raft_surface_extruder_nr" } } + }, + "machine_scan_first_layer": + { + "default_value": false, + "description": "Whether to scan the first layer for layer adhesion problems.", + "enabled": false, + "label": "Scan the first layer", + "settable_per_extruder": false, + "settable_per_mesh": false, + "settable_per_meshgroup": false, + "type": "bool" } } }, @@ -9257,6 +9311,42 @@ "default_value": true, "settable_per_mesh": true }, + "retraction_during_travel_ratio": + { + "label": "Retraction During Travel Move", + "description": "The ratio of retraction performed during the travel move, with the remainder completed while the nozzle is stationary, before traveling", + "unit": "%", + "type": "float", + "default_value": 0, + "minimum_value": 0, + "maximum_value": 100, + "enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\"", + "settable_per_mesh": false, + "settable_per_extruder": true + }, + "keep_retracting_during_travel": + { + "label": "Keep Retracting During Travel", + "description": "When retraction during travel is enabled, and there is more than enough time to perform a full retract during a travel move, spread the retraction over the whole travel move with a lower retraction speed, so that we do not travel with a non-retracting nozzle. This can help reducing oozing.", + "type": "bool", + "default_value": false, + "enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\" and retraction_during_travel_ratio > 0", + "settable_per_mesh": false, + "settable_per_extruder": true + }, + "prime_during_travel_ratio": + { + "label": "Prime During Travel Move", + "description": "The ratio of priming performed during the travel move, with the remainder completed while the nozzle is stationary, after traveling", + "unit": "%", + "type": "float", + "default_value": 0, + "minimum_value": 0, + "maximum_value": 100, + "enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\"", + "settable_per_mesh": false, + "settable_per_extruder": true + }, "scarf_joint_seam_length": { "label": "Scarf Seam Length", diff --git a/resources/definitions/geeetech_M1.def.json b/resources/definitions/geeetech_M1.def.json new file mode 100644 index 0000000000..2782c7c87c --- /dev/null +++ b/resources/definitions/geeetech_M1.def.json @@ -0,0 +1,59 @@ +{ + "version": 2, + "name": "Geeetech M1", + "inherits": "Geeetech_Base_Single_Extruder", + "metadata": + { + "visible": true, + "machine_extruder_trains": { "0": "Geeetech_Single_Extruder" } + }, + "overrides": + { + "adhesion_type": { "value": "'brim'" }, + "brim_width": { "value": 2 }, + "gantry_height": { "value": 35 }, + "machine_depth": { "default_value": 105 }, + "machine_end_gcode": { "default_value": "G91 ;Switch to relative positioning\nG1 E-2.5 F2700 ;Retract filament\nG1 E-1.5 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Move away\nG1 Z10 ;lift print head\nG90 ;Switch to absolute positioning\nG28 X Y ;homing XY\nM106 S0 ;off Fan\nM104 S0 ;Cooldown hotend\nM140 S0 ;Cooldown bed\nM84 X Y E ;Disable steppers" }, + "machine_head_with_fans_polygon": + { + "default_value": [ + [-31, 31], + [34, 31], + [34, -40], + [-31, -40] + ] + }, + "machine_height": { "default_value": 95 }, + "machine_name": { "default_value": "Geeetech M1" }, + "machine_start_gcode": { "default_value": ";Official wiki URL for Geeetech M1:https://www.geeetech.com/wiki/index.php/Geeetech_M1_3D_printer \nM104 S{material_print_temperature_layer_0} ; Set Hotend Temperature\nM140 S{material_bed_temperature_layer_0} ; Set Bed Temperature\n;M190 S{material_bed_temperature_layer_0} ; Wait for Bed Temperature\nM109 S{material_print_temperature_layer_0} ; Wait for Hotend Temperature\nG92 E0 ; Reset Extruder\nG28 ; Home all axes\nM107 ;Off Fan\nM300 S2500 P1000 ;Play a short tune\nG1 Z0.28 ;Move Z Axis up little to prevent scratching of Heat Bed\nG92 E0 ;Reset Extruder\nG1 Y3 F2400 ;Move to start position\nG1 X75 E40 F500 ;Draw a filament line\nG92 E0 ;Reset Extruder\n;G1 E-0.2 F3000 ;Retract a little\nG1 Z2.0 F3000 ;Move Z Axis up little to prevent scratching of Heat Bed\nG1 X70 Y3 Z0.27 F3000 ;Quickly wipe away from the filament line\nG92 E0 ;Reset Extruder" }, + "machine_width": { "default_value": 105 }, + "material_bed_temperature": { "maximum_value": 60 }, + "material_print_temperature": { "maximum_value": 230 }, + "retraction_amount": { "value": 2 }, + "speed_print": + { + "maximum_value_warning": "200", + "value": 120 + }, + "speed_topbottom": + { + "maximum_value_warning": "200", + "value": 60 + }, + "speed_wall": + { + "maximum_value_warning": "200", + "value": 80 + }, + "speed_wall_0": + { + "maximum_value_warning": "200", + "value": 50 + }, + "speed_wall_x": + { + "maximum_value_warning": "200", + "value": 80 + } + } +} \ No newline at end of file diff --git a/resources/definitions/geeetech_M1S.def.json b/resources/definitions/geeetech_M1S.def.json new file mode 100644 index 0000000000..79bfec5a89 --- /dev/null +++ b/resources/definitions/geeetech_M1S.def.json @@ -0,0 +1,59 @@ +{ + "version": 2, + "name": "Geeetech M1S", + "inherits": "Geeetech_Base_Single_Extruder", + "metadata": + { + "visible": true, + "machine_extruder_trains": { "0": "Geeetech_Single_Extruder" } + }, + "overrides": + { + "adhesion_type": { "value": "'brim'" }, + "brim_width": { "value": 2 }, + "gantry_height": { "value": 35 }, + "machine_depth": { "default_value": 105 }, + "machine_end_gcode": { "default_value": "G91 ;Switch to relative positioning\nG1 E-2.5 F2700 ;Retract filament\nG1 E-1.5 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Move away\nG1 Z10 ;lift print head\nG90 ;Switch to absolute positioning\nG28 X Y ;homing XY\nM106 S0 ;off Fan\nM104 S0 ;Cooldown hotend\nM140 S0 ;Cooldown bed\nM84 X Y E ;Disable steppers" }, + "machine_head_with_fans_polygon": + { + "default_value": [ + [-31, 31], + [34, 31], + [34, -40], + [-31, -40] + ] + }, + "machine_height": { "default_value": 95 }, + "machine_name": { "default_value": "Geeetech M1S" }, + "machine_start_gcode": { "default_value": ";Official wiki URL for Geeetech M1S:https://www.geeetech.com/wiki/index.php/Geeetech_M1S_3D_printer \nM104 S{material_print_temperature_layer_0} ; Set Hotend Temperature\nM140 S{material_bed_temperature_layer_0} ; Set Bed Temperature\n;M190 S{material_bed_temperature_layer_0} ; Wait for Bed Temperature\nM109 S{material_print_temperature_layer_0} ; Wait for Hotend Temperature\nG92 E0 ; Reset Extruder\nG28 ; Home all axes\nM107 ;Off Fan\nM300 S2500 P1000 ;Play a short tune\nG1 Z0.28 ;Move Z Axis up little to prevent scratching of Heat Bed\nG92 E0 ;Reset Extruder\nG1 Y3 F2400 ;Move to start position\nG1 X75 E40 F500 ;Draw a filament line\nG92 E0 ;Reset Extruder\n;G1 E-0.2 F3000 ;Retract a little\nG1 Z2.0 F3000 ;Move Z Axis up little to prevent scratching of Heat Bed\nG1 X70 Y3 Z0.27 F3000 ;Quickly wipe away from the filament line\nG92 E0 ;Reset Extruder" }, + "machine_width": { "default_value": 105 }, + "material_bed_temperature": { "maximum_value": 85 }, + "material_print_temperature": { "maximum_value": 250 }, + "retraction_amount": { "value": 2 }, + "speed_print": + { + "maximum_value_warning": "200", + "value": 120 + }, + "speed_topbottom": + { + "maximum_value_warning": "200", + "value": 60 + }, + "speed_wall": + { + "maximum_value_warning": "200", + "value": 80 + }, + "speed_wall_0": + { + "maximum_value_warning": "200", + "value": 50 + }, + "speed_wall_x": + { + "maximum_value_warning": "200", + "value": 80 + } + } +} \ No newline at end of file diff --git a/resources/definitions/geeetech_Thunder.def.json b/resources/definitions/geeetech_Thunder.def.json index cc3e4044f8..cf0f9dd5e9 100644 --- a/resources/definitions/geeetech_Thunder.def.json +++ b/resources/definitions/geeetech_Thunder.def.json @@ -81,14 +81,10 @@ "machine_max_jerk_xy": { "value": 45 }, "machine_max_jerk_z": { "value": 0.8 }, "machine_name": { "default_value": "Geeetech Thunder" }, - "machine_start_gcode": { "default_value": ";Official viki homepage for Thunder:https://www.geeetech.com/wiki/index.php/Geeetech_Thunder_3D_printer \n\nM104 S{material_print_temperature_layer_0} ; Set Hotend Temperature\nM190 S{material_bed_temperature_layer_0} ; Wait for Bed Temperature\nM109 S{material_print_temperature_layer_0} ; Wait for Hotend Temperature\nG92 E0 ; Reset Extruder\nG28 ; Home all axes\nM107 P0 ;Off Main Fan\nM107 P1 ;Off Aux Fan\nM2012 P8 S1 F100 ; ON Light\n;M106 P0 S383 ; ON MainFan 150% if need\n;M106 P1 S255 ; ON Aux Fan 100% if need\nG1 Z5.0 F3000 ;Move Z Axis up little to prevent scratching of Heat Bed\nG1 X0.1 Y20 Z0.8 F5000 ; Move to start position\nG1 X0.1 Y200.0 Z1.2 F1500 E30 ; Draw the first line\nG92 E0 ; Reset Extruder\nG1 X0.4 Y200.0 Z1.2 F3000 ; Move to side a little\nG1 X0.4 Y20 Z1.2 F1500 E25 ; Draw the second line\nG92 E0 ; Reset Extruder\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X5 Y20 Z0.4 F3000.0 ; Scrape off nozzle residue" }, + "machine_start_gcode": { "default_value": ";Official wiki URL for Thunder:https://www.geeetech.com/wiki/index.php/Geeetech_Thunder_3D_printer \n\nM104 S{material_print_temperature_layer_0} ; Set Hotend Temperature\nM190 S{material_bed_temperature_layer_0} ; Wait for Bed Temperature\nM109 S{material_print_temperature_layer_0} ; Wait for Hotend Temperature\nG92 E0 ; Reset Extruder\nG28 ; Home all axes\nM107 P0 ;Off Main Fan\nM107 P1 ;Off Aux Fan\nM2012 P8 S1 F100 ; ON Light\n;M106 P0 S383 ; ON MainFan 150% if need\n;M106 P1 S255 ; ON Aux Fan 100% if need\nG1 Z5.0 F3000 ;Move Z Axis up little to prevent scratching of Heat Bed\nG1 X0.1 Y20 Z0.8 F5000 ; Move to start position\nG1 X0.1 Y200.0 Z1.2 F1500 E30 ; Draw the first line\nG92 E0 ; Reset Extruder\nG1 X0.4 Y200.0 Z1.2 F3000 ; Move to side a little\nG1 X0.4 Y20 Z1.2 F1500 E25 ; Draw the second line\nG92 E0 ; Reset Extruder\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X5 Y20 Z0.4 F3000.0 ; Scrape off nozzle residue" }, "machine_width": { "default_value": 250 }, "material_flow_layer_0": { "value": 95 }, - "material_print_temperature": - { - "maximum_value": "250", - "value": "200 if speed_infill <=150 else 205 if speed_infill <= 200 else 215 if speed_infill <= 260 else 220" - }, + "material_print_temperature": { "maximum_value": "250" }, "material_print_temperature_layer_0": { "maximum_value_warning": 300, diff --git a/resources/definitions/hellbot_hidra.def.json b/resources/definitions/hellbot_hidra.def.json index fe1d580354..bf8eb16608 100644 --- a/resources/definitions/hellbot_hidra.def.json +++ b/resources/definitions/hellbot_hidra.def.json @@ -21,7 +21,7 @@ 0, 5 ], - "platform_texture": "hellbot_hidra.png" + "platform_texture": "Hellbot_Hidra_and_Hidra_Plus_V2.png" }, "overrides": { diff --git a/resources/definitions/hellbot_hidra_plus.def.json b/resources/definitions/hellbot_hidra_plus.def.json index dc718dc5f2..70938b5b00 100644 --- a/resources/definitions/hellbot_hidra_plus.def.json +++ b/resources/definitions/hellbot_hidra_plus.def.json @@ -21,7 +21,7 @@ 0, 5 ], - "platform_texture": "hellbot_hidra_plus.png" + "platform_texture": "Hellbot_Hidra_and_Hidra_Plus_V2.png" }, "overrides": { diff --git a/resources/definitions/sovol_sv01.def.json b/resources/definitions/sovol_sv01.def.json index 9d77c1c3f6..5c87d8124e 100644 --- a/resources/definitions/sovol_sv01.def.json +++ b/resources/definitions/sovol_sv01.def.json @@ -1,11 +1,11 @@ { "version": 2, "name": "Sovol SV01", - "inherits": "sovol_base_bowden", + "inherits": "sovol_base_titan", "metadata": { "visible": true, - "quality_definition": "sovol_base_bowden" + "quality_definition": "sovol_base_titan" }, "overrides": { diff --git a/resources/definitions/sovol_sv08.def.json b/resources/definitions/sovol_sv08.def.json new file mode 100644 index 0000000000..a662b19618 --- /dev/null +++ b/resources/definitions/sovol_sv08.def.json @@ -0,0 +1,130 @@ +{ + "version": 2, + "name": "Sovol SV08", + "inherits": "fdmprinter", + "metadata": + { + "visible": true, + "author": "Steinar H. Gunderson", + "manufacturer": "Sovol 3D", + "file_formats": "text/x-gcode", + "platform": "sovol_sv08_buildplate_model.stl", + "exclude_materials": [], + "first_start_actions": [ "MachineSettingsAction" ], + "has_machine_quality": true, + "has_materials": true, + "has_variants": true, + "machine_extruder_trains": { "0": "sovol_sv08_extruder" }, + "preferred_material": "generic_abs", + "preferred_quality_type": "fast", + "preferred_variant_name": "0.4mm Nozzle", + "quality_definition": "sovol_sv08", + "variants_name": "Nozzle Size" + }, + "overrides": + { + "acceleration_enabled": { "default_value": false }, + "acceleration_layer_0": { "value": 1800 }, + "acceleration_print": { "default_value": 2200 }, + "acceleration_roofing": { "value": 1800 }, + "acceleration_travel_layer_0": { "value": 1800 }, + "acceleration_wall_0": { "value": 1800 }, + "adhesion_type": { "default_value": "skirt" }, + "alternate_extra_perimeter": { "default_value": true }, + "bridge_fan_speed": { "default_value": 100 }, + "bridge_fan_speed_2": { "resolve": "max(cool_fan_speed, 50)" }, + "bridge_fan_speed_3": { "resolve": "max(cool_fan_speed, 20)" }, + "bridge_settings_enabled": { "default_value": true }, + "bridge_wall_coast": { "default_value": 10 }, + "cool_fan_full_at_height": { "value": "resolveOrValue('layer_height_0') + resolveOrValue('layer_height') * max(1, cool_fan_full_layer - 1)" }, + "cool_fan_full_layer": { "value": 4 }, + "cool_fan_speed_min": { "value": "cool_fan_speed" }, + "cool_min_layer_time": { "default_value": 15 }, + "cool_min_layer_time_fan_speed_max": { "default_value": 20 }, + "fill_outline_gaps": { "default_value": true }, + "gantry_height": { "value": 30 }, + "infill_before_walls": { "default_value": false }, + "infill_enable_travel_optimization": { "default_value": true }, + "jerk_enabled": { "default_value": false }, + "jerk_roofing": { "value": 10 }, + "jerk_wall_0": { "value": 10 }, + "layer_height_0": { "resolve": "max(0.2, min(extruderValues('layer_height')))" }, + "line_width": { "value": "machine_nozzle_size * 1.125" }, + "machine_acceleration": { "default_value": 1500 }, + "machine_depth": { "default_value": 350 }, + "machine_end_gcode": { "default_value": "END_PRINT\n" }, + "machine_endstop_positive_direction_x": { "default_value": true }, + "machine_endstop_positive_direction_y": { "default_value": true }, + "machine_endstop_positive_direction_z": { "default_value": false }, + "machine_feeder_wheel_diameter": { "default_value": 7.5 }, + "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, + "machine_head_with_fans_polygon": + { + "default_value": [ + [-35, 65], + [-35, -50], + [35, -50], + [35, 65] + ] + }, + "machine_heated_bed": { "default_value": true }, + "machine_height": { "default_value": 345 }, + "machine_max_acceleration_e": { "default_value": 5000 }, + "machine_max_acceleration_x": { "default_value": 40000 }, + "machine_max_acceleration_y": { "default_value": 40000 }, + "machine_max_acceleration_z": { "default_value": 500 }, + "machine_max_feedrate_e": { "default_value": 50 }, + "machine_max_feedrate_x": { "default_value": 700 }, + "machine_max_feedrate_y": { "default_value": 700 }, + "machine_max_feedrate_z": { "default_value": 20 }, + "machine_max_jerk_e": { "default_value": 5 }, + "machine_max_jerk_xy": { "default_value": 20 }, + "machine_max_jerk_z": { "default_value": 0.5 }, + "machine_name": { "default_value": "SV08" }, + "machine_start_gcode": { "default_value": "G28 ; Move to zero\nG90 ; Absolute positioning\nG1 X0 F9000\nG1 Y20\nG1 Z0.600 F600\nG1 Y0 F9000\nSTART_PRINT EXTRUDER_TEMP={material_print_temperature_layer_0} BED_TEMP={material_bed_temperature_layer_0}\nG90 ; Absolute positioning (START_PRINT might have changed it)\nG1 X0 F9000\nG1 Y20\nG1 Z0.600 F600\nG1 Y0 F9000\nM400\nG91 ; Relative positioning\nM83 ; Relative extrusion\nM140 S{material_bed_temperature_layer_0} ; Set bed temp\nM104 S{material_print_temperature_layer_0} ; Set extruder temp\nM190 S{material_bed_temperature_layer_0} ; Wait for bed temp\nM109 S{material_print_temperature_layer_0} ; Wait for extruder temp\n{if machine_nozzle_size >= 0.4}\n; Standard Sovol blob and purge line.\nG1 E25 F300 ; Purge blob\nG4 P1000 ; Wait a bit to let it finish\nG1 E-0.200 Z5 F600 ; Retract\nG1 X88.000 F9000 ; Move right and then down for purge line\nG1 Z-5.000 F600\nG1 X87.000 E20.88 F1800 ; Purge line right\nG1 X87.000 E13.92 F1800\nG1 Y1 E0.16 F1800 ; Small movement back for next line\nG1 X-87.000 E13.92 F1800 ; Purge line left\nG1 X-87.000 E20.88 F1800\nG1 Y1 E0.24 F1800 ; Small movement back for next line\nG1 X87.000 E20.88 F1800 ; Purge line right\nG1 X87.000 E13.92 F1800\nG1 E-0.200 Z1 F600\n{else}\n; The default start G-code uses too high flow for smaller nozzles,\n; which causes Klipper errors. Scale everything back by\n; (0.25/0.4)^2, i.e., for 0.25mm nozzle. This should be good\n; enough for 0.2mm as well.\nG1 E8 F300 ; Purge blob\nG4 P1000 ; Wait a bit to let it finish\nG1 E-0.078 Z5 F600 ; Retract\nG1 X88.000 F9000 ; Move right and then down for purge line\nG1 Z-5.000 F600\nG1 X87.000 E8.16 F1800 ; Purge line right\nG1 X87.000 E5.44 F1800\nG1 Y1 E0.063 F1800 ; Small movement back for next line\nG1 X-87.000 E5.44 F1800 ; Purge line left\nG1 X-87.000 E8.16 F1800\nG1 Y1 E0.094 F1800 ; Small movement back for next line\nG1 X87.000 E8.16 F1800 ; Purge line right\nG1 X87.000 E5.44 F1800\nG1 E-0.078 Z1 F600\n{endif}\nM400 ; Wait for moves to finish\nG90 ; Absolute positioning\nM82 ; Absolute extrusion mode\n" }, + "machine_steps_per_mm_x": { "default_value": 80 }, + "machine_steps_per_mm_y": { "default_value": 80 }, + "machine_steps_per_mm_z": { "default_value": 400 }, + "machine_width": { "default_value": 350 }, + "meshfix_maximum_resolution": { "default_value": 0.01 }, + "min_infill_area": { "default_value": 5.0 }, + "minimum_polygon_circumference": { "default_value": 0.2 }, + "optimize_wall_printing_order": { "default_value": true }, + "retraction_amount": { "default_value": 0.5 }, + "retraction_combing": { "value": "'noskin'" }, + "retraction_combing_max_distance": { "default_value": 10 }, + "retraction_hop": { "default_value": 0.4 }, + "retraction_hop_enabled": { "default_value": true }, + "retraction_prime_speed": + { + "maximum_value_warning": 130, + "value": "math.ceil(retraction_speed * 0.4)" + }, + "retraction_retract_speed": { "maximum_value_warning": 130 }, + "retraction_speed": + { + "default_value": 30, + "maximum_value_warning": 130 + }, + "roofing_layer_count": { "value": 1 }, + "skirt_brim_minimal_length": { "default_value": 550 }, + "speed_layer_0": { "value": "math.ceil(speed_print * 0.25)" }, + "speed_roofing": { "value": "math.ceil(speed_print * 0.33)" }, + "speed_slowdown_layers": { "default_value": 4 }, + "speed_topbottom": { "value": "math.ceil(speed_print * 0.33)" }, + "speed_travel": + { + "maximum_value_warning": 501, + "value": 300 + }, + "speed_travel_layer_0": { "value": "math.ceil(speed_travel * 0.4)" }, + "speed_wall": { "value": "math.ceil(speed_print * 0.33)" }, + "speed_wall_0": { "value": "math.ceil(speed_print * 0.33)" }, + "speed_wall_x": { "value": "math.ceil(speed_print * 0.66)" }, + "travel_avoid_other_parts": { "default_value": false }, + "wall_line_width": { "value": "machine_nozzle_size" }, + "wall_overhang_angle": { "default_value": 75 }, + "wall_overhang_speed_factors": { "default_value": "[50]" }, + "zig_zaggify_infill": { "value": true } + } +} \ No newline at end of file diff --git a/resources/definitions/toybox_alpha_one_two.def.json b/resources/definitions/toybox_alpha_one_two.def.json new file mode 100644 index 0000000000..6dc32a5fa7 --- /dev/null +++ b/resources/definitions/toybox_alpha_one_two.def.json @@ -0,0 +1,25 @@ +{ + "version": 2, + "name": "Toybox Alpha One/Two", + "inherits": "fdmprinter", + "metadata": + { + "visible": true, + "author": "lukbrew25", + "manufacturer": "Toybox", + "file_formats": "text/x-gcode", + "has_machine_quality": false, + "machine_extruder_trains": { "0": "toybox_alpha_one_two_extruder_0" } + }, + "overrides": + { + "gantry_height": { "value": "0.0" }, + "machine_depth": { "default_value": 80 }, + "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, + "machine_height": { "default_value": 90 }, + "machine_name": { "default_value": "Toybox Alpha One/Two" }, + "machine_start_gcode": { "default_value": "G90\nM82" }, + "machine_width": { "default_value": 70 }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/definitions/ultimaker.def.json b/resources/definitions/ultimaker.def.json index d8fdd7eede..c5f441479b 100644 --- a/resources/definitions/ultimaker.def.json +++ b/resources/definitions/ultimaker.def.json @@ -16,7 +16,6 @@ { "acceleration_layer_0": { "value": "acceleration_topbottom" }, "acceleration_travel_enabled": { "value": false }, - "bottom_layers": { "value": "math.ceil(round(bottom_thickness / resolveOrValue('layer_height'), 4))" }, "bridge_enable_more_layers": { "value": false }, "bridge_fan_speed": { "value": "cool_fan_speed_max" }, "bridge_fan_speed_2": { "value": "cool_fan_speed_min" }, @@ -219,7 +218,6 @@ "support_wall_count": { "value": "1 if support_structure == 'tree' else 0" }, "support_xy_distance_overhang": { "value": "0.2" }, "support_z_distance": { "value": "0" }, - "top_layers": { "value": "math.ceil(round(top_thickness / resolveOrValue('layer_height'), 4))" }, "wall_0_material_flow_layer_0": { "value": "1.10 * material_flow_layer_0" }, "wall_thickness": { "value": "wall_line_width_0 + wall_line_width_x" }, "wall_x_material_flow_layer_0": { "value": "0.95 * material_flow_layer_0" }, diff --git a/resources/definitions/ultimaker_s8.def.json b/resources/definitions/ultimaker_s8.def.json index b7de27722d..cbd6e255f8 100644 --- a/resources/definitions/ultimaker_s8.def.json +++ b/resources/definitions/ultimaker_s8.def.json @@ -98,7 +98,7 @@ { "maximum_value": "machine_max_acceleration_x", "maximum_value_warning": "machine_max_acceleration_x*0.8", - "value": "acceleration_wall_0" + "value": "acceleration_topbottom / 2" }, "acceleration_skirt_brim": { @@ -199,15 +199,19 @@ }, "adhesion_type": { "value": "'brim' if support_enable and support_structure=='tree' else 'skirt'" }, "bottom_thickness": { "value": "3*layer_height if top_layers==4 and not support_enable else top_bottom_thickness" }, - "bridge_skin_material_flow": { "value": 200 }, + "bridge_enable_more_layers": { "value": true }, + "bridge_skin_density": { "value": 70 }, + "bridge_skin_material_flow": { "value": 150 }, + "bridge_skin_material_flow_2": { "value": 70 }, "bridge_skin_speed": { "unit": "mm/s", - "value": "bridge_wall_speed" + "value": 35 }, + "bridge_skin_speed_2": { "value": "speed_print*2/3" }, "bridge_sparse_infill_max_density": { "value": 50 }, - "bridge_wall_material_flow": { "value": "bridge_skin_material_flow" }, - "bridge_wall_min_length": { "value": 10 }, + "bridge_wall_material_flow": { "value": 200 }, + "bridge_wall_min_length": { "value": 2 }, "bridge_wall_speed": { "unit": "mm/s", @@ -221,13 +225,13 @@ ] }, "cool_during_extruder_switch": { "value": "'all_fans'" }, - "cool_min_layer_time": { "value": 5 }, - "cool_min_layer_time_overhang": { "value": 9 }, - "cool_min_layer_time_overhang_min_segment_length": { "value": 2 }, + "cool_min_layer_time": { "value": 6 }, + "cool_min_layer_time_overhang": { "value": 11 }, + "cool_min_layer_time_overhang_min_segment_length": { "value": 1.5 }, "cool_min_speed": { "value": 6 }, "cool_min_temperature": { - "minimum_value_warning": "material_print_temperature-15", + "minimum_value_warning": "material_print_temperature-20", "value": "material_print_temperature-15" }, "default_material_print_temperature": { "maximum_value_warning": 320 }, @@ -235,9 +239,9 @@ "flooring_layer_count": { "value": 1 }, "gradual_flow_enabled": { "value": false }, "hole_xy_offset": { "value": 0.075 }, - "infill_material_flow": { "value": "material_flow" }, + "infill_material_flow": { "value": "material_flow if infill_sparse_density < 95 else 95" }, "infill_overlap": { "value": 10 }, - "infill_pattern": { "value": "'zigzag' if infill_sparse_density > 80 else 'grid'" }, + "infill_pattern": { "value": "'zigzag' if infill_sparse_density > 50 else 'grid'" }, "infill_sparse_density": { "value": 15 }, "infill_wall_line_count": { "value": "1 if infill_sparse_density > 80 else 0" }, "initial_bottom_layers": { "value": 2 }, @@ -281,7 +285,7 @@ { "maximum_value_warning": "machine_max_jerk_xy / 2", "unit": "m/s\u00b3", - "value": "jerk_wall_0" + "value": "jerk_print" }, "jerk_skirt_brim": { @@ -438,10 +442,11 @@ "retraction_hop": { "value": 1 }, "retraction_hop_after_extruder_switch_height": { "value": 2 }, "retraction_hop_enabled": { "value": true }, - "retraction_min_travel": { "value": "5 if support_enable and support_structure=='tree' else line_width * 2.5" }, + "retraction_min_travel": { "value": "2.5 if support_enable and support_structure=='tree' else line_width * 2.5" }, "retraction_prime_speed": { "value": 15 }, "skin_edge_support_thickness": { "value": 0 }, - "skin_material_flow": { "value": 95 }, + "skin_material_flow": { "value": 93 }, + "skin_outline_count": { "value": 0 }, "skin_overlap": { "value": 0 }, "skin_preshrink": { "value": 0 }, "skirt_brim_minimal_length": { "value": 1000 }, @@ -571,38 +576,46 @@ "value": "speed_wall" }, "support_angle": { "value": 60 }, - "support_bottom_distance": { "maximum_value_warning": "3*layer_height" }, + "support_bottom_distance": + { + "maximum_value_warning": "3*layer_height", + "value": "support_z_distance" + }, "support_bottom_offset": { "value": 0 }, "support_brim_width": { "value": 10 }, "support_interface_enable": { "value": true }, "support_interface_offset": { "value": "support_offset" }, "support_line_width": { "value": "1.25*line_width" }, - "support_offset": { "value": "1.2 if support_structure == 'tree' else 0.8" }, + "support_offset": { "value": 0.8 }, "support_pattern": { "value": "'gyroid' if support_structure == 'tree' else 'lines'" }, "support_roof_height": { "minimum_value_warning": 0 }, "support_structure": { "value": "'normal'" }, "support_top_distance": { "maximum_value_warning": "3*layer_height" }, "support_tree_angle": { "value": 50 }, "support_tree_angle_slow": { "value": 35 }, - "support_tree_bp_diameter": { "value": 15 }, - "support_tree_branch_diameter": { "value": 8 }, - "support_tree_tip_diameter": { "value": 1.0 }, - "support_tree_top_rate": { "value": 20 }, - "support_xy_distance_overhang": { "value": "machine_nozzle_size" }, - "support_z_distance": { "value": "0.4*material_shrinkage_percentage_z/100.0" }, - "top_bottom_thickness": { "value": "round(4*layer_height, 2)" }, + "support_tree_bp_diameter": { "value": 20 }, + "support_tree_branch_diameter": { "value": 5 }, + "support_tree_branch_diameter_angle": { "value": 5 }, + "support_tree_max_diameter": { "value": 15 }, + "support_tree_tip_diameter": { "value": 2.0 }, + "support_tree_top_rate": { "value": 10 }, + "support_xy_distance": { "value": 1.2 }, + "support_xy_distance_overhang": { "value": "1.5*machine_nozzle_size" }, + "support_z_distance": { "value": "2*layer_height" }, + "top_bottom_thickness": { "value": "wall_thickness" }, "travel_avoid_other_parts": { "value": true }, "travel_avoid_supports": { "value": true }, "wall_0_acceleration": { "value": 1000 }, "wall_0_deceleration": { "value": 1000 }, "wall_0_end_speed_ratio": { "value": 100 }, + "wall_0_inset": { "value": 0.05 }, "wall_0_speed_split_distance": { "value": 0.2 }, "wall_0_start_speed_ratio": { "value": 100 }, "wall_0_wipe_dist": { "value": 0 }, "wall_material_flow": { "value": 95 }, "wall_overhang_angle": { "value": 45 }, "wall_x_material_flow": { "value": 100 }, - "xy_offset": { "value": 0.05 }, + "xy_offset": { "value": 0.075 }, "z_seam_corner": { "value": "'z_seam_corner_weighted'" }, "z_seam_position": { "value": "'backright'" }, "z_seam_type": { "value": "'sharpest_corner'" } diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json new file mode 100644 index 0000000000..5537606b12 --- /dev/null +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "name": "ACE Pro Color 1", + "inherits": "fdmextruder", + "metadata": + { + "machine": "anycubic_kobra3v2_ACE_PRO", + "position": "0" + }, + "overrides": + { + "extruder_nr": { "default_value": 0 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json new file mode 100644 index 0000000000..2370427eea --- /dev/null +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "name": "ACE Pro Color 2", + "inherits": "fdmextruder", + "metadata": + { + "machine": "anycubic_kobra3v2_ACE_PRO", + "position": "1" + }, + "overrides": + { + "extruder_nr": { "default_value": 1 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json new file mode 100644 index 0000000000..ae860e5f7a --- /dev/null +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "name": "ACE Pro Color 3", + "inherits": "fdmextruder", + "metadata": + { + "machine": "anycubic_kobra3v2_ACE_PRO", + "position": "2" + }, + "overrides": + { + "extruder_nr": { "default_value": 2 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json new file mode 100644 index 0000000000..fed2c1fc6b --- /dev/null +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "name": "ACE Pro Color 4", + "inherits": "fdmextruder", + "metadata": + { + "machine": "anycubic_kobra3v2_ACE_PRO", + "position": "3" + }, + "overrides": + { + "extruder_nr": { "default_value": 3 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/extruders/anycubic_kobra3v2_extruder_0.def.json b/resources/extruders/anycubic_kobra3v2_extruder_0.def.json new file mode 100644 index 0000000000..f5983cf3fb --- /dev/null +++ b/resources/extruders/anycubic_kobra3v2_extruder_0.def.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "name": "Extruder 1", + "inherits": "fdmextruder", + "metadata": + { + "machine": "anycubic_kobra3v2", + "position": "0" + }, + "overrides": + { + "extruder_nr": { "default_value": 0 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/extruders/bambulab_a1_extruder_0.def.json b/resources/extruders/bambulab_a1_extruder_0.def.json new file mode 100644 index 0000000000..e2a89b97c4 --- /dev/null +++ b/resources/extruders/bambulab_a1_extruder_0.def.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "name": "Extruder", + "inherits": "fdmextruder", + "metadata": + { + "machine": "bambulab_a1", + "position": "0" + }, + "overrides": + { + "extruder_nr": { "default_value": 0 }, + "machine_extruder_change_duration": { "default_value": 29 }, + "machine_extruder_end_code": { "default_value": ";===== A1 extruder end {extruder_nr} begin =====\nG392 S0\nM1007 S0 ; turn off mass estimation\nM204 S9000\n\nG91 ; set relative positioning\nG1 Z3.0 F1200\nG90 ; back to abolute positioning\n\nG1 X267 F18000\nG1 Y128 F9000\n\nM400\nM106 P1 S0\nM106 P2 S0\n{if material_print_temperature > 142, extruder_nr}\nM104 S{material_print_temperature, extruder_nr}\n{endif}\n\n\nM620.11 S1 I{extruder_nr} E-18 F1200\nM400\n\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A0 F{material_flush_purge_speed}\n\nM628 S1\nG92 E0\nG1 E-18 F{material_flush_purge_speed}\nM400\nM629 S1\n\n;===== A1 extruder end {extruder_nr} finish =====\n" }, + "machine_extruder_start_code": { "default_value": ";===== A1 extruder start {extruder_nr} begin =====\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A1 F{material_flush_purge_speed} L{material_flush_purge_length} H{machine_nozzle_size} T{material_print_temperature, extruder_nr}\n\nM400\nG92 E0\n\n{if not prime_tower_enable}\n\n; FLUSH_START\n; always use highest temperature to flush\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature, extruder_nr}\nM106 P1 S60\nG1 E{material_flush_purge_length / 4.0} F{min(extruderValues('material_flush_purge_speed'))} ; do not need pulsatile flushing for start part\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{min(extruderValues('material_flush_purge_speed'))}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\nM400\nM1002 set_filament_type:{material_type, extruder_nr}\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n\nM400\nM106 P1 S60\nG1 E6 F{material_flush_purge_speed, extruder_nr} ;Compensate for filament spillage during waiting temperature\n\n{endif} ; prime_tower_enable\n\nM400\nG92 E0\n; G1 E-{retraction_amount} F1800\nM400\nM106 P1 S178\nM400 S4\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM622.1 S0\n\nM621 S{extruder_nr}A\nG392 S0\n\nM1007 S1\n;===== A1 extruder start {extruder_nr} finish =====\n" }, + "material_diameter": { "default_value": 1.75 }, + "switch_extruder_retraction_amount": { "default_value": 18 } + } +} \ No newline at end of file diff --git a/resources/extruders/bambulab_a1_extruder_1.def.json b/resources/extruders/bambulab_a1_extruder_1.def.json new file mode 100644 index 0000000000..b61a74d199 --- /dev/null +++ b/resources/extruders/bambulab_a1_extruder_1.def.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "name": "Extruder", + "inherits": "fdmextruder", + "metadata": + { + "machine": "bambulab_a1", + "position": "1" + }, + "overrides": + { + "extruder_nr": { "default_value": 1 }, + "machine_extruder_change_duration": { "default_value": 29 }, + "machine_extruder_end_code": { "default_value": ";===== A1 extruder end {extruder_nr} begin =====\nG392 S0\nM1007 S0 ; turn off mass estimation\nM204 S9000\n\nG91 ; set relative positioning\nG1 Z3.0 F1200\nG90 ; back to abolute positioning\n\nG1 X267 F18000\nG1 Y128 F9000\n\nM400\nM106 P1 S0\nM106 P2 S0\n{if material_print_temperature > 142, extruder_nr}\nM104 S{material_print_temperature, extruder_nr}\n{endif}\n\n\nM620.11 S1 I{extruder_nr} E-18 F1200\nM400\n\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A0 F{material_flush_purge_speed}\n\nM628 S1\nG92 E0\nG1 E-18 F{material_flush_purge_speed}\nM400\nM629 S1\n\n;===== A1 extruder end {extruder_nr} finish =====\n" }, + "machine_extruder_start_code": { "default_value": ";===== A1 extruder start {extruder_nr} begin =====\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A1 F{material_flush_purge_speed} L{material_flush_purge_length} H{machine_nozzle_size} T{material_print_temperature, extruder_nr}\n\nM400\nG92 E0\n\n{if not prime_tower_enable}\n\n; FLUSH_START\n; always use highest temperature to flush\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature, extruder_nr}\nM106 P1 S60\nG1 E{material_flush_purge_length / 4.0} F{min(extruderValues('material_flush_purge_speed'))} ; do not need pulsatile flushing for start part\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{min(extruderValues('material_flush_purge_speed'))}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\nM400\nM1002 set_filament_type:{material_type, extruder_nr}\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n\nM400\nM106 P1 S60\nG1 E6 F{material_flush_purge_speed, extruder_nr} ;Compensate for filament spillage during waiting temperature\n\n{endif} ; prime_tower_enable\n\nM400\nG92 E0\n; G1 E-{retraction_amount} F1800\nM400\nM106 P1 S178\nM400 S4\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM622.1 S0\n\nM621 S{extruder_nr}A\nG392 S0\n\nM1007 S1\n;===== A1 extruder start {extruder_nr} finish =====\n" }, + "material_diameter": { "default_value": 1.75 }, + "switch_extruder_retraction_amount": { "default_value": 18 } + } +} \ No newline at end of file diff --git a/resources/extruders/bambulab_a1_extruder_2.def.json b/resources/extruders/bambulab_a1_extruder_2.def.json new file mode 100644 index 0000000000..d439de1d30 --- /dev/null +++ b/resources/extruders/bambulab_a1_extruder_2.def.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "name": "Extruder", + "inherits": "fdmextruder", + "metadata": + { + "machine": "bambulab_a1", + "position": "2" + }, + "overrides": + { + "extruder_nr": { "default_value": 2 }, + "machine_extruder_change_duration": { "default_value": 29 }, + "machine_extruder_end_code": { "default_value": ";===== A1 extruder end {extruder_nr} begin =====\nG392 S0\nM1007 S0 ; turn off mass estimation\nM204 S9000\n\nG91 ; set relative positioning\nG1 Z3.0 F1200\nG90 ; back to abolute positioning\n\nG1 X267 F18000\nG1 Y128 F9000\n\nM400\nM106 P1 S0\nM106 P2 S0\n{if material_print_temperature > 142, extruder_nr}\nM104 S{material_print_temperature, extruder_nr}\n{endif}\n\n\nM620.11 S1 I{extruder_nr} E-18 F1200\nM400\n\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A0 F{material_flush_purge_speed}\n\nM628 S1\nG92 E0\nG1 E-18 F{material_flush_purge_speed}\nM400\nM629 S1\n\n;===== A1 extruder end {extruder_nr} finish =====\n" }, + "machine_extruder_start_code": { "default_value": ";===== A1 extruder start {extruder_nr} begin =====\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A1 F{material_flush_purge_speed} L{material_flush_purge_length} H{machine_nozzle_size} T{material_print_temperature, extruder_nr}\n\nM400\nG92 E0\n\n{if not prime_tower_enable}\n\n; FLUSH_START\n; always use highest temperature to flush\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature, extruder_nr}\nM106 P1 S60\nG1 E{material_flush_purge_length / 4.0} F{min(extruderValues('material_flush_purge_speed'))} ; do not need pulsatile flushing for start part\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{min(extruderValues('material_flush_purge_speed'))}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\nM400\nM1002 set_filament_type:{material_type, extruder_nr}\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n\nM400\nM106 P1 S60\nG1 E6 F{material_flush_purge_speed, extruder_nr} ;Compensate for filament spillage during waiting temperature\n\n{endif} ; prime_tower_enable\n\nM400\nG92 E0\n; G1 E-{retraction_amount} F1800\nM400\nM106 P1 S178\nM400 S4\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM622.1 S0\n\nM621 S{extruder_nr}A\nG392 S0\n\nM1007 S1\n;===== A1 extruder start {extruder_nr} finish =====\n" }, + "material_diameter": { "default_value": 1.75 }, + "switch_extruder_retraction_amount": { "default_value": 18 } + } +} \ No newline at end of file diff --git a/resources/extruders/bambulab_a1_extruder_3.def.json b/resources/extruders/bambulab_a1_extruder_3.def.json new file mode 100644 index 0000000000..13b7ec8699 --- /dev/null +++ b/resources/extruders/bambulab_a1_extruder_3.def.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "name": "Extruder", + "inherits": "fdmextruder", + "metadata": + { + "machine": "bambulab_a1", + "position": "3" + }, + "overrides": + { + "extruder_nr": { "default_value": 3 }, + "machine_extruder_change_duration": { "default_value": 29 }, + "machine_extruder_end_code": { "default_value": ";===== A1 extruder end {extruder_nr} begin =====\nG392 S0\nM1007 S0 ; turn off mass estimation\nM204 S9000\n\nG91 ; set relative positioning\nG1 Z3.0 F1200\nG90 ; back to abolute positioning\n\nG1 X267 F18000\nG1 Y128 F9000\n\nM400\nM106 P1 S0\nM106 P2 S0\n{if material_print_temperature > 142, extruder_nr}\nM104 S{material_print_temperature, extruder_nr}\n{endif}\n\n\nM620.11 S1 I{extruder_nr} E-18 F1200\nM400\n\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A0 F{material_flush_purge_speed}\n\nM628 S1\nG92 E0\nG1 E-18 F{material_flush_purge_speed}\nM400\nM629 S1\n\n;===== A1 extruder end {extruder_nr} finish =====\n" }, + "machine_extruder_start_code": { "default_value": ";===== A1 extruder start {extruder_nr} begin =====\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A1 F{material_flush_purge_speed} L{material_flush_purge_length} H{machine_nozzle_size} T{material_print_temperature, extruder_nr}\n\nM400\nG92 E0\n\n{if not prime_tower_enable}\n\n; FLUSH_START\n; always use highest temperature to flush\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature, extruder_nr}\nM106 P1 S60\nG1 E{material_flush_purge_length / 4.0} F{min(extruderValues('material_flush_purge_speed'))} ; do not need pulsatile flushing for start part\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{min(extruderValues('material_flush_purge_speed'))}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\nM400\nM1002 set_filament_type:{material_type, extruder_nr}\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n\nM400\nM106 P1 S60\nG1 E6 F{material_flush_purge_speed, extruder_nr} ;Compensate for filament spillage during waiting temperature\n\n{endif} ; prime_tower_enable\n\nM400\nG92 E0\n; G1 E-{retraction_amount} F1800\nM400\nM106 P1 S178\nM400 S4\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM622.1 S0\n\nM621 S{extruder_nr}A\nG392 S0\n\nM1007 S1\n;===== A1 extruder start {extruder_nr} finish =====\n" }, + "material_diameter": { "default_value": 1.75 }, + "switch_extruder_retraction_amount": { "default_value": 18 } + } +} \ No newline at end of file diff --git a/resources/extruders/bambulab_a1mini_extruder_0.def.json b/resources/extruders/bambulab_a1mini_extruder_0.def.json new file mode 100644 index 0000000000..57627b20b1 --- /dev/null +++ b/resources/extruders/bambulab_a1mini_extruder_0.def.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "name": "Extruder", + "inherits": "fdmextruder", + "metadata": + { + "machine": "bambulab_a1mini", + "position": "0" + }, + "overrides": + { + "extruder_nr": { "default_value": 0 }, + "machine_extruder_change_duration": { "default_value": 29 }, + "machine_extruder_end_code": { "default_value": ";===== A1mini extruder end {extruder_nr} begin =====\nG392 S0\nM1007 S0 ; turn off mass estimation\nM204 S9000\n\nG91 ; set relative positioning\nG1 Z3.0 F1200\nG90 ; back to abolute positioning\n\nG1 X180 F18000\nG1 Y90 F9000\n\nM400\nM106 P1 S0\nM106 P2 S0\n{if material_print_temperature > 142, extruder_nr}\nM104 S{material_print_temperature, extruder_nr}\n{endif}\n\n\nM620.11 S1 I{extruder_nr} E-18 F1200\nM400\n\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A0 F{material_flush_purge_speed}\n\nM628 S1\nG92 E0\nG1 E-18 F{material_flush_purge_speed}\nM400\nM629 S1\n\n;===== A1mini extruder end {extruder_nr} finish =====\n" }, + "machine_extruder_start_code": { "default_value": ";===== A1mini extruder start {extruder_nr} begin =====\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A1 F{material_flush_purge_speed} L{material_flush_purge_length} H{machine_nozzle_size} T{material_print_temperature, extruder_nr}\n\nM400\nG92 E0\n\n{if not prime_tower_enable}\n\n; FLUSH_START\n; always use highest temperature to flush\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature, extruder_nr}\nM106 P1 S60\nG1 E{material_flush_purge_length / 4.0} F{min(extruderValues('material_flush_purge_speed'))} ; do not need pulsatile flushing for start part\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{min(extruderValues('material_flush_purge_speed'))}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\nM400\nM1002 set_filament_type:{material_type, extruder_nr}\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n\nM400\nM106 P1 S60\nG1 E5 F{material_flush_purge_speed, extruder_nr} ;Compensate for filament spillage during waiting temperature\n\n{endif} ; prime_tower_enable\n\nM400\nG92 E0\n; G1 E-{retraction_amount} F1800\nM400\nM106 P1 S178\nM400 S3\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nM400\nM106 P1 S0\n\nM622.1 S0\n\nM621 S{extruder_nr}A\nG392 S0\n\nM1007 S1\n;===== A1mini extruder start {extruder_nr} finish =====\n" }, + "material_diameter": { "default_value": 1.75 }, + "switch_extruder_retraction_amount": { "default_value": 18 } + } +} \ No newline at end of file diff --git a/resources/extruders/bambulab_a1mini_extruder_1.def.json b/resources/extruders/bambulab_a1mini_extruder_1.def.json new file mode 100644 index 0000000000..02f2a102ff --- /dev/null +++ b/resources/extruders/bambulab_a1mini_extruder_1.def.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "name": "Extruder", + "inherits": "fdmextruder", + "metadata": + { + "machine": "bambulab_a1mini", + "position": "1" + }, + "overrides": + { + "extruder_nr": { "default_value": 1 }, + "machine_extruder_change_duration": { "default_value": 29 }, + "machine_extruder_end_code": { "default_value": ";===== A1mini extruder end {extruder_nr} begin =====\nG392 S0\nM1007 S0 ; turn off mass estimation\nM204 S9000\n\nG91 ; set relative positioning\nG1 Z3.0 F1200\nG90 ; back to abolute positioning\n\nG1 X180 F18000\nG1 Y90 F9000\n\nM400\nM106 P1 S0\nM106 P2 S0\n{if material_print_temperature > 142, extruder_nr}\nM104 S{material_print_temperature, extruder_nr}\n{endif}\n\n\nM620.11 S1 I{extruder_nr} E-18 F1200\nM400\n\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A0 F{material_flush_purge_speed}\n\nM628 S1\nG92 E0\nG1 E-18 F{material_flush_purge_speed}\nM400\nM629 S1\n\n;===== A1mini extruder end {extruder_nr} finish =====\n" }, + "machine_extruder_start_code": { "default_value": ";===== A1mini extruder start {extruder_nr} begin =====\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A1 F{material_flush_purge_speed} L{material_flush_purge_length} H{machine_nozzle_size} T{material_print_temperature, extruder_nr}\n\nM400\nG92 E0\n\n{if not prime_tower_enable}\n\n; FLUSH_START\n; always use highest temperature to flush\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature, extruder_nr}\nM106 P1 S60\nG1 E{material_flush_purge_length / 4.0} F{min(extruderValues('material_flush_purge_speed'))} ; do not need pulsatile flushing for start part\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{min(extruderValues('material_flush_purge_speed'))}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\nM400\nM1002 set_filament_type:{material_type, extruder_nr}\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n\nM400\nM106 P1 S60\nG1 E5 F{material_flush_purge_speed, extruder_nr} ;Compensate for filament spillage during waiting temperature\n\n{endif} ; prime_tower_enable\n\nM400\nG92 E0\n; G1 E-{retraction_amount} F1800\nM400\nM106 P1 S178\nM400 S3\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nM400\nM106 P1 S0\n\nM622.1 S0\n\nM621 S{extruder_nr}A\nG392 S0\n\nM1007 S1\n;===== A1mini extruder start {extruder_nr} finish =====\n" }, + "material_diameter": { "default_value": 1.75 }, + "switch_extruder_retraction_amount": { "default_value": 18 } + } +} \ No newline at end of file diff --git a/resources/extruders/bambulab_a1mini_extruder_2.def.json b/resources/extruders/bambulab_a1mini_extruder_2.def.json new file mode 100644 index 0000000000..9b16132547 --- /dev/null +++ b/resources/extruders/bambulab_a1mini_extruder_2.def.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "name": "Extruder", + "inherits": "fdmextruder", + "metadata": + { + "machine": "bambulab_a1mini", + "position": "2" + }, + "overrides": + { + "extruder_nr": { "default_value": 2 }, + "machine_extruder_change_duration": { "default_value": 29 }, + "machine_extruder_end_code": { "default_value": ";===== A1mini extruder end {extruder_nr} begin =====\nG392 S0\nM1007 S0 ; turn off mass estimation\nM204 S9000\n\nG91 ; set relative positioning\nG1 Z3.0 F1200\nG90 ; back to abolute positioning\n\nG1 X180 F18000\nG1 Y90 F9000\n\nM400\nM106 P1 S0\nM106 P2 S0\n{if material_print_temperature > 142, extruder_nr}\nM104 S{material_print_temperature, extruder_nr}\n{endif}\n\n\nM620.11 S1 I{extruder_nr} E-18 F1200\nM400\n\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A0 F{material_flush_purge_speed}\n\nM628 S1\nG92 E0\nG1 E-18 F{material_flush_purge_speed}\nM400\nM629 S1\n\n;===== A1mini extruder end {extruder_nr} finish =====\n" }, + "machine_extruder_start_code": { "default_value": ";===== A1mini extruder start {extruder_nr} begin =====\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A1 F{material_flush_purge_speed} L{material_flush_purge_length} H{machine_nozzle_size} T{material_print_temperature, extruder_nr}\n\nM400\nG92 E0\n\n{if not prime_tower_enable}\n\n; FLUSH_START\n; always use highest temperature to flush\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature, extruder_nr}\nM106 P1 S60\nG1 E{material_flush_purge_length / 4.0} F{min(extruderValues('material_flush_purge_speed'))} ; do not need pulsatile flushing for start part\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{min(extruderValues('material_flush_purge_speed'))}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\nM400\nM1002 set_filament_type:{material_type, extruder_nr}\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n\nM400\nM106 P1 S60\nG1 E5 F{material_flush_purge_speed, extruder_nr} ;Compensate for filament spillage during waiting temperature\n\n{endif} ; prime_tower_enable\n\nM400\nG92 E0\n; G1 E-{retraction_amount} F1800\nM400\nM106 P1 S178\nM400 S3\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nM400\nM106 P1 S0\n\nM622.1 S0\n\nM621 S{extruder_nr}A\nG392 S0\n\nM1007 S1\n;===== A1mini extruder start {extruder_nr} finish =====\n" }, + "material_diameter": { "default_value": 1.75 }, + "switch_extruder_retraction_amount": { "default_value": 18 } + } +} \ No newline at end of file diff --git a/resources/extruders/bambulab_a1mini_extruder_3.def.json b/resources/extruders/bambulab_a1mini_extruder_3.def.json new file mode 100644 index 0000000000..ee63528e5f --- /dev/null +++ b/resources/extruders/bambulab_a1mini_extruder_3.def.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "name": "Extruder", + "inherits": "fdmextruder", + "metadata": + { + "machine": "bambulab_a1mini", + "position": "3" + }, + "overrides": + { + "extruder_nr": { "default_value": 3 }, + "machine_extruder_change_duration": { "default_value": 29 }, + "machine_extruder_end_code": { "default_value": ";===== A1mini extruder end {extruder_nr} begin =====\nG392 S0\nM1007 S0 ; turn off mass estimation\nM204 S9000\n\nG91 ; set relative positioning\nG1 Z3.0 F1200\nG90 ; back to abolute positioning\n\nG1 X180 F18000\nG1 Y90 F9000\n\nM400\nM106 P1 S0\nM106 P2 S0\n{if material_print_temperature > 142, extruder_nr}\nM104 S{material_print_temperature, extruder_nr}\n{endif}\n\n\nM620.11 S1 I{extruder_nr} E-18 F1200\nM400\n\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A0 F{material_flush_purge_speed}\n\nM628 S1\nG92 E0\nG1 E-18 F{material_flush_purge_speed}\nM400\nM629 S1\n\n;===== A1mini extruder end {extruder_nr} finish =====\n" }, + "machine_extruder_start_code": { "default_value": ";===== A1mini extruder start {extruder_nr} begin =====\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\nM620.10 A1 F{material_flush_purge_speed} L{material_flush_purge_length} H{machine_nozzle_size} T{material_print_temperature, extruder_nr}\n\nM400\nG92 E0\n\n{if not prime_tower_enable}\n\n; FLUSH_START\n; always use highest temperature to flush\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature, extruder_nr}\nM106 P1 S60\nG1 E{material_flush_purge_length / 4.0} F{min(extruderValues('material_flush_purge_speed'))} ; do not need pulsatile flushing for start part\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{min(extruderValues('material_flush_purge_speed'))}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\nM400\nM1002 set_filament_type:{material_type, extruder_nr}\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n\nM400\nM106 P1 S60\nG1 E5 F{material_flush_purge_speed, extruder_nr} ;Compensate for filament spillage during waiting temperature\n\n{endif} ; prime_tower_enable\n\nM400\nG92 E0\n; G1 E-{retraction_amount} F1800\nM400\nM106 P1 S178\nM400 S3\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nG1 X-3.5 F18000\nG1 X-13.5 F3000\nM400\nM106 P1 S0\n\nM622.1 S0\n\nM621 S{extruder_nr}A\nG392 S0\n\nM1007 S1\n;===== A1mini extruder start {extruder_nr} finish =====\n" }, + "material_diameter": { "default_value": 1.75 }, + "switch_extruder_retraction_amount": { "default_value": 18 } + } +} \ No newline at end of file diff --git a/resources/extruders/bambulab_x1_extruder_0.def.json b/resources/extruders/bambulab_x1_extruder_0.def.json new file mode 100644 index 0000000000..3ff6e83c83 --- /dev/null +++ b/resources/extruders/bambulab_x1_extruder_0.def.json @@ -0,0 +1,18 @@ +{ + "version": 2, + "name": "Extruder", + "inherits": "fdmextruder", + "metadata": + { + "machine": "bambulab_x1", + "position": "0" + }, + "overrides": + { + "extruder_nr": { "default_value": 0 }, + "machine_extruder_change_duration": { "default_value": 29 }, + "machine_extruder_end_code": { "default_value": "M204 S9000\n\nG91 ; set relative positioning\nG1 Z3.0 F1200\nG90 ; back to abolute positioning\n\nG1 X70 F21000\nG1 Y245\nG1 Y265 F3000\nM400\nM106 P1 S0\nM106 P2 S0\n\n{if material_print_temperature > 142}\nM104 S{material_print_temperature}\n{endif}\n\nM620.11 S1 I{extruder_nr} E-18 F1200\nM400\n\nG1 X90 F3000\nG1 Y255 F4000\nG1 X100 F5000\nG1 X120 F15000\nG1 X20 Y50 F21000\nG1 Y-3\n\n;{if toolchange_count == 2}\n; get travel path for change filament\n;M620.1 X[travel_point_1_x] Y[travel_point_1_y] F21000 P0\n;M620.1 X[travel_point_2_x] Y[travel_point_2_y] F21000 P1\n;M620.1 X[travel_point_3_x] Y[travel_point_3_y] F21000 P2\n;{endif}\n\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\n" }, + "machine_extruder_start_code": { "default_value": "M620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\n\nG92 E0\n\n; always use highest temperature to flush\n{if material_type == 'PETG'}\nM109 S260\n{elsif material_type == 'PVA'}\nM109 S210\n{else}\nM109 S{material_print_temperature}\n{endif}\n\n{if not prime_tower_enable}\n\nM83\n; FLUSH_START\n; always use highest temperature to flush\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature, extruder_nr}\nM106 P1 S60\nG1 E{material_flush_purge_length / 4.0} F{min(extruderValues('material_flush_purge_speed'))} ; do not need pulsatile flushing for start part\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{min(extruderValues('material_flush_purge_speed'))}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\nM400\nM1002 set_filament_type:{material_type, extruder_nr}\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; FLUSH_START\nM400\nM109 S{material_print_temperature}\nG1 E6 F{material_flush_purge_speed, extruder_nr} ;Compensate for filament spillage during waiting temperature\n; FLUSH_END\n\n{endif} ; prime_tower_enable\n\nM400\nG92 E0\n;G1 E-[new_retract_length_toolchange] F1800\nM106 P1 S255\nM400 S3\n\nG1 X70 F5000\nG1 X90 F3000\nG1 Y255 F4000\nG1 X105 F5000\nG1 Y265 F5000\nG1 X70 F10000\nG1 X100 F5000\nG1 X70 F10000\nG1 X100 F5000\n\nG1 X70 F10000\nG1 X80 F15000\nG1 X60\nG1 X80\nG1 X60\nG1 X80 ; shake to put down garbage\nG1 X100 F5000\nG1 X165 F15000; wipe and shake\nG1 Y256 ; move Y to aside, prevent collision\nM400\n\nM621 S{extruder_nr}A\n" }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/extruders/bambulab_x1_extruder_1.def.json b/resources/extruders/bambulab_x1_extruder_1.def.json new file mode 100644 index 0000000000..777e735fa6 --- /dev/null +++ b/resources/extruders/bambulab_x1_extruder_1.def.json @@ -0,0 +1,18 @@ +{ + "version": 2, + "name": "Extruder", + "inherits": "fdmextruder", + "metadata": + { + "machine": "bambulab_x1", + "position": "1" + }, + "overrides": + { + "extruder_nr": { "default_value": 1 }, + "machine_extruder_change_duration": { "default_value": 29 }, + "machine_extruder_end_code": { "default_value": "M204 S9000\n\nG91 ; set relative positioning\nG1 Z3.0 F1200\nG90 ; back to abolute positioning\n\nG1 X70 F21000\nG1 Y245\nG1 Y265 F3000\nM400\nM106 P1 S0\nM106 P2 S0\n\n{if material_print_temperature > 142}\nM104 S{material_print_temperature}\n{endif}\n\nM620.11 S1 I{extruder_nr} E-18 F1200\nM400\n\nG1 X90 F3000\nG1 Y255 F4000\nG1 X100 F5000\nG1 X120 F15000\nG1 X20 Y50 F21000\nG1 Y-3\n\n;{if toolchange_count == 2}\n; get travel path for change filament\n;M620.1 X[travel_point_1_x] Y[travel_point_1_y] F21000 P0\n;M620.1 X[travel_point_2_x] Y[travel_point_2_y] F21000 P1\n;M620.1 X[travel_point_3_x] Y[travel_point_3_y] F21000 P2\n;{endif}\n\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\n" }, + "machine_extruder_start_code": { "default_value": "M620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\n\nG92 E0\n\n; always use highest temperature to flush\n{if material_type == 'PETG'}\nM109 S260\n{elsif material_type == 'PVA'}\nM109 S210\n{else}\nM109 S{material_print_temperature}\n{endif}\n\n{if not prime_tower_enable}\n\nM83\n; FLUSH_START\n; always use highest temperature to flush\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature, extruder_nr}\nM106 P1 S60\nG1 E{material_flush_purge_length / 4.0} F{min(extruderValues('material_flush_purge_speed'))} ; do not need pulsatile flushing for start part\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{min(extruderValues('material_flush_purge_speed'))}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\nM400\nM1002 set_filament_type:{material_type, extruder_nr}\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; FLUSH_START\nM400\nM109 S{material_print_temperature}\nG1 E6 F{material_flush_purge_speed, extruder_nr} ;Compensate for filament spillage during waiting temperature\n; FLUSH_END\n\n{endif} ; prime_tower_enable\n\nM400\nG92 E0\n;G1 E-[new_retract_length_toolchange] F1800\nM106 P1 S255\nM400 S3\n\nG1 X70 F5000\nG1 X90 F3000\nG1 Y255 F4000\nG1 X105 F5000\nG1 Y265 F5000\nG1 X70 F10000\nG1 X100 F5000\nG1 X70 F10000\nG1 X100 F5000\n\nG1 X70 F10000\nG1 X80 F15000\nG1 X60\nG1 X80\nG1 X60\nG1 X80 ; shake to put down garbage\nG1 X100 F5000\nG1 X165 F15000; wipe and shake\nG1 Y256 ; move Y to aside, prevent collision\nM400\n\nM621 S{extruder_nr}A\n" }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/extruders/bambulab_x1_extruder_2.def.json b/resources/extruders/bambulab_x1_extruder_2.def.json new file mode 100644 index 0000000000..3fad4c692d --- /dev/null +++ b/resources/extruders/bambulab_x1_extruder_2.def.json @@ -0,0 +1,18 @@ +{ + "version": 2, + "name": "Extruder", + "inherits": "fdmextruder", + "metadata": + { + "machine": "bambulab_x1", + "position": "2" + }, + "overrides": + { + "extruder_nr": { "default_value": 2 }, + "machine_extruder_change_duration": { "default_value": 29 }, + "machine_extruder_end_code": { "default_value": "M204 S9000\n\nG91 ; set relative positioning\nG1 Z3.0 F1200\nG90 ; back to abolute positioning\n\nG1 X70 F21000\nG1 Y245\nG1 Y265 F3000\nM400\nM106 P1 S0\nM106 P2 S0\n\n{if material_print_temperature > 142}\nM104 S{material_print_temperature}\n{endif}\n\nM620.11 S1 I{extruder_nr} E-18 F1200\nM400\n\nG1 X90 F3000\nG1 Y255 F4000\nG1 X100 F5000\nG1 X120 F15000\nG1 X20 Y50 F21000\nG1 Y-3\n\n;{if toolchange_count == 2}\n; get travel path for change filament\n;M620.1 X[travel_point_1_x] Y[travel_point_1_y] F21000 P0\n;M620.1 X[travel_point_2_x] Y[travel_point_2_y] F21000 P1\n;M620.1 X[travel_point_3_x] Y[travel_point_3_y] F21000 P2\n;{endif}\n\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\n" }, + "machine_extruder_start_code": { "default_value": "M620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\n\nG92 E0\n\n; always use highest temperature to flush\n{if material_type == 'PETG'}\nM109 S260\n{elsif material_type == 'PVA'}\nM109 S210\n{else}\nM109 S{material_print_temperature}\n{endif}\n\n{if not prime_tower_enable}\n\nM83\n; FLUSH_START\n; always use highest temperature to flush\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature, extruder_nr}\nM106 P1 S60\nG1 E{material_flush_purge_length / 4.0} F{min(extruderValues('material_flush_purge_speed'))} ; do not need pulsatile flushing for start part\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{min(extruderValues('material_flush_purge_speed'))}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\nM400\nM1002 set_filament_type:{material_type, extruder_nr}\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; FLUSH_START\nM400\nM109 S{material_print_temperature}\nG1 E6 F{material_flush_purge_speed, extruder_nr} ;Compensate for filament spillage during waiting temperature\n; FLUSH_END\n\n{endif} ; prime_tower_enable\n\nM400\nG92 E0\n;G1 E-[new_retract_length_toolchange] F1800\nM106 P1 S255\nM400 S3\n\nG1 X70 F5000\nG1 X90 F3000\nG1 Y255 F4000\nG1 X105 F5000\nG1 Y265 F5000\nG1 X70 F10000\nG1 X100 F5000\nG1 X70 F10000\nG1 X100 F5000\n\nG1 X70 F10000\nG1 X80 F15000\nG1 X60\nG1 X80\nG1 X60\nG1 X80 ; shake to put down garbage\nG1 X100 F5000\nG1 X165 F15000; wipe and shake\nG1 Y256 ; move Y to aside, prevent collision\nM400\n\nM621 S{extruder_nr}A\n" }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/extruders/bambulab_x1_extruder_3.def.json b/resources/extruders/bambulab_x1_extruder_3.def.json new file mode 100644 index 0000000000..751ec2b459 --- /dev/null +++ b/resources/extruders/bambulab_x1_extruder_3.def.json @@ -0,0 +1,18 @@ +{ + "version": 2, + "name": "Extruder", + "inherits": "fdmextruder", + "metadata": + { + "machine": "bambulab_x1", + "position": "3" + }, + "overrides": + { + "extruder_nr": { "default_value": 3 }, + "machine_extruder_change_duration": { "default_value": 29 }, + "machine_extruder_end_code": { "default_value": "M204 S9000\n\nG91 ; set relative positioning\nG1 Z3.0 F1200\nG90 ; back to abolute positioning\n\nG1 X70 F21000\nG1 Y245\nG1 Y265 F3000\nM400\nM106 P1 S0\nM106 P2 S0\n\n{if material_print_temperature > 142}\nM104 S{material_print_temperature}\n{endif}\n\nM620.11 S1 I{extruder_nr} E-18 F1200\nM400\n\nG1 X90 F3000\nG1 Y255 F4000\nG1 X100 F5000\nG1 X120 F15000\nG1 X20 Y50 F21000\nG1 Y-3\n\n;{if toolchange_count == 2}\n; get travel path for change filament\n;M620.1 X[travel_point_1_x] Y[travel_point_1_y] F21000 P0\n;M620.1 X[travel_point_2_x] Y[travel_point_2_y] F21000 P1\n;M620.1 X[travel_point_3_x] Y[travel_point_3_y] F21000 P2\n;{endif}\n\nM620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\n" }, + "machine_extruder_start_code": { "default_value": "M620.1 E F{material_flush_purge_speed} T{material_print_temperature, extruder_nr}\n\nG92 E0\n\n; always use highest temperature to flush\n{if material_type == 'PETG'}\nM109 S260\n{elsif material_type == 'PVA'}\nM109 S210\n{else}\nM109 S{material_print_temperature}\n{endif}\n\n{if not prime_tower_enable}\n\nM83\n; FLUSH_START\n; always use highest temperature to flush\nM400\nM1002 set_filament_type:UNKNOWN\nM109 S{material_print_temperature, extruder_nr}\nM106 P1 S60\nG1 E{material_flush_purge_length / 4.0} F{min(extruderValues('material_flush_purge_speed'))} ; do not need pulsatile flushing for start part\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{min(extruderValues('material_flush_purge_speed'))}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.23} F{material_flush_purge_speed, extruder_nr}\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\nM400\nM1002 set_filament_type:{material_type, extruder_nr}\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; WIPE\nM400\nM106 P1 S178\nM400 S3\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nG1 X-38.2 F18000\nG1 X-48.2 F3000\nM400\nM106 P1 S0\n\nM106 P1 S60\n; FLUSH_START\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\nG1 E{(material_flush_purge_length / 4.0) * 0.18} F{material_flush_purge_speed, extruder_nr}\nG1 E{(material_flush_purge_length / 4.0) * 0.02} F50\n; FLUSH_END\nG1 E-{retraction_amount * 2} F1800\nG1 E{retraction_amount * 2} F300\n\n; FLUSH_START\nM400\nM109 S{material_print_temperature}\nG1 E6 F{material_flush_purge_speed, extruder_nr} ;Compensate for filament spillage during waiting temperature\n; FLUSH_END\n\n{endif} ; prime_tower_enable\n\nM400\nG92 E0\n;G1 E-[new_retract_length_toolchange] F1800\nM106 P1 S255\nM400 S3\n\nG1 X70 F5000\nG1 X90 F3000\nG1 Y255 F4000\nG1 X105 F5000\nG1 Y265 F5000\nG1 X70 F10000\nG1 X100 F5000\nG1 X70 F10000\nG1 X100 F5000\n\nG1 X70 F10000\nG1 X80 F15000\nG1 X60\nG1 X80\nG1 X60\nG1 X80 ; shake to put down garbage\nG1 X100 F5000\nG1 X165 F15000; wipe and shake\nG1 Y256 ; move Y to aside, prevent collision\nM400\n\nM621 S{extruder_nr}A\n" }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/extruders/biqu_b2_extruder_0.def.json b/resources/extruders/biqu_b2_extruder_0.def.json new file mode 100644 index 0000000000..4bfb9686b3 --- /dev/null +++ b/resources/extruders/biqu_b2_extruder_0.def.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "name": "Extruder 1", + "inherits": "fdmextruder", + "metadata": + { + "machine": "biqu_b2", + "position": "0" + }, + "overrides": + { + "extruder_nr": { "default_value": 0 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/extruders/biqu_b2_extruder_1.def.json b/resources/extruders/biqu_b2_extruder_1.def.json new file mode 100644 index 0000000000..fc5778b071 --- /dev/null +++ b/resources/extruders/biqu_b2_extruder_1.def.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "name": "Extruder 2", + "inherits": "fdmextruder", + "metadata": + { + "machine": "biqu_b2", + "position": "1" + }, + "overrides": + { + "extruder_nr": { "default_value": 1 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/extruders/sovol_sv08_extruder.def.json b/resources/extruders/sovol_sv08_extruder.def.json new file mode 100644 index 0000000000..d0ccdee1de --- /dev/null +++ b/resources/extruders/sovol_sv08_extruder.def.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "name": "Nozzle Size", + "inherits": "fdmextruder", + "metadata": + { + "machine": "sovol_sv08", + "position": "0" + }, + "overrides": + { + "extruder_nr": + { + "default_value": 0, + "maximum_value": 1 + }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/extruders/toybox_alpha_one_two_extruder_0.def.json b/resources/extruders/toybox_alpha_one_two_extruder_0.def.json new file mode 100644 index 0000000000..f7f773257c --- /dev/null +++ b/resources/extruders/toybox_alpha_one_two_extruder_0.def.json @@ -0,0 +1,17 @@ +{ + "version": 2, + "name": "Extruder 1", + "inherits": "fdmextruder", + "metadata": + { + "machine": "toybox_alpha_one_two", + "position": "0" + }, + "overrides": + { + "extruder_nr": { "default_value": 0 }, + "machine_nozzle_offset_x": { "default_value": -3.0 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} \ No newline at end of file diff --git a/resources/images/Hellbot_Hidra_and_Hidra_Plus_V2.png b/resources/images/Hellbot_Hidra_and_Hidra_Plus_V2.png new file mode 100644 index 0000000000..1cbdea4aae Binary files /dev/null and b/resources/images/Hellbot_Hidra_and_Hidra_Plus_V2.png differ diff --git a/resources/images/bambulab-buildplate.png b/resources/images/bambulab-buildplate.png new file mode 100644 index 0000000000..930337a695 Binary files /dev/null and b/resources/images/bambulab-buildplate.png differ diff --git a/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_abs_0.2mm_quick.inst.cfg b/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_abs_0.2mm_quick.inst.cfg new file mode 100644 index 0000000000..9889424797 --- /dev/null +++ b/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_abs_0.2mm_quick.inst.cfg @@ -0,0 +1,22 @@ +[general] +definition = ultimaker_s8 +name = Quick +version = 4 + +[metadata] +intent_category = quick +material = generic_abs +quality_type = draft +setting_version = 25 +type = intent +variant = AA+ 0.4 + +[values] +cool_min_layer_time = 5 +cool_min_layer_time_overhang = 9 +cool_min_speed = 6 +cool_min_temperature = =material_print_temperature - 15 +speed_wall_x = =speed_print +speed_wall_x_roofing = =speed_wall +wall_line_width_x = =wall_line_width + diff --git a/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_petg_0.2mm_quick.inst.cfg b/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_petg_0.2mm_quick.inst.cfg new file mode 100644 index 0000000000..ae38c02395 --- /dev/null +++ b/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_petg_0.2mm_quick.inst.cfg @@ -0,0 +1,22 @@ +[general] +definition = ultimaker_s8 +name = Quick +version = 4 + +[metadata] +intent_category = quick +material = generic_petg +quality_type = draft +setting_version = 25 +type = intent +variant = AA+ 0.4 + +[values] +cool_min_layer_time = 5 +cool_min_layer_time_overhang = 9 +cool_min_speed = 6 +cool_min_temperature = =material_print_temperature - 15 +speed_wall_x = =speed_print +speed_wall_x_roofing = =speed_wall +wall_line_width_x = =wall_line_width + diff --git a/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.2mm_quick.inst.cfg b/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.2mm_quick.inst.cfg new file mode 100644 index 0000000000..4509317076 --- /dev/null +++ b/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.2mm_quick.inst.cfg @@ -0,0 +1,22 @@ +[general] +definition = ultimaker_s8 +name = Quick +version = 4 + +[metadata] +intent_category = quick +material = generic_pla +quality_type = draft +setting_version = 25 +type = intent +variant = AA+ 0.4 + +[values] +cool_min_layer_time = 5 +cool_min_layer_time_overhang = 9 +cool_min_speed = 6 +cool_min_temperature = =material_print_temperature - 15 +speed_wall_x = =speed_print +speed_wall_x_roofing = =speed_wall +wall_line_width_x = =wall_line_width + diff --git a/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.2mm_quick.inst.cfg b/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.2mm_quick.inst.cfg new file mode 100644 index 0000000000..4f75b631df --- /dev/null +++ b/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.2mm_quick.inst.cfg @@ -0,0 +1,22 @@ +[general] +definition = ultimaker_s8 +name = Quick +version = 4 + +[metadata] +intent_category = quick +material = generic_tough_pla +quality_type = draft +setting_version = 25 +type = intent +variant = AA+ 0.4 + +[values] +cool_min_layer_time = 5 +cool_min_layer_time_overhang = 9 +cool_min_speed = 6 +cool_min_temperature = =material_print_temperature - 15 +speed_wall_x = =speed_print +speed_wall_x_roofing = =speed_wall +wall_line_width_x = =wall_line_width + diff --git a/resources/meshes/anycubic_kobra3v2_buildplate.stl b/resources/meshes/anycubic_kobra3v2_buildplate.stl new file mode 100644 index 0000000000..9d526d0eda Binary files /dev/null and b/resources/meshes/anycubic_kobra3v2_buildplate.stl differ diff --git a/resources/meshes/bambulab_a1mini.obj b/resources/meshes/bambulab_a1mini.obj new file mode 100644 index 0000000000..5bddcd90a6 --- /dev/null +++ b/resources/meshes/bambulab_a1mini.obj @@ -0,0 +1,1978 @@ +# Blender 4.4.0 +# www.blender.org +mtllib bambulabs-a1mini.mtl +o bambulab_a1m +v 181.982834 180.261444 -0.600000 +v 181.982834 180.261459 0.000000 +v 182.000000 180.000000 0.000000 +v -2.000000 180.000000 0.000000 +v 182.000000 180.000000 -0.600000 +v 182.000000 -7.131783 0.000000 +v -2.000000 0.000000 0.000000 +v -1.982835 -0.261453 0.000000 +v 179.800003 -6.343558 0.000000 +v 49.852898 -2.600989 0.000000 +v 50.133736 -2.809235 0.000000 +v 162.852371 -2.814206 0.000000 +v 49.552856 -2.421132 0.000000 +v 49.236404 -2.271438 0.000000 +v 48.906788 -2.153522 0.000000 +v 48.567451 -2.068540 0.000000 +v 48.221615 -2.017208 0.000000 +v 47.871796 -2.000000 0.000000 +v 0.000000 -2.000000 0.000000 +v -0.261449 -1.982835 0.000000 +v -0.518171 -1.931701 0.000000 +v -0.765877 -1.847535 0.000000 +v -1.000370 -1.731819 0.000000 +v -1.217698 -1.586549 0.000000 +v -1.414195 -1.414204 0.000000 +v -1.586541 -1.217708 0.000000 +v -1.731813 -1.000380 0.000000 +v -1.847531 -0.765886 0.000000 +v -1.931700 -0.518178 0.000000 +v 171.138290 -2.814206 0.000000 +v 179.211777 -2.814206 0.000000 +v 179.342834 -2.828996 0.000000 +v 167.274673 -2.814206 0.000000 +v 179.800003 -3.402431 0.000000 +v 179.785217 -3.271375 0.000000 +v 179.741669 -3.147067 0.000000 +v 179.671616 -3.035642 0.000000 +v 179.578568 -2.942598 0.000000 +v 179.467148 -2.872545 0.000000 +v 181.931702 180.518173 -0.600000 +v 181.931702 180.518173 0.000000 +v -1.982835 180.261444 0.000000 +v 181.847534 180.765869 -0.600000 +v 181.847534 180.765884 0.000000 +v -1.931701 180.518173 0.000000 +v 181.731812 181.000366 -0.600000 +v 181.731812 181.000381 0.000000 +v -1.847535 180.765869 0.000000 +v 181.586548 181.217697 -0.600000 +v 181.586548 181.217712 0.000000 +v -1.731819 181.000366 0.000000 +v 181.414200 181.414200 -0.600000 +v 181.414200 181.414200 0.000000 +v -1.586549 181.217697 0.000000 +v 181.217712 181.586548 -0.600000 +v 181.217697 181.586548 0.000000 +v -1.414204 181.414200 0.000000 +v 181.000381 181.731812 -0.600000 +v 181.000366 181.731812 0.000000 +v -1.217708 181.586548 0.000000 +v 180.765884 181.847534 -0.600000 +v 180.765869 181.847534 0.000000 +v -1.000380 181.731812 0.000000 +v 180.518173 181.931702 -0.600000 +v 180.518173 181.931702 0.000000 +v -0.765886 181.847534 0.000000 +v 180.261459 181.982834 -0.600000 +v 180.261444 181.982834 0.000000 +v 148.928421 182.000000 0.000000 +v -0.518178 181.931702 0.000000 +v 180.000000 182.000000 -0.600000 +v 180.000000 182.000000 0.000000 +v 111.443146 182.000000 0.000000 +v 148.928421 182.000000 -0.600000 +v -0.518171 181.931702 -0.600000 +v 31.071573 182.000000 -0.600000 +v 68.556854 182.000000 -0.600000 +v 111.443146 182.000000 -0.600000 +v -0.261449 181.982834 -0.600000 +v 0.000000 182.000000 -0.600000 +v -0.765877 181.847534 -0.600000 +v -1.000370 181.731812 -0.600000 +v -1.217698 181.586548 -0.600000 +v -1.414195 181.414200 -0.600000 +v -1.586541 181.217712 -0.600000 +v -1.731813 181.000381 -0.600000 +v -1.847531 180.765884 -0.600000 +v -1.931700 180.518173 -0.600000 +v -1.982835 180.261459 -0.600000 +v 182.000000 -7.131783 -0.600000 +v -2.000000 180.000000 -0.600000 +v 179.671616 -3.035635 -0.600000 +v 179.741653 -3.147059 -0.600000 +v -2.000000 0.000000 -0.600000 +v -1.982835 -0.261449 -0.600000 +v -0.261453 181.982834 0.000000 +v 31.071573 182.000000 0.000000 +v 0.000000 182.000000 0.000000 +v 68.556854 182.000000 0.000000 +v 68.164452 182.019302 0.000000 +v 31.332619 182.017105 0.000000 +v 31.589205 182.068146 0.000000 +v 31.332619 182.017105 -0.600000 +v 67.776512 182.076889 0.000000 +v 31.836939 182.152237 0.000000 +v 31.589205 182.068146 -0.600000 +v 67.395866 182.172211 0.000000 +v 32.071579 182.267960 0.000000 +v 31.836939 182.152237 -0.600000 +v 67.026123 182.304489 0.000000 +v 32.289101 182.413300 0.000000 +v 32.071579 182.267960 -0.600000 +v 66.671143 182.472397 0.000000 +v 32.485786 182.585785 0.000000 +v 32.289101 182.413300 -0.600000 +v 61.780155 187.119843 0.000000 +v 37.019848 187.119843 0.000000 +v 32.485786 182.585785 -0.600000 +v 65.728424 183.171570 0.000000 +v 66.019547 182.907745 0.000000 +v 66.334579 182.674149 0.000000 +v 61.529186 187.344116 0.000000 +v 37.270813 187.344116 0.000000 +v 37.019848 187.119843 -0.600000 +v 61.254848 187.538727 0.000000 +v 37.545155 187.538727 0.000000 +v 37.270813 187.344116 -0.600000 +v 60.960442 187.701431 0.000000 +v 37.839558 187.701431 0.000000 +v 37.545155 187.538727 -0.600000 +v 60.649696 187.830139 0.000000 +v 38.150303 187.830139 0.000000 +v 37.839558 187.701431 -0.600000 +v 60.326481 187.923279 0.000000 +v 38.473522 187.923279 0.000000 +v 38.150303 187.830139 -0.600000 +v 59.994873 187.979645 0.000000 +v 38.805126 187.979645 0.000000 +v 38.473522 187.923279 -0.600000 +v 59.658833 187.998520 0.000000 +v 39.141167 187.998520 0.000000 +v 38.805126 187.979645 -0.600000 +v 39.141167 187.998520 -0.600000 +v 59.658833 187.998520 -0.600000 +v 59.994873 187.979645 -0.600000 +v 60.326481 187.923279 -0.600000 +v 60.649696 187.830139 -0.600000 +v 60.960442 187.701431 -0.600000 +v 61.254848 187.538727 -0.600000 +v 61.529186 187.344116 -0.600000 +v 61.780155 187.119843 -0.600000 +v 65.728424 183.171570 -0.600000 +v 66.019547 182.907745 -0.600000 +v 66.334579 182.674149 -0.600000 +v 66.671143 182.472397 -0.600000 +v 67.026123 182.304489 -0.600000 +v 67.395866 182.172211 -0.600000 +v 67.776512 182.076889 -0.600000 +v 68.164452 182.019302 -0.600000 +v 148.667389 182.017105 0.000000 +v 111.835548 182.019302 0.000000 +v 148.410797 182.068146 0.000000 +v 112.223488 182.076889 0.000000 +v 111.835548 182.019302 -0.600000 +v 148.163055 182.152237 0.000000 +v 112.604134 182.172211 0.000000 +v 112.223488 182.076889 -0.600000 +v 147.928421 182.267960 0.000000 +v 112.973877 182.304489 0.000000 +v 112.604134 182.172211 -0.600000 +v 147.710892 182.413300 0.000000 +v 113.328857 182.472397 0.000000 +v 112.973877 182.304489 -0.600000 +v 147.514221 182.585785 0.000000 +v 113.665421 182.674149 0.000000 +v 113.328857 182.472397 -0.600000 +v 142.980148 187.119843 0.000000 +v 113.980453 182.907745 0.000000 +v 113.665421 182.674149 -0.600000 +v 114.271576 183.171570 0.000000 +v 113.980453 182.907745 -0.600000 +v 118.219849 187.119843 0.000000 +v 114.271576 183.171570 -0.600000 +v 142.729187 187.344116 0.000000 +v 118.470818 187.344116 0.000000 +v 118.219849 187.119843 -0.600000 +v 142.454849 187.538727 0.000000 +v 118.745155 187.538727 0.000000 +v 118.470818 187.344116 -0.600000 +v 142.160446 187.701431 0.000000 +v 119.039558 187.701431 0.000000 +v 118.745155 187.538727 -0.600000 +v 141.849701 187.830139 0.000000 +v 119.350304 187.830139 0.000000 +v 119.039558 187.701431 -0.600000 +v 141.526474 187.923279 0.000000 +v 119.673523 187.923279 0.000000 +v 119.350304 187.830139 -0.600000 +v 141.194870 187.979645 0.000000 +v 120.005127 187.979645 0.000000 +v 119.673523 187.923279 -0.600000 +v 140.858826 187.998520 0.000000 +v 120.341164 187.998520 0.000000 +v 120.005127 187.979645 -0.600000 +v 120.341164 187.998520 -0.600000 +v 140.858826 187.998520 -0.600000 +v 141.194870 187.979645 -0.600000 +v 141.526474 187.923279 -0.600000 +v 141.849701 187.830139 -0.600000 +v 142.160446 187.701431 -0.600000 +v 142.454849 187.538727 -0.600000 +v 142.729187 187.344116 -0.600000 +v 142.980148 187.119843 -0.600000 +v 147.514221 182.585785 -0.600000 +v 147.710892 182.413300 -0.600000 +v 147.928421 182.267960 -0.600000 +v 148.163055 182.152237 -0.600000 +v 148.410797 182.068146 -0.600000 +v 148.667389 182.017105 -0.600000 +v 55.436195 -8.087358 0.000000 +v 181.982834 -7.393232 0.000000 +v 57.954765 -7.065864 0.000000 +v 179.211777 -6.931783 0.000000 +v 174.789474 -6.931783 0.000000 +v 179.342834 -6.916994 0.000000 +v 179.467133 -6.873447 0.000000 +v 179.578568 -6.803397 0.000000 +v 179.671616 -6.710354 0.000000 +v 179.741653 -6.598929 0.000000 +v 179.785217 -6.474618 0.000000 +v 181.931702 -7.649953 0.000000 +v 181.982834 -7.393236 -0.600000 +v 181.847534 -7.897660 0.000000 +v 181.931702 -7.649961 -0.600000 +v 181.731812 -8.132153 0.000000 +v 181.847534 -7.897669 -0.600000 +v 55.695721 -8.322548 0.000000 +v 181.586548 -8.349481 0.000000 +v 181.731812 -8.132162 -0.600000 +v 55.976562 -8.530794 0.000000 +v 181.414200 -8.545978 0.000000 +v 181.586548 -8.349491 -0.600000 +v 56.276600 -8.710651 0.000000 +v 181.217712 -8.718324 0.000000 +v 181.414200 -8.545987 -0.600000 +v 56.593052 -8.860345 0.000000 +v 181.000381 -8.863596 0.000000 +v 181.217697 -8.718331 -0.600000 +v 56.922668 -8.978261 0.000000 +v 180.765884 -8.979314 0.000000 +v 181.000366 -8.863602 -0.600000 +v 57.262009 -9.063243 0.000000 +v 180.518173 -9.063482 0.000000 +v 180.765869 -8.979318 -0.600000 +v 57.607841 -9.114575 0.000000 +v 180.261459 -9.114618 0.000000 +v 180.518173 -9.063484 -0.600000 +v 57.957661 -9.131783 0.000000 +v 180.000000 -9.131783 0.000000 +v 180.261444 -9.114618 -0.600000 +v 180.000000 -9.131783 -0.600000 +v 57.957661 -9.131783 -0.600000 +v 57.607841 -9.114575 -0.600000 +v 57.262009 -9.063243 -0.600000 +v 56.922668 -8.978261 -0.600000 +v 56.593052 -8.860345 -0.600000 +v 56.276600 -8.710651 -0.600000 +v 55.976562 -8.530794 -0.600000 +v 55.695721 -8.322548 -0.600000 +v 56.531078 -5.102371 0.000000 +v 50.393261 -3.044425 0.000000 +v 55.436195 -8.087358 -0.600000 +v 56.476082 -5.331511 0.000000 +v 56.457661 -5.565891 0.000000 +v 56.476318 -5.801738 0.000000 +v 56.531746 -6.031444 0.000000 +v 56.622189 -6.248860 0.000000 +v 56.744408 -6.447986 0.000000 +v 56.897247 -6.626748 0.000000 +v 57.076130 -6.779497 0.000000 +v 57.276272 -6.902172 0.000000 +v 57.492882 -6.992045 0.000000 +v 57.720848 -7.047056 0.000000 +v 50.393261 -3.044425 -0.600000 +v 57.726280 -4.083871 0.000000 +v 57.960556 -4.065919 0.000000 +v 162.322479 -3.147059 0.000000 +v 57.497280 -4.138318 0.000000 +v 57.279163 -4.228147 0.000000 +v 57.077423 -4.351354 0.000000 +v 56.897232 -4.505049 0.000000 +v 56.743641 -4.684847 0.000000 +v 56.621258 -4.884737 0.000000 +v 162.721313 -2.828995 0.000000 +v 162.597000 -2.872542 0.000000 +v 162.485580 -2.942592 0.000000 +v 162.392532 -3.035635 0.000000 +v 50.133736 -2.809235 -0.600000 +v 49.852898 -2.600989 -0.600000 +v 49.552856 -2.421132 -0.600000 +v 49.236404 -2.271438 -0.600000 +v 48.906788 -2.153522 -0.600000 +v 48.567451 -2.068540 -0.600000 +v 48.221615 -2.017208 -0.600000 +v 47.871796 -2.000000 -0.600000 +v 0.000000 -2.000000 -0.600000 +v -0.261453 -1.982835 -0.600000 +v -0.518178 -1.931700 -0.600000 +v -0.765886 -1.847531 -0.600000 +v -1.000380 -1.731813 -0.600000 +v -1.217708 -1.586541 -0.600000 +v -1.414204 -1.414195 -0.600000 +v -1.586549 -1.217698 -0.600000 +v -1.731819 -1.000370 -0.600000 +v -1.847535 -0.765877 -0.600000 +v -1.931701 -0.518171 -0.600000 +v 162.264145 -3.402431 0.000000 +v 59.439003 -5.330044 0.000000 +v 59.457661 -5.565891 0.000000 +v 59.457661 -5.565891 -0.600000 +v 59.439240 -5.800272 0.000000 +v 59.439003 -5.801738 -0.600000 +v 59.383575 -5.100339 0.000000 +v 59.384243 -5.102371 -0.600000 +v 59.439240 -5.331511 -0.600000 +v 59.293133 -4.882923 0.000000 +v 59.294064 -4.884737 -0.600000 +v 59.170914 -4.683797 0.000000 +v 59.171680 -4.684847 -0.600000 +v 59.018074 -4.505035 0.000000 +v 59.018089 -4.505049 -0.600000 +v 58.839191 -4.352286 0.000000 +v 58.639050 -4.229611 0.000000 +v 58.837898 -4.351354 -0.600000 +v 58.422440 -4.139738 0.000000 +v 58.636158 -4.228147 -0.600000 +v 58.194473 -4.084727 0.000000 +v 58.418041 -4.138318 -0.600000 +v 58.189041 -4.083871 -0.600000 +v 57.954765 -4.065919 -0.600000 +v 162.278931 -3.271371 0.000000 +v 57.720848 -4.084727 -0.600000 +v 57.492882 -4.139738 -0.600000 +v 57.276272 -4.229611 -0.600000 +v 57.076130 -4.352286 -0.600000 +v 56.744408 -4.683797 -0.600000 +v 56.897247 -4.505035 -0.600000 +v 56.622189 -4.882923 -0.600000 +v 56.531746 -5.100339 -0.600000 +v 56.476318 -5.330044 -0.600000 +v 56.457661 -5.565891 -0.600000 +v 56.531078 -6.029412 -0.600000 +v 56.476082 -5.800272 -0.600000 +v 56.621258 -6.247046 -0.600000 +v 56.743641 -6.446936 -0.600000 +v 56.897232 -6.626734 -0.600000 +v 57.077423 -6.780429 -0.600000 +v 57.279163 -6.903636 -0.600000 +v 57.497280 -6.993465 -0.600000 +v 57.726280 -7.047912 -0.600000 +v 170.925842 -6.931783 0.000000 +v 57.960556 -7.065864 -0.600000 +v 58.189041 -7.047912 0.000000 +v 162.852371 -6.931783 0.000000 +v 58.194473 -7.047056 -0.600000 +v 58.418041 -6.993465 0.000000 +v 162.721298 -6.916993 0.000000 +v 58.636158 -6.903636 0.000000 +v 58.422440 -6.992045 -0.600000 +v 162.485565 -6.803391 0.000000 +v 58.837898 -6.780429 0.000000 +v 58.639050 -6.902172 -0.600000 +v 162.597000 -6.873444 0.000000 +v 162.392532 -6.710347 0.000000 +v 59.018089 -6.626734 0.000000 +v 58.839191 -6.779497 -0.600000 +v 162.278931 -6.474614 0.000000 +v 59.171680 -6.446936 0.000000 +v 59.170914 -6.447986 -0.600000 +v 162.322479 -6.598922 0.000000 +v 59.018074 -6.626748 -0.600000 +v 162.264145 -6.343558 0.000000 +v 59.294064 -6.247046 0.000000 +v 59.293133 -6.248860 -0.600000 +v 59.384243 -6.029412 0.000000 +v 59.383575 -6.031444 -0.600000 +v 179.211777 -2.814206 -0.600000 +v 179.342834 -2.828995 -0.600000 +v 167.351654 -2.819277 0.000000 +v 171.047104 -2.828715 0.000000 +v 171.138290 -2.814206 -0.600000 +v 167.499771 -2.858983 0.000000 +v 170.965240 -2.870517 0.000000 +v 171.047104 -2.828715 -0.600000 +v 167.426849 -2.834250 0.000000 +v 167.568832 -2.893058 0.000000 +v 170.900497 -2.935263 0.000000 +v 170.965240 -2.870517 -0.600000 +v 167.690613 -2.986493 0.000000 +v 170.858688 -3.017124 0.000000 +v 170.900497 -2.935263 -0.600000 +v 167.632584 -2.935639 0.000000 +v 170.844177 -3.108319 0.000000 +v 170.858688 -3.017124 -0.600000 +v 171.133820 -6.429701 0.000000 +v 170.854202 -3.184409 0.000000 +v 170.844177 -3.108319 -0.600000 +v 170.883606 -3.255397 0.000000 +v 170.854202 -3.184409 -0.600000 +v 170.930328 -3.316288 0.000000 +v 170.883606 -3.255397 -0.600000 +v 171.219955 -6.637671 0.000000 +v 174.373535 -6.759496 0.000000 +v 170.930328 -3.316288 -0.600000 +v 171.209946 -6.561580 0.000000 +v 171.180542 -6.490592 0.000000 +v 171.163651 -6.810726 0.000000 +v 174.431549 -6.810350 0.000000 +v 174.373535 -6.759496 -0.600000 +v 171.205460 -6.728865 0.000000 +v 174.495316 -6.852931 0.000000 +v 174.431549 -6.810350 -0.600000 +v 171.098907 -6.875472 0.000000 +v 174.564362 -6.887006 0.000000 +v 174.495316 -6.852931 -0.600000 +v 171.017044 -6.917274 0.000000 +v 174.637283 -6.911739 0.000000 +v 174.564362 -6.887006 -0.600000 +v 174.712479 -6.926712 0.000000 +v 174.637283 -6.911739 -0.600000 +v 174.712479 -6.926712 -0.600000 +v 174.789474 -6.931783 -0.600000 +v 179.211777 -6.931783 -0.600000 +v 179.342834 -6.916993 -0.600000 +v 179.467148 -6.873444 -0.600000 +v 179.578568 -6.803391 -0.600000 +v 179.671616 -6.710347 -0.600000 +v 179.741669 -6.598922 -0.600000 +v 179.785217 -6.474614 -0.600000 +v 179.800003 -6.343558 -0.600000 +v 179.800003 -3.402431 -0.600000 +v 179.785217 -3.271371 -0.600000 +v 179.578568 -2.942592 -0.600000 +v 179.467133 -2.872542 -0.600000 +v 167.274673 -2.814206 -0.600000 +v 167.351654 -2.819277 -0.600000 +v 162.852371 -2.814206 -0.600000 +v 162.721298 -2.828996 -0.600000 +v 162.597000 -2.872545 -0.600000 +v 162.485565 -2.942598 -0.600000 +v 162.392532 -3.035642 -0.600000 +v 162.322479 -3.147067 -0.600000 +v 162.278931 -3.271375 -0.600000 +v 162.264145 -3.402431 -0.600000 +v 162.264145 -6.343558 -0.600000 +v 162.278931 -6.474618 -0.600000 +v 162.322479 -6.598929 -0.600000 +v 162.392532 -6.710354 -0.600000 +v 162.485580 -6.803397 -0.600000 +v 162.597000 -6.873447 -0.600000 +v 162.721313 -6.916994 -0.600000 +v 162.852371 -6.931783 -0.600000 +v 170.925842 -6.931783 -0.600000 +v 171.017044 -6.917274 -0.600000 +v 171.098907 -6.875472 -0.600000 +v 171.163651 -6.810726 -0.600000 +v 171.205460 -6.728865 -0.600000 +v 171.219955 -6.637671 -0.600000 +v 171.209946 -6.561580 -0.600000 +v 171.180542 -6.490592 -0.600000 +v 171.133820 -6.429701 -0.600000 +v 167.690613 -2.986493 -0.600000 +v 167.632584 -2.935639 -0.600000 +v 167.568832 -2.893058 -0.600000 +v 167.499771 -2.858983 -0.600000 +v 167.426849 -2.834250 -0.600000 +vn 0.9978 0.0656 -0.0000 +vn -0.0000 -0.0000 1.0000 +vn 0.9979 0.0654 -0.0000 +vn 1.0000 -0.0000 -0.0000 +vn 0.9807 0.1954 -0.0000 +vn 0.9807 0.1953 -0.0000 +vn 0.9469 0.3217 -0.0000 +vn 0.9468 0.3218 -0.0000 +vn 0.8968 0.4425 -0.0000 +vn 0.8967 0.4426 -0.0000 +vn 0.8314 0.5557 -0.0000 +vn 0.7518 0.6594 -0.0000 +vn 0.6594 0.7518 -0.0000 +vn 0.5557 0.8314 -0.0000 +vn 0.4425 0.8968 -0.0000 +vn 0.3217 0.9468 -0.0000 +vn 0.3218 0.9468 -0.0000 +vn 0.1953 0.9807 -0.0000 +vn 0.1954 0.9807 -0.0000 +vn 0.0655 0.9979 -0.0000 +vn -0.0000 1.0000 -0.0000 +vn -0.0000 -0.0000 -1.0000 +vn -1.0000 -0.0000 -0.0000 +vn -0.9979 -0.0655 -0.0000 +vn -0.9979 0.0655 -0.0000 +vn -0.9807 0.1953 -0.0000 +vn -0.9468 0.3217 -0.0000 +vn -0.9807 0.1954 -0.0000 +vn -0.8968 0.4425 -0.0000 +vn -0.8314 0.5557 -0.0000 +vn -0.7518 0.6594 -0.0000 +vn -0.6594 0.7518 -0.0000 +vn -0.5557 0.8314 -0.0000 +vn -0.4425 0.8967 -0.0000 +vn -0.3217 0.9468 -0.0000 +vn -0.1953 0.9807 -0.0000 +vn -0.0655 0.9979 -0.0000 +vn -0.0654 0.9979 -0.0000 +vn -0.1951 0.9808 -0.0000 +vn -0.3214 0.9469 -0.0000 +vn -0.4423 0.8969 -0.0000 +vn -0.5556 0.8315 -0.0000 +vn -0.6593 0.7518 -0.0000 +vn -0.7071 0.7071 -0.0000 +vn -0.6663 0.7457 -0.0000 +vn -0.5786 0.8156 -0.0000 +vn -0.6664 0.7456 -0.0000 +vn -0.4837 0.8752 -0.0000 +vn -0.3827 0.9239 -0.0000 +vn -0.2769 0.9609 -0.0000 +vn -0.1676 0.9859 -0.0000 +vn -0.0561 0.9984 -0.0000 +vn 0.0561 0.9984 -0.0000 +vn 0.1676 0.9859 -0.0000 +vn 0.2769 0.9609 -0.0000 +vn 0.3827 0.9239 -0.0000 +vn 0.4837 0.8752 -0.0000 +vn 0.5786 0.8156 -0.0000 +vn 0.6663 0.7457 -0.0000 +vn 0.7071 0.7071 -0.0000 +vn 0.6715 0.7410 -0.0000 +vn 0.5956 0.8033 -0.0000 +vn 0.5142 0.8577 -0.0000 +vn 0.4276 0.9040 -0.0000 +vn 0.3368 0.9416 -0.0000 +vn 0.2429 0.9700 -0.0000 +vn 0.1468 0.9892 -0.0000 +vn 0.0491 0.9988 -0.0000 +vn -0.0491 0.9988 -0.0000 +vn -0.1468 0.9892 -0.0000 +vn -0.2429 0.9700 -0.0000 +vn -0.3369 0.9416 -0.0000 +vn -0.4276 0.9040 -0.0000 +vn -0.3368 0.9416 -0.0000 +vn -0.5141 0.8577 -0.0000 +vn -0.5956 0.8033 -0.0000 +vn -0.5142 0.8577 -0.0000 +vn -0.6715 0.7410 -0.0000 +vn 0.3826 0.9239 -0.0000 +vn 0.6664 0.7456 -0.0000 +vn 0.5556 0.8315 -0.0000 +vn 0.6593 0.7518 -0.0000 +vn 0.4423 0.8969 -0.0000 +vn 0.5555 0.8315 -0.0000 +vn 0.3214 0.9469 -0.0000 +vn 0.4424 0.8968 -0.0000 +vn 0.1951 0.9808 -0.0000 +vn 0.0654 0.9979 -0.0000 +vn 0.9979 -0.0654 -0.0000 +vn 0.9807 -0.1953 -0.0000 +vn 0.9978 -0.0656 -0.0000 +vn 0.9468 -0.3218 -0.0000 +vn 0.9807 -0.1954 -0.0000 +vn 0.8967 -0.4426 -0.0000 +vn 0.9469 -0.3217 -0.0000 +vn 0.8314 -0.5557 -0.0000 +vn 0.8968 -0.4425 -0.0000 +vn 0.7517 -0.6595 -0.0000 +vn 0.6594 -0.7518 -0.0000 +vn 0.7518 -0.6593 -0.0000 +vn 0.5557 -0.8314 -0.0000 +vn 0.4425 -0.8968 -0.0000 +vn 0.3218 -0.9468 -0.0000 +vn 0.1954 -0.9807 -0.0000 +vn 0.3217 -0.9468 -0.0000 +vn 0.0655 -0.9979 -0.0000 +vn 0.1953 -0.9807 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn -0.0491 -0.9988 -0.0000 +vn -0.1468 -0.9892 -0.0000 +vn -0.2429 -0.9700 -0.0000 +vn -0.3368 -0.9416 -0.0000 +vn -0.4276 -0.9040 -0.0000 +vn -0.5141 -0.8577 -0.0000 +vn -0.5956 -0.8033 -0.0000 +vn -0.6715 -0.7410 -0.0000 +vn -0.7071 -0.7071 -0.0000 +vn -0.0655 -0.9979 -0.0000 +vn -0.1953 -0.9807 -0.0000 +vn -0.3217 -0.9468 -0.0000 +vn -0.4425 -0.8968 -0.0000 +vn -0.5557 -0.8314 -0.0000 +vn -0.6594 -0.7518 -0.0000 +vn -0.7518 -0.6594 -0.0000 +vn -0.8314 -0.5557 -0.0000 +vn -0.8968 -0.4425 -0.0000 +vn -0.9468 -0.3217 -0.0000 +vn -0.9807 -0.1953 -0.0000 +vn -0.9969 -0.0788 -0.0000 +vn -0.9969 0.0784 0.0002 +vn -0.9969 0.0788 -0.0000 +vn -0.9721 -0.2345 -0.0003 +vn -0.9969 -0.0784 -0.0002 +vn -0.9724 -0.2334 0.0002 +vn -0.9233 -0.3841 -0.0003 +vn -0.9238 -0.3828 0.0003 +vn -0.8523 -0.5231 -0.0002 +vn -0.8528 -0.5222 0.0003 +vn -0.7601 -0.6499 -0.0000 +vn -0.7604 -0.6495 0.0002 +vn -0.6494 -0.7605 -0.0000 +vn -0.5226 -0.8526 -0.0002 +vn -0.6490 -0.7608 0.0002 +vn -0.3832 -0.9237 -0.0004 +vn -0.5212 -0.8534 0.0004 +vn -0.2346 -0.9721 -0.0006 +vn -0.3808 -0.9247 0.0006 +vn -0.0801 -0.9968 -0.0007 +vn -0.2313 -0.9729 0.0007 +vn 0.0764 -0.9971 -0.0007 +vn -0.0764 -0.9971 0.0007 +vn 0.2313 -0.9729 -0.0007 +vn 0.0801 -0.9968 0.0007 +vn 0.3808 -0.9246 -0.0006 +vn 0.2346 -0.9721 0.0006 +vn 0.5212 -0.8534 -0.0004 +vn 0.3832 -0.9237 0.0004 +vn 0.6489 -0.7608 -0.0002 +vn 0.5226 -0.8526 0.0002 +vn 0.7603 -0.6495 -0.0002 +vn 0.7601 -0.6498 -0.0000 +vn 0.6494 -0.7605 -0.0000 +vn 0.8528 -0.5222 -0.0003 +vn 0.8523 -0.5231 0.0002 +vn 0.9238 -0.3828 -0.0003 +vn 0.9233 -0.3841 0.0003 +vn 0.9724 -0.2334 -0.0002 +vn 0.9721 -0.2345 0.0003 +vn 0.9969 -0.0784 0.0002 +vn 0.9969 0.0788 -0.0000 +vn 0.9969 -0.0788 -0.0000 +vn 0.9721 0.2345 -0.0003 +vn 0.9724 0.2334 0.0002 +vn 0.9969 0.0784 -0.0002 +vn 0.9233 0.3841 -0.0003 +vn 0.9238 0.3828 0.0003 +vn 0.8523 0.5231 -0.0002 +vn 0.8528 0.5222 0.0003 +vn 0.7601 0.6498 -0.0000 +vn 0.7603 0.6495 0.0002 +vn 0.6494 0.7605 -0.0000 +vn 0.5226 0.8526 -0.0002 +vn 0.6489 0.7608 0.0002 +vn 0.3832 0.9237 -0.0004 +vn 0.5212 0.8534 0.0004 +vn 0.2346 0.9721 -0.0006 +vn 0.3808 0.9246 0.0006 +vn 0.0801 0.9968 -0.0007 +vn 0.2313 0.9729 0.0007 +vn -0.0764 0.9971 -0.0007 +vn 0.0764 0.9971 0.0007 +vn -0.2313 0.9729 -0.0007 +vn -0.0801 0.9968 0.0007 +vn -0.3808 0.9247 -0.0006 +vn -0.2346 0.9721 0.0006 +vn -0.5212 0.8534 -0.0004 +vn -0.3832 0.9237 0.0004 +vn -0.6490 0.7608 -0.0002 +vn -0.5226 0.8526 0.0002 +vn -0.7604 0.6495 -0.0002 +vn -0.7601 0.6499 -0.0000 +vn -0.6494 0.7605 -0.0000 +vn -0.8528 0.5222 -0.0003 +vn -0.8523 0.5231 0.0002 +vn -0.9238 0.3828 -0.0003 +vn -0.9233 0.3841 0.0003 +vn -0.9724 0.2334 -0.0002 +vn -0.9721 0.2345 0.0003 +vn -0.1121 -0.9937 -0.0000 +vn 0.1572 -0.9876 -0.0000 +vn 0.4548 -0.8906 -0.0000 +vn 0.1571 -0.9876 -0.0000 +vn 0.7071 -0.7071 -0.0000 +vn 0.8906 -0.4548 -0.0000 +vn 0.7072 -0.7070 -0.0000 +vn 0.9876 -0.1570 -0.0000 +vn 0.9914 0.1306 -0.0000 +vn 0.9875 -0.1575 -0.0000 +vn 0.9240 0.3823 -0.0000 +vn 0.9914 0.1309 -0.0000 +vn 0.7934 0.6087 -0.0000 +vn 0.9238 0.3829 -0.0000 +vn 0.7932 0.6089 -0.0000 +vn 0.6590 0.7521 -0.0000 +vn 0.5552 0.8317 -0.0000 +vn 0.6592 0.7520 -0.0000 +vn 0.4426 0.8967 -0.0000 +vn 0.5554 0.8316 -0.0000 +vn 0.3211 0.9470 -0.0000 +vn 0.3213 0.9470 -0.0000 +vn 0.0657 0.9978 -0.0000 +vn -0.1121 0.9937 -0.0000 +vn -0.3306 0.9438 -0.0000 +vn -0.5321 0.8467 -0.0000 +vn -0.3307 0.9437 -0.0000 +vn -0.5323 0.8466 -0.0000 +vn -0.8466 0.5322 -0.0000 +vn -0.7072 0.7070 -0.0000 +vn -0.9438 0.3306 -0.0000 +vn -0.8465 0.5324 -0.0000 +vn -0.9937 0.1120 -0.0000 +vn -0.9937 0.1122 -0.0000 +vn -0.9937 -0.1122 -0.0000 +vn -0.9438 -0.3306 -0.0000 +vn -0.9937 -0.1120 -0.0000 +vn -0.8465 -0.5324 -0.0000 +vn -0.9438 -0.3305 -0.0000 +vn -0.7072 -0.7070 -0.0000 +vn -0.8466 -0.5322 -0.0000 +vn -0.5323 -0.8466 -0.0000 +vn -0.3307 -0.9437 -0.0000 +vn -0.5321 -0.8467 -0.0000 +vn -0.3306 -0.9438 -0.0000 +vn -0.0658 -0.9978 -0.0000 +vn -0.0657 -0.9978 -0.0000 +vn 0.1121 -0.9937 -0.0000 +vn 0.3306 -0.9438 -0.0000 +vn 0.5323 -0.8466 -0.0000 +vn 0.7073 -0.7070 -0.0000 +vn 0.5321 -0.8467 -0.0000 +vn 0.8466 -0.5322 -0.0000 +vn 0.9437 -0.3307 -0.0000 +vn 0.8466 -0.5323 -0.0000 +vn 0.9937 -0.1120 -0.0000 +vn 0.9438 -0.3306 -0.0000 +vn 0.9937 -0.1122 -0.0000 +vn 0.9937 0.1122 -0.0000 +vn 0.9438 0.3306 -0.0000 +vn 0.9937 0.1120 -0.0000 +vn 0.8466 0.5323 -0.0000 +vn 0.9437 0.3307 -0.0000 +vn 0.8466 0.5322 -0.0000 +vn 0.5321 0.8467 -0.0000 +vn 0.7073 0.7070 -0.0000 +vn 0.3306 0.9438 -0.0000 +vn 0.5323 0.8466 -0.0000 +vn 0.1121 0.9937 -0.0000 +vn -0.1572 0.9876 -0.0000 +vn -0.4547 0.8907 -0.0000 +vn -0.1571 0.9876 -0.0000 +vn -0.7070 0.7073 -0.0000 +vn -0.4549 0.8905 -0.0000 +vn -0.8906 0.4548 -0.0000 +vn -0.9876 0.1570 -0.0000 +vn -0.9914 -0.1306 -0.0000 +vn -0.9239 -0.3826 -0.0000 +vn -0.9915 -0.1302 -0.0000 +vn -0.7934 -0.6087 -0.0000 +vn -0.9238 -0.3829 -0.0000 +vn -0.6590 -0.7521 -0.0000 +vn -0.5555 -0.8315 -0.0000 +vn -0.4424 -0.8968 -0.0000 +vn -0.5554 -0.8316 -0.0000 +vn -0.3212 -0.9470 -0.0000 +vn -0.4426 -0.8967 -0.0000 +vn -0.3211 -0.9470 -0.0000 +vn 0.0001 -0.0000 -1.0000 +vt 0.000000 0.000000 +vt 0.000000 0.959425 +vt 1.000000 0.959425 +vt 0.999907 0.960752 +vt 0.000000 0.046324 +vt 0.000093 0.044997 +vt 0.988044 0.014144 +vt 1.000000 0.010146 +vt 0.281809 0.033129 +vt 0.283336 0.032073 +vt 0.895937 0.032048 +vt 0.280179 0.034042 +vt 0.278459 0.034801 +vt 0.276667 0.035399 +vt 0.274823 0.035830 +vt 0.272944 0.036091 +vt 0.271042 0.036178 +vt 0.010870 0.036178 +vt 0.009449 0.036265 +vt 0.008053 0.036524 +vt 0.006707 0.036951 +vt 0.005433 0.037538 +vt 0.004252 0.038275 +vt 0.003184 0.039150 +vt 0.002247 0.040146 +vt 0.001458 0.041249 +vt 0.000829 0.042438 +vt 0.000371 0.043695 +vt 0.940969 0.032048 +vt 0.984847 0.032048 +vt 0.985559 0.031973 +vt 0.919971 0.032048 +vt 0.988044 0.029064 +vt 0.987963 0.029729 +vt 0.987727 0.030359 +vt 0.987346 0.030924 +vt 0.986840 0.031396 +vt 0.986235 0.031752 +vt 0.000093 0.960752 +vt 0.999629 0.962054 +vt 0.000371 0.962054 +vt 0.999171 0.963311 +vt 0.000829 0.963310 +vt 0.998542 0.964500 +vt 0.001458 0.964500 +vt 0.997753 0.965603 +vt 0.002247 0.965603 +vt 0.996816 0.966599 +vt 0.003184 0.966599 +vt 0.995748 0.967474 +vt 0.004252 0.967474 +vt 0.994567 0.968210 +vt 0.005433 0.968210 +vt 0.993293 0.968798 +vt 0.006707 0.968798 +vt 0.991947 0.969225 +vt 0.990551 0.969484 +vt 0.820263 0.969571 +vt 0.008053 0.969225 +vt 0.989130 0.969571 +vt 0.616539 0.969571 +vt 0.179737 0.969571 +vt 0.010870 0.969571 +vt 0.009449 0.969484 +vt 0.383461 0.969571 +vt 0.381329 0.969669 +vt 0.181156 0.969658 +vt 0.182550 0.969917 +vt 0.379220 0.969961 +vt 0.183896 0.970343 +vt 0.377151 0.970445 +vt 0.185172 0.970930 +vt 0.375142 0.971116 +vt 0.186354 0.971667 +vt 0.373213 0.971967 +vt 0.187423 0.972543 +vt 0.346631 0.995543 +vt 0.212064 0.995543 +vt 0.368089 0.975514 +vt 0.369671 0.974176 +vt 0.371384 0.972991 +vt 0.345267 0.996681 +vt 0.213428 0.996681 +vt 0.343776 0.997668 +vt 0.214919 0.997668 +vt 0.342176 0.998493 +vt 0.216519 0.998493 +vt 0.340488 0.999146 +vt 0.218208 0.999146 +vt 0.338731 0.999618 +vt 0.219965 0.999618 +vt 0.336929 0.999904 +vt 0.221767 0.999904 +vt 0.335102 1.000000 +vt 0.223593 1.000000 +vt 0.818845 0.969658 +vt 0.618672 0.969669 +vt 0.817450 0.969917 +vt 0.620780 0.969961 +vt 0.816104 0.970343 +vt 0.622849 0.970445 +vt 0.814829 0.970930 +vt 0.624858 0.971116 +vt 0.813646 0.971667 +vt 0.626787 0.971967 +vt 0.812577 0.972543 +vt 0.628616 0.972991 +vt 0.787936 0.995543 +vt 0.630329 0.974176 +vt 0.631911 0.975514 +vt 0.653369 0.995543 +vt 0.786572 0.996681 +vt 0.654733 0.996681 +vt 0.785081 0.997668 +vt 0.656224 0.997668 +vt 0.783481 0.998493 +vt 0.657824 0.998493 +vt 0.781792 0.999146 +vt 0.659513 0.999146 +vt 0.780035 0.999618 +vt 0.661269 0.999618 +vt 0.778233 0.999904 +vt 0.663071 0.999904 +vt 0.776407 1.000000 +vt 0.664898 1.000000 +vt 0.312153 0.005298 +vt 0.999907 0.008819 +vt 0.325841 0.010480 +vt 0.984847 0.011160 +vt 0.960812 0.011160 +vt 0.985559 0.011235 +vt 0.986234 0.011456 +vt 0.986840 0.011811 +vt 0.987346 0.012283 +vt 0.987726 0.012849 +vt 0.987963 0.013479 +vt 0.999629 0.007517 +vt 0.999171 0.006260 +vt 0.998542 0.005071 +vt 0.313564 0.004105 +vt 0.997753 0.003968 +vt 0.315090 0.003049 +vt 0.996816 0.002972 +vt 0.316721 0.002136 +vt 0.995749 0.002097 +vt 0.318441 0.001377 +vt 0.994567 0.001360 +vt 0.320232 0.000779 +vt 0.993293 0.000773 +vt 0.322076 0.000348 +vt 0.991947 0.000347 +vt 0.323956 0.000087 +vt 0.990551 0.000087 +vt 0.325857 0.000000 +vt 0.989130 0.000000 +vt 0.318104 0.020440 +vt 0.284746 0.030880 +vt 0.317805 0.019278 +vt 0.317705 0.018089 +vt 0.317806 0.016893 +vt 0.318107 0.015727 +vt 0.318599 0.014624 +vt 0.319263 0.013614 +vt 0.320094 0.012708 +vt 0.321066 0.011933 +vt 0.322154 0.011310 +vt 0.323331 0.010854 +vt 0.324570 0.010575 +vt 0.324599 0.025607 +vt 0.325873 0.025698 +vt 0.893057 0.030359 +vt 0.323355 0.025331 +vt 0.322169 0.024875 +vt 0.321073 0.024250 +vt 0.320094 0.023471 +vt 0.319259 0.022558 +vt 0.318594 0.021544 +vt 0.895225 0.031973 +vt 0.894549 0.031752 +vt 0.893943 0.031396 +vt 0.893438 0.030924 +vt 0.892740 0.029064 +vt 0.333908 0.019285 +vt 0.334009 0.018089 +vt 0.333909 0.016900 +vt 0.333606 0.020451 +vt 0.333115 0.021554 +vt 0.332451 0.022564 +vt 0.331620 0.023471 +vt 0.330648 0.024245 +vt 0.329560 0.024868 +vt 0.328383 0.025324 +vt 0.327144 0.025603 +vt 0.892820 0.029729 +vt 0.939814 0.011160 +vt 0.895937 0.011160 +vt 0.895224 0.011235 +vt 0.329544 0.011303 +vt 0.328359 0.010847 +vt 0.327114 0.010571 +vt 0.893943 0.011811 +vt 0.330641 0.011928 +vt 0.894549 0.011456 +vt 0.893438 0.012283 +vt 0.331620 0.012708 +vt 0.892820 0.013479 +vt 0.332455 0.013620 +vt 0.893057 0.012849 +vt 0.892740 0.014144 +vt 0.333120 0.014634 +vt 0.333610 0.015738 +vt 0.920389 0.032022 +vt 0.940473 0.031974 +vt 0.921194 0.031821 +vt 0.940029 0.031762 +vt 0.920798 0.031946 +vt 0.921570 0.031648 +vt 0.939677 0.031434 +vt 0.922232 0.031174 +vt 0.939449 0.031018 +vt 0.921916 0.031432 +vt 0.939371 0.030556 +vt 0.940945 0.013707 +vt 0.939425 0.030170 +vt 0.939585 0.029810 +vt 0.939839 0.029501 +vt 0.941413 0.012652 +vt 0.958552 0.012034 +vt 0.941358 0.013038 +vt 0.941199 0.013398 +vt 0.941107 0.011774 +vt 0.958867 0.011776 +vt 0.941334 0.012189 +vt 0.959214 0.011560 +vt 0.940755 0.011446 +vt 0.959589 0.011387 +vt 0.940310 0.011234 +vt 0.959985 0.011262 +vt 0.960394 0.011186 +s 0 +usemtl Material.001 +f 1/1/1 2/1/1 3/1/1 +f 4/2/2 3/3/2 2/4/2 +f 5/1/3 1/1/3 3/1/3 +f 6/1/4 5/1/4 3/1/4 +f 4/2/2 7/5/2 3/3/2 +f 8/6/2 3/3/2 7/5/2 +f 9/7/2 6/8/2 3/3/2 +f 10/9/2 11/10/2 3/3/2 +f 12/11/2 3/3/2 11/10/2 +f 13/12/2 10/9/2 3/3/2 +f 14/13/2 13/12/2 3/3/2 +f 15/14/2 14/13/2 3/3/2 +f 16/15/2 15/14/2 3/3/2 +f 17/16/2 16/15/2 3/3/2 +f 18/17/2 17/16/2 3/3/2 +f 19/18/2 18/17/2 3/3/2 +f 20/19/2 19/18/2 3/3/2 +f 21/20/2 20/19/2 3/3/2 +f 22/21/2 21/20/2 3/3/2 +f 23/22/2 22/21/2 3/3/2 +f 24/23/2 23/22/2 3/3/2 +f 25/24/2 24/23/2 3/3/2 +f 26/25/2 25/24/2 3/3/2 +f 27/26/2 26/25/2 3/3/2 +f 28/27/2 27/26/2 3/3/2 +f 29/28/2 28/27/2 3/3/2 +f 8/6/2 29/28/2 3/3/2 +f 30/29/2 31/30/2 3/3/2 +f 32/31/2 3/3/2 31/30/2 +f 33/32/2 30/29/2 3/3/2 +f 34/33/2 9/7/2 3/3/2 +f 35/34/2 34/33/2 3/3/2 +f 36/35/2 35/34/2 3/3/2 +f 37/36/2 36/35/2 3/3/2 +f 38/37/2 37/36/2 3/3/2 +f 39/38/2 38/37/2 3/3/2 +f 32/31/2 39/38/2 3/3/2 +f 12/11/2 33/32/2 3/3/2 +f 40/1/5 41/1/5 2/1/5 +f 42/39/2 2/4/2 41/40/2 +f 1/1/6 40/1/6 2/1/6 +f 42/39/2 4/2/2 2/4/2 +f 43/1/7 44/1/7 41/1/7 +f 45/41/2 41/40/2 44/42/2 +f 40/1/8 43/1/8 41/1/8 +f 45/41/2 42/39/2 41/40/2 +f 46/1/9 47/1/9 44/1/9 +f 48/43/2 44/42/2 47/44/2 +f 43/1/10 46/1/10 44/1/10 +f 48/43/2 45/41/2 44/42/2 +f 49/1/11 50/1/11 47/1/11 +f 51/45/2 47/44/2 50/46/2 +f 46/1/11 49/1/11 47/1/11 +f 51/45/2 48/43/2 47/44/2 +f 52/1/12 53/1/12 50/1/12 +f 54/47/2 50/46/2 53/48/2 +f 49/1/12 52/1/12 50/1/12 +f 54/47/2 51/45/2 50/46/2 +f 55/1/13 56/1/13 53/1/13 +f 57/49/2 53/48/2 56/50/2 +f 52/1/13 55/1/13 53/1/13 +f 57/49/2 54/47/2 53/48/2 +f 58/1/14 59/1/14 56/1/14 +f 60/51/2 56/50/2 59/52/2 +f 55/1/14 58/1/14 56/1/14 +f 60/51/2 57/49/2 56/50/2 +f 61/1/15 62/1/15 59/1/15 +f 63/53/2 59/52/2 62/54/2 +f 58/1/15 61/1/15 59/1/15 +f 63/53/2 60/51/2 59/52/2 +f 64/1/16 65/1/16 62/1/16 +f 66/55/2 62/54/2 65/56/2 +f 61/1/17 64/1/17 62/1/17 +f 66/55/2 63/53/2 62/54/2 +f 67/1/18 68/1/18 65/1/18 +f 65/56/2 68/57/2 69/58/2 +f 64/1/19 67/1/19 65/1/19 +f 70/59/2 66/55/2 65/56/2 +f 71/1/20 72/1/20 68/1/20 +f 68/57/2 72/60/2 69/58/2 +f 67/1/20 71/1/20 68/1/20 +f 65/56/2 69/58/2 73/61/2 +f 74/1/21 72/1/21 71/1/21 +f 73/61/2 70/59/2 65/56/2 +f 74/1/21 69/1/21 72/1/21 +f 74/1/22 71/1/22 67/1/22 +f 75/1/22 76/1/22 77/1/22 +f 74/1/22 67/1/22 64/1/22 +f 64/1/22 78/1/22 74/1/22 +f 64/1/22 77/1/22 78/1/22 +f 77/1/22 64/1/22 75/1/22 +f 79/1/22 80/1/22 76/1/22 +f 75/1/22 64/1/22 61/1/22 +f 75/1/22 79/1/22 76/1/22 +f 81/1/22 61/1/22 58/1/22 +f 81/1/22 75/1/22 61/1/22 +f 82/1/22 58/1/22 55/1/22 +f 82/1/22 81/1/22 58/1/22 +f 83/1/22 55/1/22 52/1/22 +f 83/1/22 82/1/22 55/1/22 +f 84/1/22 52/1/22 49/1/22 +f 84/1/22 83/1/22 52/1/22 +f 85/1/22 49/1/22 46/1/22 +f 85/1/22 84/1/22 49/1/22 +f 86/1/22 46/1/22 43/1/22 +f 86/1/22 85/1/22 46/1/22 +f 87/1/22 43/1/22 40/1/22 +f 87/1/22 86/1/22 43/1/22 +f 88/1/22 40/1/22 1/1/22 +f 88/1/22 87/1/22 40/1/22 +f 89/1/22 1/1/22 5/1/22 +f 89/1/22 88/1/22 1/1/22 +f 90/1/4 5/1/4 6/1/4 +f 89/1/22 5/1/22 91/1/22 +f 92/1/22 91/1/22 5/1/22 +f 93/1/22 5/1/22 90/1/22 +f 93/1/22 92/1/22 5/1/22 +f 94/1/23 7/1/23 4/1/23 +f 95/1/24 8/1/24 7/1/24 +f 94/1/24 95/1/24 7/1/24 +f 91/1/25 4/1/25 42/1/25 +f 91/1/23 94/1/23 4/1/23 +f 89/1/26 42/1/26 45/1/26 +f 89/1/25 91/1/25 42/1/25 +f 88/1/27 45/1/27 48/1/27 +f 89/1/28 45/1/28 88/1/28 +f 87/1/29 48/1/29 51/1/29 +f 88/1/27 48/1/27 87/1/27 +f 86/1/30 51/1/30 54/1/30 +f 87/1/29 51/1/29 86/1/29 +f 85/1/31 54/1/31 57/1/31 +f 86/1/30 54/1/30 85/1/30 +f 84/1/32 57/1/32 60/1/32 +f 85/1/31 57/1/31 84/1/31 +f 83/1/33 60/1/33 63/1/33 +f 84/1/32 60/1/32 83/1/32 +f 82/1/34 63/1/34 66/1/34 +f 83/1/33 63/1/33 82/1/33 +f 81/1/35 66/1/35 70/1/35 +f 82/1/34 66/1/34 81/1/34 +f 75/1/36 70/1/36 96/1/36 +f 81/1/35 70/1/35 75/1/35 +f 97/62/2 98/63/2 96/64/2 +f 79/1/37 96/1/37 98/1/37 +f 97/62/2 96/64/2 70/59/2 +f 70/59/2 99/65/2 97/62/2 +f 70/59/2 73/61/2 99/65/2 +f 75/1/36 96/1/36 79/1/36 +f 80/1/21 98/1/21 97/1/21 +f 79/1/37 98/1/37 80/1/37 +f 100/66/2 101/67/2 97/62/2 +f 76/1/38 97/1/38 101/1/38 +f 99/65/2 100/66/2 97/62/2 +f 80/1/21 97/1/21 76/1/21 +f 100/66/2 102/68/2 101/67/2 +f 103/1/39 101/1/39 102/1/39 +f 76/1/38 101/1/38 103/1/38 +f 104/69/2 105/70/2 102/68/2 +f 106/1/40 102/1/40 105/1/40 +f 100/66/2 104/69/2 102/68/2 +f 103/1/39 102/1/39 106/1/39 +f 107/71/2 108/72/2 105/70/2 +f 109/1/41 105/1/41 108/1/41 +f 104/69/2 107/71/2 105/70/2 +f 106/1/40 105/1/40 109/1/40 +f 110/73/2 111/74/2 108/72/2 +f 112/1/42 108/1/42 111/1/42 +f 107/71/2 110/73/2 108/72/2 +f 109/1/41 108/1/41 112/1/41 +f 113/75/2 114/76/2 111/74/2 +f 115/1/43 111/1/43 114/1/43 +f 110/73/2 113/75/2 111/74/2 +f 112/1/42 111/1/42 115/1/42 +f 116/77/2 117/78/2 114/76/2 +f 118/1/44 114/1/44 117/1/44 +f 119/79/2 116/77/2 114/76/2 +f 120/80/2 119/79/2 114/76/2 +f 121/81/2 120/80/2 114/76/2 +f 113/75/2 121/81/2 114/76/2 +f 115/1/43 114/1/43 118/1/43 +f 122/82/2 123/83/2 117/78/2 +f 124/1/45 117/1/45 123/1/45 +f 116/77/2 122/82/2 117/78/2 +f 118/1/44 117/1/44 124/1/44 +f 125/84/2 126/85/2 123/83/2 +f 127/1/46 123/1/46 126/1/46 +f 122/82/2 125/84/2 123/83/2 +f 124/1/47 123/1/47 127/1/47 +f 128/86/2 129/87/2 126/85/2 +f 130/1/48 126/1/48 129/1/48 +f 125/84/2 128/86/2 126/85/2 +f 127/1/46 126/1/46 130/1/46 +f 131/88/2 132/89/2 129/87/2 +f 133/1/49 129/1/49 132/1/49 +f 128/86/2 131/88/2 129/87/2 +f 130/1/48 129/1/48 133/1/48 +f 134/90/2 135/91/2 132/89/2 +f 136/1/50 132/1/50 135/1/50 +f 131/88/2 134/90/2 132/89/2 +f 133/1/49 132/1/49 136/1/49 +f 137/92/2 138/93/2 135/91/2 +f 139/1/51 135/1/51 138/1/51 +f 134/90/2 137/92/2 135/91/2 +f 136/1/50 135/1/50 139/1/50 +f 140/94/2 141/95/2 138/93/2 +f 142/1/52 138/1/52 141/1/52 +f 137/92/2 140/94/2 138/93/2 +f 139/1/51 138/1/51 142/1/51 +f 143/1/21 141/1/21 140/1/21 +f 142/1/52 141/1/52 143/1/52 +f 144/1/53 140/1/53 137/1/53 +f 143/1/21 140/1/21 144/1/21 +f 145/1/54 137/1/54 134/1/54 +f 144/1/53 137/1/53 145/1/53 +f 146/1/55 134/1/55 131/1/55 +f 145/1/54 134/1/54 146/1/54 +f 147/1/56 131/1/56 128/1/56 +f 146/1/55 131/1/55 147/1/55 +f 148/1/57 128/1/57 125/1/57 +f 147/1/56 128/1/56 148/1/56 +f 149/1/58 125/1/58 122/1/58 +f 148/1/57 125/1/57 149/1/57 +f 150/1/59 122/1/59 116/1/59 +f 149/1/58 122/1/58 150/1/58 +f 151/1/60 116/1/60 119/1/60 +f 150/1/59 116/1/59 151/1/59 +f 152/1/61 119/1/61 120/1/61 +f 151/1/60 119/1/60 152/1/60 +f 153/1/62 120/1/62 121/1/62 +f 152/1/61 120/1/61 153/1/61 +f 154/1/63 121/1/63 113/1/63 +f 153/1/62 121/1/62 154/1/62 +f 155/1/64 113/1/64 110/1/64 +f 154/1/63 113/1/63 155/1/63 +f 156/1/65 110/1/65 107/1/65 +f 155/1/64 110/1/64 156/1/64 +f 157/1/66 107/1/66 104/1/66 +f 156/1/65 107/1/65 157/1/65 +f 158/1/67 104/1/67 100/1/67 +f 157/1/66 104/1/66 158/1/66 +f 159/1/68 100/1/68 99/1/68 +f 158/1/67 100/1/67 159/1/67 +f 77/1/21 99/1/21 73/1/21 +f 159/1/68 99/1/68 77/1/68 +f 160/96/2 161/97/2 73/61/2 +f 78/1/69 73/1/69 161/1/69 +f 69/58/2 160/96/2 73/61/2 +f 77/1/21 73/1/21 78/1/21 +f 162/98/2 163/99/2 161/97/2 +f 164/1/70 161/1/70 163/1/70 +f 160/96/2 162/98/2 161/97/2 +f 78/1/69 161/1/69 164/1/69 +f 165/100/2 166/101/2 163/99/2 +f 167/1/71 163/1/71 166/1/71 +f 162/98/2 165/100/2 163/99/2 +f 164/1/70 163/1/70 167/1/70 +f 168/102/2 169/103/2 166/101/2 +f 170/1/72 166/1/72 169/1/72 +f 165/100/2 168/102/2 166/101/2 +f 167/1/71 166/1/71 170/1/71 +f 171/104/2 172/105/2 169/103/2 +f 173/1/73 169/1/73 172/1/73 +f 168/102/2 171/104/2 169/103/2 +f 170/1/74 169/1/74 173/1/74 +f 174/106/2 175/107/2 172/105/2 +f 176/1/75 172/1/75 175/1/75 +f 171/104/2 174/106/2 172/105/2 +f 173/1/73 172/1/73 176/1/73 +f 177/108/2 178/109/2 175/107/2 +f 179/1/76 175/1/76 178/1/76 +f 174/106/2 177/108/2 175/107/2 +f 176/1/77 175/1/77 179/1/77 +f 177/108/2 180/110/2 178/109/2 +f 181/1/78 178/1/78 180/1/78 +f 179/1/76 178/1/76 181/1/76 +f 177/108/2 182/111/2 180/110/2 +f 183/1/44 180/1/44 182/1/44 +f 181/1/78 180/1/78 183/1/78 +f 184/112/2 185/113/2 182/111/2 +f 186/1/47 182/1/47 185/1/47 +f 177/108/2 184/112/2 182/111/2 +f 183/1/44 182/1/44 186/1/44 +f 187/114/2 188/115/2 185/113/2 +f 189/1/46 185/1/46 188/1/46 +f 184/112/2 187/114/2 185/113/2 +f 186/1/45 185/1/45 189/1/45 +f 190/116/2 191/117/2 188/115/2 +f 192/1/48 188/1/48 191/1/48 +f 187/114/2 190/116/2 188/115/2 +f 189/1/46 188/1/46 192/1/46 +f 193/118/2 194/119/2 191/117/2 +f 195/1/49 191/1/49 194/1/49 +f 190/116/2 193/118/2 191/117/2 +f 192/1/48 191/1/48 195/1/48 +f 196/120/2 197/121/2 194/119/2 +f 198/1/50 194/1/50 197/1/50 +f 193/118/2 196/120/2 194/119/2 +f 195/1/49 194/1/49 198/1/49 +f 199/122/2 200/123/2 197/121/2 +f 201/1/51 197/1/51 200/1/51 +f 196/120/2 199/122/2 197/121/2 +f 198/1/50 197/1/50 201/1/50 +f 202/124/2 203/125/2 200/123/2 +f 204/1/52 200/1/52 203/1/52 +f 199/122/2 202/124/2 200/123/2 +f 201/1/51 200/1/51 204/1/51 +f 205/1/21 203/1/21 202/1/21 +f 204/1/52 203/1/52 205/1/52 +f 206/1/53 202/1/53 199/1/53 +f 205/1/21 202/1/21 206/1/21 +f 207/1/54 199/1/54 196/1/54 +f 206/1/53 199/1/53 207/1/53 +f 208/1/55 196/1/55 193/1/55 +f 207/1/54 196/1/54 208/1/54 +f 209/1/56 193/1/56 190/1/56 +f 208/1/55 193/1/55 209/1/55 +f 210/1/57 190/1/57 187/1/57 +f 209/1/79 190/1/79 210/1/79 +f 211/1/58 187/1/58 184/1/58 +f 210/1/57 187/1/57 211/1/57 +f 212/1/59 184/1/59 177/1/59 +f 211/1/58 184/1/58 212/1/58 +f 213/1/60 177/1/60 174/1/60 +f 212/1/80 177/1/80 213/1/80 +f 214/1/13 174/1/13 171/1/13 +f 213/1/60 174/1/60 214/1/60 +f 215/1/81 171/1/81 168/1/81 +f 214/1/82 171/1/82 215/1/82 +f 216/1/83 168/1/83 165/1/83 +f 215/1/84 168/1/84 216/1/84 +f 217/1/85 165/1/85 162/1/85 +f 216/1/86 165/1/86 217/1/86 +f 218/1/87 162/1/87 160/1/87 +f 217/1/85 162/1/85 218/1/85 +f 219/1/88 160/1/88 69/1/88 +f 218/1/87 160/1/87 219/1/87 +f 219/1/88 69/1/88 74/1/88 +f 220/126/2 221/127/2 6/8/2 +f 90/1/89 6/1/89 221/1/89 +f 222/128/2 220/126/2 6/8/2 +f 6/8/2 223/129/2 224/130/2 +f 225/131/2 223/129/2 6/8/2 +f 226/132/2 225/131/2 6/8/2 +f 227/133/2 226/132/2 6/8/2 +f 228/134/2 227/133/2 6/8/2 +f 229/135/2 228/134/2 6/8/2 +f 230/136/2 229/135/2 6/8/2 +f 9/7/2 230/136/2 6/8/2 +f 220/126/2 231/137/2 221/127/2 +f 232/1/90 221/1/90 231/1/90 +f 90/1/91 221/1/91 232/1/91 +f 220/126/2 233/138/2 231/137/2 +f 234/1/92 231/1/92 233/1/92 +f 232/1/93 231/1/93 234/1/93 +f 220/126/2 235/139/2 233/138/2 +f 236/1/94 233/1/94 235/1/94 +f 234/1/95 233/1/95 236/1/95 +f 237/140/2 238/141/2 235/139/2 +f 239/1/96 235/1/96 238/1/96 +f 220/126/2 237/140/2 235/139/2 +f 236/1/97 235/1/97 239/1/97 +f 240/142/2 241/143/2 238/141/2 +f 242/1/98 238/1/98 241/1/98 +f 237/140/2 240/142/2 238/141/2 +f 239/1/96 238/1/96 242/1/96 +f 243/144/2 244/145/2 241/143/2 +f 245/1/99 241/1/99 244/1/99 +f 240/142/2 243/144/2 241/143/2 +f 242/1/100 241/1/100 245/1/100 +f 246/146/2 247/147/2 244/145/2 +f 248/1/101 244/1/101 247/1/101 +f 243/144/2 246/146/2 244/145/2 +f 245/1/99 244/1/99 248/1/99 +f 249/148/2 250/149/2 247/147/2 +f 251/1/102 247/1/102 250/1/102 +f 246/146/2 249/148/2 247/147/2 +f 248/1/101 247/1/101 251/1/101 +f 252/150/2 253/151/2 250/149/2 +f 254/1/103 250/1/103 253/1/103 +f 249/148/2 252/150/2 250/149/2 +f 251/1/102 250/1/102 254/1/102 +f 255/152/2 256/153/2 253/151/2 +f 257/1/104 253/1/104 256/1/104 +f 252/150/2 255/152/2 253/151/2 +f 254/1/105 253/1/105 257/1/105 +f 258/154/2 259/155/2 256/153/2 +f 260/1/106 256/1/106 259/1/106 +f 255/152/2 258/154/2 256/153/2 +f 257/1/107 256/1/107 260/1/107 +f 261/1/108 259/1/108 258/1/108 +f 260/1/106 259/1/106 261/1/106 +f 262/1/109 258/1/109 255/1/109 +f 261/1/108 258/1/108 262/1/108 +f 263/1/110 255/1/110 252/1/110 +f 262/1/109 255/1/109 263/1/109 +f 264/1/111 252/1/111 249/1/111 +f 263/1/110 252/1/110 264/1/110 +f 265/1/112 249/1/112 246/1/112 +f 264/1/111 249/1/111 265/1/111 +f 266/1/113 246/1/113 243/1/113 +f 265/1/112 246/1/112 266/1/112 +f 267/1/114 243/1/114 240/1/114 +f 266/1/113 243/1/113 267/1/113 +f 268/1/115 240/1/115 237/1/115 +f 267/1/114 240/1/114 268/1/114 +f 269/1/116 237/1/116 220/1/116 +f 268/1/115 237/1/115 269/1/115 +f 270/156/2 271/157/2 220/126/2 +f 272/1/117 220/1/117 271/1/117 +f 273/158/2 270/156/2 220/126/2 +f 274/159/2 273/158/2 220/126/2 +f 275/160/2 274/159/2 220/126/2 +f 276/161/2 275/160/2 220/126/2 +f 277/162/2 276/161/2 220/126/2 +f 278/163/2 277/162/2 220/126/2 +f 279/164/2 278/163/2 220/126/2 +f 280/165/2 279/164/2 220/126/2 +f 281/166/2 280/165/2 220/126/2 +f 282/167/2 281/166/2 220/126/2 +f 283/168/2 282/167/2 220/126/2 +f 222/128/2 283/168/2 220/126/2 +f 269/1/116 220/1/116 272/1/116 +f 12/11/2 11/10/2 271/157/2 +f 284/1/116 271/1/116 11/1/116 +f 285/169/2 286/170/2 271/157/2 +f 287/171/2 271/157/2 286/170/2 +f 288/172/2 285/169/2 271/157/2 +f 289/173/2 288/172/2 271/157/2 +f 290/174/2 289/173/2 271/157/2 +f 291/175/2 290/174/2 271/157/2 +f 292/176/2 291/175/2 271/157/2 +f 293/177/2 292/176/2 271/157/2 +f 270/156/2 293/177/2 271/157/2 +f 294/178/2 12/11/2 271/157/2 +f 295/179/2 294/178/2 271/157/2 +f 296/180/2 295/179/2 271/157/2 +f 297/181/2 296/180/2 271/157/2 +f 287/171/2 297/181/2 271/157/2 +f 272/1/117 271/1/117 284/1/117 +f 298/1/115 11/1/115 10/1/115 +f 284/1/116 11/1/116 298/1/116 +f 299/1/114 10/1/114 13/1/114 +f 298/1/115 10/1/115 299/1/115 +f 300/1/113 13/1/113 14/1/113 +f 299/1/114 13/1/114 300/1/114 +f 301/1/112 14/1/112 15/1/112 +f 300/1/113 14/1/113 301/1/113 +f 302/1/111 15/1/111 16/1/111 +f 301/1/112 15/1/112 302/1/112 +f 303/1/110 16/1/110 17/1/110 +f 302/1/111 16/1/111 303/1/111 +f 304/1/109 17/1/109 18/1/109 +f 303/1/110 17/1/110 304/1/110 +f 305/1/108 18/1/108 19/1/108 +f 304/1/109 18/1/109 305/1/109 +f 306/1/118 19/1/118 20/1/118 +f 305/1/108 19/1/108 306/1/108 +f 307/1/119 20/1/119 21/1/119 +f 307/1/118 306/1/118 20/1/118 +f 308/1/120 21/1/120 22/1/120 +f 308/1/119 307/1/119 21/1/119 +f 309/1/121 22/1/121 23/1/121 +f 309/1/120 308/1/120 22/1/120 +f 310/1/122 23/1/122 24/1/122 +f 310/1/121 309/1/121 23/1/121 +f 311/1/123 24/1/123 25/1/123 +f 311/1/122 310/1/122 24/1/122 +f 312/1/124 25/1/124 26/1/124 +f 312/1/123 311/1/123 25/1/123 +f 313/1/125 26/1/125 27/1/125 +f 313/1/124 312/1/124 26/1/124 +f 314/1/126 27/1/126 28/1/126 +f 314/1/125 313/1/125 27/1/125 +f 315/1/127 28/1/127 29/1/127 +f 315/1/126 314/1/126 28/1/126 +f 316/1/128 29/1/128 8/1/128 +f 316/1/127 315/1/127 29/1/127 +f 95/1/128 316/1/128 8/1/128 +f 317/182/2 318/183/2 319/184/2 +f 320/1/129 319/1/129 318/1/129 +f 317/182/2 319/184/2 321/185/2 +f 322/1/130 321/1/130 319/1/130 +f 322/1/131 319/1/131 320/1/131 +f 317/182/2 323/186/2 318/183/2 +f 324/1/132 318/1/132 323/1/132 +f 325/1/133 320/1/133 318/1/133 +f 325/1/134 318/1/134 324/1/134 +f 317/182/2 326/187/2 323/186/2 +f 327/1/135 323/1/135 326/1/135 +f 324/1/136 323/1/136 327/1/136 +f 317/182/2 328/188/2 326/187/2 +f 329/1/137 326/1/137 328/1/137 +f 327/1/138 326/1/138 329/1/138 +f 317/182/2 330/189/2 328/188/2 +f 331/1/139 328/1/139 330/1/139 +f 329/1/140 328/1/140 331/1/140 +f 317/182/2 332/190/2 330/189/2 +f 331/1/141 330/1/141 332/1/141 +f 317/182/2 333/191/2 332/190/2 +f 334/1/142 332/1/142 333/1/142 +f 331/1/143 332/1/143 334/1/143 +f 317/182/2 335/192/2 333/191/2 +f 336/1/144 333/1/144 335/1/144 +f 334/1/145 333/1/145 336/1/145 +f 317/182/2 337/193/2 335/192/2 +f 338/1/146 335/1/146 337/1/146 +f 336/1/147 335/1/147 338/1/147 +f 317/182/2 286/170/2 337/193/2 +f 339/1/148 337/1/148 286/1/148 +f 338/1/149 337/1/149 339/1/149 +f 340/1/150 286/1/150 285/1/150 +f 341/194/2 287/171/2 286/170/2 +f 317/182/2 341/194/2 286/170/2 +f 339/1/151 286/1/151 340/1/151 +f 342/1/152 285/1/152 288/1/152 +f 340/1/153 285/1/153 342/1/153 +f 343/1/154 288/1/154 289/1/154 +f 342/1/155 288/1/155 343/1/155 +f 344/1/156 289/1/156 290/1/156 +f 343/1/157 289/1/157 344/1/157 +f 345/1/158 290/1/158 291/1/158 +f 344/1/159 290/1/159 345/1/159 +f 346/1/160 291/1/160 292/1/160 +f 347/1/161 291/1/161 346/1/161 +f 345/1/162 291/1/162 347/1/162 +f 348/1/163 292/1/163 293/1/163 +f 346/1/164 292/1/164 348/1/164 +f 349/1/165 293/1/165 270/1/165 +f 348/1/166 293/1/166 349/1/166 +f 350/1/167 270/1/167 273/1/167 +f 349/1/168 270/1/168 350/1/168 +f 350/1/169 273/1/169 274/1/169 +f 351/1/170 274/1/170 275/1/170 +f 350/1/171 274/1/171 351/1/171 +f 352/1/172 275/1/172 276/1/172 +f 353/1/173 275/1/173 352/1/173 +f 351/1/174 275/1/174 353/1/174 +f 354/1/175 276/1/175 277/1/175 +f 352/1/176 276/1/176 354/1/176 +f 355/1/177 277/1/177 278/1/177 +f 354/1/178 277/1/178 355/1/178 +f 356/1/179 278/1/179 279/1/179 +f 355/1/180 278/1/180 356/1/180 +f 356/1/181 279/1/181 280/1/181 +f 357/1/182 280/1/182 281/1/182 +f 356/1/183 280/1/183 357/1/183 +f 358/1/184 281/1/184 282/1/184 +f 357/1/185 281/1/185 358/1/185 +f 359/1/186 282/1/186 283/1/186 +f 358/1/187 282/1/187 359/1/187 +f 360/1/188 283/1/188 222/1/188 +f 359/1/189 283/1/189 360/1/189 +f 6/8/2 224/130/2 361/195/2 +f 362/1/190 222/1/190 363/1/190 +f 360/1/191 222/1/191 362/1/191 +f 6/8/2 361/195/2 364/196/2 +f 365/1/192 363/1/192 366/1/192 +f 362/1/193 363/1/193 365/1/193 +f 367/197/2 368/198/2 366/199/2 +f 369/1/194 366/1/194 368/1/194 +f 364/196/2 222/128/2 6/8/2 +f 364/196/2 363/200/2 222/128/2 +f 364/196/2 367/197/2 366/199/2 +f 364/196/2 366/199/2 363/200/2 +f 365/1/195 366/1/195 369/1/195 +f 370/201/2 371/202/2 368/198/2 +f 372/1/196 368/1/196 371/1/196 +f 373/203/2 370/201/2 368/198/2 +f 367/197/2 373/203/2 368/198/2 +f 369/1/197 368/1/197 372/1/197 +f 374/204/2 375/205/2 371/202/2 +f 376/1/198 371/1/198 375/1/198 +f 370/201/2 374/204/2 371/202/2 +f 372/1/199 371/1/199 376/1/199 +f 377/206/2 378/207/2 375/205/2 +f 379/1/200 375/1/200 378/1/200 +f 380/208/2 377/206/2 375/205/2 +f 374/204/2 380/208/2 375/205/2 +f 381/1/201 375/1/201 379/1/201 +f 376/1/202 375/1/202 381/1/202 +f 382/209/2 383/210/2 378/207/2 +f 384/1/203 378/1/203 383/1/203 +f 377/206/2 382/209/2 378/207/2 +f 379/1/204 378/1/204 384/1/204 +f 317/182/2 385/211/2 383/210/2 +f 386/1/205 383/1/205 385/1/205 +f 382/209/2 317/182/2 383/210/2 +f 384/1/206 383/1/206 386/1/206 +f 317/182/2 321/185/2 385/211/2 +f 322/1/207 385/1/207 321/1/207 +f 386/1/208 385/1/208 322/1/208 +f 387/1/108 31/1/108 30/1/108 +f 388/1/209 32/1/209 31/1/209 +f 388/1/209 31/1/209 387/1/209 +f 389/212/2 390/213/2 30/29/2 +f 391/1/210 30/1/210 390/1/210 +f 389/212/2 30/29/2 33/32/2 +f 387/1/108 30/1/108 391/1/108 +f 392/214/2 393/215/2 390/213/2 +f 394/1/211 390/1/211 393/1/211 +f 395/216/2 392/214/2 390/213/2 +f 389/212/2 395/216/2 390/213/2 +f 394/1/212 391/1/212 390/1/212 +f 396/217/2 397/218/2 393/215/2 +f 398/1/213 393/1/213 397/1/213 +f 392/214/2 396/217/2 393/215/2 +f 394/1/211 393/1/211 398/1/211 +f 399/219/2 400/220/2 397/218/2 +f 401/1/214 397/1/214 400/1/214 +f 402/221/2 399/219/2 397/218/2 +f 396/217/2 402/221/2 397/218/2 +f 398/1/215 397/1/215 401/1/215 +f 399/219/2 403/222/2 400/220/2 +f 404/1/216 400/1/216 403/1/216 +f 401/1/214 400/1/214 404/1/214 +f 405/223/2 406/224/2 403/222/2 +f 407/1/217 403/1/217 406/1/217 +f 399/219/2 405/223/2 403/222/2 +f 404/1/218 403/1/218 407/1/218 +f 405/223/2 408/225/2 406/224/2 +f 409/1/219 406/1/219 408/1/219 +f 407/1/220 406/1/220 409/1/220 +f 405/223/2 410/226/2 408/225/2 +f 411/1/221 408/1/221 410/1/221 +f 409/1/222 408/1/222 411/1/222 +f 412/227/2 413/228/2 410/226/2 +f 414/1/60 410/1/60 413/1/60 +f 415/229/2 412/227/2 410/226/2 +f 416/230/2 415/229/2 410/226/2 +f 405/223/2 416/230/2 410/226/2 +f 411/1/223 410/1/223 414/1/223 +f 417/231/2 418/232/2 413/228/2 +f 419/1/224 413/1/224 418/1/224 +f 420/233/2 417/231/2 413/228/2 +f 412/227/2 420/233/2 413/228/2 +f 414/1/60 413/1/60 419/1/60 +f 417/231/2 421/234/2 418/232/2 +f 422/1/225 418/1/225 421/1/225 +f 419/1/226 418/1/226 422/1/226 +f 423/235/2 424/236/2 421/234/2 +f 425/1/227 421/1/227 424/1/227 +f 417/231/2 423/235/2 421/234/2 +f 422/1/228 421/1/228 425/1/228 +f 426/237/2 427/238/2 424/236/2 +f 428/1/229 424/1/229 427/1/229 +f 423/235/2 426/237/2 424/236/2 +f 425/1/227 424/1/227 428/1/227 +f 426/237/2 429/239/2 427/238/2 +f 430/1/18 427/1/18 429/1/18 +f 428/1/230 427/1/230 430/1/230 +f 361/195/2 224/130/2 429/239/2 +f 431/1/231 429/1/231 224/1/231 +f 426/237/2 361/195/2 429/239/2 +f 430/1/18 429/1/18 431/1/18 +f 432/1/21 224/1/21 223/1/21 +f 431/1/231 224/1/231 432/1/231 +f 433/1/232 223/1/232 225/1/232 +f 432/1/21 223/1/21 433/1/21 +f 434/1/233 225/1/233 226/1/233 +f 433/1/232 225/1/232 434/1/232 +f 435/1/234 226/1/234 227/1/234 +f 434/1/235 226/1/235 435/1/235 +f 436/1/44 227/1/44 228/1/44 +f 435/1/236 227/1/236 436/1/236 +f 437/1/237 228/1/237 229/1/237 +f 436/1/238 228/1/238 437/1/238 +f 438/1/239 229/1/239 230/1/239 +f 437/1/240 229/1/240 438/1/240 +f 439/1/241 230/1/241 9/1/241 +f 438/1/239 230/1/239 439/1/239 +f 440/1/23 9/1/23 34/1/23 +f 439/1/242 9/1/242 440/1/242 +f 441/1/243 34/1/243 35/1/243 +f 440/1/23 34/1/23 441/1/23 +f 442/1/244 35/1/244 36/1/244 +f 441/1/245 35/1/245 442/1/245 +f 93/1/246 36/1/246 37/1/246 +f 442/1/247 36/1/247 93/1/247 +f 92/1/248 37/1/248 38/1/248 +f 93/1/249 37/1/249 92/1/249 +f 443/1/250 38/1/250 39/1/250 +f 92/1/117 38/1/117 443/1/117 +f 444/1/251 39/1/251 32/1/251 +f 443/1/252 39/1/252 444/1/252 +f 444/1/253 32/1/253 388/1/253 +f 445/1/108 33/1/108 12/1/108 +f 446/1/254 389/1/254 33/1/254 +f 446/1/255 33/1/255 445/1/255 +f 447/1/256 12/1/256 294/1/256 +f 445/1/108 12/1/108 447/1/108 +f 448/1/257 294/1/257 295/1/257 +f 447/1/256 294/1/256 448/1/256 +f 449/1/258 295/1/258 296/1/258 +f 448/1/257 295/1/257 449/1/257 +f 450/1/259 296/1/259 297/1/259 +f 449/1/260 296/1/260 450/1/260 +f 451/1/261 297/1/261 287/1/261 +f 450/1/213 297/1/213 451/1/213 +f 452/1/262 287/1/262 341/1/262 +f 451/1/263 287/1/263 452/1/263 +f 453/1/264 341/1/264 317/1/264 +f 452/1/265 341/1/265 453/1/265 +f 454/1/4 317/1/4 382/1/4 +f 453/1/266 317/1/266 454/1/266 +f 455/1/267 382/1/267 377/1/267 +f 454/1/4 382/1/4 455/1/4 +f 456/1/268 377/1/268 380/1/268 +f 456/1/269 455/1/269 377/1/269 +f 457/1/270 380/1/270 374/1/270 +f 456/1/271 380/1/271 457/1/271 +f 458/1/60 374/1/60 370/1/60 +f 457/1/272 374/1/272 458/1/272 +f 459/1/273 370/1/273 373/1/273 +f 458/1/274 370/1/274 459/1/274 +f 460/1/275 373/1/275 367/1/275 +f 459/1/276 373/1/276 460/1/276 +f 461/1/277 367/1/277 364/1/277 +f 460/1/275 367/1/275 461/1/275 +f 462/1/21 364/1/21 361/1/21 +f 461/1/277 364/1/277 462/1/277 +f 463/1/278 361/1/278 426/1/278 +f 462/1/21 361/1/21 463/1/21 +f 464/1/279 426/1/279 423/1/279 +f 463/1/280 426/1/280 464/1/280 +f 465/1/281 423/1/281 417/1/281 +f 464/1/282 423/1/282 465/1/282 +f 466/1/283 417/1/283 420/1/283 +f 465/1/238 417/1/238 466/1/238 +f 467/1/284 420/1/284 412/1/284 +f 466/1/283 420/1/283 467/1/283 +f 468/1/285 412/1/285 415/1/285 +f 467/1/284 412/1/284 468/1/284 +f 469/1/286 415/1/286 416/1/286 +f 468/1/287 415/1/287 469/1/287 +f 470/1/288 416/1/288 405/1/288 +f 469/1/289 416/1/289 470/1/289 +f 471/1/117 405/1/117 399/1/117 +f 470/1/288 405/1/288 471/1/288 +f 472/1/290 399/1/290 402/1/290 +f 471/1/117 399/1/117 472/1/117 +f 473/1/291 402/1/291 396/1/291 +f 472/1/290 402/1/290 473/1/290 +f 474/1/292 396/1/292 392/1/292 +f 473/1/293 396/1/293 474/1/293 +f 475/1/294 392/1/294 395/1/294 +f 474/1/295 392/1/295 475/1/295 +f 476/1/119 395/1/119 389/1/119 +f 475/1/296 395/1/296 476/1/296 +f 476/1/119 389/1/119 446/1/119 +f 305/1/22 306/1/22 307/1/22 +f 305/1/22 307/1/22 308/1/22 +f 305/1/22 308/1/22 309/1/22 +f 305/1/22 309/1/22 310/1/22 +f 305/1/22 310/1/22 311/1/22 +f 387/1/22 311/1/22 312/1/22 +f 387/1/22 305/1/22 311/1/22 +f 387/1/22 312/1/22 313/1/22 +f 387/1/22 313/1/22 314/1/22 +f 387/1/22 314/1/22 315/1/22 +f 387/1/22 315/1/22 316/1/22 +f 387/1/22 316/1/22 95/1/22 +f 387/1/22 95/1/22 94/1/22 +f 387/1/22 94/1/22 91/1/22 +f 388/1/22 387/1/22 91/1/22 +f 444/1/22 388/1/22 91/1/22 +f 443/1/22 444/1/22 91/1/22 +f 92/1/297 443/1/297 91/1/297 +f 387/1/22 304/1/22 305/1/22 +f 387/1/22 303/1/22 304/1/22 +f 387/1/22 302/1/22 303/1/22 +f 387/1/22 301/1/22 302/1/22 +f 387/1/22 300/1/22 301/1/22 +f 387/1/22 299/1/22 300/1/22 +f 299/1/22 387/1/22 391/1/22 +f 451/1/22 284/1/22 298/1/22 +f 299/1/22 391/1/22 445/1/22 +f 299/1/22 445/1/22 447/1/22 +f 450/1/22 451/1/22 298/1/22 +f 449/1/22 450/1/22 298/1/22 +f 448/1/22 449/1/22 298/1/22 +f 447/1/22 448/1/22 298/1/22 +f 447/1/22 298/1/22 299/1/22 +f 358/1/22 272/1/22 284/1/22 +f 357/1/22 358/1/22 284/1/22 +f 356/1/22 357/1/22 284/1/22 +f 355/1/22 356/1/22 284/1/22 +f 354/1/22 355/1/22 284/1/22 +f 352/1/22 354/1/22 284/1/22 +f 353/1/22 352/1/22 284/1/22 +f 351/1/22 353/1/22 284/1/22 +f 350/1/22 351/1/22 284/1/22 +f 349/1/22 350/1/22 284/1/22 +f 348/1/22 349/1/22 284/1/22 +f 346/1/22 348/1/22 284/1/22 +f 347/1/22 346/1/22 284/1/22 +f 345/1/22 347/1/22 284/1/22 +f 344/1/22 345/1/22 284/1/22 +f 343/1/22 344/1/22 284/1/22 +f 342/1/22 343/1/22 284/1/22 +f 340/1/22 342/1/22 284/1/22 +f 454/1/22 340/1/22 284/1/22 +f 453/1/22 454/1/22 284/1/22 +f 452/1/22 453/1/22 284/1/22 +f 451/1/22 452/1/22 284/1/22 +f 239/1/22 269/1/22 272/1/22 +f 236/1/22 239/1/22 272/1/22 +f 362/1/22 236/1/22 272/1/22 +f 360/1/22 362/1/22 272/1/22 +f 359/1/22 360/1/22 272/1/22 +f 358/1/22 359/1/22 272/1/22 +f 242/1/22 268/1/22 269/1/22 +f 239/1/22 242/1/22 269/1/22 +f 245/1/22 267/1/22 268/1/22 +f 242/1/22 245/1/22 268/1/22 +f 248/1/22 266/1/22 267/1/22 +f 245/1/22 248/1/22 267/1/22 +f 251/1/22 265/1/22 266/1/22 +f 248/1/22 251/1/22 266/1/22 +f 254/1/22 264/1/22 265/1/22 +f 251/1/22 254/1/22 265/1/22 +f 260/1/22 263/1/22 264/1/22 +f 257/1/22 260/1/22 264/1/22 +f 254/1/22 257/1/22 264/1/22 +f 261/1/22 262/1/22 263/1/22 +f 260/1/22 261/1/22 263/1/22 +f 362/1/22 234/1/22 236/1/22 +f 362/1/22 232/1/22 234/1/22 +f 362/1/22 90/1/22 232/1/22 +f 365/1/22 369/1/22 90/1/22 +f 462/1/22 90/1/22 369/1/22 +f 362/1/22 365/1/22 90/1/22 +f 442/1/22 93/1/22 90/1/22 +f 441/1/22 442/1/22 90/1/22 +f 440/1/22 441/1/22 90/1/22 +f 439/1/22 440/1/22 90/1/22 +f 438/1/22 439/1/22 90/1/22 +f 437/1/22 438/1/22 90/1/22 +f 436/1/22 437/1/22 90/1/22 +f 435/1/22 436/1/22 90/1/22 +f 434/1/22 435/1/22 90/1/22 +f 433/1/22 434/1/22 90/1/22 +f 432/1/22 433/1/22 90/1/22 +f 463/1/22 432/1/22 90/1/22 +f 462/1/22 463/1/22 90/1/22 +f 164/1/22 219/1/22 74/1/22 +f 78/1/22 164/1/22 74/1/22 +f 164/1/22 218/1/22 219/1/22 +f 167/1/22 217/1/22 218/1/22 +f 164/1/22 167/1/22 218/1/22 +f 170/1/22 216/1/22 217/1/22 +f 167/1/22 170/1/22 217/1/22 +f 173/1/22 215/1/22 216/1/22 +f 170/1/22 173/1/22 216/1/22 +f 176/1/22 214/1/22 215/1/22 +f 173/1/22 176/1/22 215/1/22 +f 186/1/22 213/1/22 214/1/22 +f 183/1/22 186/1/22 214/1/22 +f 181/1/22 183/1/22 214/1/22 +f 179/1/22 181/1/22 214/1/22 +f 176/1/22 179/1/22 214/1/22 +f 189/1/22 212/1/22 213/1/22 +f 186/1/22 189/1/22 213/1/22 +f 192/1/22 211/1/22 212/1/22 +f 189/1/22 192/1/22 212/1/22 +f 195/1/22 210/1/22 211/1/22 +f 192/1/22 195/1/22 211/1/22 +f 198/1/22 209/1/22 210/1/22 +f 195/1/22 198/1/22 210/1/22 +f 201/1/22 208/1/22 209/1/22 +f 198/1/22 201/1/22 209/1/22 +f 204/1/22 207/1/22 208/1/22 +f 201/1/22 204/1/22 208/1/22 +f 205/1/22 206/1/22 207/1/22 +f 204/1/22 205/1/22 207/1/22 +f 103/1/22 159/1/22 77/1/22 +f 76/1/22 103/1/22 77/1/22 +f 106/1/22 158/1/22 159/1/22 +f 103/1/22 106/1/22 159/1/22 +f 109/1/22 157/1/22 158/1/22 +f 106/1/22 109/1/22 158/1/22 +f 112/1/22 156/1/22 157/1/22 +f 109/1/22 112/1/22 157/1/22 +f 115/1/22 155/1/22 156/1/22 +f 112/1/22 115/1/22 156/1/22 +f 118/1/22 154/1/22 155/1/22 +f 115/1/22 118/1/22 155/1/22 +f 124/1/22 153/1/22 154/1/22 +f 118/1/22 124/1/22 154/1/22 +f 124/1/22 152/1/22 153/1/22 +f 124/1/22 151/1/22 152/1/22 +f 127/1/22 150/1/22 151/1/22 +f 124/1/22 127/1/22 151/1/22 +f 130/1/22 149/1/22 150/1/22 +f 127/1/22 130/1/22 150/1/22 +f 133/1/22 148/1/22 149/1/22 +f 130/1/22 133/1/22 149/1/22 +f 136/1/22 147/1/22 148/1/22 +f 133/1/22 136/1/22 148/1/22 +f 139/1/22 146/1/22 147/1/22 +f 136/1/22 139/1/22 147/1/22 +f 142/1/22 145/1/22 146/1/22 +f 139/1/22 142/1/22 146/1/22 +f 143/1/22 144/1/22 145/1/22 +f 142/1/22 143/1/22 145/1/22 +f 455/1/22 322/1/22 320/1/22 +f 455/1/22 320/1/22 325/1/22 +f 455/1/22 386/1/22 322/1/22 +f 455/1/22 384/1/22 386/1/22 +f 455/1/22 379/1/22 384/1/22 +f 457/1/22 381/1/22 379/1/22 +f 456/1/22 379/1/22 455/1/22 +f 456/1/22 457/1/22 379/1/22 +f 458/1/22 376/1/22 381/1/22 +f 457/1/22 458/1/22 381/1/22 +f 460/1/22 372/1/22 376/1/22 +f 459/1/22 460/1/22 376/1/22 +f 458/1/22 459/1/22 376/1/22 +f 462/1/22 369/1/22 372/1/22 +f 461/1/22 462/1/22 372/1/22 +f 460/1/22 461/1/22 372/1/22 +f 455/1/22 339/1/22 340/1/22 +f 454/1/22 455/1/22 340/1/22 +f 455/1/22 338/1/22 339/1/22 +f 455/1/22 336/1/22 338/1/22 +f 455/1/22 334/1/22 336/1/22 +f 455/1/22 331/1/22 334/1/22 +f 455/1/22 329/1/22 331/1/22 +f 455/1/22 327/1/22 329/1/22 +f 455/1/22 324/1/22 327/1/22 +f 455/1/22 325/1/22 324/1/22 +f 445/1/22 391/1/22 394/1/22 +f 464/1/22 431/1/22 432/1/22 +f 463/1/22 464/1/22 432/1/22 +f 464/1/22 430/1/22 431/1/22 +f 465/1/22 428/1/22 430/1/22 +f 464/1/22 465/1/22 430/1/22 +f 465/1/22 425/1/22 428/1/22 +f 466/1/22 422/1/22 425/1/22 +f 465/1/22 466/1/22 425/1/22 +f 467/1/22 419/1/22 422/1/22 +f 466/1/22 467/1/22 422/1/22 +f 471/1/22 414/1/22 419/1/22 +f 470/1/22 471/1/22 419/1/22 +f 469/1/22 470/1/22 419/1/22 +f 468/1/22 469/1/22 419/1/22 +f 467/1/22 468/1/22 419/1/22 +f 472/1/22 411/1/22 414/1/22 +f 471/1/22 472/1/22 414/1/22 +f 472/1/22 409/1/22 411/1/22 +f 472/1/22 407/1/22 409/1/22 +f 472/1/22 404/1/22 407/1/22 +f 473/1/22 401/1/22 404/1/22 +f 472/1/22 473/1/22 404/1/22 +f 474/1/22 398/1/22 401/1/22 +f 473/1/22 474/1/22 401/1/22 +f 476/1/22 394/1/22 398/1/22 +f 475/1/22 476/1/22 398/1/22 +f 474/1/22 475/1/22 398/1/22 +f 446/1/22 445/1/22 394/1/22 +f 476/1/22 446/1/22 394/1/22 diff --git a/resources/meshes/bambulab_x1.obj b/resources/meshes/bambulab_x1.obj new file mode 100644 index 0000000000..4adf0ada85 --- /dev/null +++ b/resources/meshes/bambulab_x1.obj @@ -0,0 +1,1999 @@ +# Blender 4.4.0 +# www.blender.org +mtllib bambulabs-3dp-X1.mtl +o bambulabs-3dp-X1 +v 107.097450 259.555542 -0.400000 +v 95.464462 259.464478 -0.400000 +v 100.535538 264.535522 -0.400000 +v 100.535538 264.535522 0.000000 +v 157.463837 264.535522 -0.400000 +v 162.534912 259.464478 -0.400000 +v 150.070450 260.000000 -0.400000 +v 100.857323 264.830383 -0.400000 +v 100.857323 264.830383 0.000000 +v 107.221794 259.707062 -0.400000 +v 107.373367 259.831451 -0.400000 +v 107.546310 259.923889 -0.400000 +v 107.733925 259.980804 -0.400000 +v 107.928932 260.000000 -0.400000 +v 106.948174 259.195160 -0.400000 +v 95.142677 259.169617 -0.400000 +v 95.464462 259.464478 0.000000 +v 107.005081 259.382751 -0.400000 +v 106.928947 259.000000 -0.400000 +v 94.796776 258.904236 -0.400000 +v 95.142677 259.169617 0.000000 +v 106.948174 258.804840 -0.400000 +v 94.429001 258.669922 -0.400000 +v 94.796776 258.904236 0.000000 +v 107.005081 258.617249 -0.400000 +v 94.042107 258.468506 -0.400000 +v 94.429001 258.669922 0.000000 +v 107.097450 258.444458 -0.400000 +v 93.638947 258.301514 -0.400000 +v 94.042107 258.468506 0.000000 +v 107.221794 258.292938 -0.400000 +v 93.222939 258.170349 -0.400000 +v 93.638947 258.301514 0.000000 +v 107.546310 258.076111 -0.400000 +v 92.797203 258.075989 -0.400000 +v 93.222939 258.170349 0.000000 +v 107.373367 258.168549 -0.400000 +v 107.733925 258.019196 -0.400000 +v 92.364960 258.019043 -0.400000 +v 92.797203 258.075989 0.000000 +v 107.928932 258.000000 -0.400000 +v 91.928932 258.000000 -0.400000 +v 92.364960 258.019043 0.000000 +v 252.094131 257.828613 -0.400000 +v 150.070450 258.000000 -0.400000 +v 91.928932 258.000000 0.000000 +v 7.500000 258.000000 -0.400000 +v 166.070450 258.000000 -0.400000 +v 6.964659 257.980865 -0.400000 +v 7.500000 258.000000 0.000000 +v 6.432667 257.923645 -0.400000 +v 6.964659 257.980865 0.000000 +v 5.905871 257.828613 -0.400000 +v 6.432667 257.923645 0.000000 +v 252.613068 257.696167 -0.400000 +v 5.386930 257.696167 -0.400000 +v 5.905871 257.828613 0.000000 +v 253.121048 257.527069 -0.400000 +v 4.878953 257.527069 -0.400000 +v 5.386930 257.696167 0.000000 +v 253.615540 257.322266 -0.400000 +v 4.384461 257.322266 -0.400000 +v 4.878953 257.527069 0.000000 +v 254.094299 257.082642 -0.400000 +v 3.905704 257.082642 -0.400000 +v 4.384461 257.322266 0.000000 +v 254.554901 256.809326 -0.400000 +v 3.445103 256.809326 -0.400000 +v 3.905704 257.082642 0.000000 +v 254.994598 256.504028 -0.400000 +v 3.005406 256.504028 -0.400000 +v 3.445103 256.809326 0.000000 +v 255.411362 256.168182 -0.400000 +v 2.588632 256.168182 -0.400000 +v 3.005406 256.504028 0.000000 +v 255.803299 255.803299 -0.400000 +v 2.196699 255.803299 -0.400000 +v 2.588632 256.168182 0.000000 +v 256.168182 255.411362 -0.400000 +v 1.831816 255.411362 -0.400000 +v 2.196699 255.803299 0.000000 +v 256.504028 254.994598 -0.400000 +v 1.495970 254.994598 -0.400000 +v 1.831816 255.411362 0.000000 +v 256.809326 254.554901 -0.400000 +v 1.190664 254.554901 -0.400000 +v 1.495970 254.994598 0.000000 +v 257.082642 254.094299 -0.400000 +v 0.917371 254.094299 -0.400000 +v 1.190664 254.554901 0.000000 +v 257.322266 253.615540 -0.400000 +v 0.677742 253.615540 -0.400000 +v 0.917371 254.094299 0.000000 +v 257.527069 253.121048 -0.400000 +v 0.472916 253.121048 -0.400000 +v 0.677742 253.615540 0.000000 +v 257.696167 252.613068 -0.400000 +v 0.303827 252.613068 -0.400000 +v 0.472916 253.121048 0.000000 +v 257.828613 252.094131 -0.400000 +v 0.171380 252.094131 -0.400000 +v 0.303827 252.613068 0.000000 +v 257.923645 251.567337 -0.400000 +v 0.076351 251.567337 -0.400000 +v 0.171380 252.094131 0.000000 +v 257.980865 251.035339 -0.400000 +v 0.019136 251.035339 -0.400000 +v 0.076351 251.567337 0.000000 +v 258.000000 250.500000 -0.400000 +v 0.000000 250.500000 -0.400000 +v 0.019136 251.035339 0.000000 +v 248.500000 0.000000 -0.400000 +v 0.000000 7.500000 -0.400000 +v 0.000000 250.500000 0.000000 +v 258.000000 -2.500000 -0.400000 +v 248.695023 -0.019211 -0.400000 +v 0.019136 6.964659 -0.400000 +v 0.000000 7.500000 0.000000 +v 0.076351 6.432667 -0.400000 +v 0.019136 6.964659 0.000000 +v 0.171380 5.905871 -0.400000 +v 0.076351 6.432667 0.000000 +v 0.303827 5.386930 -0.400000 +v 0.171380 5.905871 0.000000 +v 0.472916 4.878953 -0.400000 +v 0.303827 5.386930 0.000000 +v 0.677742 4.384461 -0.400000 +v 0.472916 4.878953 0.000000 +v 0.917371 3.905704 -0.400000 +v 0.677742 4.384461 0.000000 +v 1.190664 3.445103 -0.400000 +v 0.917371 3.905704 0.000000 +v 1.495970 3.005406 -0.400000 +v 1.190664 3.445103 0.000000 +v 1.831816 2.588632 -0.400000 +v 1.495970 3.005406 0.000000 +v 2.196699 2.196699 -0.400000 +v 1.831816 2.588632 0.000000 +v 2.588632 1.831816 -0.400000 +v 2.196699 2.196699 0.000000 +v 3.005406 1.495970 -0.400000 +v 2.588632 1.831816 0.000000 +v 3.445103 1.190664 -0.400000 +v 3.005406 1.495970 0.000000 +v 3.905704 0.917371 -0.400000 +v 3.445103 1.190664 0.000000 +v 4.384461 0.677742 -0.400000 +v 3.905704 0.917371 0.000000 +v 4.878953 0.472916 -0.400000 +v 4.384461 0.677742 0.000000 +v 236.474869 0.000000 -0.400000 +v 4.878953 0.472916 0.000000 +v 5.386930 0.303827 -0.400000 +v 231.318024 0.000000 -0.400000 +v 5.386930 0.303827 0.000000 +v 5.905871 0.171380 -0.400000 +v 225.500000 0.000000 -0.400000 +v 5.905871 0.171380 0.000000 +v 6.432667 0.076351 -0.400000 +v 6.432667 0.076351 0.000000 +v 6.964659 0.019136 -0.400000 +v 69.928932 0.000000 -0.400000 +v 7.500000 0.000000 -0.400000 +v 6.964659 0.019136 0.000000 +v 7.500000 0.000000 0.000000 +v 225.304977 -0.019211 -0.400000 +v 70.364960 -0.019053 -0.400000 +v 69.928932 0.000000 0.000000 +v 225.117325 -0.076134 -0.400000 +v 70.797203 -0.075985 -0.400000 +v 70.364960 -0.019053 0.000000 +v 224.944382 -0.168571 -0.400000 +v 71.222939 -0.170362 -0.400000 +v 70.797203 -0.075985 0.000000 +v 224.792892 -0.292893 -0.400000 +v 71.638947 -0.301509 -0.400000 +v 71.222939 -0.170362 0.000000 +v 224.668564 -0.444384 -0.400000 +v 72.042107 -0.468502 -0.400000 +v 71.638947 -0.301509 0.000000 +v 224.576126 -0.617322 -0.400000 +v 72.429001 -0.669929 -0.400000 +v 72.042107 -0.468502 0.000000 +v 224.519211 -0.804971 -0.400000 +v 72.796776 -0.904236 -0.400000 +v 72.429001 -0.669929 0.000000 +v 224.500000 -1.000000 -0.400000 +v 73.142677 -1.169622 -0.400000 +v 72.796776 -0.904236 0.000000 +v 224.500000 -6.000000 -0.400000 +v 73.464462 -1.464466 -0.400000 +v 73.142677 -1.169622 0.000000 +v 254.994598 -8.504030 -0.400000 +v 80.535538 -8.535534 -0.400000 +v 73.464462 -1.464466 0.000000 +v 255.411362 -8.168184 -0.400000 +v 225.500000 -7.000000 -0.400000 +v 225.304977 -6.980789 -0.400000 +v 225.117325 -6.923866 -0.400000 +v 224.944382 -6.831429 -0.400000 +v 224.792892 -6.707107 -0.400000 +v 224.668564 -6.555616 -0.400000 +v 224.576126 -6.382678 -0.400000 +v 224.519211 -6.195029 -0.400000 +v 254.554901 -8.809336 -0.400000 +v 80.857323 -8.830379 -0.400000 +v 80.535538 -8.535534 0.000000 +v 254.094299 -9.082629 -0.400000 +v 81.203224 -9.095764 -0.400000 +v 80.857323 -8.830379 0.000000 +v 253.615540 -9.322258 -0.400000 +v 81.570999 -9.330070 -0.400000 +v 81.203224 -9.095764 0.000000 +v 253.121048 -9.527083 -0.400000 +v 81.957893 -9.531498 -0.400000 +v 81.570999 -9.330070 0.000000 +v 252.613068 -9.696173 -0.400000 +v 82.361053 -9.698491 -0.400000 +v 81.957893 -9.531498 0.000000 +v 252.094131 -9.828620 -0.400000 +v 82.777061 -9.829638 -0.400000 +v 82.361053 -9.698491 0.000000 +v 251.567337 -9.923649 -0.400000 +v 83.202797 -9.924015 -0.400000 +v 82.777061 -9.829638 0.000000 +v 251.035339 -9.980864 -0.400000 +v 83.635040 -9.980947 -0.400000 +v 83.202797 -9.924015 0.000000 +v 250.500000 -10.000000 -0.400000 +v 84.071068 -10.000000 -0.400000 +v 83.635040 -9.980947 0.000000 +v 84.071068 -10.000000 0.000000 +v 250.500000 -10.000000 0.000000 +v 251.035339 -9.980864 0.000000 +v 251.567337 -9.923649 0.000000 +v 252.094131 -9.828620 0.000000 +v 252.613068 -9.696173 0.000000 +v 253.121048 -9.527083 0.000000 +v 253.615540 -9.322258 0.000000 +v 254.094299 -9.082629 0.000000 +v 254.554901 -8.809336 0.000000 +v 254.994598 -8.504030 0.000000 +v 255.803299 -7.803301 -0.400000 +v 255.411362 -8.168184 0.000000 +v 256.168182 -7.411368 -0.400000 +v 255.803299 -7.803301 0.000000 +v 248.500000 -7.000000 -0.400000 +v 256.504028 -6.994594 -0.400000 +v 256.168182 -7.411368 0.000000 +v 242.681976 -7.000000 -0.400000 +v 237.525131 -7.000000 -0.400000 +v 249.331436 -6.555616 -0.400000 +v 256.809326 -6.554897 -0.400000 +v 256.504028 -6.994594 0.000000 +v 249.207108 -6.707107 -0.400000 +v 249.055618 -6.831429 -0.400000 +v 248.882675 -6.923866 -0.400000 +v 248.695023 -6.980789 -0.400000 +v 249.480789 -6.195029 -0.400000 +v 257.082642 -6.094296 -0.400000 +v 256.809326 -6.554897 0.000000 +v 249.423874 -6.382678 -0.400000 +v 249.500000 -6.000000 -0.400000 +v 257.322266 -5.615539 -0.400000 +v 257.082642 -6.094296 0.000000 +v 249.500000 -1.000000 -0.400000 +v 257.527069 -5.121047 -0.400000 +v 257.322266 -5.615539 0.000000 +v 257.696167 -4.613070 -0.400000 +v 257.527069 -5.121047 0.000000 +v 257.828613 -4.094129 -0.400000 +v 257.696167 -4.613070 0.000000 +v 257.923645 -3.567333 -0.400000 +v 257.828613 -4.094129 0.000000 +v 257.980865 -3.035341 -0.400000 +v 257.923645 -3.567333 0.000000 +v 257.980865 -3.035341 0.000000 +v 258.000000 -2.500000 0.000000 +v 248.882675 -0.076134 -0.400000 +v 249.055618 -0.168571 -0.400000 +v 249.207108 -0.292893 -0.400000 +v 249.331436 -0.444384 -0.400000 +v 249.423874 -0.617322 -0.400000 +v 249.480789 -0.804971 -0.400000 +v 258.000000 250.500000 0.000000 +v 257.980865 251.035339 0.000000 +v 257.923645 251.567337 0.000000 +v 257.828613 252.094131 0.000000 +v 257.696167 252.613068 0.000000 +v 257.527069 253.121048 0.000000 +v 257.322266 253.615540 0.000000 +v 257.082642 254.094299 0.000000 +v 256.809326 254.554901 0.000000 +v 256.504028 254.994598 0.000000 +v 256.168182 255.411362 0.000000 +v 255.803299 255.803299 0.000000 +v 255.411362 256.168182 0.000000 +v 254.994598 256.504028 0.000000 +v 254.554901 256.809326 0.000000 +v 254.094299 257.082642 0.000000 +v 253.615540 257.322266 0.000000 +v 253.121048 257.527069 0.000000 +v 252.613068 257.696167 0.000000 +v 252.094131 257.828613 0.000000 +v 251.567337 257.923645 -0.400000 +v 251.567337 257.923645 0.000000 +v 251.035339 257.980865 -0.400000 +v 250.500000 258.000000 -0.400000 +v 251.035339 257.980865 0.000000 +v 250.500000 258.000000 0.000000 +v 150.265457 258.019196 -0.400000 +v 165.634415 258.019043 -0.400000 +v 166.070450 258.000000 0.000000 +v 150.453064 258.076111 -0.400000 +v 165.202179 258.075989 -0.400000 +v 165.634415 258.019043 0.000000 +v 150.626007 258.168549 -0.400000 +v 164.776443 258.170349 -0.400000 +v 165.202179 258.075989 0.000000 +v 150.777588 258.292938 -0.400000 +v 164.360428 258.301514 -0.400000 +v 164.776443 258.170349 0.000000 +v 150.901932 258.444458 -0.400000 +v 163.957275 258.468506 -0.400000 +v 164.360428 258.301514 0.000000 +v 150.994293 258.617249 -0.400000 +v 163.570374 258.669922 -0.400000 +v 163.957275 258.468506 0.000000 +v 151.051208 258.804840 -0.400000 +v 163.202606 258.904236 -0.400000 +v 163.570374 258.669922 0.000000 +v 151.070435 259.000000 -0.400000 +v 162.856705 259.169617 -0.400000 +v 163.202606 258.904236 0.000000 +v 150.994293 259.382751 -0.400000 +v 162.856705 259.169617 0.000000 +v 151.051208 259.195160 -0.400000 +v 162.534912 259.464478 0.000000 +v 150.265457 259.980804 -0.400000 +v 150.453064 259.923889 -0.400000 +v 150.626007 259.831451 -0.400000 +v 150.777588 259.707062 -0.400000 +v 150.901932 259.555542 -0.400000 +v 157.142059 264.830383 -0.400000 +v 157.463837 264.535522 0.000000 +v 101.203224 265.095764 -0.400000 +v 156.796158 265.095764 -0.400000 +v 157.142059 264.830383 0.000000 +v 101.570999 265.330078 -0.400000 +v 156.428375 265.330078 -0.400000 +v 156.796158 265.095764 0.000000 +v 101.957893 265.531494 -0.400000 +v 156.041489 265.531494 -0.400000 +v 156.428375 265.330078 0.000000 +v 102.361053 265.698486 -0.400000 +v 155.638321 265.698486 -0.400000 +v 156.041489 265.531494 0.000000 +v 102.777061 265.829651 -0.400000 +v 155.222321 265.829651 -0.400000 +v 155.638321 265.698486 0.000000 +v 103.202797 265.924011 -0.400000 +v 154.796585 265.924011 -0.400000 +v 155.222321 265.829651 0.000000 +v 103.635040 265.980957 -0.400000 +v 154.364334 265.980957 -0.400000 +v 154.796585 265.924011 0.000000 +v 104.071068 266.000000 -0.400000 +v 153.928314 266.000000 -0.400000 +v 154.364334 265.980957 0.000000 +v 153.928314 266.000000 0.000000 +v 104.071068 266.000000 0.000000 +v 103.635040 265.980957 0.000000 +v 103.202797 265.924011 0.000000 +v 102.777061 265.829651 0.000000 +v 102.361053 265.698486 0.000000 +v 101.957893 265.531494 0.000000 +v 101.570999 265.330078 0.000000 +v 101.203224 265.095764 0.000000 +v 237.878677 -6.146447 -0.400000 +v 236.121323 -0.853553 -0.400000 +v 241.974869 -6.707107 -0.400000 +v 241.974869 -6.707107 0.000000 +v 237.958099 -6.750034 -0.400000 +v 242.126358 -6.831429 -0.400000 +v 242.126358 -6.831429 0.000000 +v 237.958099 -6.249966 -0.400000 +v 238.008087 -6.370639 -0.400000 +v 238.025131 -6.500000 -0.400000 +v 238.008087 -6.629361 -0.400000 +v 232.025131 -0.292893 -0.400000 +v 236.041901 -0.750034 -0.400000 +v 236.121323 -0.853553 0.000000 +v 235.991913 -0.629361 -0.400000 +v 236.041901 -0.750034 0.000000 +v 235.974869 -0.500000 -0.400000 +v 235.991913 -0.629361 0.000000 +v 235.991913 -0.370639 -0.400000 +v 235.974869 -0.500000 0.000000 +v 236.041901 -0.249966 -0.400000 +v 235.991913 -0.370639 0.000000 +v 231.873642 -0.168571 -0.400000 +v 236.121323 -0.146447 -0.400000 +v 236.041901 -0.249966 0.000000 +v 231.700699 -0.076134 -0.400000 +v 236.224838 -0.067023 -0.400000 +v 236.121323 -0.146447 0.000000 +v 231.513046 -0.019211 -0.400000 +v 236.345520 -0.017038 -0.400000 +v 236.224838 -0.067023 0.000000 +v 236.345520 -0.017038 0.000000 +v 236.474869 0.000000 0.000000 +v 248.500000 0.000000 0.000000 +v 248.695023 -0.019211 0.000000 +v 248.882675 -0.076134 0.000000 +v 249.055618 -0.168571 0.000000 +v 249.207108 -0.292893 0.000000 +v 249.331436 -0.444384 0.000000 +v 249.423874 -0.617322 0.000000 +v 249.480789 -0.804971 0.000000 +v 249.500000 -1.000000 0.000000 +v 249.500000 -6.000000 0.000000 +v 249.480789 -6.195029 0.000000 +v 249.423874 -6.382678 0.000000 +v 249.331436 -6.555616 0.000000 +v 249.207108 -6.707107 0.000000 +v 249.055618 -6.831429 0.000000 +v 248.882675 -6.923866 0.000000 +v 248.695023 -6.980789 0.000000 +v 248.500000 -7.000000 0.000000 +v 237.654480 -6.982962 -0.400000 +v 242.486954 -6.980789 -0.400000 +v 242.681976 -7.000000 0.000000 +v 237.775162 -6.932977 -0.400000 +v 242.299301 -6.923866 -0.400000 +v 242.486954 -6.980789 0.000000 +v 237.878677 -6.853553 -0.400000 +v 242.299301 -6.923866 0.000000 +v 232.025131 -0.292893 0.000000 +v 231.873642 -0.168571 0.000000 +v 237.878677 -6.146447 0.000000 +v 237.958099 -6.249966 0.000000 +v 238.008087 -6.370639 0.000000 +v 238.025131 -6.500000 0.000000 +v 238.008087 -6.629361 0.000000 +v 237.958099 -6.750034 0.000000 +v 237.878677 -6.853553 0.000000 +v 237.775162 -6.932977 0.000000 +v 237.654480 -6.982962 0.000000 +v 237.525131 -7.000000 0.000000 +v 225.500000 -7.000000 0.000000 +v 225.304977 -6.980789 0.000000 +v 225.117325 -6.923866 0.000000 +v 224.944382 -6.831429 0.000000 +v 224.792892 -6.707107 0.000000 +v 224.668564 -6.555616 0.000000 +v 224.576126 -6.382678 0.000000 +v 224.519211 -6.195029 0.000000 +v 224.500000 -6.000000 0.000000 +v 224.500000 -1.000000 0.000000 +v 224.519211 -0.804971 0.000000 +v 224.576126 -0.617322 0.000000 +v 224.668564 -0.444384 0.000000 +v 224.792892 -0.292893 0.000000 +v 224.944382 -0.168571 0.000000 +v 225.117325 -0.076134 0.000000 +v 225.304977 -0.019211 0.000000 +v 225.500000 0.000000 0.000000 +v 231.318024 0.000000 0.000000 +v 231.513046 -0.019211 0.000000 +v 231.700699 -0.076134 0.000000 +v 107.928932 258.000000 0.000000 +v 150.070450 258.000000 0.000000 +v 107.733925 258.019196 0.000000 +v 107.546310 258.076111 0.000000 +v 107.373367 258.168549 0.000000 +v 107.221794 258.292938 0.000000 +v 107.097450 258.444458 0.000000 +v 107.005081 258.617249 0.000000 +v 106.948174 258.804840 0.000000 +v 106.928947 259.000000 0.000000 +v 106.948174 259.195160 0.000000 +v 107.005081 259.382751 0.000000 +v 107.097450 259.555542 0.000000 +v 107.221794 259.707062 0.000000 +v 107.373367 259.831451 0.000000 +v 107.546310 259.923889 0.000000 +v 107.733925 259.980804 0.000000 +v 107.928932 260.000000 0.000000 +v 150.070450 260.000000 0.000000 +v 150.265457 259.980804 0.000000 +v 150.453064 259.923889 0.000000 +v 150.626007 259.831451 0.000000 +v 150.777588 259.707062 0.000000 +v 150.901932 259.555542 0.000000 +v 150.994293 259.382751 0.000000 +v 151.051208 259.195160 0.000000 +v 151.070435 259.000000 0.000000 +v 151.051208 258.804840 0.000000 +v 150.994293 258.617249 0.000000 +v 150.901932 258.444458 0.000000 +v 150.777588 258.292938 0.000000 +v 150.626007 258.168549 0.000000 +v 150.453064 258.076111 0.000000 +v 150.265457 258.019196 0.000000 +vn -0.0000 -0.0000 -1.0000 +vn -0.7071 0.7071 -0.0000 +vn -0.6756 0.7373 -0.0000 +vn -0.6087 0.7934 -0.0000 +vn -0.5373 0.8434 -0.0000 +vn -0.4618 0.8870 -0.0000 +vn -0.3827 0.9239 -0.0000 +vn -0.3007 0.9537 -0.0000 +vn -0.2164 0.9763 -0.0000 +vn -0.1306 0.9914 -0.0000 +vn -0.0436 0.9990 -0.0000 +vn -0.0000 1.0000 -0.0000 +vn -0.0357 0.9994 -0.0000 +vn -0.1069 0.9943 -0.0000 +vn -0.1775 0.9841 -0.0000 +vn -0.2473 0.9689 -0.0000 +vn -0.3158 0.9488 -0.0000 +vn -0.3826 0.9239 -0.0000 +vn -0.4476 0.8942 -0.0000 +vn -0.5103 0.8600 -0.0000 +vn -0.5703 0.8214 -0.0000 +vn -0.6275 0.7787 -0.0000 +vn -0.6814 0.7319 -0.0000 +vn -0.7319 0.6814 -0.0000 +vn -0.7786 0.6275 -0.0000 +vn -0.8214 0.5703 -0.0000 +vn -0.8600 0.5103 -0.0000 +vn -0.8942 0.4476 -0.0000 +vn -0.9239 0.3827 -0.0000 +vn -0.9488 0.3158 -0.0000 +vn -0.9689 0.2473 -0.0000 +vn -0.9841 0.1775 -0.0000 +vn -0.9943 0.1069 -0.0000 +vn -0.9994 0.0357 -0.0000 +vn -1.0000 -0.0000 -0.0000 +vn -0.9994 -0.0357 -0.0000 +vn -0.9943 -0.1069 -0.0000 +vn -0.9841 -0.1775 -0.0000 +vn -0.9689 -0.2473 -0.0000 +vn -0.9488 -0.3158 -0.0000 +vn -0.9239 -0.3827 -0.0000 +vn -0.8942 -0.4476 -0.0000 +vn -0.8600 -0.5103 -0.0000 +vn -0.8214 -0.5703 -0.0000 +vn -0.7787 -0.6275 -0.0000 +vn -0.7319 -0.6814 -0.0000 +vn -0.6814 -0.7319 -0.0000 +vn -0.6275 -0.7787 -0.0000 +vn -0.5703 -0.8214 -0.0000 +vn -0.5103 -0.8600 -0.0000 +vn -0.4476 -0.8942 -0.0000 +vn -0.3827 -0.9239 -0.0000 +vn -0.3158 -0.9488 -0.0000 +vn -0.2473 -0.9689 -0.0000 +vn -0.1775 -0.9841 -0.0000 +vn -0.1069 -0.9943 -0.0000 +vn -0.0357 -0.9994 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn -0.0437 -0.9990 -0.0000 +vn -0.1306 -0.9914 -0.0000 +vn -0.2164 -0.9763 -0.0000 +vn -0.3007 -0.9537 -0.0000 +vn -0.4618 -0.8870 -0.0000 +vn -0.5373 -0.8434 -0.0000 +vn -0.6087 -0.7934 -0.0000 +vn -0.6756 -0.7373 -0.0000 +vn -0.7071 -0.7071 -0.0000 +vn 0.0357 -0.9994 -0.0000 +vn 0.1069 -0.9943 -0.0000 +vn 0.1775 -0.9841 -0.0000 +vn 0.2473 -0.9689 -0.0000 +vn 0.3158 -0.9488 -0.0000 +vn 0.3827 -0.9239 -0.0000 +vn 0.4476 -0.8942 -0.0000 +vn 0.5103 -0.8600 -0.0000 +vn 0.5703 -0.8214 -0.0000 +vn 0.6275 -0.7786 -0.0000 +vn 0.5704 -0.8214 -0.0000 +vn 0.6814 -0.7319 -0.0000 +vn 0.7319 -0.6814 -0.0000 +vn 0.7787 -0.6275 -0.0000 +vn 0.8214 -0.5703 -0.0000 +vn 0.8600 -0.5103 -0.0000 +vn 0.8943 -0.4475 -0.0000 +vn 0.9239 -0.3827 -0.0000 +vn 0.8942 -0.4476 -0.0000 +vn 0.9488 -0.3159 -0.0000 +vn 0.9239 -0.3826 -0.0000 +vn 0.9689 -0.2473 -0.0000 +vn 0.9488 -0.3158 -0.0000 +vn 0.9841 -0.1775 -0.0000 +vn 0.9943 -0.1070 -0.0000 +vn 0.9994 -0.0357 -0.0000 +vn 0.9943 -0.1069 -0.0000 +vn 1.0000 -0.0000 -0.0000 +vn 0.9994 -0.0358 -0.0000 +vn 0.9994 0.0358 -0.0000 +vn 0.9943 0.1069 -0.0000 +vn 0.9994 0.0357 -0.0000 +vn 0.9841 0.1775 -0.0000 +vn 0.9943 0.1070 -0.0000 +vn 0.9689 0.2473 -0.0000 +vn 0.9488 0.3158 -0.0000 +vn 0.9239 0.3826 -0.0000 +vn 0.9488 0.3159 -0.0000 +vn 0.8942 0.4476 -0.0000 +vn 0.9239 0.3827 -0.0000 +vn 0.8600 0.5103 -0.0000 +vn 0.8943 0.4475 -0.0000 +vn 0.8214 0.5703 -0.0000 +vn 0.7786 0.6275 -0.0000 +vn 0.7319 0.6814 -0.0000 +vn 0.6814 0.7319 -0.0000 +vn 0.6275 0.7786 -0.0000 +vn 0.5703 0.8214 -0.0000 +vn 0.5103 0.8600 -0.0000 +vn 0.4476 0.8942 -0.0000 +vn 0.3826 0.9239 -0.0000 +vn 0.3159 0.9488 -0.0000 +vn 0.3827 0.9239 -0.0000 +vn 0.2473 0.9689 -0.0000 +vn 0.3158 0.9488 -0.0000 +vn 0.1775 0.9841 -0.0000 +vn 0.1069 0.9943 -0.0000 +vn 0.0357 0.9994 -0.0000 +vn 0.0436 0.9990 -0.0000 +vn 0.1306 0.9914 -0.0000 +vn 0.2164 0.9763 -0.0000 +vn 0.3007 0.9537 -0.0000 +vn 0.4618 0.8870 -0.0000 +vn 0.5373 0.8434 -0.0000 +vn 0.6087 0.7934 -0.0000 +vn 0.6756 0.7373 -0.0000 +vn 0.7071 0.7071 -0.0000 +vn 0.6343 0.7731 -0.0000 +vn 0.6344 0.7730 -0.0000 +vn 0.7933 0.6089 -0.0000 +vn 0.9238 0.3829 -0.0000 +vn 0.7935 0.6085 -0.0000 +vn 0.9915 0.1304 -0.0000 +vn 0.9914 -0.1310 -0.0000 +vn 0.9914 0.1310 -0.0000 +vn 0.9915 -0.1304 -0.0000 +vn 0.7935 -0.6085 -0.0000 +vn 0.9238 -0.3829 -0.0000 +vn 0.6088 -0.7933 -0.0000 +vn 0.7933 -0.6089 -0.0000 +vn 0.3826 -0.9239 -0.0000 +vn 0.6087 -0.7934 -0.0000 +vn 0.1306 -0.9914 -0.0000 +vn -0.0980 -0.9952 -0.0000 +vn -0.2903 -0.9569 -0.0000 +vn -0.4714 -0.8819 -0.0000 +vn -0.6343 -0.7731 -0.0000 +vn -0.7730 -0.6344 -0.0000 +vn -0.6344 -0.7730 -0.0000 +vn -0.8819 -0.4714 -0.0000 +vn -0.9570 -0.2903 -0.0000 +vn -0.8819 -0.4715 -0.0000 +vn -0.9952 -0.0979 -0.0000 +vn -0.9952 -0.0981 -0.0000 +vn -0.9952 0.0981 -0.0000 +vn -0.9570 0.2903 -0.0000 +vn -0.9952 0.0979 -0.0000 +vn -0.8819 0.4715 -0.0000 +vn -0.7730 0.6344 -0.0000 +vn -0.8819 0.4714 -0.0000 +vn -0.6344 0.7730 -0.0000 +vn -0.4714 0.8819 -0.0000 +vn -0.6343 0.7731 -0.0000 +vn -0.2903 0.9569 -0.0000 +vn -0.0980 0.9952 -0.0000 +vn 0.0980 0.9952 -0.0000 +vn 0.2903 0.9569 -0.0000 +vn 0.4714 0.8819 -0.0000 +vn -0.7934 -0.6087 -0.0000 +vn -0.9239 -0.3826 -0.0000 +vn -0.9915 -0.1304 -0.0000 +vn -0.9238 -0.3829 -0.0000 +vn -0.9914 0.1307 -0.0000 +vn -0.9914 -0.1307 -0.0000 +vn -0.9238 0.3829 -0.0000 +vn -0.9915 0.1304 -0.0000 +vn -0.7934 0.6087 -0.0000 +vn -0.9239 0.3826 -0.0000 +vn -0.6088 0.7933 -0.0000 +vn 0.2902 0.9570 -0.0000 +vn 0.7730 0.6344 -0.0000 +vn 0.8819 0.4714 -0.0000 +vn 0.7729 0.6345 -0.0000 +vn 0.9570 0.2903 -0.0000 +vn 0.9952 0.0979 -0.0000 +vn 0.9952 0.0981 -0.0000 +vn 0.9952 -0.0981 -0.0000 +vn 0.9570 -0.2903 -0.0000 +vn 0.9952 -0.0979 -0.0000 +vn 0.8819 -0.4714 -0.0000 +vn 0.7729 -0.6345 -0.0000 +vn 0.6344 -0.7730 -0.0000 +vn 0.7730 -0.6344 -0.0000 +vn 0.4714 -0.8819 -0.0000 +vn 0.2902 -0.9570 -0.0000 +vn 0.0980 -0.9952 -0.0000 +vn 0.2903 -0.9569 -0.0000 +vn -0.2902 -0.9570 -0.0000 +vn 0.8819 0.4715 -0.0000 +vn 0.9569 0.2903 -0.0000 +vn 0.9952 0.0980 -0.0000 +vn 0.9570 0.2902 -0.0000 +vn 0.9952 -0.0980 -0.0000 +vn 0.9570 -0.2902 -0.0000 +vn 0.8819 -0.4715 -0.0000 +vn 0.9569 -0.2903 -0.0000 +vn -0.9569 -0.2903 -0.0000 +vn -0.8820 -0.4713 -0.0000 +vn -0.9952 -0.0980 -0.0000 +vn -0.9952 0.0980 -0.0000 +vn -0.9569 0.2903 -0.0000 +vn -0.8820 0.4713 -0.0000 +vn -0.0000 -0.0000 1.0000 +vt 0.000000 0.000000 +vt 0.401686 0.999931 +vt 0.596621 1.000000 +vt 0.403376 1.000000 +vt 0.598311 0.999931 +vt 0.400011 0.999725 +vt 0.599987 0.999725 +vt 0.398361 0.999383 +vt 0.601637 0.999383 +vt 0.396748 0.998908 +vt 0.603249 0.998908 +vt 0.395186 0.998302 +vt 0.604812 0.998302 +vt 0.393686 0.997573 +vt 0.606312 0.997573 +vt 0.392261 0.996724 +vt 0.607737 0.996724 +vt 0.390920 0.995762 +vt 0.609078 0.995762 +vt 0.389673 0.994694 +vt 0.610325 0.994694 +vt 0.584891 0.976650 +vt 0.629980 0.976321 +vt 0.370017 0.976321 +vt 0.418329 0.978261 +vt 0.581668 0.978261 +vt 0.584409 0.977199 +vt 0.583822 0.977650 +vt 0.583151 0.977985 +vt 0.582424 0.978191 +vt 0.585470 0.975345 +vt 0.631228 0.975252 +vt 0.585249 0.976024 +vt 0.585544 0.974638 +vt 0.632568 0.974291 +vt 0.585470 0.973931 +vt 0.633994 0.973442 +vt 0.585249 0.973251 +vt 0.635493 0.972712 +vt 0.584891 0.972625 +vt 0.637056 0.972107 +vt 0.584409 0.972076 +vt 0.638668 0.971632 +vt 0.583151 0.971290 +vt 0.640319 0.971290 +vt 0.583822 0.971625 +vt 0.582424 0.971084 +vt 0.641994 0.971084 +vt 0.581668 0.971014 +vt 0.643684 0.971014 +vt 0.022891 0.970394 +vt 0.418329 0.971014 +vt 0.356314 0.971014 +vt 0.973005 0.970945 +vt 0.970930 0.971014 +vt 0.975067 0.970738 +vt 0.977109 0.970394 +vt 0.020880 0.969914 +vt 0.979120 0.969914 +vt 0.018911 0.969301 +vt 0.981089 0.969301 +vt 0.016994 0.968559 +vt 0.983006 0.968559 +vt 0.015138 0.967691 +vt 0.984862 0.967691 +vt 0.013353 0.966700 +vt 0.986647 0.966700 +vt 0.011649 0.965594 +vt 0.988351 0.965594 +vt 0.010033 0.964377 +vt 0.989967 0.964377 +vt 0.008514 0.963055 +vt 0.991486 0.963055 +vt 0.007100 0.961635 +vt 0.992900 0.961635 +vt 0.005798 0.960125 +vt 0.994202 0.960125 +vt 0.004615 0.958532 +vt 0.995385 0.958532 +vt 0.003556 0.956863 +vt 0.996444 0.956863 +vt 0.002627 0.955129 +vt 0.997373 0.955129 +vt 0.001833 0.953337 +vt 0.998167 0.953337 +vt 0.001178 0.951497 +vt 0.998822 0.951497 +vt 0.000664 0.949617 +vt 0.999336 0.949617 +vt 0.000296 0.947708 +vt 0.999704 0.947708 +vt 0.000074 0.945780 +vt 0.999926 0.945780 +vt 0.000000 0.943841 +vt 1.000000 0.943841 +vt 0.967054 0.032609 +vt 1.000000 0.027174 +vt 0.029070 0.036232 +vt 0.271042 0.036232 +vt 0.874031 0.036232 +vt 0.026995 0.036301 +vt 0.024933 0.036509 +vt 0.022891 0.036853 +vt 0.020880 0.037333 +vt 0.018911 0.037945 +vt 0.016994 0.038687 +vt 0.015138 0.039556 +vt 0.013353 0.040546 +vt 0.011649 0.041652 +vt 0.010033 0.042869 +vt 0.008514 0.044191 +vt 0.007100 0.045611 +vt 0.005798 0.047121 +vt 0.004615 0.048714 +vt 0.003556 0.050383 +vt 0.002627 0.052118 +vt 0.001833 0.053909 +vt 0.001178 0.055750 +vt 0.000664 0.057630 +vt 0.000296 0.059539 +vt 0.000074 0.061466 +vt 0.000000 0.063406 +vt 0.963178 0.036232 +vt 0.916569 0.036232 +vt 0.896582 0.036232 +vt 0.966980 0.033315 +vt 0.966759 0.033995 +vt 0.966401 0.034622 +vt 0.965919 0.035171 +vt 0.965332 0.035621 +vt 0.964661 0.035956 +vt 0.963934 0.036162 +vt 0.967054 0.014493 +vt 0.999926 0.025234 +vt 0.999704 0.023307 +vt 0.999336 0.021398 +vt 0.998822 0.019518 +vt 0.998167 0.017677 +vt 0.997373 0.015886 +vt 0.996444 0.014151 +vt 0.966759 0.013106 +vt 0.995385 0.012482 +vt 0.966980 0.013786 +vt 0.963934 0.010939 +vt 0.994202 0.010889 +vt 0.964661 0.011145 +vt 0.965332 0.011480 +vt 0.965919 0.011931 +vt 0.966401 0.012480 +vt 0.963178 0.010870 +vt 0.992900 0.009379 +vt 0.312153 0.005306 +vt 0.991486 0.007959 +vt 0.989967 0.006637 +vt 0.988351 0.005420 +vt 0.986647 0.004314 +vt 0.313401 0.004238 +vt 0.984862 0.003324 +vt 0.314741 0.003276 +vt 0.983006 0.002456 +vt 0.316167 0.002427 +vt 0.981089 0.001714 +vt 0.317666 0.001697 +vt 0.979120 0.001101 +vt 0.319229 0.001092 +vt 0.977109 0.000621 +vt 0.320841 0.000617 +vt 0.975067 0.000277 +vt 0.324167 0.000069 +vt 0.973005 0.000069 +vt 0.322491 0.000275 +vt 0.325857 0.000000 +vt 0.970930 0.000000 +vt 0.870155 0.014493 +vt 0.284746 0.030926 +vt 0.940628 0.010870 +vt 0.920640 0.010870 +vt 0.874031 0.010870 +vt 0.870230 0.013786 +vt 0.870450 0.013106 +vt 0.870808 0.012480 +vt 0.871290 0.011931 +vt 0.871878 0.011480 +vt 0.872548 0.011145 +vt 0.873275 0.010939 +vt 0.870155 0.032609 +vt 0.283499 0.031994 +vt 0.282158 0.032956 +vt 0.870230 0.033315 +vt 0.280733 0.033805 +vt 0.870450 0.033995 +vt 0.279233 0.034534 +vt 0.870808 0.034622 +vt 0.277670 0.035139 +vt 0.871290 0.035171 +vt 0.276058 0.035615 +vt 0.872548 0.035956 +vt 0.274408 0.035957 +vt 0.871878 0.035621 +vt 0.873275 0.036162 +vt 0.272732 0.036163 +vt 0.029070 0.971014 +vt 0.026995 0.970945 +vt 0.024933 0.970738 +vt 0.417573 0.971084 +vt 0.358004 0.971084 +vt 0.416846 0.971290 +vt 0.359679 0.971290 +vt 0.416176 0.971625 +vt 0.361329 0.971632 +vt 0.415588 0.972076 +vt 0.362942 0.972107 +vt 0.415106 0.972625 +vt 0.364504 0.972712 +vt 0.414748 0.973251 +vt 0.366004 0.973442 +vt 0.414528 0.973931 +vt 0.367429 0.974291 +vt 0.414453 0.974638 +vt 0.368770 0.975252 +vt 0.414748 0.976024 +vt 0.414528 0.975345 +vt 0.417573 0.978191 +vt 0.416846 0.977985 +vt 0.416176 0.977650 +vt 0.415588 0.977199 +vt 0.415106 0.976650 +vt 0.897337 0.036162 +vt 0.916068 0.036170 +vt 0.915600 0.035989 +vt 0.898065 0.035956 +vt 0.915199 0.035701 +vt 0.898735 0.035621 +vt 0.914891 0.035326 +vt 0.899322 0.035171 +vt 0.914697 0.034889 +vt 0.914631 0.034420 +vt 0.914697 0.033952 +vt 0.922010 0.013962 +vt 0.914891 0.033514 +vt 0.915199 0.033139 +vt 0.922512 0.013150 +vt 0.937887 0.011931 +vt 0.922318 0.013587 +vt 0.922318 0.011775 +vt 0.938474 0.011480 +vt 0.922512 0.012212 +vt 0.922578 0.012681 +vt 0.922010 0.011400 +vt 0.939144 0.011145 +vt 0.921609 0.011112 +vt 0.939872 0.010939 +vt 0.921141 0.010931 +s 0 +usemtl Material.001 +f 1/1/1 2/1/1 3/1/1 +f 4/1/2 3/1/2 2/1/2 +f 5/1/1 6/1/1 3/1/1 +f 7/1/1 3/1/1 6/1/1 +f 8/1/1 5/1/1 3/1/1 +f 9/1/3 8/1/3 3/1/3 +f 10/1/1 1/1/1 3/1/1 +f 11/1/1 10/1/1 3/1/1 +f 12/1/1 11/1/1 3/1/1 +f 13/1/1 12/1/1 3/1/1 +f 14/1/1 13/1/1 3/1/1 +f 7/1/1 14/1/1 3/1/1 +f 9/1/3 3/1/3 4/1/3 +f 15/1/1 16/1/1 2/1/1 +f 17/1/3 2/1/3 16/1/3 +f 18/1/1 15/1/1 2/1/1 +f 1/1/1 18/1/1 2/1/1 +f 4/1/2 2/1/2 17/1/2 +f 19/1/1 20/1/1 16/1/1 +f 21/1/4 16/1/4 20/1/4 +f 15/1/1 19/1/1 16/1/1 +f 17/1/3 16/1/3 21/1/3 +f 22/1/1 23/1/1 20/1/1 +f 24/1/5 20/1/5 23/1/5 +f 19/1/1 22/1/1 20/1/1 +f 21/1/4 20/1/4 24/1/4 +f 25/1/1 26/1/1 23/1/1 +f 27/1/6 23/1/6 26/1/6 +f 22/1/1 25/1/1 23/1/1 +f 24/1/5 23/1/5 27/1/5 +f 28/1/1 29/1/1 26/1/1 +f 30/1/7 26/1/7 29/1/7 +f 25/1/1 28/1/1 26/1/1 +f 27/1/6 26/1/6 30/1/6 +f 31/1/1 32/1/1 29/1/1 +f 33/1/8 29/1/8 32/1/8 +f 28/1/1 31/1/1 29/1/1 +f 30/1/7 29/1/7 33/1/7 +f 34/1/1 35/1/1 32/1/1 +f 36/1/9 32/1/9 35/1/9 +f 37/1/1 34/1/1 32/1/1 +f 31/1/1 37/1/1 32/1/1 +f 33/1/8 32/1/8 36/1/8 +f 38/1/1 39/1/1 35/1/1 +f 40/1/10 35/1/10 39/1/10 +f 34/1/1 38/1/1 35/1/1 +f 36/1/9 35/1/9 40/1/9 +f 41/1/1 42/1/1 39/1/1 +f 43/1/11 39/1/11 42/1/11 +f 38/1/1 41/1/1 39/1/1 +f 40/1/10 39/1/10 43/1/10 +f 44/1/1 41/1/1 45/1/1 +f 46/1/12 42/1/12 47/1/12 +f 44/1/1 45/1/1 48/1/1 +f 43/1/11 42/1/11 46/1/11 +f 49/1/1 47/1/1 42/1/1 +f 50/1/13 47/1/13 49/1/13 +f 46/1/12 47/1/12 50/1/12 +f 51/1/1 49/1/1 42/1/1 +f 52/1/14 49/1/14 51/1/14 +f 44/1/1 42/1/1 41/1/1 +f 50/1/13 49/1/13 52/1/13 +f 44/1/1 53/1/1 51/1/1 +f 54/1/15 51/1/15 53/1/15 +f 44/1/1 51/1/1 42/1/1 +f 52/1/14 51/1/14 54/1/14 +f 55/1/1 56/1/1 53/1/1 +f 57/1/16 53/1/16 56/1/16 +f 44/1/1 55/1/1 53/1/1 +f 54/1/15 53/1/15 57/1/15 +f 58/1/1 59/1/1 56/1/1 +f 60/1/17 56/1/17 59/1/17 +f 55/1/1 58/1/1 56/1/1 +f 57/1/16 56/1/16 60/1/16 +f 61/1/1 62/1/1 59/1/1 +f 63/1/18 59/1/18 62/1/18 +f 58/1/1 61/1/1 59/1/1 +f 60/1/17 59/1/17 63/1/17 +f 64/1/1 65/1/1 62/1/1 +f 66/1/19 62/1/19 65/1/19 +f 61/1/1 64/1/1 62/1/1 +f 63/1/18 62/1/18 66/1/18 +f 67/1/1 68/1/1 65/1/1 +f 69/1/20 65/1/20 68/1/20 +f 64/1/1 67/1/1 65/1/1 +f 66/1/19 65/1/19 69/1/19 +f 70/1/1 71/1/1 68/1/1 +f 72/1/21 68/1/21 71/1/21 +f 67/1/1 70/1/1 68/1/1 +f 69/1/20 68/1/20 72/1/20 +f 73/1/1 74/1/1 71/1/1 +f 75/1/22 71/1/22 74/1/22 +f 70/1/1 73/1/1 71/1/1 +f 72/1/21 71/1/21 75/1/21 +f 76/1/1 77/1/1 74/1/1 +f 78/1/23 74/1/23 77/1/23 +f 73/1/1 76/1/1 74/1/1 +f 75/1/22 74/1/22 78/1/22 +f 79/1/1 80/1/1 77/1/1 +f 81/1/24 77/1/24 80/1/24 +f 76/1/1 79/1/1 77/1/1 +f 78/1/23 77/1/23 81/1/23 +f 82/1/1 83/1/1 80/1/1 +f 84/1/25 80/1/25 83/1/25 +f 79/1/1 82/1/1 80/1/1 +f 81/1/24 80/1/24 84/1/24 +f 85/1/1 86/1/1 83/1/1 +f 87/1/26 83/1/26 86/1/26 +f 82/1/1 85/1/1 83/1/1 +f 84/1/25 83/1/25 87/1/25 +f 88/1/1 89/1/1 86/1/1 +f 90/1/27 86/1/27 89/1/27 +f 85/1/1 88/1/1 86/1/1 +f 87/1/26 86/1/26 90/1/26 +f 91/1/1 92/1/1 89/1/1 +f 93/1/28 89/1/28 92/1/28 +f 88/1/1 91/1/1 89/1/1 +f 90/1/27 89/1/27 93/1/27 +f 94/1/1 95/1/1 92/1/1 +f 96/1/29 92/1/29 95/1/29 +f 91/1/1 94/1/1 92/1/1 +f 93/1/28 92/1/28 96/1/28 +f 97/1/1 98/1/1 95/1/1 +f 99/1/30 95/1/30 98/1/30 +f 94/1/1 97/1/1 95/1/1 +f 96/1/29 95/1/29 99/1/29 +f 100/1/1 101/1/1 98/1/1 +f 102/1/31 98/1/31 101/1/31 +f 97/1/1 100/1/1 98/1/1 +f 99/1/30 98/1/30 102/1/30 +f 103/1/1 104/1/1 101/1/1 +f 105/1/32 101/1/32 104/1/32 +f 100/1/1 103/1/1 101/1/1 +f 102/1/31 101/1/31 105/1/31 +f 106/1/1 107/1/1 104/1/1 +f 108/1/33 104/1/33 107/1/33 +f 103/1/1 106/1/1 104/1/1 +f 105/1/32 104/1/32 108/1/32 +f 109/1/1 110/1/1 107/1/1 +f 111/1/34 107/1/34 110/1/34 +f 106/1/1 109/1/1 107/1/1 +f 108/1/33 107/1/33 111/1/33 +f 112/1/1 113/1/1 110/1/1 +f 114/1/35 110/1/35 113/1/35 +f 109/1/1 115/1/1 110/1/1 +f 116/1/1 110/1/1 115/1/1 +f 116/1/1 112/1/1 110/1/1 +f 111/1/34 110/1/34 114/1/34 +f 112/1/1 117/1/1 113/1/1 +f 118/1/36 113/1/36 117/1/36 +f 114/1/35 113/1/35 118/1/35 +f 112/1/1 119/1/1 117/1/1 +f 120/1/37 117/1/37 119/1/37 +f 118/1/36 117/1/36 120/1/36 +f 112/1/1 121/1/1 119/1/1 +f 122/1/38 119/1/38 121/1/38 +f 120/1/37 119/1/37 122/1/37 +f 112/1/1 123/1/1 121/1/1 +f 124/1/39 121/1/39 123/1/39 +f 122/1/38 121/1/38 124/1/38 +f 112/1/1 125/1/1 123/1/1 +f 126/1/40 123/1/40 125/1/40 +f 124/1/39 123/1/39 126/1/39 +f 112/1/1 127/1/1 125/1/1 +f 128/1/41 125/1/41 127/1/41 +f 126/1/40 125/1/40 128/1/40 +f 112/1/1 129/1/1 127/1/1 +f 130/1/42 127/1/42 129/1/42 +f 128/1/41 127/1/41 130/1/41 +f 112/1/1 131/1/1 129/1/1 +f 132/1/43 129/1/43 131/1/43 +f 130/1/42 129/1/42 132/1/42 +f 112/1/1 133/1/1 131/1/1 +f 134/1/44 131/1/44 133/1/44 +f 132/1/43 131/1/43 134/1/43 +f 112/1/1 135/1/1 133/1/1 +f 136/1/45 133/1/45 135/1/45 +f 134/1/44 133/1/44 136/1/44 +f 112/1/1 137/1/1 135/1/1 +f 138/1/46 135/1/46 137/1/46 +f 136/1/45 135/1/45 138/1/45 +f 112/1/1 139/1/1 137/1/1 +f 140/1/47 137/1/47 139/1/47 +f 138/1/46 137/1/46 140/1/46 +f 112/1/1 141/1/1 139/1/1 +f 142/1/48 139/1/48 141/1/48 +f 140/1/47 139/1/47 142/1/47 +f 112/1/1 143/1/1 141/1/1 +f 144/1/49 141/1/49 143/1/49 +f 142/1/48 141/1/48 144/1/48 +f 112/1/1 145/1/1 143/1/1 +f 146/1/50 143/1/50 145/1/50 +f 144/1/49 143/1/49 146/1/49 +f 112/1/1 147/1/1 145/1/1 +f 148/1/51 145/1/51 147/1/51 +f 146/1/50 145/1/50 148/1/50 +f 112/1/1 149/1/1 147/1/1 +f 150/1/52 147/1/52 149/1/52 +f 148/1/51 147/1/51 150/1/51 +f 149/1/1 112/1/1 151/1/1 +f 152/1/53 149/1/53 153/1/53 +f 150/1/52 149/1/52 152/1/52 +f 149/1/1 151/1/1 154/1/1 +f 155/1/54 153/1/54 156/1/54 +f 152/1/53 153/1/53 155/1/53 +f 149/1/1 154/1/1 157/1/1 +f 158/1/55 156/1/55 159/1/55 +f 155/1/54 156/1/54 158/1/54 +f 157/1/1 153/1/1 149/1/1 +f 160/1/56 159/1/56 161/1/56 +f 158/1/55 159/1/55 160/1/55 +f 162/1/1 163/1/1 161/1/1 +f 164/1/57 161/1/57 163/1/57 +f 162/1/1 161/1/1 159/1/1 +f 157/1/1 156/1/1 153/1/1 +f 156/1/1 157/1/1 162/1/1 +f 162/1/1 159/1/1 156/1/1 +f 160/1/56 161/1/56 164/1/56 +f 165/1/58 163/1/58 162/1/58 +f 164/1/57 163/1/57 165/1/57 +f 166/1/1 167/1/1 162/1/1 +f 168/1/59 162/1/59 167/1/59 +f 157/1/1 166/1/1 162/1/1 +f 165/1/58 162/1/58 168/1/58 +f 169/1/1 170/1/1 167/1/1 +f 171/1/60 167/1/60 170/1/60 +f 166/1/1 169/1/1 167/1/1 +f 168/1/59 167/1/59 171/1/59 +f 172/1/1 173/1/1 170/1/1 +f 174/1/61 170/1/61 173/1/61 +f 169/1/1 172/1/1 170/1/1 +f 171/1/60 170/1/60 174/1/60 +f 175/1/1 176/1/1 173/1/1 +f 177/1/62 173/1/62 176/1/62 +f 172/1/1 175/1/1 173/1/1 +f 174/1/61 173/1/61 177/1/61 +f 178/1/1 179/1/1 176/1/1 +f 180/1/52 176/1/52 179/1/52 +f 175/1/1 178/1/1 176/1/1 +f 177/1/62 176/1/62 180/1/62 +f 181/1/1 182/1/1 179/1/1 +f 183/1/63 179/1/63 182/1/63 +f 178/1/1 181/1/1 179/1/1 +f 180/1/52 179/1/52 183/1/52 +f 184/1/1 185/1/1 182/1/1 +f 186/1/64 182/1/64 185/1/64 +f 181/1/1 184/1/1 182/1/1 +f 183/1/63 182/1/63 186/1/63 +f 187/1/1 188/1/1 185/1/1 +f 189/1/65 185/1/65 188/1/65 +f 184/1/1 187/1/1 185/1/1 +f 186/1/64 185/1/64 189/1/64 +f 190/1/1 191/1/1 188/1/1 +f 192/1/66 188/1/66 191/1/66 +f 187/1/1 190/1/1 188/1/1 +f 189/1/65 188/1/65 192/1/65 +f 193/1/1 194/1/1 191/1/1 +f 195/1/67 191/1/67 194/1/67 +f 196/1/1 193/1/1 191/1/1 +f 197/1/1 196/1/1 191/1/1 +f 198/1/1 197/1/1 191/1/1 +f 199/1/1 198/1/1 191/1/1 +f 200/1/1 199/1/1 191/1/1 +f 201/1/1 200/1/1 191/1/1 +f 202/1/1 201/1/1 191/1/1 +f 203/1/1 202/1/1 191/1/1 +f 204/1/1 203/1/1 191/1/1 +f 190/1/1 204/1/1 191/1/1 +f 192/1/66 191/1/66 195/1/66 +f 205/1/1 206/1/1 194/1/1 +f 207/1/66 194/1/66 206/1/66 +f 193/1/1 205/1/1 194/1/1 +f 195/1/67 194/1/67 207/1/67 +f 208/1/1 209/1/1 206/1/1 +f 210/1/65 206/1/65 209/1/65 +f 205/1/1 208/1/1 206/1/1 +f 207/1/66 206/1/66 210/1/66 +f 211/1/1 212/1/1 209/1/1 +f 213/1/64 209/1/64 212/1/64 +f 208/1/1 211/1/1 209/1/1 +f 210/1/65 209/1/65 213/1/65 +f 214/1/1 215/1/1 212/1/1 +f 216/1/63 212/1/63 215/1/63 +f 211/1/1 214/1/1 212/1/1 +f 213/1/64 212/1/64 216/1/64 +f 217/1/1 218/1/1 215/1/1 +f 219/1/52 215/1/52 218/1/52 +f 214/1/1 217/1/1 215/1/1 +f 216/1/63 215/1/63 219/1/63 +f 220/1/1 221/1/1 218/1/1 +f 222/1/62 218/1/62 221/1/62 +f 217/1/1 220/1/1 218/1/1 +f 219/1/52 218/1/52 222/1/52 +f 223/1/1 224/1/1 221/1/1 +f 225/1/61 221/1/61 224/1/61 +f 220/1/1 223/1/1 221/1/1 +f 222/1/62 221/1/62 225/1/62 +f 226/1/1 227/1/1 224/1/1 +f 228/1/60 224/1/60 227/1/60 +f 223/1/1 226/1/1 224/1/1 +f 225/1/61 224/1/61 228/1/61 +f 229/1/1 230/1/1 227/1/1 +f 231/1/59 227/1/59 230/1/59 +f 226/1/1 229/1/1 227/1/1 +f 228/1/60 227/1/60 231/1/60 +f 232/1/58 230/1/58 229/1/58 +f 231/1/59 230/1/59 232/1/59 +f 233/1/68 229/1/68 226/1/68 +f 232/1/58 229/1/58 233/1/58 +f 234/1/69 226/1/69 223/1/69 +f 233/1/68 226/1/68 234/1/68 +f 235/1/70 223/1/70 220/1/70 +f 234/1/69 223/1/69 235/1/69 +f 236/1/71 220/1/71 217/1/71 +f 235/1/70 220/1/70 236/1/70 +f 237/1/72 217/1/72 214/1/72 +f 236/1/71 217/1/71 237/1/71 +f 238/1/73 214/1/73 211/1/73 +f 237/1/72 214/1/72 238/1/72 +f 239/1/74 211/1/74 208/1/74 +f 238/1/73 211/1/73 239/1/73 +f 240/1/75 208/1/75 205/1/75 +f 239/1/74 208/1/74 240/1/74 +f 241/1/76 205/1/76 193/1/76 +f 240/1/75 205/1/75 241/1/75 +f 242/1/77 193/1/77 196/1/77 +f 241/1/78 193/1/78 242/1/78 +f 197/1/1 243/1/1 196/1/1 +f 244/1/79 196/1/79 243/1/79 +f 242/1/77 196/1/77 244/1/77 +f 197/1/1 245/1/1 243/1/1 +f 246/1/80 243/1/80 245/1/80 +f 244/1/79 243/1/79 246/1/79 +f 247/1/1 248/1/1 245/1/1 +f 249/1/81 245/1/81 248/1/81 +f 250/1/1 247/1/1 245/1/1 +f 251/1/1 250/1/1 245/1/1 +f 197/1/1 251/1/1 245/1/1 +f 246/1/80 245/1/80 249/1/80 +f 252/1/1 253/1/1 248/1/1 +f 254/1/82 248/1/82 253/1/82 +f 255/1/1 252/1/1 248/1/1 +f 256/1/1 255/1/1 248/1/1 +f 257/1/1 256/1/1 248/1/1 +f 258/1/1 257/1/1 248/1/1 +f 247/1/1 258/1/1 248/1/1 +f 249/1/81 248/1/81 254/1/81 +f 259/1/1 260/1/1 253/1/1 +f 261/1/83 253/1/83 260/1/83 +f 262/1/1 259/1/1 253/1/1 +f 252/1/1 262/1/1 253/1/1 +f 254/1/82 253/1/82 261/1/82 +f 263/1/1 264/1/1 260/1/1 +f 265/1/84 260/1/84 264/1/84 +f 259/1/1 263/1/1 260/1/1 +f 261/1/83 260/1/83 265/1/83 +f 266/1/1 267/1/1 264/1/1 +f 268/1/85 264/1/85 267/1/85 +f 263/1/1 266/1/1 264/1/1 +f 265/1/86 264/1/86 268/1/86 +f 266/1/1 269/1/1 267/1/1 +f 270/1/87 267/1/87 269/1/87 +f 268/1/88 267/1/88 270/1/88 +f 266/1/1 271/1/1 269/1/1 +f 272/1/89 269/1/89 271/1/89 +f 270/1/90 269/1/90 272/1/90 +f 266/1/1 273/1/1 271/1/1 +f 274/1/91 271/1/91 273/1/91 +f 272/1/89 271/1/89 274/1/89 +f 266/1/1 275/1/1 273/1/1 +f 276/1/92 273/1/92 275/1/92 +f 274/1/91 273/1/91 276/1/91 +f 266/1/1 115/1/1 275/1/1 +f 277/1/93 275/1/93 115/1/93 +f 276/1/94 275/1/94 277/1/94 +f 278/1/95 115/1/95 109/1/95 +f 279/1/1 116/1/1 115/1/1 +f 280/1/1 279/1/1 115/1/1 +f 281/1/1 280/1/1 115/1/1 +f 282/1/1 281/1/1 115/1/1 +f 283/1/1 282/1/1 115/1/1 +f 284/1/1 283/1/1 115/1/1 +f 266/1/1 284/1/1 115/1/1 +f 277/1/96 115/1/96 278/1/96 +f 285/1/97 109/1/97 106/1/97 +f 278/1/95 109/1/95 285/1/95 +f 286/1/98 106/1/98 103/1/98 +f 285/1/99 106/1/99 286/1/99 +f 287/1/100 103/1/100 100/1/100 +f 286/1/101 103/1/101 287/1/101 +f 288/1/102 100/1/102 97/1/102 +f 287/1/100 100/1/100 288/1/100 +f 289/1/103 97/1/103 94/1/103 +f 288/1/102 97/1/102 289/1/102 +f 290/1/104 94/1/104 91/1/104 +f 289/1/105 94/1/105 290/1/105 +f 291/1/106 91/1/106 88/1/106 +f 290/1/107 91/1/107 291/1/107 +f 292/1/108 88/1/108 85/1/108 +f 291/1/109 88/1/109 292/1/109 +f 293/1/110 85/1/110 82/1/110 +f 292/1/108 85/1/108 293/1/108 +f 294/1/111 82/1/111 79/1/111 +f 293/1/110 82/1/110 294/1/110 +f 295/1/112 79/1/112 76/1/112 +f 294/1/111 79/1/111 295/1/111 +f 296/1/113 76/1/113 73/1/113 +f 295/1/112 76/1/112 296/1/112 +f 297/1/114 73/1/114 70/1/114 +f 296/1/113 73/1/113 297/1/113 +f 298/1/115 70/1/115 67/1/115 +f 297/1/114 70/1/114 298/1/114 +f 299/1/116 67/1/116 64/1/116 +f 298/1/115 67/1/115 299/1/115 +f 300/1/117 64/1/117 61/1/117 +f 299/1/116 64/1/116 300/1/116 +f 301/1/118 61/1/118 58/1/118 +f 300/1/117 61/1/117 301/1/117 +f 302/1/119 58/1/119 55/1/119 +f 301/1/120 58/1/120 302/1/120 +f 303/1/121 55/1/121 44/1/121 +f 302/1/122 55/1/122 303/1/122 +f 304/1/123 44/1/123 305/1/123 +f 303/1/121 44/1/121 304/1/121 +f 306/1/124 305/1/124 307/1/124 +f 304/1/123 305/1/123 306/1/123 +f 48/1/1 308/1/1 307/1/1 +f 309/1/125 307/1/125 308/1/125 +f 48/1/1 307/1/1 305/1/1 +f 48/1/1 305/1/1 44/1/1 +f 306/1/124 307/1/124 309/1/124 +f 310/1/12 308/1/12 48/1/12 +f 309/1/125 308/1/125 310/1/125 +f 311/1/1 312/1/1 48/1/1 +f 313/1/126 48/1/126 312/1/126 +f 45/1/1 311/1/1 48/1/1 +f 310/1/12 48/1/12 313/1/12 +f 314/1/1 315/1/1 312/1/1 +f 316/1/127 312/1/127 315/1/127 +f 311/1/1 314/1/1 312/1/1 +f 313/1/126 312/1/126 316/1/126 +f 317/1/1 318/1/1 315/1/1 +f 319/1/128 315/1/128 318/1/128 +f 314/1/1 317/1/1 315/1/1 +f 316/1/127 315/1/127 319/1/127 +f 320/1/1 321/1/1 318/1/1 +f 322/1/129 318/1/129 321/1/129 +f 317/1/1 320/1/1 318/1/1 +f 319/1/128 318/1/128 322/1/128 +f 323/1/1 324/1/1 321/1/1 +f 325/1/120 321/1/120 324/1/120 +f 320/1/1 323/1/1 321/1/1 +f 322/1/129 321/1/129 325/1/129 +f 326/1/1 327/1/1 324/1/1 +f 328/1/130 324/1/130 327/1/130 +f 323/1/1 326/1/1 324/1/1 +f 325/1/120 324/1/120 328/1/120 +f 329/1/1 330/1/1 327/1/1 +f 331/1/131 327/1/131 330/1/131 +f 326/1/1 329/1/1 327/1/1 +f 328/1/130 327/1/130 331/1/130 +f 332/1/1 333/1/1 330/1/1 +f 334/1/132 330/1/132 333/1/132 +f 329/1/1 332/1/1 330/1/1 +f 331/1/131 330/1/131 334/1/131 +f 335/1/1 6/1/1 333/1/1 +f 336/1/133 333/1/133 6/1/133 +f 337/1/1 335/1/1 333/1/1 +f 332/1/1 337/1/1 333/1/1 +f 334/1/132 333/1/132 336/1/132 +f 338/1/134 6/1/134 5/1/134 +f 339/1/1 7/1/1 6/1/1 +f 340/1/1 339/1/1 6/1/1 +f 341/1/1 340/1/1 6/1/1 +f 342/1/1 341/1/1 6/1/1 +f 343/1/1 342/1/1 6/1/1 +f 335/1/1 343/1/1 6/1/1 +f 336/1/133 6/1/133 338/1/133 +f 8/1/1 344/1/1 5/1/1 +f 345/1/133 5/1/133 344/1/133 +f 338/1/134 5/1/134 345/1/134 +f 346/1/1 347/1/1 344/1/1 +f 348/1/132 344/1/132 347/1/132 +f 8/1/1 346/1/1 344/1/1 +f 345/1/133 344/1/133 348/1/133 +f 349/1/1 350/1/1 347/1/1 +f 351/1/131 347/1/131 350/1/131 +f 346/1/1 349/1/1 347/1/1 +f 348/1/132 347/1/132 351/1/132 +f 352/1/1 353/1/1 350/1/1 +f 354/1/130 350/1/130 353/1/130 +f 349/1/1 352/1/1 350/1/1 +f 351/1/131 350/1/131 354/1/131 +f 355/1/1 356/1/1 353/1/1 +f 357/1/120 353/1/120 356/1/120 +f 352/1/1 355/1/1 353/1/1 +f 354/1/130 353/1/130 357/1/130 +f 358/1/1 359/1/1 356/1/1 +f 360/1/129 356/1/129 359/1/129 +f 355/1/1 358/1/1 356/1/1 +f 357/1/120 356/1/120 360/1/120 +f 361/1/1 362/1/1 359/1/1 +f 363/1/128 359/1/128 362/1/128 +f 358/1/1 361/1/1 359/1/1 +f 360/1/129 359/1/129 363/1/129 +f 364/1/1 365/1/1 362/1/1 +f 366/1/127 362/1/127 365/1/127 +f 361/1/1 364/1/1 362/1/1 +f 363/1/128 362/1/128 366/1/128 +f 367/1/1 368/1/1 365/1/1 +f 369/1/126 365/1/126 368/1/126 +f 364/1/1 367/1/1 365/1/1 +f 366/1/127 365/1/127 369/1/127 +f 370/1/12 368/1/12 367/1/12 +f 369/1/126 368/1/126 370/1/126 +f 371/1/11 367/1/11 364/1/11 +f 370/1/12 367/1/12 371/1/12 +f 372/1/10 364/1/10 361/1/10 +f 372/1/11 371/1/11 364/1/11 +f 373/1/9 361/1/9 358/1/9 +f 372/1/10 361/1/10 373/1/10 +f 374/1/8 358/1/8 355/1/8 +f 373/1/9 358/1/9 374/1/9 +f 375/1/7 355/1/7 352/1/7 +f 374/1/8 355/1/8 375/1/8 +f 376/1/6 352/1/6 349/1/6 +f 375/1/7 352/1/7 376/1/7 +f 377/1/5 349/1/5 346/1/5 +f 376/1/6 349/1/6 377/1/6 +f 378/1/4 346/1/4 8/1/4 +f 377/1/5 346/1/5 378/1/5 +f 378/1/4 8/1/4 9/1/4 +f 379/1/1 380/1/1 381/1/1 +f 382/1/134 381/1/134 380/1/134 +f 383/1/1 381/1/1 384/1/1 +f 385/1/135 384/1/135 381/1/135 +f 386/1/1 379/1/1 381/1/1 +f 387/1/1 386/1/1 381/1/1 +f 388/1/1 387/1/1 381/1/1 +f 389/1/1 388/1/1 381/1/1 +f 383/1/1 389/1/1 381/1/1 +f 385/1/136 381/1/136 382/1/136 +f 390/1/1 391/1/1 380/1/1 +f 392/1/137 380/1/137 391/1/137 +f 379/1/1 390/1/1 380/1/1 +f 382/1/134 380/1/134 392/1/134 +f 390/1/1 393/1/1 391/1/1 +f 394/1/138 391/1/138 393/1/138 +f 392/1/139 391/1/139 394/1/139 +f 390/1/1 395/1/1 393/1/1 +f 396/1/140 393/1/140 395/1/140 +f 394/1/104 393/1/104 396/1/104 +f 390/1/1 397/1/1 395/1/1 +f 398/1/141 395/1/141 397/1/141 +f 396/1/142 395/1/142 398/1/142 +f 390/1/1 399/1/1 397/1/1 +f 400/1/88 397/1/88 399/1/88 +f 398/1/143 397/1/143 400/1/143 +f 401/1/1 402/1/1 399/1/1 +f 403/1/144 399/1/144 402/1/144 +f 401/1/1 399/1/1 390/1/1 +f 400/1/145 399/1/145 403/1/145 +f 404/1/1 405/1/1 402/1/1 +f 406/1/146 402/1/146 405/1/146 +f 401/1/1 404/1/1 402/1/1 +f 403/1/147 402/1/147 406/1/147 +f 407/1/1 408/1/1 405/1/1 +f 409/1/148 405/1/148 408/1/148 +f 404/1/1 407/1/1 405/1/1 +f 406/1/149 405/1/149 409/1/149 +f 154/1/1 151/1/1 408/1/1 +f 410/1/150 408/1/150 151/1/150 +f 407/1/1 154/1/1 408/1/1 +f 409/1/73 408/1/73 410/1/73 +f 411/1/58 151/1/58 112/1/58 +f 410/1/150 151/1/150 411/1/150 +f 412/1/151 112/1/151 116/1/151 +f 412/1/58 411/1/58 112/1/58 +f 413/1/152 116/1/152 279/1/152 +f 412/1/151 116/1/151 413/1/151 +f 414/1/153 279/1/153 280/1/153 +f 413/1/152 279/1/152 414/1/152 +f 415/1/154 280/1/154 281/1/154 +f 414/1/153 280/1/153 415/1/153 +f 416/1/155 281/1/155 282/1/155 +f 415/1/156 281/1/156 416/1/156 +f 417/1/157 282/1/157 283/1/157 +f 416/1/155 282/1/155 417/1/155 +f 418/1/158 283/1/158 284/1/158 +f 417/1/159 283/1/159 418/1/159 +f 419/1/160 284/1/160 266/1/160 +f 418/1/158 284/1/158 419/1/158 +f 420/1/35 266/1/35 263/1/35 +f 419/1/161 266/1/161 420/1/161 +f 421/1/162 263/1/162 259/1/162 +f 420/1/35 263/1/35 421/1/35 +f 422/1/163 259/1/163 262/1/163 +f 421/1/164 259/1/164 422/1/164 +f 423/1/165 262/1/165 252/1/165 +f 422/1/163 262/1/163 423/1/163 +f 424/1/166 252/1/166 255/1/166 +f 423/1/167 252/1/167 424/1/167 +f 425/1/168 255/1/168 256/1/168 +f 424/1/166 255/1/166 425/1/166 +f 426/1/169 256/1/169 257/1/169 +f 425/1/170 256/1/170 426/1/170 +f 427/1/171 257/1/171 258/1/171 +f 426/1/169 257/1/169 427/1/169 +f 428/1/172 258/1/172 247/1/172 +f 427/1/171 258/1/171 428/1/171 +f 429/1/12 247/1/12 250/1/12 +f 428/1/172 247/1/172 429/1/172 +f 430/1/1 431/1/1 250/1/1 +f 432/1/173 250/1/173 431/1/173 +f 251/1/1 430/1/1 250/1/1 +f 429/1/12 250/1/12 432/1/12 +f 433/1/1 434/1/1 431/1/1 +f 435/1/174 431/1/174 434/1/174 +f 430/1/1 433/1/1 431/1/1 +f 432/1/173 431/1/173 435/1/173 +f 436/1/1 384/1/1 434/1/1 +f 437/1/175 434/1/175 384/1/175 +f 433/1/1 436/1/1 434/1/1 +f 435/1/174 434/1/174 437/1/174 +f 436/1/1 383/1/1 384/1/1 +f 437/1/175 384/1/175 385/1/175 +f 438/1/67 390/1/67 379/1/67 +f 439/1/156 401/1/156 390/1/156 +f 439/1/156 390/1/156 438/1/156 +f 440/1/176 379/1/176 386/1/176 +f 438/1/67 379/1/67 440/1/67 +f 441/1/177 386/1/177 387/1/177 +f 440/1/176 386/1/176 441/1/176 +f 442/1/178 387/1/178 388/1/178 +f 441/1/179 387/1/179 442/1/179 +f 443/1/180 388/1/180 389/1/180 +f 442/1/181 388/1/181 443/1/181 +f 444/1/182 389/1/182 383/1/182 +f 443/1/183 389/1/183 444/1/183 +f 445/1/184 383/1/184 436/1/184 +f 444/1/185 383/1/185 445/1/185 +f 446/1/4 436/1/4 433/1/4 +f 445/1/184 436/1/184 446/1/184 +f 447/1/18 433/1/18 430/1/18 +f 446/1/186 433/1/186 447/1/186 +f 448/1/10 430/1/10 251/1/10 +f 447/1/18 430/1/18 448/1/18 +f 449/1/12 251/1/12 197/1/12 +f 448/1/10 251/1/10 449/1/10 +f 450/1/173 197/1/173 198/1/173 +f 450/1/12 449/1/12 197/1/12 +f 451/1/174 198/1/174 199/1/174 +f 450/1/173 198/1/173 451/1/173 +f 452/1/175 199/1/175 200/1/175 +f 451/1/187 199/1/187 452/1/187 +f 453/1/136 200/1/136 201/1/136 +f 452/1/175 200/1/175 453/1/175 +f 454/1/188 201/1/188 202/1/188 +f 453/1/136 201/1/136 454/1/136 +f 455/1/189 202/1/189 203/1/189 +f 454/1/190 202/1/190 455/1/190 +f 456/1/191 203/1/191 204/1/191 +f 455/1/189 203/1/189 456/1/189 +f 457/1/192 204/1/192 190/1/192 +f 456/1/191 204/1/191 457/1/191 +f 458/1/95 190/1/95 187/1/95 +f 457/1/193 190/1/193 458/1/193 +f 459/1/194 187/1/194 184/1/194 +f 458/1/95 187/1/95 459/1/95 +f 460/1/195 184/1/195 181/1/195 +f 459/1/196 184/1/196 460/1/196 +f 461/1/197 181/1/197 178/1/197 +f 460/1/195 181/1/195 461/1/195 +f 462/1/198 178/1/198 175/1/198 +f 461/1/197 178/1/197 462/1/197 +f 463/1/199 175/1/199 172/1/199 +f 462/1/200 175/1/200 463/1/200 +f 464/1/201 172/1/201 169/1/201 +f 463/1/199 172/1/199 464/1/199 +f 465/1/202 169/1/202 166/1/202 +f 464/1/201 169/1/201 465/1/201 +f 466/1/203 166/1/203 157/1/203 +f 465/1/204 166/1/204 466/1/204 +f 467/1/58 157/1/58 154/1/58 +f 466/1/203 157/1/203 467/1/203 +f 468/1/151 154/1/151 407/1/151 +f 467/1/58 154/1/58 468/1/58 +f 469/1/205 407/1/205 404/1/205 +f 468/1/151 407/1/151 469/1/151 +f 470/1/153 404/1/153 401/1/153 +f 469/1/152 404/1/152 470/1/152 +f 470/1/153 401/1/153 439/1/153 +f 471/1/173 41/1/173 38/1/173 +f 472/1/12 45/1/12 41/1/12 +f 472/1/12 41/1/12 471/1/12 +f 473/1/174 38/1/174 34/1/174 +f 471/1/173 38/1/173 473/1/173 +f 474/1/175 34/1/175 37/1/175 +f 473/1/174 34/1/174 474/1/174 +f 475/1/136 37/1/136 31/1/136 +f 474/1/175 37/1/175 475/1/175 +f 476/1/188 31/1/188 28/1/188 +f 475/1/136 31/1/136 476/1/136 +f 477/1/206 28/1/206 25/1/206 +f 476/1/188 28/1/188 477/1/188 +f 478/1/207 25/1/207 22/1/207 +f 477/1/206 25/1/206 478/1/206 +f 479/1/208 22/1/208 19/1/208 +f 478/1/209 22/1/209 479/1/209 +f 480/1/210 19/1/210 15/1/210 +f 479/1/208 19/1/208 480/1/208 +f 481/1/211 15/1/211 18/1/211 +f 480/1/210 15/1/210 481/1/210 +f 482/1/212 18/1/212 1/1/212 +f 481/1/213 18/1/213 482/1/213 +f 483/1/200 1/1/200 10/1/200 +f 482/1/212 1/1/212 483/1/212 +f 484/1/199 10/1/199 11/1/199 +f 483/1/200 10/1/200 484/1/200 +f 485/1/201 11/1/201 12/1/201 +f 484/1/199 11/1/199 485/1/199 +f 486/1/204 12/1/204 13/1/204 +f 485/1/201 12/1/201 486/1/201 +f 487/1/203 13/1/203 14/1/203 +f 486/1/204 13/1/204 487/1/204 +f 488/1/58 14/1/58 7/1/58 +f 487/1/203 14/1/203 488/1/203 +f 489/1/151 7/1/151 339/1/151 +f 489/1/58 488/1/58 7/1/58 +f 490/1/152 339/1/152 340/1/152 +f 489/1/151 339/1/151 490/1/151 +f 491/1/153 340/1/153 341/1/153 +f 490/1/152 340/1/152 491/1/152 +f 492/1/156 341/1/156 342/1/156 +f 491/1/153 341/1/153 492/1/153 +f 493/1/155 342/1/155 343/1/155 +f 492/1/156 342/1/156 493/1/156 +f 494/1/159 343/1/159 335/1/159 +f 493/1/155 343/1/155 494/1/155 +f 495/1/214 335/1/214 337/1/214 +f 494/1/215 335/1/215 495/1/215 +f 496/1/216 337/1/216 332/1/216 +f 495/1/214 337/1/214 496/1/214 +f 497/1/217 332/1/217 329/1/217 +f 496/1/216 332/1/216 497/1/216 +f 498/1/218 329/1/218 326/1/218 +f 497/1/217 329/1/217 498/1/217 +f 499/1/219 326/1/219 323/1/219 +f 498/1/218 326/1/218 499/1/218 +f 500/1/166 323/1/166 320/1/166 +f 499/1/165 323/1/165 500/1/165 +f 501/1/168 320/1/168 317/1/168 +f 500/1/166 320/1/166 501/1/166 +f 502/1/169 317/1/169 314/1/169 +f 501/1/168 317/1/168 502/1/168 +f 503/1/171 314/1/171 311/1/171 +f 502/1/169 314/1/169 503/1/169 +f 504/1/172 311/1/172 45/1/172 +f 503/1/171 311/1/171 504/1/171 +f 504/1/172 45/1/172 472/1/172 +f 372/2/220 370/3/220 371/4/220 +f 372/2/220 369/5/220 370/3/220 +f 373/6/220 366/7/220 369/5/220 +f 372/2/220 373/6/220 369/5/220 +f 374/8/220 363/9/220 366/7/220 +f 373/6/220 374/8/220 366/7/220 +f 375/10/220 360/11/220 363/9/220 +f 374/8/220 375/10/220 363/9/220 +f 376/12/220 357/13/220 360/11/220 +f 375/10/220 376/12/220 360/11/220 +f 377/14/220 354/15/220 357/13/220 +f 376/12/220 377/14/220 357/13/220 +f 378/16/220 351/17/220 354/15/220 +f 377/14/220 378/16/220 354/15/220 +f 9/18/220 348/19/220 351/17/220 +f 378/16/220 9/18/220 351/17/220 +f 4/20/220 345/21/220 348/19/220 +f 9/18/220 4/20/220 348/19/220 +f 494/22/220 338/23/220 345/21/220 +f 4/20/220 17/24/220 345/21/220 +f 488/25/220 345/21/220 17/24/220 +f 489/26/220 345/21/220 488/25/220 +f 493/27/220 494/22/220 345/21/220 +f 492/28/220 493/27/220 345/21/220 +f 491/29/220 492/28/220 345/21/220 +f 490/30/220 491/29/220 345/21/220 +f 489/26/220 490/30/220 345/21/220 +f 496/31/220 336/32/220 338/23/220 +f 495/33/220 496/31/220 338/23/220 +f 494/22/220 495/33/220 338/23/220 +f 497/34/220 334/35/220 336/32/220 +f 496/31/220 497/34/220 336/32/220 +f 498/36/220 331/37/220 334/35/220 +f 497/34/220 498/36/220 334/35/220 +f 499/38/220 328/39/220 331/37/220 +f 498/36/220 499/38/220 331/37/220 +f 500/40/220 325/41/220 328/39/220 +f 499/38/220 500/40/220 328/39/220 +f 501/42/220 322/43/220 325/41/220 +f 500/40/220 501/42/220 325/41/220 +f 503/44/220 319/45/220 322/43/220 +f 502/46/220 503/44/220 322/43/220 +f 501/42/220 502/46/220 322/43/220 +f 504/47/220 316/48/220 319/45/220 +f 503/44/220 504/47/220 319/45/220 +f 472/49/220 313/50/220 316/48/220 +f 504/47/220 472/49/220 316/48/220 +f 57/51/220 472/49/220 471/52/220 +f 57/51/220 471/52/220 46/53/220 +f 309/54/220 310/55/220 313/50/220 +f 306/56/220 309/54/220 313/50/220 +f 57/51/220 313/50/220 472/49/220 +f 57/51/220 304/57/220 306/56/220 +f 57/51/220 306/56/220 313/50/220 +f 60/58/220 303/59/220 304/57/220 +f 57/51/220 60/58/220 304/57/220 +f 63/60/220 302/61/220 303/59/220 +f 60/58/220 63/60/220 303/59/220 +f 66/62/220 301/63/220 302/61/220 +f 63/60/220 66/62/220 302/61/220 +f 69/64/220 300/65/220 301/63/220 +f 66/62/220 69/64/220 301/63/220 +f 72/66/220 299/67/220 300/65/220 +f 69/64/220 72/66/220 300/65/220 +f 75/68/220 298/69/220 299/67/220 +f 72/66/220 75/68/220 299/67/220 +f 78/70/220 297/71/220 298/69/220 +f 75/68/220 78/70/220 298/69/220 +f 81/72/220 296/73/220 297/71/220 +f 78/70/220 81/72/220 297/71/220 +f 84/74/220 295/75/220 296/73/220 +f 81/72/220 84/74/220 296/73/220 +f 87/76/220 294/77/220 295/75/220 +f 84/74/220 87/76/220 295/75/220 +f 90/78/220 293/79/220 294/77/220 +f 87/76/220 90/78/220 294/77/220 +f 93/80/220 292/81/220 293/79/220 +f 90/78/220 93/80/220 293/79/220 +f 96/82/220 291/83/220 292/81/220 +f 93/80/220 96/82/220 292/81/220 +f 99/84/220 290/85/220 291/83/220 +f 96/82/220 99/84/220 291/83/220 +f 102/86/220 289/87/220 290/85/220 +f 99/84/220 102/86/220 290/85/220 +f 105/88/220 288/89/220 289/87/220 +f 102/86/220 105/88/220 289/87/220 +f 108/90/220 287/91/220 288/89/220 +f 105/88/220 108/90/220 288/89/220 +f 111/92/220 286/93/220 287/91/220 +f 108/90/220 111/92/220 287/91/220 +f 114/94/220 285/95/220 286/93/220 +f 111/92/220 114/94/220 286/93/220 +f 420/96/220 278/97/220 285/95/220 +f 165/98/220 168/99/220 285/95/220 +f 467/100/220 285/95/220 168/99/220 +f 164/101/220 165/98/220 285/95/220 +f 160/102/220 164/101/220 285/95/220 +f 158/103/220 160/102/220 285/95/220 +f 155/104/220 158/103/220 285/95/220 +f 152/105/220 155/104/220 285/95/220 +f 150/106/220 152/105/220 285/95/220 +f 148/107/220 150/106/220 285/95/220 +f 146/108/220 148/107/220 285/95/220 +f 144/109/220 146/108/220 285/95/220 +f 142/110/220 144/109/220 285/95/220 +f 140/111/220 142/110/220 285/95/220 +f 138/112/220 140/111/220 285/95/220 +f 136/113/220 138/112/220 285/95/220 +f 134/114/220 136/113/220 285/95/220 +f 132/115/220 134/114/220 285/95/220 +f 130/116/220 132/115/220 285/95/220 +f 128/117/220 130/116/220 285/95/220 +f 126/118/220 128/117/220 285/95/220 +f 124/119/220 126/118/220 285/95/220 +f 122/120/220 124/119/220 285/95/220 +f 120/121/220 122/120/220 285/95/220 +f 118/122/220 120/121/220 285/95/220 +f 114/94/220 118/122/220 285/95/220 +f 412/123/220 285/95/220 411/124/220 +f 468/125/220 411/124/220 285/95/220 +f 419/126/220 420/96/220 285/95/220 +f 418/127/220 419/126/220 285/95/220 +f 417/128/220 418/127/220 285/95/220 +f 416/129/220 417/128/220 285/95/220 +f 415/130/220 416/129/220 285/95/220 +f 414/131/220 415/130/220 285/95/220 +f 413/132/220 414/131/220 285/95/220 +f 412/123/220 413/132/220 285/95/220 +f 467/100/220 468/125/220 285/95/220 +f 421/133/220 277/134/220 278/97/220 +f 420/96/220 421/133/220 278/97/220 +f 421/133/220 276/135/220 277/134/220 +f 421/133/220 274/136/220 276/135/220 +f 421/133/220 272/137/220 274/136/220 +f 421/133/220 270/138/220 272/137/220 +f 421/133/220 268/139/220 270/138/220 +f 421/133/220 265/140/220 268/139/220 +f 423/141/220 261/142/220 265/140/220 +f 422/143/220 423/141/220 265/140/220 +f 421/133/220 422/143/220 265/140/220 +f 428/144/220 254/145/220 261/142/220 +f 427/146/220 428/144/220 261/142/220 +f 426/147/220 427/146/220 261/142/220 +f 425/148/220 426/147/220 261/142/220 +f 424/149/220 425/148/220 261/142/220 +f 423/141/220 424/149/220 261/142/220 +f 429/150/220 249/151/220 254/145/220 +f 428/144/220 429/150/220 254/145/220 +f 207/152/220 246/153/220 249/151/220 +f 429/150/220 207/152/220 249/151/220 +f 207/152/220 244/154/220 246/153/220 +f 207/152/220 242/155/220 244/154/220 +f 207/152/220 241/156/220 242/155/220 +f 210/157/220 240/158/220 241/156/220 +f 207/152/220 210/157/220 241/156/220 +f 213/159/220 239/160/220 240/158/220 +f 210/157/220 213/159/220 240/158/220 +f 216/161/220 238/162/220 239/160/220 +f 213/159/220 216/161/220 239/160/220 +f 219/163/220 237/164/220 238/162/220 +f 216/161/220 219/163/220 238/162/220 +f 222/165/220 236/166/220 237/164/220 +f 219/163/220 222/165/220 237/164/220 +f 225/167/220 235/168/220 236/166/220 +f 222/165/220 225/167/220 236/166/220 +f 231/169/220 234/170/220 235/168/220 +f 228/171/220 231/169/220 235/168/220 +f 225/167/220 228/171/220 235/168/220 +f 232/172/220 233/173/220 234/170/220 +f 231/169/220 232/172/220 234/170/220 +f 458/174/220 195/175/220 207/152/220 +f 429/150/220 432/176/220 207/152/220 +f 449/177/220 207/152/220 432/176/220 +f 450/178/220 207/152/220 449/177/220 +f 457/179/220 458/174/220 207/152/220 +f 456/180/220 457/179/220 207/152/220 +f 455/181/220 456/180/220 207/152/220 +f 454/182/220 455/181/220 207/152/220 +f 453/183/220 454/182/220 207/152/220 +f 452/184/220 453/183/220 207/152/220 +f 451/185/220 452/184/220 207/152/220 +f 450/178/220 451/185/220 207/152/220 +f 459/186/220 192/187/220 195/175/220 +f 458/174/220 459/186/220 195/175/220 +f 459/186/220 189/188/220 192/187/220 +f 460/189/220 186/190/220 189/188/220 +f 459/186/220 460/189/220 189/188/220 +f 461/191/220 183/192/220 186/190/220 +f 460/189/220 461/191/220 186/190/220 +f 462/193/220 180/194/220 183/192/220 +f 461/191/220 462/193/220 183/192/220 +f 463/195/220 177/196/220 180/194/220 +f 462/193/220 463/195/220 180/194/220 +f 465/197/220 174/198/220 177/196/220 +f 464/199/220 465/197/220 177/196/220 +f 463/195/220 464/199/220 177/196/220 +f 466/200/220 171/201/220 174/198/220 +f 465/197/220 466/200/220 174/198/220 +f 467/100/220 168/99/220 171/201/220 +f 466/200/220 467/100/220 171/201/220 +f 46/53/220 50/202/220 52/203/220 +f 46/53/220 52/203/220 54/204/220 +f 46/53/220 54/204/220 57/51/220 +f 473/205/220 43/206/220 46/53/220 +f 471/52/220 473/205/220 46/53/220 +f 474/207/220 40/208/220 43/206/220 +f 473/205/220 474/207/220 43/206/220 +f 475/209/220 36/210/220 40/208/220 +f 474/207/220 475/209/220 40/208/220 +f 476/211/220 33/212/220 36/210/220 +f 475/209/220 476/211/220 36/210/220 +f 477/213/220 30/214/220 33/212/220 +f 476/211/220 477/213/220 33/212/220 +f 478/215/220 27/216/220 30/214/220 +f 477/213/220 478/215/220 30/214/220 +f 479/217/220 24/218/220 27/216/220 +f 478/215/220 479/217/220 27/216/220 +f 480/219/220 21/220/220 24/218/220 +f 479/217/220 480/219/220 24/218/220 +f 482/221/220 17/24/220 21/220/220 +f 481/222/220 482/221/220 21/220/220 +f 480/219/220 481/222/220 21/220/220 +f 487/223/220 488/25/220 17/24/220 +f 486/224/220 487/223/220 17/24/220 +f 485/225/220 486/224/220 17/24/220 +f 484/226/220 485/225/220 17/24/220 +f 483/227/220 484/226/220 17/24/220 +f 482/221/220 483/227/220 17/24/220 +f 469/228/220 410/229/220 411/124/220 +f 468/125/220 469/228/220 411/124/220 +f 469/228/220 409/230/220 410/229/220 +f 470/231/220 406/232/220 409/230/220 +f 469/228/220 470/231/220 409/230/220 +f 439/233/220 403/234/220 406/232/220 +f 470/231/220 439/233/220 406/232/220 +f 438/235/220 400/236/220 403/234/220 +f 439/233/220 438/235/220 403/234/220 +f 438/235/220 398/237/220 400/236/220 +f 438/235/220 396/238/220 398/237/220 +f 440/239/220 394/240/220 396/238/220 +f 438/235/220 440/239/220 396/238/220 +f 440/239/220 392/241/220 394/240/220 +f 442/242/220 382/243/220 392/241/220 +f 441/244/220 442/242/220 392/241/220 +f 440/239/220 441/244/220 392/241/220 +f 445/245/220 385/246/220 382/243/220 +f 444/247/220 445/245/220 382/243/220 +f 443/248/220 444/247/220 382/243/220 +f 442/242/220 443/248/220 382/243/220 +f 446/249/220 437/250/220 385/246/220 +f 445/245/220 446/249/220 385/246/220 +f 447/251/220 435/252/220 437/250/220 +f 446/249/220 447/251/220 437/250/220 +f 449/177/220 432/176/220 435/252/220 +f 448/253/220 449/177/220 435/252/220 +f 447/251/220 448/253/220 435/252/220 diff --git a/resources/meshes/sovol_sv08_buildplate_model.stl b/resources/meshes/sovol_sv08_buildplate_model.stl new file mode 100644 index 0000000000..91acb50bd4 Binary files /dev/null and b/resources/meshes/sovol_sv08_buildplate_model.stl differ diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 77efc45fe8..bc5cc6a044 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -456,45 +456,32 @@ UM.MainWindow } } - UM.PreferencesDialog + Component { - id: preferences - - Component.onCompleted: + id: preferencesDialogComponent + Cura.PreferencesDialog { - //; Remove & re-add the general page as we want to use our own instead of uranium standard. - removePage(0); - insertPage(0, catalog.i18nc("@title:tab","General"), Qt.resolvedUrl("Preferences/GeneralPage.qml")); - - removePage(1); - insertPage(1, catalog.i18nc("@title:tab","Settings"), Qt.resolvedUrl("Preferences/SettingVisibilityPage.qml")); - - insertPage(2, catalog.i18nc("@title:tab", "Printers"), Qt.resolvedUrl("Preferences/MachinesPage.qml")); - - insertPage(3, catalog.i18nc("@title:tab", "Materials"), Qt.resolvedUrl("Preferences/Materials/MaterialsPage.qml")); - - insertPage(4, catalog.i18nc("@title:tab", "Profiles"), Qt.resolvedUrl("Preferences/ProfilesPage.qml")); - currentPage = 0; + selfDestroy: true } + } - onVisibleChanged: - { - // When the dialog closes, switch to the General page. - // This prevents us from having a heavy page like Setting Visibility active in the background. - setPage(0); - } + function showPreferencesDialog() + { + var dialog = preferencesDialogComponent.createObject(base) + dialog.show() + return dialog } Connections { target: Cura.Actions.preferences - function onTriggered() { preferences.visible = true } + function onTriggered() { showPreferencesDialog() } } Connections { target: CuraApplication - function onShowPreferencesWindow() { preferences.visible = true } + function onShowPreferencesWindow() { showPreferencesDialog() } } Connections @@ -511,8 +498,8 @@ UM.MainWindow target: Cura.Actions.configureMachines function onTriggered() { - preferences.visible = true; - preferences.setPage(2); + var dialog = showPreferencesDialog() + dialog.currentPage = 2; } } @@ -521,8 +508,8 @@ UM.MainWindow target: Cura.Actions.manageProfiles function onTriggered() { - preferences.visible = true; - preferences.setPage(4); + var dialog = showPreferencesDialog() + dialog.currentPage = 4; } } @@ -531,8 +518,8 @@ UM.MainWindow target: Cura.Actions.manageMaterials function onTriggered() { - preferences.visible = true; - preferences.setPage(3) + var dialog = showPreferencesDialog() + dialog.currentPage = 3; } } @@ -541,11 +528,11 @@ UM.MainWindow target: Cura.Actions.configureSettingVisibility function onTriggered(source) { - preferences.visible = true; - preferences.setPage(1); + var dialog = showPreferencesDialog() + dialog.currentPage = 1; if(source && source.key) { - preferences.getCurrentItem().scrollToSection(source.key); + dialog.currentItem.scrollToSection(source.key); } } } diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelectorButton.qml b/resources/qml/ModeSelectorButton.qml similarity index 91% rename from resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelectorButton.qml rename to resources/qml/ModeSelectorButton.qml index 1bbc726b9d..65a6ee4a75 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelectorButton.qml +++ b/resources/qml/ModeSelectorButton.qml @@ -17,7 +17,7 @@ Rectangle color: mouseArea.containsMouse || selected ? UM.Theme.getColor("background_3") : UM.Theme.getColor("background_1") property bool selected: false - property string profileName: "" + property alias text: mainLabel.text property string icon: "" property string custom_icon: "" property alias tooltipText: tooltip.text @@ -42,18 +42,18 @@ Rectangle Item { - width: intentIcon.width + width: mainIcon.width anchors { top: parent.top - bottom: qualityLabel.top + bottom: mainLabel.top horizontalCenter: parent.horizontalCenter topMargin: UM.Theme.getSize("narrow_margin").height } Item { - id: intentIcon + id: mainIcon width: UM.Theme.getSize("recommended_button_icon").width height: UM.Theme.getSize("recommended_button_icon").height @@ -90,7 +90,7 @@ Rectangle { id: initialLabel anchors.centerIn: parent - text: profileName.charAt(0).toUpperCase() + text: base.text.charAt(0).toUpperCase() font: UM.Theme.getFont("small_bold") horizontalAlignment: Text.AlignHCenter } @@ -102,8 +102,7 @@ Rectangle UM.Label { - id: qualityLabel - text: profileName + id: mainLabel anchors { bottom: parent.bottom diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index 62cab53a78..42469c6cf6 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -360,17 +360,6 @@ UM.PreferencesPage } } - UM.Label - { - id: languageCaption - - //: Language change warning - text: catalog.i18nc("@label", "*You will need to restart the application for these changes to have effect.") - wrapMode: Text.WordWrap - font.italic: true - - } - Item { //: Spacer @@ -705,7 +694,7 @@ UM.PreferencesPage UM.CheckBox { id: singleInstanceCheckbox - text: catalog.i18nc("@option:check","Use a single instance of Cura") + text: catalog.i18nc("@option:check","Use a single instance of Cura *") checked: boolCheck(UM.Preferences.getValue("cura/single_instance")) onCheckedChanged: UM.Preferences.setValue("cura/single_instance", checked) @@ -1101,8 +1090,6 @@ UM.PreferencesPage } } - - /* Multi-buildplate functionality is disabled because it's broken. See CURA-4975 for the ticket to remove it. Item { //: Spacer @@ -1110,6 +1097,18 @@ UM.PreferencesPage width: UM.Theme.getSize("default_margin").height } + UM.Label + { + id: languageCaption + + //: Language change warning + text: catalog.i18nc("@label", "*You will need to restart the application for these changes to have effect.") + wrapMode: Text.WordWrap + font.italic: true + } + + /* Multi-buildplate functionality is disabled because it's broken. See CURA-4975 for the ticket to remove it. + Label { font.bold: true diff --git a/resources/qml/Preferences/PreferencesDialog.qml b/resources/qml/Preferences/PreferencesDialog.qml new file mode 100644 index 0000000000..f175370205 --- /dev/null +++ b/resources/qml/Preferences/PreferencesDialog.qml @@ -0,0 +1,130 @@ +// Copyright (c) 2022 Ultimaker B.V. +// Uranium is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.1 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +import ".." + +import UM 1.6 as UM + +UM.Dialog +{ + id: base + + title: catalog.i18nc("@title:window", "Preferences") + minimumWidth: UM.Theme.getSize("modal_window_minimum").width + minimumHeight: UM.Theme.getSize("modal_window_minimum").height + width: minimumWidth + height: minimumHeight + backgroundColor: UM.Theme.getColor("background_2") + + property alias currentPage: pagesList.currentIndex + property alias currentItem: pagesList.currentItem + + Item + { + id: test + anchors.fill: parent + + ListView + { + id: pagesList + width: UM.Theme.getSize("preferences_page_list_item").width + anchors.top: parent.top + anchors.bottom: parent.bottom + + ScrollBar.vertical: UM.ScrollBar {} + clip: true + model: [ + { + name: catalog.i18nc("@title:tab", "General"), + item: Qt.resolvedUrl("GeneralPage.qml") + }, + { + name: catalog.i18nc("@title:tab", "Settings"), + item: Qt.resolvedUrl("SettingVisibilityPage.qml") + }, + { + name: catalog.i18nc("@title:tab", "Printers"), + item: Qt.resolvedUrl("MachinesPage.qml") + }, + { + name: catalog.i18nc("@title:tab", "Materials"), + item: Qt.resolvedUrl("Materials/MaterialsPage.qml") + }, + { + name: catalog.i18nc("@title:tab", "Profiles"), + item: Qt.resolvedUrl("ProfilesPage.qml") + } + ] + + delegate: Rectangle + { + width: parent ? parent.width : 0 + height: pageLabel.height + + color: ListView.isCurrentItem ? UM.Theme.getColor("background_3") : UM.Theme.getColor("main_background") + + UM.Label + { + id: pageLabel + anchors.centerIn: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + width: parent.width + height: UM.Theme.getSize("preferences_page_list_item").height + color: UM.Theme.getColor("text_default") + text: modelData.name + } + MouseArea + { + anchors.fill: parent + onClicked: pagesList.currentIndex = index + } + } + + onCurrentIndexChanged: stackView.replace(model[currentIndex].item) + } + + StackView + { + id: stackView + anchors + { + left: pagesList.right + leftMargin: UM.Theme.getSize("narrow_margin").width + top: parent.top + bottom: parent.bottom + right: parent.right + } + + initialItem: Item { property bool resetEnabled: false } + + replaceEnter: Transition + { + NumberAnimation + { + properties: "opacity" + from: 0 + to: 1 + duration: 100 + } + } + replaceExit: Transition + { + NumberAnimation + { + properties: "opacity" + from: 1 + to: 0 + duration: 100 + } + } + } + + UM.I18nCatalog { id: catalog; name: "uranium"; } + } +} diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml index 19c57e5130..1559f6cec3 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml @@ -7,7 +7,6 @@ import QtQuick.Layouts 2.10 import UM 1.5 as UM import Cura 1.7 as Cura -import ".." Item { @@ -28,9 +27,9 @@ Item id: intentSelectionRepeater model: Cura.IntentSelectionModel {} - RecommendedQualityProfileSelectorButton + Cura.ModeSelectorButton { - profileName: model.name + text: model.name icon: model.icon ? model.icon : "" custom_icon: model.custom_icon ? model.custom_icon : "" tooltipText: model.description ? model.description : "" diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedSupportSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedSupportSelector.qml index c1b36676c7..48a164819d 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedSupportSelector.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedSupportSelector.qml @@ -46,7 +46,7 @@ RecommendedSettingSection contents: [ RecommendedSettingItem { - settingName: catalog.i18nc("@action:label", "Support Type") + settingName: catalog.i18nc("@action:label", "Support Structure") tooltipText: catalog.i18nc("@label", "Chooses between the techniques available to generate support. \n\n\"Normal\" support creates a support structure directly below the overhanging parts and drops those areas straight down. \n\n\"Tree\" support creates branches towards the overhanging areas that support the model on the tips of those branches, and allows the branches to crawl around the model to support it from the build plate as much as possible.") isCompressed: enableSupportRow.isCompressed diff --git a/resources/qml/PrinterSelector/MachineSelector.qml b/resources/qml/PrinterSelector/MachineSelector.qml index 1bad1d70bc..e8ee98fe8f 100644 --- a/resources/qml/PrinterSelector/MachineSelector.qml +++ b/resources/qml/PrinterSelector/MachineSelector.qml @@ -16,6 +16,7 @@ Cura.ExpandablePopup property bool isConnectedCloudPrinter: machineManager.activeMachineHasCloudConnection property bool isCloudRegistered: machineManager.activeMachineHasCloudRegistration property bool isGroup: machineManager.activeMachineIsGroup + property bool isActive: machineManager.activeMachineIsActive property string machineName: { if (isNetworkPrinter && machineManager.activeMachineNetworkGroupName != "") { @@ -40,7 +41,14 @@ Cura.ExpandablePopup } else if (isConnectedCloudPrinter && Cura.API.connectionStatus.isInternetReachable) { - return "printer_cloud_connected" + if (isActive) + { + return "printer_cloud_connected" + } + else + { + return "printer_cloud_inactive" + } } else if (isCloudRegistered) { @@ -53,7 +61,7 @@ Cura.ExpandablePopup } function getConnectionStatusMessage() { - if (connectionStatus == "printer_cloud_not_available") + if (connectionStatus === "printer_cloud_not_available") { if(Cura.API.connectionStatus.isInternetReachable) { @@ -78,6 +86,10 @@ Cura.ExpandablePopup return catalog.i18nc("@status", "The cloud connection is currently unavailable. Please check your internet connection.") } } + else if(connectionStatus === "printer_cloud_inactive") + { + return catalog.i18nc("@status", "This printer is deactivated and can not accept commands or jobs.") + } else { return "" @@ -130,14 +142,18 @@ Cura.ExpandablePopup source: { - if (connectionStatus == "printer_connected") + if (connectionStatus === "printer_connected") { return UM.Theme.getIcon("CheckBlueBG", "low") } - else if (connectionStatus == "printer_cloud_connected" || connectionStatus == "printer_cloud_not_available") + else if (connectionStatus === "printer_cloud_connected" || connectionStatus === "printer_cloud_not_available") { return UM.Theme.getIcon("CloudBadge", "low") } + else if (connectionStatus === "printer_cloud_inactive") + { + return UM.Theme.getIcon("WarningBadge", "low") + } else { return "" @@ -147,7 +163,21 @@ Cura.ExpandablePopup width: UM.Theme.getSize("printer_status_icon").width height: UM.Theme.getSize("printer_status_icon").height - color: connectionStatus == "printer_cloud_not_available" ? UM.Theme.getColor("cloud_unavailable") : UM.Theme.getColor("primary") + color: + { + if (connectionStatus === "printer_cloud_not_available") + { + return UM.Theme.getColor("cloud_unavailable") + } + else if(connectionStatus === "printer_cloud_inactive") + { + return UM.Theme.getColor("cloud_inactive") + } + else + { + return UM.Theme.getColor("primary") + } + } visible: (isNetworkPrinter || isCloudRegistered) && source != "" diff --git a/resources/qml/ViewsSelector.qml b/resources/qml/ViewsSelector.qml index e76e5dbb67..b0e31ac532 100644 --- a/resources/qml/ViewsSelector.qml +++ b/resources/qml/ViewsSelector.qml @@ -38,7 +38,7 @@ Cura.ExpandablePopup { if (activeView == null) { - UM.Controller.setActiveView(viewModel.getItem(0).id) + UM.Controller.activeStage.setActiveView(viewModel.getItem(0).id) } } @@ -110,7 +110,7 @@ Cura.ExpandablePopup onClicked: { toggleContent() - UM.Controller.setActiveView(id) + UM.Controller.activeStage.setActiveView(id) } } } diff --git a/resources/qml/qmldir b/resources/qml/qmldir index 8fce82c858..98140608c6 100644 --- a/resources/qml/qmldir +++ b/resources/qml/qmldir @@ -52,3 +52,7 @@ NumericTextFieldWithUnit 1.0 NumericTextFieldWithUnit.qml PrintHeadMinMaxTextField 1.0 PrintHeadMinMaxTextField.qml SimpleCheckBox 1.0 SimpleCheckBox.qml RenameDialog 1.0 RenameDialog.qml + +# Cura/Preferences + +PreferencesDialog 1.0 PreferencesDialog.qml diff --git a/resources/quality/bambu/bambulab_a1_0.4_PLA_standard.inst.cfg b/resources/quality/bambu/bambulab_a1_0.4_PLA_standard.inst.cfg new file mode 100644 index 0000000000..9f2878702e --- /dev/null +++ b/resources/quality/bambu/bambulab_a1_0.4_PLA_standard.inst.cfg @@ -0,0 +1,14 @@ +[general] +definition = bambulab_a1 +name = Standard +version = 4 + +[metadata] +material = generic_pla +quality_type = normal +setting_version = 25 +type = quality +variant = 0.4mm + +[values] + diff --git a/resources/quality/bambu/bambulab_a1_normal.inst.cfg b/resources/quality/bambu/bambulab_a1_normal.inst.cfg new file mode 100644 index 0000000000..a3fd08879e --- /dev/null +++ b/resources/quality/bambu/bambulab_a1_normal.inst.cfg @@ -0,0 +1,15 @@ +[general] +definition = bambulab_a1 +name = Normal +version = 4 + +[metadata] +global_quality = True +quality_type = normal +setting_version = 25 +type = quality +weight = 0 + +[values] +layer_height = 0.2 + diff --git a/resources/quality/bambu/bambulab_a1mini_0.4_PLA_standard.inst.cfg b/resources/quality/bambu/bambulab_a1mini_0.4_PLA_standard.inst.cfg new file mode 100644 index 0000000000..95c665155d --- /dev/null +++ b/resources/quality/bambu/bambulab_a1mini_0.4_PLA_standard.inst.cfg @@ -0,0 +1,14 @@ +[general] +definition = bambulab_a1mini +name = Standard +version = 4 + +[metadata] +material = generic_pla +quality_type = normal +setting_version = 25 +type = quality +variant = 0.4mm + +[values] + diff --git a/resources/quality/bambu/bambulab_a1mini_normal.inst.cfg b/resources/quality/bambu/bambulab_a1mini_normal.inst.cfg new file mode 100644 index 0000000000..afb403d8f1 --- /dev/null +++ b/resources/quality/bambu/bambulab_a1mini_normal.inst.cfg @@ -0,0 +1,15 @@ +[general] +definition = bambulab_a1mini +name = Normal +version = 4 + +[metadata] +global_quality = True +quality_type = normal +setting_version = 25 +type = quality +weight = 0 + +[values] +layer_height = 0.2 + diff --git a/resources/quality/bambu/bambulab_x1_0.4_PLA_standard.inst.cfg b/resources/quality/bambu/bambulab_x1_0.4_PLA_standard.inst.cfg new file mode 100644 index 0000000000..01ba90dcb9 --- /dev/null +++ b/resources/quality/bambu/bambulab_x1_0.4_PLA_standard.inst.cfg @@ -0,0 +1,14 @@ +[general] +definition = bambulab_x1 +name = Standard +version = 4 + +[metadata] +material = generic_pla +quality_type = normal +setting_version = 25 +type = quality +variant = X1 0.4mm + +[values] + diff --git a/resources/quality/bambu/bambulab_x1_normal.inst.cfg b/resources/quality/bambu/bambulab_x1_normal.inst.cfg new file mode 100644 index 0000000000..22c501ee36 --- /dev/null +++ b/resources/quality/bambu/bambulab_x1_normal.inst.cfg @@ -0,0 +1,15 @@ +[general] +definition = bambulab_x1 +name = Normal +version = 4 + +[metadata] +global_quality = True +quality_type = normal +setting_version = 25 +type = quality +weight = 0 + +[values] +layer_height = 0.2 + diff --git a/resources/quality/biqu/b2/biqu_b2_0.4_PLA_adaptive.inst.cfg b/resources/quality/biqu/b2/biqu_b2_0.4_PLA_adaptive.inst.cfg new file mode 100644 index 0000000000..b9f8323096 --- /dev/null +++ b/resources/quality/biqu/b2/biqu_b2_0.4_PLA_adaptive.inst.cfg @@ -0,0 +1,14 @@ +[general] +definition = biqu_b2 +name = Dynamic Quality +version = 4 + +[metadata] +material = generic_pla_175 +quality_type = adaptive +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] + diff --git a/resources/quality/biqu/b2/biqu_b2_0.4_PLA_draft.inst.cfg b/resources/quality/biqu/b2/biqu_b2_0.4_PLA_draft.inst.cfg new file mode 100644 index 0000000000..57b48a55a5 --- /dev/null +++ b/resources/quality/biqu/b2/biqu_b2_0.4_PLA_draft.inst.cfg @@ -0,0 +1,14 @@ +[general] +definition = biqu_b2 +name = Draft Quality +version = 4 + +[metadata] +material = generic_pla_175 +quality_type = draft +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] + diff --git a/resources/quality/biqu/b2/biqu_b2_0.4_PLA_low.inst.cfg b/resources/quality/biqu/b2/biqu_b2_0.4_PLA_low.inst.cfg new file mode 100644 index 0000000000..d11d6658d4 --- /dev/null +++ b/resources/quality/biqu/b2/biqu_b2_0.4_PLA_low.inst.cfg @@ -0,0 +1,14 @@ +[general] +definition = biqu_b2 +name = Low Quality +version = 4 + +[metadata] +material = generic_pla_175 +quality_type = low +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] + diff --git a/resources/quality/biqu/b2/biqu_b2_0.4_PLA_standard.inst.cfg b/resources/quality/biqu/b2/biqu_b2_0.4_PLA_standard.inst.cfg new file mode 100644 index 0000000000..fa641937bb --- /dev/null +++ b/resources/quality/biqu/b2/biqu_b2_0.4_PLA_standard.inst.cfg @@ -0,0 +1,14 @@ +[general] +definition = biqu_b2 +name = Standard Quality +version = 4 + +[metadata] +material = generic_pla_175 +quality_type = standard +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] + diff --git a/resources/quality/biqu/b2/biqu_b2_0.4_PLA_super.inst.cfg b/resources/quality/biqu/b2/biqu_b2_0.4_PLA_super.inst.cfg new file mode 100644 index 0000000000..7aadcb580c --- /dev/null +++ b/resources/quality/biqu/b2/biqu_b2_0.4_PLA_super.inst.cfg @@ -0,0 +1,14 @@ +[general] +definition = biqu_b2 +name = Super Quality +version = 4 + +[metadata] +material = generic_pla_175 +quality_type = super +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] + diff --git a/resources/quality/biqu/b2/biqu_b2_0.4_PLA_ultra.inst.cfg b/resources/quality/biqu/b2/biqu_b2_0.4_PLA_ultra.inst.cfg new file mode 100644 index 0000000000..fbd8a08905 --- /dev/null +++ b/resources/quality/biqu/b2/biqu_b2_0.4_PLA_ultra.inst.cfg @@ -0,0 +1,14 @@ +[general] +definition = biqu_b2 +name = Ultra Quality +version = 4 + +[metadata] +material = generic_pla_175 +quality_type = ultra +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] + diff --git a/resources/quality/biqu/b2/biqu_b2_global_adaptive.inst.cfg b/resources/quality/biqu/b2/biqu_b2_global_adaptive.inst.cfg new file mode 100644 index 0000000000..54484e046e --- /dev/null +++ b/resources/quality/biqu/b2/biqu_b2_global_adaptive.inst.cfg @@ -0,0 +1,29 @@ +[general] +definition = biqu_b2 +name = Dynamic Quality +version = 4 + +[metadata] +global_quality = True +quality_type = adaptive +setting_version = 25 +type = quality +weight = -2 + +[values] +adaptive_layer_height_enabled = true +layer_height = 0.16 +layer_height_0 = 0.20 +material_final_print_temperature = 195 +material_initial_print_temperature = 195 +material_print_temperature = 190 +material_standby_temperature = 195 +optimize_wall_printing_order = True +prime_tower_min_volume = 150 +support_interface_height = =layer_height*6 +switch_extruder_prime_speed = 10 +switch_extruder_retraction_amount = 40 +switch_extruder_retraction_speeds = 30 +top_bottom_thickness = =layer_height_0+layer_height*4 +wall_thickness = =line_width*3 + diff --git a/resources/quality/biqu/b2/biqu_b2_global_draft.inst.cfg b/resources/quality/biqu/b2/biqu_b2_global_draft.inst.cfg new file mode 100644 index 0000000000..e5ebe77ad2 --- /dev/null +++ b/resources/quality/biqu/b2/biqu_b2_global_draft.inst.cfg @@ -0,0 +1,28 @@ +[general] +definition = biqu_b2 +name = Draft Quality +version = 4 + +[metadata] +global_quality = True +quality_type = draft +setting_version = 25 +type = quality +weight = -5 + +[values] +layer_height = 0.32 +layer_height_0 = 0.32 +material_final_print_temperature = 195 +material_initial_print_temperature = 195 +material_print_temperature = 190 +material_standby_temperature = 195 +optimize_wall_printing_order = True +prime_tower_min_volume = 150 +support_interface_height = =layer_height*4 +switch_extruder_prime_speed = 10 +switch_extruder_retraction_amount = 40 +switch_extruder_retraction_speeds = 30 +top_bottom_thickness = =layer_height_0+layer_height*3 +wall_thickness = =line_width*2 + diff --git a/resources/quality/biqu/b2/biqu_b2_global_low.inst.cfg b/resources/quality/biqu/b2/biqu_b2_global_low.inst.cfg new file mode 100644 index 0000000000..2a0f57981f --- /dev/null +++ b/resources/quality/biqu/b2/biqu_b2_global_low.inst.cfg @@ -0,0 +1,28 @@ +[general] +definition = biqu_b2 +name = Low Quality +version = 4 + +[metadata] +global_quality = True +quality_type = low +setting_version = 25 +type = quality +weight = -4 + +[values] +layer_height = 0.28 +layer_height_0 = 0.28 +material_final_print_temperature = 195 +material_initial_print_temperature = 195 +material_print_temperature = 190 +material_standby_temperature = 195 +optimize_wall_printing_order = True +prime_tower_min_volume = 150 +support_interface_height = =layer_height*4 +switch_extruder_prime_speed = 10 +switch_extruder_retraction_amount = 40 +switch_extruder_retraction_speeds = 30 +top_bottom_thickness = =layer_height_0+layer_height*3 +wall_thickness = =line_width*3 + diff --git a/resources/quality/biqu/b2/biqu_b2_global_standard.inst.cfg b/resources/quality/biqu/b2/biqu_b2_global_standard.inst.cfg new file mode 100644 index 0000000000..e2860c23e3 --- /dev/null +++ b/resources/quality/biqu/b2/biqu_b2_global_standard.inst.cfg @@ -0,0 +1,29 @@ +[general] +definition = biqu_b2 +name = Standard Quality +version = 4 + +[metadata] +global_quality = True +position = 0 +quality_type = standard +setting_version = 25 +type = quality +weight = -3 + +[values] +layer_height = 0.2 +layer_height_0 = 0.2 +material_final_print_temperature = 195 +material_initial_print_temperature = 195 +material_print_temperature = 190 +material_standby_temperature = 195 +optimize_wall_printing_order = True +prime_tower_min_volume = 150 +support_interface_height = =layer_height*4 +switch_extruder_prime_speed = 10 +switch_extruder_retraction_amount = 40 +switch_extruder_retraction_speeds = 30 +top_bottom_thickness = =layer_height_0+layer_height*3 +wall_thickness = =line_width*3 + diff --git a/resources/quality/biqu/b2/biqu_b2_global_super.inst.cfg b/resources/quality/biqu/b2/biqu_b2_global_super.inst.cfg new file mode 100644 index 0000000000..9343fe00df --- /dev/null +++ b/resources/quality/biqu/b2/biqu_b2_global_super.inst.cfg @@ -0,0 +1,28 @@ +[general] +definition = biqu_b2 +name = Super Quality +version = 4 + +[metadata] +global_quality = True +quality_type = super +setting_version = 25 +type = quality +weight = -1 + +[values] +layer_height = 0.12 +layer_height_0 = 0.12 +material_final_print_temperature = 195 +material_initial_print_temperature = 195 +material_print_temperature = 190 +material_standby_temperature = 195 +optimize_wall_printing_order = True +prime_tower_min_volume = 150 +support_interface_height = =layer_height*8 +switch_extruder_prime_speed = 10 +switch_extruder_retraction_amount = 40 +switch_extruder_retraction_speeds = 30 +top_bottom_thickness = =layer_height_0+layer_height*6 +wall_thickness = =line_width*3 + diff --git a/resources/quality/biqu/b2/biqu_b2_global_ultra.inst.cfg b/resources/quality/biqu/b2/biqu_b2_global_ultra.inst.cfg new file mode 100644 index 0000000000..4625e672e3 --- /dev/null +++ b/resources/quality/biqu/b2/biqu_b2_global_ultra.inst.cfg @@ -0,0 +1,28 @@ +[general] +definition = biqu_b2 +name = Ultra Quality +version = 4 + +[metadata] +global_quality = True +quality_type = ultra +setting_version = 25 +type = quality +weight = 0 + +[values] +layer_height = 0.08 +layer_height_0 = 0.12 +material_final_print_temperature = 195 +material_initial_print_temperature = 195 +material_print_temperature = 190 +material_standby_temperature = 195 +optimize_wall_printing_order = True +prime_tower_min_volume = 150 +support_interface_height = =layer_height*12 +switch_extruder_prime_speed = 10 +switch_extruder_retraction_amount = 40 +switch_extruder_retraction_speeds = 30 +top_bottom_thickness = =layer_height_0+layer_height*10 +wall_thickness = =line_width*4 + diff --git a/resources/quality/normal.inst.cfg b/resources/quality/normal.inst.cfg index 4ca290b0b5..5d82bf612c 100644 --- a/resources/quality/normal.inst.cfg +++ b/resources/quality/normal.inst.cfg @@ -11,4 +11,5 @@ type = quality weight = 0 [values] +layer_height = 0.1 diff --git a/resources/quality/sovol/ABS/sovol_sv08_0.4_ABS_standard.inst.cfg b/resources/quality/sovol/ABS/sovol_sv08_0.4_ABS_standard.inst.cfg new file mode 100644 index 0000000000..4fdd35f5ba --- /dev/null +++ b/resources/quality/sovol/ABS/sovol_sv08_0.4_ABS_standard.inst.cfg @@ -0,0 +1,27 @@ +[general] +definition = sovol_sv08 +name = Standard Quality +version = 4 + +[metadata] +material = generic_abs +quality_type = standard +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] +bridge_fan_speed = 30 +bridge_settings_enabled = True +cool_fan_enabled = True +cool_fan_speed = 10 +cool_fan_speed_max = 30 +cool_min_layer_time = 4 +cool_min_layer_time_fan_speed_max = 30 +cool_min_speed = 10 +material_bed_temperature = 95 +material_flow = 98 +material_max_flowrate = 21 +material_print_temperature = 270 +material_print_temperature_layer_0 = 280 + diff --git a/resources/quality/sovol/PETG/sovol_sv08_0.4_PETG_standard.inst.cfg b/resources/quality/sovol/PETG/sovol_sv08_0.4_PETG_standard.inst.cfg new file mode 100644 index 0000000000..3cf9d68d04 --- /dev/null +++ b/resources/quality/sovol/PETG/sovol_sv08_0.4_PETG_standard.inst.cfg @@ -0,0 +1,27 @@ +[general] +definition = sovol_sv08 +name = Standard Quality +version = 4 + +[metadata] +material = generic_petg +quality_type = standard +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] +bridge_fan_speed = 70 +bridge_settings_enabled = True +cool_fan_enabled = True +cool_fan_speed = 10 +cool_fan_speed_max = 30 +cool_min_layer_time = 5 +cool_min_layer_time_fan_speed_max = 30 +cool_min_speed = 10 +material_bed_temperature = 75 +material_flow = 98 +material_max_flowrate = 17 +material_print_temperature = 235 +material_print_temperature_layer_0 = 250 + diff --git a/resources/quality/sovol/PLA/sovol_sv08_0.4_PLA_standard.inst.cfg b/resources/quality/sovol/PLA/sovol_sv08_0.4_PLA_standard.inst.cfg new file mode 100644 index 0000000000..daca9ebd94 --- /dev/null +++ b/resources/quality/sovol/PLA/sovol_sv08_0.4_PLA_standard.inst.cfg @@ -0,0 +1,27 @@ +[general] +definition = sovol_sv08 +name = Standard Quality +version = 4 + +[metadata] +material = generic_pla +quality_type = standard +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] +bridge_fan_speed = 100 +bridge_settings_enabled = True +cool_fan_enabled = True +cool_fan_speed = 50 +cool_fan_speed_max = 70 +cool_min_layer_time = 5 +cool_min_layer_time_fan_speed_max = 50 +cool_min_speed = 10 +material_bed_temperature = 65 +material_flow = 98 +material_max_flowrate = 21 +material_print_temperature = 220 +material_print_temperature_layer_0 = 235 + diff --git a/resources/quality/sovol/TPU/sovol_sv08_0.4_TPU_standard.inst.cfg b/resources/quality/sovol/TPU/sovol_sv08_0.4_TPU_standard.inst.cfg new file mode 100644 index 0000000000..c1b44b46b0 --- /dev/null +++ b/resources/quality/sovol/TPU/sovol_sv08_0.4_TPU_standard.inst.cfg @@ -0,0 +1,27 @@ +[general] +definition = sovol_sv08 +name = Standard Quality +version = 4 + +[metadata] +material = generic_tpu +quality_type = standard +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] +bridge_fan_speed = 100 +bridge_settings_enabled = True +cool_fan_enabled = True +cool_fan_speed = 80 +cool_fan_speed_max = 100 +cool_min_layer_time = 5 +cool_min_layer_time_fan_speed_max = 50 +cool_min_speed = 10 +material_bed_temperature = 65 +material_flow = 98 +material_max_flowrate = 3.6 +material_print_temperature = 240 +material_print_temperature_layer_0 = 235 + diff --git a/resources/quality/sovol/sovol_sv08_global.inst.cfg b/resources/quality/sovol/sovol_sv08_global.inst.cfg new file mode 100644 index 0000000000..4030ec9441 --- /dev/null +++ b/resources/quality/sovol/sovol_sv08_global.inst.cfg @@ -0,0 +1,31 @@ +[general] +definition = sovol_sv08 +name = 0.20mm Standard +version = 4 + +[metadata] +global_quality = True +quality_type = standard +setting_version = 25 +type = quality + +[values] +acceleration_enabled = True +acceleration_layer_0 = 3000 +acceleration_print = 20000 +acceleration_roofing = =acceleration_wall_0 +acceleration_topbottom = =acceleration_wall +acceleration_travel = 40000 +acceleration_wall_0 = 8000 +acceleration_wall_x = 12000 +layer_height = 0.2 +skirt_brim_speed = 80 +speed_infill = 200 +speed_ironing = 15 +speed_layer_0 = 30 +speed_print = 600 +speed_slowdown_layers = 3 +speed_travel = =speed_print +speed_wall_0 = 200 +speed_wall_x = 300 + diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_abs_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_abs_0.2mm.inst.cfg index 37767673aa..cc5e850220 100644 --- a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_abs_0.2mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_abs_0.2mm.inst.cfg @@ -14,7 +14,10 @@ weight = -2 [values] cool_min_layer_time = 4 cool_min_layer_time_fan_speed_max = 9 -cool_min_temperature = =material_print_temperature - 10 +cool_min_temperature = =material_print_temperature - 20 retraction_prime_speed = 15 +speed_wall_x = =speed_wall +speed_wall_x_roofing = =speed_wall * 0.8 support_structure = tree +wall_line_width_x = =wall_line_width * 1.25 diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_petg_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_petg_0.2mm.inst.cfg index a22e4fbeec..9abcd5ddd2 100644 --- a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_petg_0.2mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_petg_0.2mm.inst.cfg @@ -15,5 +15,7 @@ weight = -2 cool_min_layer_time = 4 material_print_temperature = =default_material_print_temperature + 5 retraction_prime_speed = 15 -support_structure = tree +speed_wall_x = =speed_wall +speed_wall_x_roofing = =speed_wall * 0.8 +wall_line_width_x = =wall_line_width * 1.25 diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.15mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.15mm.inst.cfg index 6c445180f8..9feab61e0e 100644 --- a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.15mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.15mm.inst.cfg @@ -12,8 +12,12 @@ variant = AA+ 0.4 weight = -1 [values] +cool_min_temperature = =material_print_temperature - 20 material_final_print_temperature = =material_print_temperature - 15 material_initial_print_temperature = =material_print_temperature - 15 retraction_prime_speed = =retraction_speed +speed_wall_x = =speed_wall +speed_wall_x_roofing = =speed_wall * 0.8 support_structure = tree +wall_line_width_x = =wall_line_width * 1.25 diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.1mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.1mm.inst.cfg index d3d99eec9e..8431bb9c43 100644 --- a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.1mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.1mm.inst.cfg @@ -12,9 +12,13 @@ variant = AA+ 0.4 weight = 0 [values] +cool_min_temperature = =material_print_temperature - 20 material_final_print_temperature = =material_print_temperature - 15 material_initial_print_temperature = =material_print_temperature - 15 retraction_prime_speed = =retraction_speed +speed_wall_x = =speed_wall +speed_wall_x_roofing = =speed_wall * 0.8 support_structure = tree top_bottom_thickness = =round(6*layer_height,3) +wall_line_width_x = =wall_line_width * 1.25 diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.2mm.inst.cfg index 2e015f8a88..7a5d19dc2c 100644 --- a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.2mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_pla_0.2mm.inst.cfg @@ -12,8 +12,12 @@ variant = AA+ 0.4 weight = -2 [values] +cool_min_temperature = =material_print_temperature - 20 material_final_print_temperature = =material_print_temperature - 15 material_initial_print_temperature = =material_print_temperature - 15 retraction_prime_speed = =retraction_speed +speed_wall_x = =speed_wall +speed_wall_x_roofing = =speed_wall * 0.8 support_structure = tree +wall_line_width_x = =wall_line_width * 1.25 diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.15mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.15mm.inst.cfg index e3477c1e7d..f17d3fde40 100644 --- a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.15mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.15mm.inst.cfg @@ -12,7 +12,11 @@ variant = AA+ 0.4 weight = -1 [values] +cool_min_temperature = =material_print_temperature - 20 retraction_prime_speed = =retraction_speed retraction_speed = 25 +speed_wall_x = =speed_wall +speed_wall_x_roofing = =speed_wall * 0.8 support_structure = tree +wall_line_width_x = =wall_line_width * 1.25 diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.1mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.1mm.inst.cfg index 6ccc0da6dd..672eae3e4a 100644 --- a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.1mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.1mm.inst.cfg @@ -12,7 +12,11 @@ variant = AA+ 0.4 weight = 0 [values] +cool_min_temperature = =material_print_temperature - 20 retraction_prime_speed = =retraction_speed +speed_wall_x = =speed_wall +speed_wall_x_roofing = =speed_wall * 0.8 support_structure = tree top_bottom_thickness = =round(6*layer_height,3) +wall_line_width_x = =wall_line_width * 1.25 diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.2mm.inst.cfg index bb629e0758..716765aac5 100644 --- a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.2mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_tough-pla_0.2mm.inst.cfg @@ -12,6 +12,10 @@ variant = AA+ 0.4 weight = -2 [values] +cool_min_temperature = =material_print_temperature - 20 retraction_prime_speed = =retraction_speed +speed_wall_x = =speed_wall +speed_wall_x_roofing = =speed_wall * 0.8 support_structure = tree +wall_line_width_x = =wall_line_width * 1.25 diff --git a/resources/themes/cura-light/icons/default/Circle.svg b/resources/themes/cura-light/icons/default/Circle.svg new file mode 100644 index 0000000000..c69b5a4e31 --- /dev/null +++ b/resources/themes/cura-light/icons/default/Circle.svg @@ -0,0 +1,5 @@ + + + + diff --git a/resources/themes/cura-light/icons/default/Eraser.svg b/resources/themes/cura-light/icons/default/Eraser.svg new file mode 100644 index 0000000000..fbe5103993 --- /dev/null +++ b/resources/themes/cura-light/icons/default/Eraser.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/themes/cura-light/icons/default/Seam.svg b/resources/themes/cura-light/icons/default/Seam.svg new file mode 100644 index 0000000000..a9615832d6 --- /dev/null +++ b/resources/themes/cura-light/icons/default/Seam.svg @@ -0,0 +1,6 @@ + + + + diff --git a/resources/themes/cura-light/icons/low/CancelBadge.svg b/resources/themes/cura-light/icons/low/CancelBadge.svg new file mode 100644 index 0000000000..25c4198083 --- /dev/null +++ b/resources/themes/cura-light/icons/low/CancelBadge.svg @@ -0,0 +1,5 @@ + + + + diff --git a/resources/themes/cura-light/icons/low/CheckBadge.svg b/resources/themes/cura-light/icons/low/CheckBadge.svg new file mode 100644 index 0000000000..a10a92c6af --- /dev/null +++ b/resources/themes/cura-light/icons/low/CheckBadge.svg @@ -0,0 +1,5 @@ + + + + diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 1ae316f96c..436aaceb3c 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -496,12 +496,17 @@ "monitor_carousel_dot_current": [119, 119, 119, 255], "cloud_unavailable": [153, 153, 153, 255], + "cloud_inactive": [253, 209, 58, 255], "connection_badge_background": [255, 255, 255, 255], "warning_badge_background": [0, 0, 0, 255], "error_badge_background": [255, 255, 255, 255], "border_field_light": [180, 180, 180, 255], - "border_main_light": [212, 212, 212, 255] + "border_main_light": [212, 212, 212, 255], + + "paint_normal_area": "background_3", + "paint_preferred_area": "um_green_5", + "paint_avoid_area": "um_red_5" }, "sizes": { diff --git a/resources/themes/daily_test_colors.json b/resources/themes/daily_test_colors.json new file mode 100644 index 0000000000..1cfa2baa74 --- /dev/null +++ b/resources/themes/daily_test_colors.json @@ -0,0 +1,16 @@ +[ + [ 62, 33, 55, 255], + [126, 196, 193, 255], + [126, 196, 193, 255], + [215, 155, 125, 255], + [228, 148, 58, 255], + [192, 199, 65, 255], + [157, 48, 59, 255], + [140, 143, 174, 255], + [ 23, 67, 75, 255], + [ 23, 67, 75, 255], + [154, 99, 72, 255], + [112, 55, 127, 255], + [100, 125, 52, 255], + [210, 100, 113, 255] +] diff --git a/resources/variants/bambu/bambulab_a1_0.4.inst.cfg b/resources/variants/bambu/bambulab_a1_0.4.inst.cfg new file mode 100644 index 0000000000..0823eb58b3 --- /dev/null +++ b/resources/variants/bambu/bambulab_a1_0.4.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = bambulab_a1 +name = 0.4mm +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.4 + diff --git a/resources/variants/bambu/bambulab_a1mini_0.4.inst.cfg b/resources/variants/bambu/bambulab_a1mini_0.4.inst.cfg new file mode 100644 index 0000000000..fceec9b0e8 --- /dev/null +++ b/resources/variants/bambu/bambulab_a1mini_0.4.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = bambulab_a1mini +name = 0.4mm +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.4 + diff --git a/resources/variants/bambu/bambulab_x1_0.4.inst.cfg b/resources/variants/bambu/bambulab_x1_0.4.inst.cfg new file mode 100644 index 0000000000..d3f5248b2d --- /dev/null +++ b/resources/variants/bambu/bambulab_x1_0.4.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = bambulab_x1 +name = X1 0.4mm +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.4 + diff --git a/resources/variants/biqu/biqu_b2_0.2.inst.cfg b/resources/variants/biqu/biqu_b2_0.2.inst.cfg new file mode 100644 index 0000000000..e86b85d50b --- /dev/null +++ b/resources/variants/biqu/biqu_b2_0.2.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = biqu_b2 +name = 0.2mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.2 + diff --git a/resources/variants/biqu/biqu_b2_0.3.inst.cfg b/resources/variants/biqu/biqu_b2_0.3.inst.cfg new file mode 100644 index 0000000000..8469c7e211 --- /dev/null +++ b/resources/variants/biqu/biqu_b2_0.3.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = biqu_b2 +name = 0.3mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.3 + diff --git a/resources/variants/biqu/biqu_b2_0.4.inst.cfg b/resources/variants/biqu/biqu_b2_0.4.inst.cfg new file mode 100644 index 0000000000..472e03489b --- /dev/null +++ b/resources/variants/biqu/biqu_b2_0.4.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = biqu_b2 +name = 0.4mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.4 + diff --git a/resources/variants/biqu/biqu_b2_0.5.inst.cfg b/resources/variants/biqu/biqu_b2_0.5.inst.cfg new file mode 100644 index 0000000000..1ab4eb72d4 --- /dev/null +++ b/resources/variants/biqu/biqu_b2_0.5.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = biqu_b2 +name = 0.5mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.5 + diff --git a/resources/variants/biqu/biqu_b2_0.6.inst.cfg b/resources/variants/biqu/biqu_b2_0.6.inst.cfg new file mode 100644 index 0000000000..0ac946844a --- /dev/null +++ b/resources/variants/biqu/biqu_b2_0.6.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = biqu_b2 +name = 0.6mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.6 + diff --git a/resources/variants/biqu/biqu_b2_0.8.inst.cfg b/resources/variants/biqu/biqu_b2_0.8.inst.cfg new file mode 100644 index 0000000000..53b6910342 --- /dev/null +++ b/resources/variants/biqu/biqu_b2_0.8.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = biqu_b2 +name = 0.8mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.8 + diff --git a/resources/variants/geeetech_variants/geeetech_M1S_0.2.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1S_0.2.inst.cfg new file mode 100644 index 0000000000..2ed913b2a4 --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1S_0.2.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1S +name = 0.2mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.2 + diff --git a/resources/variants/geeetech_variants/geeetech_M1S_0.3.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1S_0.3.inst.cfg new file mode 100644 index 0000000000..032e25592d --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1S_0.3.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1S +name = 0.3mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.3 + diff --git a/resources/variants/geeetech_variants/geeetech_M1S_0.4.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1S_0.4.inst.cfg new file mode 100644 index 0000000000..12306b7acc --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1S_0.4.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1S +name = 0.4mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.4 + diff --git a/resources/variants/geeetech_variants/geeetech_M1S_0.5.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1S_0.5.inst.cfg new file mode 100644 index 0000000000..59db9afec4 --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1S_0.5.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1S +name = 0.5mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.5 + diff --git a/resources/variants/geeetech_variants/geeetech_M1S_0.6.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1S_0.6.inst.cfg new file mode 100644 index 0000000000..ee92dc8c93 --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1S_0.6.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1S +name = 0.6mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.6 + diff --git a/resources/variants/geeetech_variants/geeetech_M1S_0.8.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1S_0.8.inst.cfg new file mode 100644 index 0000000000..0029d6101c --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1S_0.8.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1S +name = 0.8mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.8 + diff --git a/resources/variants/geeetech_variants/geeetech_M1S_1.0.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1S_1.0.inst.cfg new file mode 100644 index 0000000000..225b0b3c06 --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1S_1.0.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1S +name = 1.0mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 1.0 + diff --git a/resources/variants/geeetech_variants/geeetech_M1_0.2.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1_0.2.inst.cfg new file mode 100644 index 0000000000..77bf14e06e --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1_0.2.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1 +name = 0.2mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.2 + diff --git a/resources/variants/geeetech_variants/geeetech_M1_0.3.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1_0.3.inst.cfg new file mode 100644 index 0000000000..f9ecf2eec2 --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1_0.3.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1 +name = 0.3mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.3 + diff --git a/resources/variants/geeetech_variants/geeetech_M1_0.4.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1_0.4.inst.cfg new file mode 100644 index 0000000000..e3c124c614 --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1_0.4.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1 +name = 0.4mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.4 + diff --git a/resources/variants/geeetech_variants/geeetech_M1_0.5.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1_0.5.inst.cfg new file mode 100644 index 0000000000..5adccce2df --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1_0.5.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1 +name = 0.5mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.5 + diff --git a/resources/variants/geeetech_variants/geeetech_M1_0.6.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1_0.6.inst.cfg new file mode 100644 index 0000000000..f61f2febc0 --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1_0.6.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1 +name = 0.6mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.6 + diff --git a/resources/variants/geeetech_variants/geeetech_M1_0.8.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1_0.8.inst.cfg new file mode 100644 index 0000000000..0677557124 --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1_0.8.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1 +name = 0.8mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.8 + diff --git a/resources/variants/geeetech_variants/geeetech_M1_1.0.inst.cfg b/resources/variants/geeetech_variants/geeetech_M1_1.0.inst.cfg new file mode 100644 index 0000000000..5df54b48df --- /dev/null +++ b/resources/variants/geeetech_variants/geeetech_M1_1.0.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = geeetech_M1 +name = 1.0mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 1.0 + diff --git a/resources/variants/sovol/sovol_sv08_0.4.inst.cfg b/resources/variants/sovol/sovol_sv08_0.4.inst.cfg new file mode 100644 index 0000000000..b5c72e92d3 --- /dev/null +++ b/resources/variants/sovol/sovol_sv08_0.4.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = sovol_sv08 +name = 0.4mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_size = 0.4 + diff --git a/resources/variants/ultimaker_s3_aa04.inst.cfg b/resources/variants/ultimaker_s3_aa04.inst.cfg index 94d27e150b..e3f4aaff2b 100644 --- a/resources/variants/ultimaker_s3_aa04.inst.cfg +++ b/resources/variants/ultimaker_s3_aa04.inst.cfg @@ -12,6 +12,7 @@ type = variant brim_width = 7 machine_nozzle_cool_down_speed = 0.9 machine_nozzle_id = AA 0.4 +machine_nozzle_size = 0.4 machine_nozzle_tip_outer_diameter = 1.0 retraction_amount = 6.5 speed_print = 70 diff --git a/resources/variants/ultimaker_s3_bb04.inst.cfg b/resources/variants/ultimaker_s3_bb04.inst.cfg index 022b4f945c..7923a5a28e 100644 --- a/resources/variants/ultimaker_s3_bb04.inst.cfg +++ b/resources/variants/ultimaker_s3_bb04.inst.cfg @@ -15,6 +15,7 @@ acceleration_support_bottom = =math.ceil(acceleration_support_interface * 100 / acceleration_support_interface = =math.ceil(acceleration_support * 1500 / 2000) machine_nozzle_heat_up_speed = 1.5 machine_nozzle_id = BB 0.4 +machine_nozzle_size = 0.4 machine_nozzle_tip_outer_diameter = 1.0 speed_prime_tower = =math.ceil(speed_print * 10 / 35) speed_support = =math.ceil(speed_print * 25 / 35) diff --git a/resources/variants/ultimaker_s5_aa04.inst.cfg b/resources/variants/ultimaker_s5_aa04.inst.cfg index ef846a089d..69aae0757e 100644 --- a/resources/variants/ultimaker_s5_aa04.inst.cfg +++ b/resources/variants/ultimaker_s5_aa04.inst.cfg @@ -12,6 +12,7 @@ type = variant brim_width = 7 machine_nozzle_cool_down_speed = 0.9 machine_nozzle_id = AA 0.4 +machine_nozzle_size = 0.4 machine_nozzle_tip_outer_diameter = 1.0 retraction_amount = 6.5 speed_print = 70 diff --git a/resources/variants/ultimaker_s5_bb04.inst.cfg b/resources/variants/ultimaker_s5_bb04.inst.cfg index ce287e127b..294b39b8c7 100644 --- a/resources/variants/ultimaker_s5_bb04.inst.cfg +++ b/resources/variants/ultimaker_s5_bb04.inst.cfg @@ -15,6 +15,7 @@ acceleration_support_bottom = =math.ceil(acceleration_support_interface * 100 / acceleration_support_interface = =math.ceil(acceleration_support * 1500 / 2000) machine_nozzle_heat_up_speed = 1.5 machine_nozzle_id = BB 0.4 +machine_nozzle_size = 0.4 machine_nozzle_tip_outer_diameter = 1.0 speed_prime_tower = =math.ceil(speed_print * 10 / 35) speed_support = =math.ceil(speed_print * 25 / 35) diff --git a/resources/variants/ultimaker_s6_bb04.inst.cfg b/resources/variants/ultimaker_s6_bb04.inst.cfg index 756d6fd1d4..e0c62d9596 100644 --- a/resources/variants/ultimaker_s6_bb04.inst.cfg +++ b/resources/variants/ultimaker_s6_bb04.inst.cfg @@ -11,6 +11,7 @@ type = variant [values] machine_nozzle_heat_up_speed = 1.5 machine_nozzle_id = BB 0.4 +machine_nozzle_size = 0.4 machine_nozzle_tip_outer_diameter = 1.0 retraction_amount = 4.5 support_bottom_height = =layer_height * 2 diff --git a/resources/variants/ultimaker_s7_aa04.inst.cfg b/resources/variants/ultimaker_s7_aa04.inst.cfg index 7d5a08d117..ee4ebdceaf 100644 --- a/resources/variants/ultimaker_s7_aa04.inst.cfg +++ b/resources/variants/ultimaker_s7_aa04.inst.cfg @@ -12,6 +12,7 @@ type = variant brim_width = 7 machine_nozzle_cool_down_speed = 0.9 machine_nozzle_id = AA 0.4 +machine_nozzle_size = 0.4 machine_nozzle_tip_outer_diameter = 1.0 retraction_amount = 6.5 speed_print = 70 diff --git a/resources/variants/ultimaker_s7_bb04.inst.cfg b/resources/variants/ultimaker_s7_bb04.inst.cfg index 1bfa216167..18aa61ff4b 100644 --- a/resources/variants/ultimaker_s7_bb04.inst.cfg +++ b/resources/variants/ultimaker_s7_bb04.inst.cfg @@ -15,6 +15,7 @@ acceleration_support_bottom = =math.ceil(acceleration_support_interface * 100 / acceleration_support_interface = =math.ceil(acceleration_support * 1500 / 2000) machine_nozzle_heat_up_speed = 1.5 machine_nozzle_id = BB 0.4 +machine_nozzle_size = 0.4 machine_nozzle_tip_outer_diameter = 1.0 retraction_amount = 4.5 speed_prime_tower = =math.ceil(speed_print * 10 / 35) diff --git a/resources/variants/ultimaker_s8_bb04.inst.cfg b/resources/variants/ultimaker_s8_bb04.inst.cfg index 388ce27ec2..d92874e448 100644 --- a/resources/variants/ultimaker_s8_bb04.inst.cfg +++ b/resources/variants/ultimaker_s8_bb04.inst.cfg @@ -11,6 +11,7 @@ type = variant [values] machine_nozzle_heat_up_speed = 1.5 machine_nozzle_id = BB 0.4 +machine_nozzle_size = 0.4 machine_nozzle_tip_outer_diameter = 1.0 retraction_amount = 4.5 support_bottom_height = =layer_height * 2