diff --git a/.github/workflows/conan-package-create.yml b/.github/workflows/conan-package-create.yml index f9fbbef6f7..3e5f3dbe1c 100644 --- a/.github/workflows/conan-package-create.yml +++ b/.github/workflows/conan-package-create.yml @@ -149,5 +149,5 @@ jobs: run: conan upload "*" -r cura --all -c - name: Upload the Package(s) community - if: ${{ always() && inputs.conan_upload_community == 'true' }} + if: ${{ always() && inputs.conan_upload_community == true }} run: conan upload "*" -r cura-ce -c diff --git a/.github/workflows/conan-recipe-export.yml b/.github/workflows/conan-recipe-export.yml index cab21604fe..f38c0046c9 100644 --- a/.github/workflows/conan-recipe-export.yml +++ b/.github/workflows/conan-recipe-export.yml @@ -102,5 +102,5 @@ jobs: run: conan upload "*" -r cura --all -c - name: Upload the Package(s) community - if: ${{ always() && inputs.conan_upload_community == 'true' }} + if: ${{ always() && inputs.conan_upload_community == true }} run: conan upload "*" -r cura-ce -c diff --git a/.github/workflows/conan-recipe-version.yml b/.github/workflows/conan-recipe-version.yml index 5a24754f03..ddadfe1781 100644 --- a/.github/workflows/conan-recipe-version.yml +++ b/.github/workflows/conan-recipe-version.yml @@ -135,6 +135,9 @@ jobs: user = "_" channel = "_" else: + if latest_branch_version.prerelease and not "." in latest_branch_version.prerelease: + # The prerealese did not contain a version number, default it to 1 + latest_branch_version.prerelease += ".1" if event_name == "pull_request": actual_version = f"{latest_branch_version.major}.{latest_branch_version.minor}.{latest_branch_version.patch}-{latest_branch_version.prerelease.lower()}+{buildmetadata}pr_{issue_number}_{no_commits}" else: diff --git a/.github/workflows/requirements-conan-package.txt b/.github/workflows/requirements-conan-package.txt index 43bb7fc70a..fcc1379cfa 100644 --- a/.github/workflows/requirements-conan-package.txt +++ b/.github/workflows/requirements-conan-package.txt @@ -1,2 +1,2 @@ -conan!=1.51.0,!=1.51.1 -sip==6.5.1 +conan!=1.51.0,!=1.51.1,!=1.51.2,!=1.51.3 +sip diff --git a/.run_templates/pycharm_cura_run.run.xml.jinja b/.run_templates/pycharm_cura_run.run.xml.jinja new file mode 100644 index 0000000000..3c04c5eaef --- /dev/null +++ b/.run_templates/pycharm_cura_run.run.xml.jinja @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/.run_templates/pycharm_cura_test.run.xml.jinja b/.run_templates/pycharm_cura_test.run.xml.jinja new file mode 100644 index 0000000000..428876ee52 --- /dev/null +++ b/.run_templates/pycharm_cura_test.run.xml.jinja @@ -0,0 +1,23 @@ + + + + + diff --git a/CuraVersion.py.jinja b/CuraVersion.py.jinja index 1c30a0b5af..1c8ff0551c 100644 --- a/CuraVersion.py.jinja +++ b/CuraVersion.py.jinja @@ -11,3 +11,4 @@ CuraCloudAPIVersion = "{{ cura_cloud_api_version }}" CuraCloudAccountAPIRoot = "{{ cura_cloud_account_api_root }}" CuraMarketplaceRoot = "{{ cura_marketplace_root }}" CuraDigitalFactoryURL = "{{ cura_digital_factory_url }}" +CuraLatestURL = "{{ cura_latest_url }}" diff --git a/README.md b/README.md index 8ec3101ef6..36e829981f 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,96 @@ -# Cura -

- - - - - - - - - - - - - - - - -

+
-Ultimaker Cura is a state-of-the-art slicer application to prepare your 3D models for printing with a 3D printer. With hundreds of settings -and hundreds of community-managed print profiles, Ultimaker Cura is sure to lead your next project to a success. +
-![Screenshot](cura-logo.PNG) +[![Badge Issues]][Issues]    +[![Badge PullRequests]][PullRequests]    +[![Badge Closed]][Closed] -## Logging Issues +[![Badge Size]][#]    +[![Badge License]][License]    +[![Badge Contributors]][Contributors] -For crashes and similar issues, please attach the following information: +[![Badge Test]][Test]    +[![Badge Conan]][Conan]    -* (On Windows) The log as produced by dxdiag (start -> run -> dxdiag -> save output) -* The Cura GUI log file, located at - * `%APPDATA%\cura\\cura.log` (Windows), or usually `C:\Users\\AppData\Roaming\cura\\cura.log` - * `$HOME/Library/Application Support/cura//cura.log` (OSX) - * `$HOME/.local/share/cura//cura.log` (Ubuntu/Linux) +
+
-If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder. -An alternative is to install the [ExtensiveSupportLogging plugin](https://marketplace.ultimaker.com/app/cura/plugins/UltimakerPackages/ExtensiveSupportLogging) -this creates a zip folder of the relevant log files. If you're experiencing performance issues, we might ask you to connect the CPU profiler -in this plugin and attach the collected data to your support ticket. +![Logo] -## Running from Source -Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Running-Cura-from-Source) for details about running Cura from source. +# Ultimaker Cura -## Plugins -Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Plugin-Directory) for details about creating and using plugins. +*State-of-the-art slicer app to prepare*
+*your 3D models for your 3D printer.* -## Supported printers -Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Adding-new-machine-profiles-to-Cura) for guidelines about adding support -for new machines. +*With hundreds of settings & community-managed print profiles,*
+*Ultimaker Cura is sure to lead your next project to a success.* -## Configuring Cura -Please check out [Wiki page](https://github.com/Ultimaker/Cura/wiki/Cura-Settings) about configuration options for developers. +
+
-## Translating Cura -Please check out [Wiki page](https://github.com/Ultimaker/Cura/wiki/Translating-Cura) about how to translate Cura into other languages. +[![Button Building]][Building]    +[![Button Plugins]][Plugins]    +[![Button Machines]][Machines] + +[![Button Report]][Report]    +[![Button Settings]][Settings]    +[![Button Localize]][Localize] + +
+
+ + + + + Shows cura open on the preview screen with a large benchy model in the center. + + +
+ +
+ + + + +[Contributors]: https://github.com/Ultimaker/Cura/graphs/contributors +[PullRequests]: https://github.com/Ultimaker/Cura/pulls +[Machines]: https://github.com/Ultimaker/Cura/wiki/Adding-new-machine-profiles-to-Cura +[Building]: https://github.com/Ultimaker/Cura/wiki/Running-Cura-from-Source +[Localize]: https://github.com/Ultimaker/Cura/wiki/Translating-Cura +[Settings]: https://github.com/Ultimaker/Cura/wiki/Cura-Settings +[Plugins]: https://github.com/Ultimaker/Cura/wiki/Plugin-Directory +[Closed]: https://github.com/Ultimaker/Cura/issues?q=is%3Aissue+is%3Aclosed +[Issues]: https://github.com/Ultimaker/Cura/issues +[Conan]: https://github.com/Ultimaker/Cura/actions/workflows/conan-package.yml +[Test]: https://github.com/Ultimaker/Cura/actions/workflows/unit-test.yml + +[License]: LICENSE +[Report]: docs/Report.md +[Logo]: resources/images/cura-icon.png +[#]: # + + + + +[Badge Contributors]: https://img.shields.io/github/contributors/ultimaker/cura?style=for-the-badge&logoColor=white&labelColor=db5e8a&color=ab4a6c&logo=GitHub +[Badge PullRequests]: https://img.shields.io/github/issues-pr/ultimaker/cura?style=for-the-badge&logoColor=white&labelColor=bb9f3e&color=937d31&logo=GitExtensions +[Badge License]: https://img.shields.io/badge/License-LGPL3-336887.svg?style=for-the-badge&labelColor=458cb5&logoColor=white&logo=GNU +[Badge Closed]: https://img.shields.io/github/issues-closed/ultimaker/cura?style=for-the-badge&logoColor=white&labelColor=629944&color=446a30&logo=AddThis +[Badge Issues]: https://img.shields.io/github/issues/ultimaker/cura?style=for-the-badge&logoColor=white&labelColor=c34360&color=933349&logo=AdBlock +[Badge Conan]: https://img.shields.io/github/workflow/status/Ultimaker/Cura/conan-package?style=for-the-badge&logoColor=white&labelColor=6185aa&color=4c6987&logo=Conan&label=Conan%20Package +[Badge Test]: https://img.shields.io/github/workflow/status/Ultimaker/Cura/unit-test?style=for-the-badge&logoColor=white&labelColor=4a999d&color=346c6e&logo=Codacy&label=Unit%20Test +[Badge Size]: https://img.shields.io/github/repo-size/ultimaker/cura?style=for-the-badge&logoColor=white&labelColor=715a97&color=584674&logo=GoogleAnalytics + + + + +[Button Localize]: https://img.shields.io/badge/Help_Localize-e2467d?style=for-the-badge&logoColor=white&logo=GoogleTranslate +[Button Machines]: https://img.shields.io/badge/Adding_Machines-yellow?style=for-the-badge&logoColor=white&logo=CloudFoundry +[Button Settings]: https://img.shields.io/badge/Configuration-00979D?style=for-the-badge&logoColor=white&logo=CodeReview +[Button Building]: https://img.shields.io/badge/Building_Cura-blue?style=for-the-badge&logoColor=white&logo=GitBook +[Button Plugins]: https://img.shields.io/badge/Plugin_Usage-569A31?style=for-the-badge&logoColor=white&logo=ROS +[Button Report]: https://img.shields.io/badge/Report_Issues-C9284D?style=for-the-badge&logoColor=white&logo=Cliqz -## License -![License](https://img.shields.io/github/license/ultimaker/cura?style=flat) -Cura is released under terms of the LGPLv3 or higher. A copy of this license should be included with the software. Terms of the license can be found in the LICENSE file. Or at -http://www.gnu.org/licenses/lgpl.html -> But in general it boils down to: -> **You need to share the source of any Cura modifications** diff --git a/conandata.yml b/conandata.yml index 7db6e9a28d..4b9d8dd612 100644 --- a/conandata.yml +++ b/conandata.yml @@ -20,6 +20,9 @@ - "fdm_materials/(latest)@ultimaker/testing" - "cura_binary_data/(latest)@ultimaker/testing" - "cpython/3.10.4" + internal_requirements: + - "fdm_materials_private/(latest)@ultimaker/testing" + - "cura_private_data/(latest)@ultimaker/testing" runinfo: entrypoint: "cura_app.py" pyinstaller: @@ -32,6 +35,11 @@ package: "cura" src: "resources" dst: "share/cura/resources" + cura_private_data: + package: "cura_private_data" + src: "resources" + dst: "share/cura/resources" + internal: true uranium_plugins: package: "uranium" src: "plugins" @@ -60,6 +68,11 @@ package: "fdm_materials" src: "materials" dst: "share/cura/resources/materials" + fdm_materials_private: + package: "fdm_materials_private" + src: "resources/materials" + dst: "share/cura/resources/materials" + internal: true tcl: package: "tcl" src: "lib/tcl8.6" @@ -112,6 +125,9 @@ - "fdm_materials/(latest)@ultimaker/testing" - "cura_binary_data/(latest)@ultimaker/testing" - "cpython/3.10.4" + internal_requirements: + - "fdm_materials_private/(latest)@ultimaker/testing" + - "cura_private_data/(latest)@ultimaker/testing" runinfo: entrypoint: "cura_app.py" pyinstaller: @@ -124,6 +140,11 @@ package: "cura" src: "resources" dst: "share/cura/resources" + cura_private_data: + package: "cura_private_data" + src: "resources" + dst: "share/cura/resources" + internal: true uranium_plugins: package: "uranium" src: "plugins" @@ -152,6 +173,11 @@ package: "fdm_materials" src: "materials" dst: "share/cura/resources/materials" + fdm_materials_private: + package: "fdm_materials_private" + src: "resources/materials" + dst: "share/cura/resources/materials" + internal: true tcl: package: "tcl" src: "lib/tcl8.6" @@ -286,3 +312,113 @@ Windows: "./icons/Cura.ico" Macos: "./icons/cura.icns" Linux: "./icons/cura-128.png" +pycharm_targets: + - jinja_path: .run_templates/pycharm_cura_run.run.xml.jinja + module_name: Cura + name: cura + script_name: cura_app.py + - jinja_path: .run_templates/pycharm_cura_run.run.xml.jinja + module_name: Cura + name: cura_external_engine + parameters: --external-backend + script_name: cura_app.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in tests + script_name: tests/ + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestBuildVolume.py + script_name: tests/TestBuildVolume.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestConvexHullDecorator.py + script_name: tests/TestConvexHullDecorator.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestCuraSceneNode.py + script_name: tests/TestCuraSceneNode.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestCuraSceneNode.py + script_name: tests/TestExtruderManager.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestGCodeListDecorator.py + script_name: tests/TestGCodeListDecorator.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestIntentManager.py + script_name: tests/TestIntentManager.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestLayer.py + script_name: tests/TestLayer.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestMachineAction.py + script_name: tests/TestMachineAction.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestMachineManager.py + script_name: tests/TestMachineManager.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestOAuth2.py + script_name: tests/TestOAuth2.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestObjectsModel.py + script_name: tests/TestObjectsModel.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestPrintInformation.py + script_name: tests/TestPrintInformation.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestProfileRequirements.py + script_name: tests/TestProfileRequirements.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestThemes.py + script_name: tests/TestThemes.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestContainerManager.py + script_name: tests/Settings/TestContainerManager.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestCuraContainerRegistry.py + script_name: tests/Settings/TestCuraContainerRegistry.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestCuraStackBuilder.py + script_name: tests/Settings/TestCuraStackBuilder.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestDefinitionContainer.py + script_name: tests/Settings/TestDefinitionContainer.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestExtruderStack.py + script_name: tests/Settings/TestExtruderStack.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestGlobalStack.py + script_name: tests/Settings/TestGlobalStack.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestProfiles.py + script_name: tests/Settings/TestProfiles.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestSettingInheritanceManager.py + script_name: tests/Settings/TestSettingInheritanceManager.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestSettingOverrideDecorator.py + script_name: tests/Settings/TestSettingOverrideDecorator.py + - jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja + module_name: Cura + name: pytest in TestSettingVisibilityPresets.py + script_name: tests/Settings/TestSettingVisibilityPresets.py diff --git a/conanfile.py b/conanfile.py index 74106fd169..bcdaeda5d9 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,11 +1,6 @@ import os -import sys from pathlib import Path -from io import StringIO - -from platform import python_version - from jinja2 import Template from conans import tools @@ -14,7 +9,7 @@ from conan.tools import files from conan.tools.env import VirtualRunEnv, Environment from conan.errors import ConanInvalidConfiguration -required_conan_version = ">=1.47.0" +required_conan_version = ">=1.48.0" class CuraConan(ConanFile): @@ -59,18 +54,9 @@ class CuraConan(ConanFile): "revision": "auto" } - # TODO: Add unit tests (but they need a different jinja template - _pycharm_targets = [{ - "name": "cura", - "module_name": "Cura", - "script_name": "cura_app.py", - }, { - "name": "cura_external_engine", - "module_name": "Cura", - "script_name": "cura_app.py", - "parameters": "--external-backend" - } - ] + @property + def _pycharm_targets(self): + return self.conan_data["pycharm_targets"] # FIXME: These env vars should be defined in the runenv. _cura_env = None @@ -114,6 +100,10 @@ class CuraConan(ConanFile): def _digital_factory_url(self): return "https://digitalfactory-staging.ultimaker.com" if self._staging else "https://digitalfactory.ultimaker.com" + @property + def _cura_latest_url(self): + return "https://software.ultimaker.com/latest.json" + @property def requirements_txts(self): if self.options.devtools: @@ -175,12 +165,16 @@ class CuraConan(ConanFile): cura_cloud_api_version = self.options.cloud_api_version, cura_cloud_account_api_root = self._cloud_account_api_root, cura_marketplace_root = self._marketplace_root, - cura_digital_factory_url = self._digital_factory_url)) + cura_digital_factory_url = self._digital_factory_url, + cura_latest_url = self._cura_latest_url)) def _generate_pyinstaller_spec(self, location, entrypoint_location, icon_path, entitlements_file): pyinstaller_metadata = self._um_data()["pyinstaller"] datas = [(str(self._base_dir.joinpath("conan_install_info.json")), ".")] for data in pyinstaller_metadata["datas"].values(): + if not self.options.internal and data.get("internal", False): + continue + if "package" in data: # get the paths from conan package if data["package"] == self.name: if self.in_local_cache: @@ -262,6 +256,9 @@ class CuraConan(ConanFile): def requirements(self): for req in self._um_data()["requirements"]: self.requires(req) + if self.options.internal: + for req in self._um_data()["internal_requirements"]: + self.requires(req) def layout(self): self.folders.source = "." @@ -300,11 +297,17 @@ class CuraConan(ConanFile): self.copy("*.fdm_material", root_package = "fdm_materials", src = "@resdirs", dst = "resources/materials", keep_path = False) self.copy("*.sig", root_package = "fdm_materials", src = "@resdirs", dst = "resources/materials", keep_path = False) + if self.options.internal: + self.copy("*.fdm_material", root_package = "fdm_materials_private", src = "@resdirs", dst = "resources/materials", keep_path = False) + self.copy("*.sig", root_package = "fdm_materials_private", src = "@resdirs", dst = "resources/materials", keep_path = False) + self.copy("*", root_package = "cura_private_data", src = self.deps_cpp_info["cura_private_data"].resdirs[0], + dst = self._share_dir.joinpath("cura", "resources"), keep_path = True) + # Copy resources of cura_binary_data self.copy("*", root_package = "cura_binary_data", src = self.deps_cpp_info["cura_binary_data"].resdirs[0], - dst = "venv/share/cura", keep_path = True) + dst = self._share_dir.joinpath("cura", "resources"), keep_path = True) self.copy("*", root_package = "cura_binary_data", src = self.deps_cpp_info["cura_binary_data"].resdirs[1], - dst = "venv/share/uranium", keep_path = True) + dst =self._share_dir.joinpath("uranium", "resources"), keep_path = True) self.copy("*.dll", src = "@bindirs", dst = self._site_packages) self.copy("*.pyd", src = "@libdirs", dst = self._site_packages) @@ -332,6 +335,15 @@ class CuraConan(ConanFile): self.copy_deps("*.sig", root_package = "fdm_materials", src = self.deps_cpp_info["fdm_materials"].resdirs[0], dst = self._share_dir.joinpath("cura", "resources", "materials"), keep_path = False) + # Copy internal resources + if self.options.internal: + self.copy_deps("*.fdm_material", root_package = "fdm_materials_private", src = self.deps_cpp_info["fdm_materials_private"].resdirs[0], + dst = self._share_dir.joinpath("cura", "resources", "materials"), keep_path = False) + self.copy_deps("*.sig", root_package = "fdm_materials_private", src = self.deps_cpp_info["fdm_materials_private"].resdirs[0], + dst = self._share_dir.joinpath("cura", "resources", "materials"), keep_path = False) + self.copy_deps("*", root_package = "cura_private_data", src = self.deps_cpp_info["cura_private_data"].resdirs[0], + dst = self._share_dir.joinpath("cura", "resources"), keep_path = True) + # Copy resources of Uranium (keep folder structure) self.copy_deps("*", root_package = "uranium", src = self.deps_cpp_info["uranium"].resdirs[0], dst = self._share_dir.joinpath("uranium", "resources"), keep_path = True) diff --git a/cura-logo-dark.PNG b/cura-logo-dark.PNG new file mode 100644 index 0000000000..c176eda379 Binary files /dev/null and b/cura-logo-dark.PNG differ diff --git a/cura-logo.PNG b/cura-logo.PNG index 52da8203a8..972dcd7535 100644 Binary files a/cura-logo.PNG and b/cura-logo.PNG differ diff --git a/cura/ApplicationMetadata.py b/cura/ApplicationMetadata.py index 60d9201d8e..4938d569e7 100644 --- a/cura/ApplicationMetadata.py +++ b/cura/ApplicationMetadata.py @@ -9,12 +9,20 @@ DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura" DEFAULT_CURA_VERSION = "dev" DEFAULT_CURA_BUILD_TYPE = "" DEFAULT_CURA_DEBUG_MODE = False +DEFAULT_CURA_LATEST_URL = "https://software.ultimaker.com/latest.json" # Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for # example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the # CuraVersion.py.in template. CuraSDKVersion = "8.1.0" +try: + from cura.CuraVersion import CuraLatestURL + if CuraLatestURL == "": + CuraLatestURL = DEFAULT_CURA_LATEST_URL +except ImportError: + CuraLatestURL = DEFAULT_CURA_LATEST_URL + try: from cura.CuraVersion import CuraAppName # type: ignore if CuraAppName == "": diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index eeaead4f71..f701c94797 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -145,6 +145,8 @@ class CuraApplication(QtApplication): DefinitionChangesContainer = Resources.UserType + 10 SettingVisibilityPreset = Resources.UserType + 11 IntentInstanceContainer = Resources.UserType + 12 + AbstractMachineStack = Resources.UserType + 13 + pyqtEnum(ResourceTypes) @@ -152,6 +154,7 @@ class CuraApplication(QtApplication): super().__init__(name = ApplicationMetadata.CuraAppName, app_display_name = ApplicationMetadata.CuraAppDisplayName, version = ApplicationMetadata.CuraVersion if not ApplicationMetadata.IsAlternateVersion else ApplicationMetadata.CuraBuildType, + latest_url = ApplicationMetadata.CuraLatestURL, api_version = ApplicationMetadata.CuraSDKVersion, build_type = ApplicationMetadata.CuraBuildType, is_debug_mode = ApplicationMetadata.CuraDebugMode, @@ -422,6 +425,7 @@ class CuraApplication(QtApplication): Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility") Resources.addStorageType(self.ResourceTypes.IntentInstanceContainer, "intent") + Resources.addStorageType(self.ResourceTypes.AbstractMachineStack, "abstract_machine_instances") self._container_registry.addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality") self._container_registry.addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes") @@ -432,6 +436,7 @@ class CuraApplication(QtApplication): self._container_registry.addResourceType(self.ResourceTypes.MachineStack, "machine") self._container_registry.addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") self._container_registry.addResourceType(self.ResourceTypes.IntentInstanceContainer, "intent") + self._container_registry.addResourceType(self.ResourceTypes.AbstractMachineStack, "abstract_machine") Resources.addType(self.ResourceTypes.QmlFiles, "qml") Resources.addType(self.ResourceTypes.Firmware, "firmware") @@ -480,6 +485,7 @@ class CuraApplication(QtApplication): ("variant", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.VariantInstanceContainer, "application/x-uranium-instancecontainer"), ("setting_visibility", SettingVisibilityPresetsModel.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.SettingVisibilityPreset, "application/x-uranium-preferences"), ("machine", 2): (Resources.DefinitionContainers, "application/x-uranium-definitioncontainer"), + ("abstract_machine", 1): (Resources.DefinitionContainers, "application/x-uranium-definitioncontainer"), ("extruder", 2): (Resources.DefinitionContainers, "application/x-uranium-definitioncontainer") } ) diff --git a/cura/Machines/MachineErrorChecker.py b/cura/Machines/MachineErrorChecker.py index 2cb333d157..b113bd6dd7 100644 --- a/cura/Machines/MachineErrorChecker.py +++ b/cura/Machines/MachineErrorChecker.py @@ -45,11 +45,7 @@ class MachineErrorChecker(QObject): self._start_time = 0. # measure checking time - # This timer delays the starting of error check so we can react less frequently if the user is frequently - # changing settings. - self._error_check_timer = QTimer(self) - self._error_check_timer.setInterval(100) - self._error_check_timer.setSingleShot(True) + self._setCheckTimer() self._keys_to_check = set() # type: Set[str] @@ -66,6 +62,18 @@ class MachineErrorChecker(QObject): self._onMachineChanged() + def _setCheckTimer(self) -> None: + """A QTimer to regulate error check frequency + + This timer delays the starting of error check + so we can react less frequently if the user is frequently + changing settings. + """ + + self._error_check_timer = QTimer(self) + self._error_check_timer.setInterval(100) + self._error_check_timer.setSingleShot(True) + def _onMachineChanged(self) -> None: if self._global_stack: self._global_stack.propertyChanged.disconnect(self.startErrorCheckPropertyChanged) diff --git a/cura/PrinterOutput/Peripheral.py b/cura/PrinterOutput/Peripheral.py index 27d127832b..4613506978 100644 --- a/cura/PrinterOutput/Peripheral.py +++ b/cura/PrinterOutput/Peripheral.py @@ -1,20 +1,19 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from dataclasses import dataclass +@dataclass class Peripheral: """Data class that represents a peripheral for a printer. Output device plug-ins may specify that the printer has a certain set of peripherals. This set is then possibly shown in the interface of the monitor stage. + + Args: + type (string): A unique ID for the type of peripheral. + name (string): A human-readable name for the peripheral. """ - - def __init__(self, peripheral_type: str, name: str) -> None: - """Constructs the peripheral. - - :param peripheral_type: A unique ID for the type of peripheral. - :param name: A human-readable name for the peripheral. - """ - self.type = peripheral_type - self.name = name + type: str + name: str diff --git a/cura/Settings/AbstractMachine.py b/cura/Settings/AbstractMachine.py new file mode 100644 index 0000000000..a89201a294 --- /dev/null +++ b/cura/Settings/AbstractMachine.py @@ -0,0 +1,35 @@ +from typing import List + +from UM.Settings.ContainerStack import ContainerStack +from cura.PrinterOutput.PrinterOutputDevice import ConnectionType +from cura.Settings.GlobalStack import GlobalStack +from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase +from UM.Settings.ContainerRegistry import ContainerRegistry + + +class AbstractMachine(GlobalStack): + """ Represents a group of machines of the same type. This allows the user to select settings before selecting a printer. """ + + def __init__(self, container_id: str) -> None: + super().__init__(container_id) + self.setMetaDataEntry("type", "abstract_machine") + + def getMachines(self) -> List[ContainerStack]: + from cura.CuraApplication import CuraApplication + + application = CuraApplication.getInstance() + registry = application.getContainerRegistry() + + printer_type = self.definition.getId() + return [machine for machine in registry.findContainerStacks(type="machine") if machine.definition.id == printer_type and ConnectionType.CloudConnection in machine.configuredConnectionTypes] + + +## private: +_abstract_machine_mime = MimeType( + name = "application/x-cura-abstract-machine", + comment = "Cura Abstract Machine", + suffixes = ["global.cfg"] +) + +MimeTypeDatabase.addMimeType(_abstract_machine_mime) +ContainerRegistry.addContainerTypeByName(AbstractMachine, "abstract_machine", _abstract_machine_mime.name) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 6ff856efcb..1a711ef919 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -108,7 +108,7 @@ class CuraContainerRegistry(ContainerRegistry): :param container_type: :type{string} Type of the container (machine, quality, ...) :param container_name: :type{string} Name to check """ - container_class = ContainerStack if container_type == "machine" else InstanceContainer + container_class = ContainerStack if "machine" in container_type else InstanceContainer return self.findContainersMetadata(container_type = container_class, id = container_name, type = container_type, ignore_case = True) or \ self.findContainersMetadata(container_type = container_class, name = container_name, type = container_type) diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index 5348deb4bd..a8a1d780ea 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -427,4 +427,4 @@ class _ContainerIndexes: } # Reverse lookup: type -> index - TypeIndexMap = dict([(v, k) for k, v in IndexTypeMap.items()]) + TypeIndexMap = {v: k for k, v in IndexTypeMap.items()} diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index ff9a795c43..7eff275457 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -9,6 +9,7 @@ from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.InstanceContainer import InstanceContainer from cura.Machines.ContainerTree import ContainerTree +from .AbstractMachine import AbstractMachine from .GlobalStack import GlobalStack from .ExtruderStack import ExtruderStack @@ -27,7 +28,7 @@ class CuraStackBuilder: :return: The new global stack or None if an error occurred. """ - from cura.CuraApplication import CuraApplication + from cura.CuraApplication import CuraApplication # inline import needed due to circular import application = CuraApplication.getInstance() registry = application.getContainerRegistry() container_tree = ContainerTree.getInstance() @@ -91,7 +92,7 @@ class CuraStackBuilder: :param extruder_position: The position of the current extruder. """ - from cura.CuraApplication import CuraApplication + from cura.CuraApplication import CuraApplication # inline import needed due to circular import application = CuraApplication.getInstance() registry = application.getContainerRegistry() @@ -199,13 +200,21 @@ class CuraStackBuilder: :return: A new Global stack instance with the specified parameters. """ - - from cura.CuraApplication import CuraApplication - application = CuraApplication.getInstance() - registry = application.getContainerRegistry() - stack = GlobalStack(new_stack_id) stack.setDefinition(definition) + cls.createUserContainer(new_stack_id, definition, stack, variant_container, material_container, quality_container) + return stack + + @classmethod + def createUserContainer(cls, new_stack_id: str, definition: DefinitionContainerInterface, + stack: GlobalStack, + variant_container: "InstanceContainer", + material_container: "InstanceContainer", + quality_container: "InstanceContainer") -> None: + from cura.CuraApplication import CuraApplication + application = CuraApplication.getInstance() + + registry = application.getContainerRegistry() # Create user container user_container = cls.createUserChangesContainer(new_stack_id + "_user", definition.getId(), new_stack_id, @@ -221,8 +230,6 @@ class CuraStackBuilder: registry.addContainer(user_container) - return stack - @classmethod def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str, is_global_stack: bool) -> "InstanceContainer": @@ -259,3 +266,49 @@ class CuraStackBuilder: container_stack.definitionChanges = definition_changes_container return definition_changes_container + + @classmethod + def createAbstractMachine(cls, definition_id: str) -> Optional[AbstractMachine]: + """Create a new instance of an abstract machine. + + :param definition_id: The ID of the machine definition to use. + + :return: The new Abstract Machine or None if an error occurred. + """ + abstract_machine_id = definition_id + "_abstract_machine" + + from cura.CuraApplication import CuraApplication + application = CuraApplication.getInstance() + registry = application.getContainerRegistry() + container_tree = ContainerTree.getInstance() + + if registry.findContainerStacks(type = "abstract_machine", id = abstract_machine_id): + # This abstract machine already exists + return None + + match registry.findDefinitionContainers(type = "machine", id = definition_id): + case []: + # It should not be possible for the definition to be missing since an abstract machine will only + # be created as a result of a machine with definition_id being created. + Logger.error(f"Definition {definition_id} was not found!") + return None + case [machine_definition, *_definitions]: + machine_node = container_tree.machines[machine_definition.getId()] + name = machine_definition.getName() + + stack = AbstractMachine(abstract_machine_id) + stack.setDefinition(machine_definition) + cls.createUserContainer( + name, + machine_definition, + stack, + application.empty_variant_container, + application.empty_material_container, + machine_node.preferredGlobalQuality().container, + ) + + stack.setName(name) + + registry.addContainer(stack) + + return stack diff --git a/cura/TaskManagement/OnExitCallbackManager.py b/cura/TaskManagement/OnExitCallbackManager.py index 7894931e9c..54121a2960 100644 --- a/cura/TaskManagement/OnExitCallbackManager.py +++ b/cura/TaskManagement/OnExitCallbackManager.py @@ -10,10 +10,13 @@ if TYPE_CHECKING: # -# This class manages a all registered upon-exit checks that need to be perform when the application tries to exit. -# For example, to show a confirmation dialog when there is USB printing in progress, etc. All callbacks will be called -# in the order of when they got registered. If all callbacks "passes", that is, for example, if the user clicks "yes" -# on the exit confirmation dialog or nothing that's blocking the exit, then the application will quit after that. +# This class manages all registered upon-exit checks +# that need to be performed when the application tries to exit. +# For example, show a confirmation dialog when there is USB printing in progress. +# All callbacks will be called in the order of when they were registered. +# If all callbacks "pass", for example: +# if the user clicks "yes" on the exit confirmation dialog +# and nothing else is blocking the exit, then the application will quit. # class OnExitCallbackManager: @@ -35,10 +38,12 @@ class OnExitCallbackManager: def getIsAllChecksPassed(self) -> bool: return self._is_all_checks_passed - # Trigger the next callback if available. If not, it means that all callbacks have "passed", which means we should - # not block the application to quit, and it will call the application to actually quit. + # Trigger the next callback if there is one. + # If not, all callbacks have "passed", + # which means we should not prevent the application from quitting, + # and we call the application to actually quit. def triggerNextCallback(self) -> None: - # Get the next callback and schedule that if + # Get the next callback and schedule it this_callback = None if self._current_callback_idx < len(self._on_exit_callback_list): this_callback = self._on_exit_callback_list[self._current_callback_idx] @@ -55,10 +60,11 @@ class OnExitCallbackManager: # Tell the application to exit self._application.callLater(self._application.closeApplication) - # This is the callback function which an on-exit callback should call when it finishes, it should provide the - # "should_proceed" flag indicating whether this check has "passed", or in other words, whether quitting the - # application should be blocked. If the last on-exit callback doesn't block the quitting, it will call the next - # registered on-exit callback if available. + # Callback function which an on-exit callback calls when it finishes. + # It provides a "should_proceed" flag indicating whether the check has "passed", + # or whether quitting the application should be blocked. + # If the last on-exit callback doesn't block quitting, it will call the next + # registered on-exit callback if one is available. def onCurrentCallbackFinished(self, should_proceed: bool = True) -> None: if not should_proceed: Logger.log("d", "on-app-exit callback finished and we should not proceed.") diff --git a/docs/Report.md b/docs/Report.md new file mode 100644 index 0000000000..8b24903878 --- /dev/null +++ b/docs/Report.md @@ -0,0 +1,81 @@ + +# Reporting Issues + +Please attach the following information in case
+you want to report crashing or similar issues. + +
+ +## DxDiag + +### ![Badge Windows] + +The log as produced by **dxdiag**. + +  start    »    run    »    dxdiag    »    save output   + +
+
+ +## Cura GUI Log + +If the Cura user interface still starts, you can also
+reach these directories from the application menu: + +  Help    »    Show settings folder   + +
+ +### ![Badge Windows] + +``` +%APPDATA%\cura\<Cura Version>\cura.log +``` + +or + +``` +C:\Users\\AppData\Roaming\cura\<Cura Version>\cura.log +``` + +
+ +### ![Badge Linux] + +``` +~/.local/share/cura/<Cura Version>/cura.log +``` + +
+ +### ![Badge MacOS] + +``` +~/Library/Application Support/cura/<Cura Version>/cura.log +``` + +
+
+ +## Alternative + +An alternative is to install the **[ExtensiveSupportLogging]**
+plugin this creates a zip folder of the relevant log files. + +If you're experiencing performance issues, we might ask
+you to connect the CPU profiler in this plugin and attach
+the collected data to your support ticket. + +
+ + + + +[ExtensiveSupportLogging]: https://marketplace.ultimaker.com/app/cura/plugins/UltimakerPackages/ExtensiveSupportLogging + + + + +[Badge Windows]: https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logoColor=white&logo=Windows +[Badge Linux]: https://img.shields.io/badge/Linux-00A95C?style=for-the-badge&logoColor=white&logo=Linux +[Badge MacOS]: https://img.shields.io/badge/MacOS-403C3D?style=for-the-badge&logoColor=white&logo=MacOS diff --git a/docs/profiles/setting_properties.md b/docs/profiles/setting_properties.md index 84fb458c63..27a0d17c4d 100644 --- a/docs/profiles/setting_properties.md +++ b/docs/profiles/setting_properties.md @@ -2,32 +2,77 @@ Setting Properties ==== Each setting in Cura has a number of properties. It's not just a key and a value. This page lists the properties that a setting can define. -* `key` (string): The identifier by which the setting is referenced. This is not a human-readable name, but just a reference string, such as `layer_height_0`. Typically these are named with the most significant category first, in order to sort them better, such as `material_print_temperature`. This is not actually a real property but just an identifier; it can't be changed. -* `value` (optional): The current value of the setting. This can be a function, an arbitrary Python expression that depends on the values of other settings. If it's not present, the `default_value` is used. -* `default_value`: A default value for the setting if `value` is undefined. This property is required however. It can't be a Python expression, but it can be any JSON type. This is made separate so that CuraEngine can read it out as well for its debugging mode via the command line, without needing a complete Python interpreter. -* `label` (string): The human-readable name for the setting. This label is translated. -* `description` (string): A longer description of what the setting does when you change it. This description is translated as well. -* `type` (string): The type of value that this setting contains. Allowed types are: `bool`, `str`, `float`, `int`, `enum`, `category`, `[int]`, `vec3`, `polygon` and `polygons`. -* `unit` (optional string): A unit that is displayed at the right-hand side of the text field where the user enters the setting value. -* `resolve` (optional string): A Python expression that resolves disagreements for global settings if multiple per-extruder profiles define different values for a setting. Typically this takes the values for the setting from all stacks and computes one final value for it that will be used for the global setting. For instance, the `resolve` function for the build plate temperature is `max(extruderValues('material_bed_temperature')`, meaning that it will use the hottest bed temperature of all materials of the extruders in use. -* `limit_to_extruder` (optional): A Python expression that indicates which extruder a setting will be obtained from. This is used for settings that may be extruder-specific but the extruder is not necessarily the current extruder. For instance, support settings need to be evaluated for the support extruder. Infill settings need to be evaluated for the infill extruder if the infill extruder is changed. -* `enabled` (optional string or boolean): Whether the setting can currently be made visible for the user. This can be a simple true/false, or a Python expression that depends on other settings. Typically used for settings that don't apply when another setting is disabled, such as to hide the support settings if support is disabled. -* `minimum_value` (optional): The lowest acceptable value for this setting. If it's any lower, Cura will not allow the user to slice. By convention this is used to prevent setting values that are technically or physically impossible, such as a layer height of 0mm. This property only applies to numerical settings. -* `maximum_value` (optional): The highest acceptable value for this setting. If it's any higher, Cura will not allow the user to slice. By convention this is used to prevent setting values that are technically or physically impossible, such as a support overhang angle of more than 90 degrees. This property only applies to numerical settings. -* `minimum_value_warning` (optional): The threshold under which a warning is displayed to the user. By convention this is used to indicate that it will probably not print very nicely with such a low setting value. This property only applies to numerical settings. -* `maximum_value_warning` (optional): The threshold above which a warning is displayed to the user. By convention this is used to indicate that it will probably not print very nicely with such a high setting value. This property only applies to numerical settings. -* `settable_globally` (optional boolean): Whether the setting can be changed globally. For some mesh-type settings such as `support_mesh` this doesn't make sense, so those can't be changed globally. They are not displayed in the main settings list then. -* `settable_per_meshgroup` (optional boolean): Whether a setting can be changed per group of meshes. Currently unused in Cura. -* `settable_per_extruder` (optional boolean): Whether a setting can be changed per extruder. Some settings, like the build plate temperature, can't be adjusted separately for each extruder. An icon is shown in the interface to indicate this. If the user changes these settings they are stored in the global stack. -* `settable_per_mesh` (optional boolean): Whether a setting can be changed per mesh. The settings that can be changed per mesh are shown in the list of available settings in the per-object settings tool. -* `children` (optional list): A list of child settings. These are displayed with an indentation. If all child settings are overridden by the user, the parent setting gets greyed out to indicate that the parent setting has no effect any more. This is not strictly always the case though, because that would depend on the inheritance functions in the `value`. -* `icon` (optional string): A path to an icon to be displayed. Only applies to setting categories. -* `allow_empty` (optional bool): Whether the setting is allowed to be empty. If it's not, this will be treated as a setting error and Cura will not allow the user to slice. Only applies to string-type settings. -* `warning_description` (optional string): A warning message to display when the setting has a warning value. This is currently unused by Cura. -* `error_description` (optional string): An error message to display when the setting has an error value. This is currently unused by Cura. -* `options` (dictionary): A list of values that the user can choose from. The keys of this dictionary are keys that CuraEngine identifies the option with. The values are human-readable strings and will be translated. Only applies to (and only required for) enum-type settings. -* `comments` (optional string): Comments to other programmers about the setting. This is not used by Cura. -* `is_uuid` (optional boolean): Whether or not this setting indicates a UUID-4. If it is, the setting will indicate an error if it's not in the correct format. Only applies to string-type settings. -* `regex_blacklist_pattern` (optional string): A regular expression, where if the setting value matches with this regular expression, it gets an error state. Only applies to string-type settings. -* `error_value` (optional): If the setting value is equal to this value, it will show a setting error. This is used to display errors for non-numerical settings such as checkboxes. -* `warning_value` (optional): If the setting value is equal to this value, it will show a setting warning. This is used to display warnings for non-numerical settings such as checkboxes. \ No newline at end of file +* `key` (string): __The identifier by which the setting is referenced.__ + * This is not a human-readable name, but just a reference string, such as `layer_height_0`. + * This is not actually a real property but just an identifier; it can't be changed. + * Typically these are named with the most significant category first, in order to sort them better, such as `material_print_temperature`. +* `value` (optional): __The current value of the setting.__ + * This can be a function (an arbitrary Python expression) that depends on the values of other settings. + * If it's not present, the `default_value` is used. +* `default_value`: __A default value for the setting if `value` is undefined.__ + * This property is required. + * It can't be a Python expression, but it can be any JSON type. + * This is made separate so that CuraEngine can read it out for its debugging mode via the command line, without needing a complete Python interpreter. +* `label` (string): __The human-readable name for the setting.__ + * This label is translated. +* `description` (string): __A longer description of what the setting does when you change it.__ + * This description is translated. +* `type` (string): __The type of value that this setting contains.__ + * Allowed types are: `bool`, `str`, `float`, `int`, `enum`, `category`, `[int]`, `vec3`, `polygon` and `polygons`. +* `unit` (optional string): __A unit that is displayed at the right-hand side of the text field where the user enters the setting value.__ +* `resolve` (optional string): __A Python expression that resolves disagreements for global settings if multiple per-extruder profiles define different values for a setting.__ + * Typically this takes the values for the setting from all stacks and computes one final value for it that will be used for the global setting. For instance, the `resolve` function for the build plate temperature is `max(extruderValues('material_bed_temperature')`, meaning that it will use the hottest bed temperature of all materials of the extruders in use. +* `limit_to_extruder` (optional): __A Python expression that indicates which extruder a setting will be obtained from.__ + * This is used for settings that may be extruder-specific but the extruder is not necessarily the current extruder. For instance, support settings need to be evaluated for the support extruder. Infill settings need to be evaluated for the infill extruder if the infill extruder is changed. +* `enabled` (optional string or boolean): __Whether the setting can currently be made visible for the user.__ + * This can be a simple true/false, or a Python expression that depends on other settings. + * Typically used for settings that don't apply when another setting is disabled, such as to hide the support settings if support is disabled. +* `minimum_value` (optional): __The lowest acceptable value for this setting.__ + * If it's any lower, Cura will not allow the user to slice. + * This property only applies to numerical settings. + * By convention this is used to prevent setting values that are technically or physically impossible, such as a layer height of 0mm. +* `maximum_value` (optional): __The highest acceptable value for this setting.__ + * If it's any higher, Cura will not allow the user to slice. + * This property only applies to numerical settings. + * By convention this is used to prevent setting values that are technically or physically impossible, such as a support overhang angle of more than 90 degrees. +* `minimum_value_warning` (optional): __The threshold under which a warning is displayed to the user.__ + * This property only applies to numerical settings. + * By convention this is used to indicate that it will probably not print very nicely with such a low setting value. +* `maximum_value_warning` (optional): __The threshold above which a warning is displayed to the user.__ + * This property only applies to numerical settings. + * By convention this is used to indicate that it will probably not print very nicely with such a high setting value. +* `settable_globally` (optional boolean): __Whether the setting can be changed globally.__ + * For some mesh-type settings such as `support_mesh` this doesn't make sense, so those can't be changed globally. They are not displayed in the main settings list then. +* `settable_per_meshgroup` (optional boolean): __Whether a setting can be changed per group of meshes.__ + * *This is currently unused by Cura.* +* `settable_per_extruder` (optional boolean): __Whether a setting can be changed per extruder.__ + * Some settings, like the build plate temperature, can't be adjusted separately for each extruder. An icon is shown in the interface to indicate this. + * If the user changes these settings they are stored in the global stack. +* `settable_per_mesh` (optional boolean): __Whether a setting can be changed per mesh.__ + * The settings that can be changed per mesh are shown in the list of available settings in the per-object settings tool. +* `children` (optional list): __A list of child settings.__ + * These are displayed with an indentation. If all child settings are overridden by the user, the parent setting gets greyed out to indicate that the parent setting has no effect any more. This is not strictly always the case though, because that would depend on the inheritance functions in the `value`. +* `icon` (optional string): __A path to an icon to be displayed.__ + * Only applies to setting categories. +* `allow_empty` (optional bool): __Whether the setting is allowed to be empty.__ + * If it's not, this will be treated as a setting error and Cura will not allow the user to slice. + * Only applies to string-type settings. +* `warning_description` (optional string): __A warning message to display when the setting has a warning value.__ + * *This is currently unused by Cura.* +* `error_description` (optional string): __An error message to display when the setting has an error value.__ + * *This is currently unused by Cura.* +* `options` (dictionary): __A list of values that the user can choose from.__ + * The keys of this dictionary are keys that CuraEngine identifies the option with. + * The values are human-readable strings and will be translated. + * Only applies to (and only required for) enum-type settings. +* `comments` (optional string): __Comments to other programmers about the setting.__ + * *This is currently unused by Cura.* +* `is_uuid` (optional boolean): __Whether or not this setting indicates a UUID-4.__ + * If it is, the setting will indicate an error if it's not in the correct format. + * Only applies to string-type settings. +* `regex_blacklist_pattern` (optional string): __A regular expression, where if the setting value matches with this regular expression, it gets an error state.__ + * Only applies to string-type settings. +* `error_value` (optional): __If the setting value is equal to this value, it will show a setting error.__ + * This is used to display errors for non-numerical settings such as checkboxes. +* `warning_value` (optional): __If the setting value is equal to this value, it will show a setting warning.__ + * This is used to display warnings for non-numerical settings such as checkboxes. diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index f7f659124c..30bbf68f6c 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -400,11 +400,13 @@ class CloudOutputDeviceManager: # We do not use use MachineManager.addMachine here because we need to set the cluster ID before activating it. new_machine = CuraStackBuilder.createMachine(device.name, device.printerType, show_warning_message=False) if not new_machine: - Logger.log("e", "Failed creating a new machine") + Logger.error(f"Failed creating a new machine for {device.name}") return False self._setOutputDeviceMetadata(device, new_machine) + _abstract_machine = CuraStackBuilder.createAbstractMachine(device.printerType) + if activate: CuraApplication.getInstance().getMachineManager().setActiveMachine(new_machine.getId()) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 0c939e03f2..ce641cf032 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1225,20 +1225,7 @@ "default_value": 0.3, "value": "min_wall_line_width", "type": "float", - "settable_per_mesh": true, - "children": - { - "wall_split_middle_threshold": { - "label": "Split Middle Line Threshold", - "description": "The smallest line width, as a factor of the normal line width, above which the middle line (if there is one) will be split into two. Reduce this setting to use more, thinner lines. Increase to use fewer, wider lines. Note that this applies -as if- the entire shape should be filled with wall, so the middle here refers to the middle of the object between two outer edges of the shape, even if there actually is fill or (other) skin in the print instead of wall.", - "type": "float", - "unit": "%", - "default_value": 50, - "value": "max(1, min(99, 100 * (2 * min_even_wall_line_width - wall_line_width_0) / wall_line_width_0))", - "minimum_value": "1", - "maximum_value": "99" - } - } + "settable_per_mesh": true }, "min_odd_wall_line_width": { @@ -1250,20 +1237,7 @@ "default_value": 0.3, "value": "min_wall_line_width", "type": "float", - "settable_per_mesh": true, - "children": - { - "wall_add_middle_threshold": { - "label": "Add Middle Line Threshold", - "description": "The smallest line width, as a factor of the normal line width, above which a middle line (if there wasn't one already) will be added. Reduce this setting to use more, thinner lines. Increase to use fewer, wider lines. Note that this applies -as if- the entire shape should be filled with wall, so the middle here refers to the middle of the object between two outer edges of the shape, even if there actually is fill or (other) skin in the print instead of wall.", - "type": "float", - "unit": "%", - "default_value": 75, - "value": "max(1, min(99, 100 * min_odd_wall_line_width / wall_line_width_x))", - "minimum_value": "1", - "maximum_value": "99" - } - } + "settable_per_mesh": true } } }, @@ -8049,7 +8023,7 @@ "label": "Remove Raft Inside Corners", "description": "Remove inside corners from the raft, causing the raft to become convex.", "type": "bool", - "default_value": false, + "default_value": true, "resolve": "any(extruderValues('raft_remove_inside_corners'))", "enabled": "resolveOrValue('adhesion_type') == 'raft'", "settable_per_mesh": false, diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedInfillDensitySelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedInfillDensitySelector.qml index bb3b0cdbec..0317cb7814 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedInfillDensitySelector.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedInfillDensitySelector.qml @@ -51,7 +51,17 @@ Item { target: infillSlider property: "value" - value: parseInt(infillDensity.properties.value) + value: { + // The infill slider has a max value of 100. When it is given a value > 100 onValueChanged updates the setting to be 100. + // When changing to an intent with infillDensity > 100, it would always be clamped to 100. + // This will force the slider to ignore the first onValueChanged for values > 100 so higher values can be set. + var density = parseInt(infillDensity.properties.value) + if (density > 100) { + infillSlider.ignoreValueChange = true + } + + return density + } } // Here are the elements that are shown in the left column @@ -84,6 +94,8 @@ Item { id: infillSlider + property var ignoreValueChange: false + width: parent.width height: UM.Theme.getSize("print_setup_slider_handle").height // The handle is the widest element of the slider @@ -157,7 +169,13 @@ Item target: infillSlider function onValueChanged() { - // Don't round the value if it's already the same + if (infillSlider.ignoreValueChange) + { + infillSlider.ignoreValueChange = false + return + } + + // Don't update if the setting value, if the slider has the same value if (parseInt(infillDensity.properties.value) == infillSlider.value) { return