From 6f1adaad4345f1c3b4839d4a757e1a93ab2dd003 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 19 Oct 2023 15:48:28 +0200 Subject: [PATCH 01/42] Make Conan/Python installs available for whole project and not just the AboutDialog Generation of dependency list now happens in Also cleaned up the AboutDialog.qml CURA-10561 --- .github/workflows/linux.yml | 20 +- .github/workflows/macos.yml | 20 +- .github/workflows/windows.yml | 20 +- .gitignore | 1 - AboutDialogVersionsList.qml.jinja | 61 ----- CuraVersion.py.jinja | 5 +- conanfile.py | 135 ++-------- cura/ApplicationMetadata.py | 32 ++- cura/CuraApplication.py | 18 +- resources/qml/Dialogs/AboutDialog.qml | 344 +++++++++++++++----------- 10 files changed, 287 insertions(+), 369 deletions(-) delete mode 100644 AboutDialogVersionsList.qml.jinja diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 719a07250c..1830a023c4 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -155,7 +155,7 @@ jobs: run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache" - name: Create the Packages (Bash) - run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING --json "cura_inst/conan_install_info.json" + run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING - name: Upload the Package(s) if: always() @@ -206,12 +206,7 @@ jobs: import json from pathlib import Path - conan_install_info_path = Path("cura_inst/conan_install_info.json") - conan_info = {"installed": []} - if os.path.exists(conan_install_info_path): - with open(conan_install_info_path, "r") as f: - conan_info = json.load(f) - sorted_deps = sorted([dep["recipe"]["id"].replace('#', r' rev: ') for dep in conan_info["installed"]]) + from cura.CuraVersion import ConanInstalls summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" @@ -223,14 +218,17 @@ jobs: f.write(content) f.writelines("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n") f.writelines("## Conan packages:\n") - for dep in sorted_deps: - f.writelines(f"`{dep}`\n") + for dep_name, dep_info in ConanDependencies.items(): + f.writelines(f"`{dep_name} {dep_info["version"]} {dep_info["revision"]}`\n") - name: Summarize the used Python modules shell: python run: | import os import pkg_resources + + from cura.CuraVersion import ConanDependencies + summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" if os.path.exists(summary_env): @@ -240,8 +238,8 @@ jobs: with open(summary_env, "w") as f: f.write(content) f.writelines("## Python modules:\n") - for package in pkg_resources.working_set: - f.writelines(f"`{package.key}/{package.version}`\n") + for dep_name, dep_info in ConanDependencies.items(): + f.writelines(f"`{dep_name} {dep_info["version"]}`\n") - name: Create the Linux AppImage (Bash) run: | diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 01a64f5180..b8ade8f4da 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -155,7 +155,7 @@ jobs: run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache" - name: Create the Packages (Bash) - run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING --json "cura_inst/conan_install_info.json" + run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING" - name: Upload the Package(s) if: ${{ inputs.operating_system != 'self-hosted' }} @@ -210,12 +210,7 @@ jobs: import json from pathlib import Path - conan_install_info_path = Path("cura_inst/conan_install_info.json") - conan_info = {"installed": []} - if os.path.exists(conan_install_info_path): - with open(conan_install_info_path, "r") as f: - conan_info = json.load(f) - sorted_deps = sorted([dep["recipe"]["id"].replace('#', r' rev: ') for dep in conan_info["installed"]]) + from cura.CuraVersion import ConanInstalls summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" @@ -227,14 +222,17 @@ jobs: f.write(content) f.writelines("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n") f.writelines("## Conan packages:\n") - for dep in sorted_deps: - f.writelines(f"`{dep}`\n") + for dep_name, dep_info in ConanDependencies.items(): + f.writelines(f"`{dep_name} {dep_info["version"]} {dep_info["revision"]}`\n") - name: Summarize the used Python modules shell: python run: | import os import pkg_resources + + from cura.CuraVersion import PythonInstalls + summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" if os.path.exists(summary_env): @@ -244,8 +242,8 @@ jobs: with open(summary_env, "w") as f: f.write(content) f.writelines("## Python modules:\n") - for package in pkg_resources.working_set: - f.writelines(f"`{package.key}/{package.version}`\n") + for dep_name, dep_info in ConanDependencies.items(): + f.writelines(f"`{dep_name} {dep_info["version"]}`\n") - name: Create the Macos dmg (Bash) run: python ../cura_inst/packaging/MacOS/build_macos.py --source_path ../cura_inst --dist_path . --cura_conan_version $CURA_CONAN_VERSION --filename "${{ steps.filename.outputs.INSTALLER_FILENAME }}" --build_dmg --build_pkg --app_name "$CURA_APP_NAME" diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 9c9775cae7..067d811e9f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -122,7 +122,7 @@ jobs: run: conan config set storage.download_cache="C:\Users\runneradmin\.conan\conan_download_cache" - name: Create the Packages (Powershell) - run: conan install $Env:CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$Env:ENTERPRISE -o cura:staging=$Env:STAGING --json "cura_inst/conan_install_info.json" + run: conan install $Env:CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$Env:ENTERPRISE -o cura:staging=$Env:STAGING - name: Upload the Package(s) if: always() @@ -169,12 +169,7 @@ jobs: import json from pathlib import Path - conan_install_info_path = Path("cura_inst/conan_install_info.json") - conan_info = {"installed": []} - if os.path.exists(conan_install_info_path): - with open(conan_install_info_path, "r") as f: - conan_info = json.load(f) - sorted_deps = sorted([dep["recipe"]["id"].replace('#', r' rev: ') for dep in conan_info["installed"]]) + from cura.CuraVersion import ConanInstalls summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" @@ -186,14 +181,17 @@ jobs: f.write(content) f.writelines("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n") f.writelines("## Conan packages:\n") - for dep in sorted_deps: - f.writelines(f"`{dep}`\n") + for dep_name, dep_info in ConanDependencies.items(): + f.writelines(f"`{dep_name} {dep_info["version"]} {dep_info["revision"]}`\n") - name: Summarize the used Python modules shell: python run: | import os import pkg_resources + + from cura.CuraVersion import PythonInstalls + summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" if os.path.exists(summary_env): @@ -203,8 +201,8 @@ jobs: with open(summary_env, "w") as f: f.write(content) f.writelines("## Python modules:\n") - for package in pkg_resources.working_set: - f.writelines(f"`{package.key}/{package.version}`\n") + for dep_name, dep_info in ConanDependencies.items(): + f.writelines(f"`{dep_name} {dep_info["version"]}`\n") - name: Create PFX certificate from BASE64_PFX_CONTENT secret id: create-pfx diff --git a/.gitignore b/.gitignore index f1a72d342e..0290869b41 100644 --- a/.gitignore +++ b/.gitignore @@ -101,7 +101,6 @@ graph_info.json Ultimaker-Cura.spec .run/ /printer-linter/src/printerlinter.egg-info/ -/resources/qml/Dialogs/AboutDialogVersionsList.qml /plugins/CuraEngineGradualFlow /resources/bundled_packages/bundled_*.json curaengine_plugin_gradual_flow diff --git a/AboutDialogVersionsList.qml.jinja b/AboutDialogVersionsList.qml.jinja deleted file mode 100644 index 0503469660..0000000000 --- a/AboutDialogVersionsList.qml.jinja +++ /dev/null @@ -1,61 +0,0 @@ -import QtQuick 2.2 -import QtQuick.Controls 2.9 - -import UM 1.6 as UM -import Cura 1.5 as Cura - - -ListView -{ - id: projectBuildInfoList - visible: false - anchors.top: creditsNotes.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - height: base.height - y - (2 * UM.Theme.getSize("default_margin").height + closeButton.height) - - ScrollBar.vertical: UM.ScrollBar - { - id: projectBuildInfoListScrollBar - } - - delegate: Row - { - spacing: UM.Theme.getSize("narrow_margin").width - UM.Label - { - text: (model.name) - width: (projectBuildInfoList.width* 0.4) | 0 - elide: Text.ElideRight - } - UM.Label - { - text: (model.version) - width: (projectBuildInfoList.width *0.6) | 0 - elide: Text.ElideRight - } - - } - model: ListModel - { - id: developerInfo - } - Component.onCompleted: - { - var conan_installs = {{ conan_installs }}; - var python_installs = {{ python_installs }}; - developerInfo.append({ name : "

Conan Installs

", version : '' }); - for (var n in conan_installs) - { - developerInfo.append({ name : conan_installs[n][0], version : conan_installs[n][1] }); - } - developerInfo.append({ name : '', version : '' }); - developerInfo.append({ name : "

Python Installs

", version : '' }); - for (var n in python_installs) - { - developerInfo.append({ name : python_installs[n][0], version : python_installs[n][1] }); - } - - } -} - diff --git a/CuraVersion.py.jinja b/CuraVersion.py.jinja index 87ef7d205d..515293b8af 100644 --- a/CuraVersion.py.jinja +++ b/CuraVersion.py.jinja @@ -1,4 +1,4 @@ -# Copyright (c) 2022 UltiMaker +# Copyright (c) 2023 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. CuraAppName = "{{ cura_app_name }}" @@ -12,3 +12,6 @@ CuraCloudAccountAPIRoot = "{{ cura_cloud_account_api_root }}" CuraMarketplaceRoot = "{{ cura_marketplace_root }}" CuraDigitalFactoryURL = "{{ cura_digital_factory_url }}" CuraLatestURL = "{{ cura_latest_url }}" + +ConanInstalls = {{ conan_installs }} +PythonInstalls = {{ python_installs }} diff --git a/conanfile.py b/conanfile.py index 4c6138656b..fc56d5033c 100644 --- a/conanfile.py +++ b/conanfile.py @@ -137,18 +137,21 @@ class CuraConan(ConanFile): return "'x86_64'" return "None" - def _generate_about_versions(self, location): - with open(os.path.join(self.recipe_folder, "AboutDialogVersionsList.qml.jinja"), "r") as f: - cura_version_py = Template(f.read()) + def _conan_installs(self): + conan_installs = {} - conan_installs = [] - python_installs = [] + # list of conan installs + for dependency in self.dependencies.host.values(): + conan_installs[dependency.ref.name] = { + "version": dependency.ref.version, + "revision": dependency.ref.revision + } + return conan_installs - # list of conan installs - for _, dependency in self.dependencies.host.items(): - conan_installs.append([dependency.ref.name,dependency.ref.version]) + def _python_installs(self): + python_installs = {} - #list of python installs + # list of python installs outer = '"' if self.settings.os == "Windows" else "'" inner = "'" if self.settings.os == "Windows" else '"' python_ins_cmd = f"python -c {outer}import pkg_resources; print({inner};{inner}.join([(s.key+{inner},{inner}+ s.version) for s in pkg_resources.working_set])){outer}" @@ -157,16 +160,12 @@ class CuraConan(ConanFile): self.run(python_ins_cmd, run_environment= True, env = "conanrun", output=buffer) packages = str(buffer.getvalue()).split("-----------------\n") - package = packages[1].strip('\r\n').split(";") - for pack in package: - python_installs.append(pack.split(",")) - - with open(os.path.join(location, "AboutDialogVersionsList.qml"), "w") as f: - f.write(cura_version_py.render( - conan_installs = conan_installs, - python_installs = python_installs - )) + packages = packages[1].strip('\r\n').split(";") + for package in packages: + name, version = package.split(",") + python_installs[name] = {"version": version} + return python_installs def _generate_cura_version(self, location): with open(os.path.join(self.recipe_folder, "CuraVersion.py.jinja"), "r") as f: @@ -192,89 +191,9 @@ class CuraConan(ConanFile): cura_cloud_account_api_root = self.conan_data["urls"][self._urls]["cloud_account_api_root"], cura_marketplace_root = self.conan_data["urls"][self._urls]["marketplace_root"], cura_digital_factory_url = self.conan_data["urls"][self._urls]["digital_factory_url"], - cura_latest_url = self.conan_data["urls"][self._urls]["cura_latest_url"])) - - def _generate_pyinstaller_spec(self, location, entrypoint_location, icon_path, entitlements_file): - pyinstaller_metadata = self.conan_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: - src_path = os.path.join(self.package_folder, data["src"]) - else: - src_path = os.path.join(self.source_folder, data["src"]) - else: - src_path = os.path.join(self.deps_cpp_info[data["package"]].rootpath, data["src"]) - elif "root" in data: # get the paths relative from the install folder - src_path = os.path.join(self.install_folder, data["root"], data["src"]) - else: - continue - if Path(src_path).exists(): - datas.append((str(src_path), data["dst"])) - - binaries = [] - for binary in pyinstaller_metadata["binaries"].values(): - if "package" in binary: # get the paths from conan package - src_path = os.path.join(self.deps_cpp_info[binary["package"]].rootpath, binary["src"]) - elif "root" in binary: # get the paths relative from the sourcefolder - src_path = str(self.source_path.joinpath(binary["root"], binary["src"])) - if self.settings.os == "Windows": - src_path = src_path.replace("\\", "\\\\") - else: - continue - if not Path(src_path).exists(): - self.output.warning(f"Source path for binary {binary['binary']} does not exist") - continue - - for bin in Path(src_path).glob(binary["binary"] + "*[.exe|.dll|.so|.dylib|.so.]*"): - binaries.append((str(bin), binary["dst"])) - for bin in Path(src_path).glob(binary["binary"]): - binaries.append((str(bin), binary["dst"])) - - # Make sure all Conan dependencies which are shared are added to the binary list for pyinstaller - for _, dependency in self.dependencies.host.items(): - for bin_paths in dependency.cpp_info.bindirs: - binaries.extend([(f"{p}", ".") for p in Path(bin_paths).glob("**/*.dll")]) - for lib_paths in dependency.cpp_info.libdirs: - binaries.extend([(f"{p}", ".") for p in Path(lib_paths).glob("**/*.so*")]) - binaries.extend([(f"{p}", ".") for p in Path(lib_paths).glob("**/*.dylib*")]) - - # Copy dynamic libs from lib path - binaries.extend([(f"{p}", ".") for p in Path(self._base_dir.joinpath("lib")).glob("**/*.dylib*")]) - binaries.extend([(f"{p}", ".") for p in Path(self._base_dir.joinpath("lib")).glob("**/*.so*")]) - - # Collect all dll's from PyQt6 and place them in the root - binaries.extend([(f"{p}", ".") for p in Path(self._site_packages, "PyQt6", "Qt6").glob("**/*.dll")]) - - with open(os.path.join(self.recipe_folder, "UltiMaker-Cura.spec.jinja"), "r") as f: - pyinstaller = Template(f.read()) - - version = self.conf_info.get("user.cura:version", default = self.version, check_type = str) - cura_version = Version(version) - - with open(os.path.join(location, "UltiMaker-Cura.spec"), "w") as f: - f.write(pyinstaller.render( - name = str(self.options.display_name).replace(" ", "-"), - display_name = self._app_name, - entrypoint = entrypoint_location, - datas = datas, - binaries = binaries, - venv_script_path = str(self._script_dir), - hiddenimports = pyinstaller_metadata["hiddenimports"], - collect_all = pyinstaller_metadata["collect_all"], - icon = icon_path, - entitlements_file = entitlements_file, - osx_bundle_identifier = "'nl.ultimaker.cura'" if self.settings.os == "Macos" else "None", - upx = str(self.settings.os == "Windows"), - strip = False, # This should be possible on Linux and MacOS but, it can also cause issues on some distributions. Safest is to disable it for now - target_arch = self._pyinstaller_spec_arch, - macos = self.settings.os == "Macos", - version = f"'{version}'", - short_version = f"'{cura_version.major}.{cura_version.minor}.{cura_version.patch}'", + cura_latest_url=self.conan_data["urls"][self._urls]["cura_latest_url"], + conan_installs=self._conan_installs(), + python_installs=self._python_installs(), )) def export_sources(self): @@ -346,7 +265,6 @@ class CuraConan(ConanFile): vr.generate() self._generate_cura_version(os.path.join(self.source_folder, "cura")) - self._generate_about_versions(os.path.join(self.source_folder, "resources","qml", "Dialogs")) if not self.in_local_cache: # Copy CuraEngine.exe to bindirs of Virtual Python Environment @@ -387,12 +305,6 @@ class CuraConan(ConanFile): copy(self, "*", cura_private_data.resdirs[0], str(self._share_dir.joinpath("cura"))) if self.options.devtools: - entitlements_file = "'{}'".format(os.path.join(self.source_folder, "packaging", "MacOS", "cura.entitlements")) - self._generate_pyinstaller_spec(location = self.generators_folder, - entrypoint_location = "'{}'".format(os.path.join(self.source_folder, self.conan_data["pyinstaller"]["runinfo"]["entrypoint"])).replace("\\", "\\\\"), - icon_path = "'{}'".format(os.path.join(self.source_folder, "packaging", self.conan_data["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"), - entitlements_file = entitlements_file if self.settings.os == "Macos" else "None") - # Update the po and pot files if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type=str): vb = VirtualBuildEnv(self) @@ -451,13 +363,6 @@ echo "CURA_APP_NAME={{ cura_app_name }}" >> ${{ env_prefix }}GITHUB_ENV save(self, os.path.join(self._script_dir, f"activate_github_actions_version_env{ext}"), activate_github_actions_version_env) self._generate_cura_version(os.path.join(self._site_packages, "cura")) - self._generate_about_versions(str(self._share_dir.joinpath("cura", "resources", "qml", "Dialogs"))) - - entitlements_file = "'{}'".format(Path(self.cpp_info.res_paths[2], "MacOS", "cura.entitlements")) - self._generate_pyinstaller_spec(location = self._base_dir, - entrypoint_location = "'{}'".format(os.path.join(self.package_folder, self.cpp_info.bindirs[0], self.conan_data["pyinstaller"]["runinfo"]["entrypoint"])).replace("\\", "\\\\"), - icon_path = "'{}'".format(os.path.join(self.package_folder, self.cpp_info.resdirs[2], self.conan_data["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"), - entitlements_file = entitlements_file if self.settings.os == "Macos" else "None") def package(self): copy(self, "cura_app.py", src = self.source_folder, dst = os.path.join(self.package_folder, self.cpp.package.bindirs[0])) diff --git a/cura/ApplicationMetadata.py b/cura/ApplicationMetadata.py index 96cfa6c64d..9d399e7ad8 100644 --- a/cura/ApplicationMetadata.py +++ b/cura/ApplicationMetadata.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 UltiMaker +# Copyright (c) 2023 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. # --------- @@ -69,13 +69,25 @@ try: except ImportError: CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME -DEPENDENCY_INFO = {} + try: - from pathlib import Path - conan_install_info = Path(__file__).parent.parent.joinpath("conan_install_info.json") - if conan_install_info.exists(): - import json - with open(conan_install_info, "r") as f: - DEPENDENCY_INFO = json.loads(f.read()) -except: - pass + from cura.CuraVersion import ConanInstalls + + if type(ConanInstalls) == dict: + CONAN_INSTALLS = ConanInstalls + else: + CONAN_INSTALLS = {} + +except ImportError: + CONAN_INSTALLS = {} + +try: + from cura.CuraVersion import PythonInstalls + + if type(PythonInstalls) == dict: + PYTHON_INSTALLS = PythonInstalls + else: + PYTHON_INSTALLS = {} + +except ImportError: + PYTHON_INSTALLS = {} diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index e075fe92f5..b51fbd9d82 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -269,6 +269,9 @@ class CuraApplication(QtApplication): CentralFileStorage.setIsEnterprise(ApplicationMetadata.IsEnterpriseVersion) Resources.setIsEnterprise(ApplicationMetadata.IsEnterpriseVersion) + self._conan_installs = ApplicationMetadata.CONAN_INSTALLS + self._python_installs = ApplicationMetadata.PYTHON_INSTALLS + @pyqtProperty(str, constant=True) def ultimakerCloudApiRootUrl(self) -> str: return UltimakerCloudConstants.CuraCloudAPIRoot @@ -851,11 +854,8 @@ class CuraApplication(QtApplication): self._log_hardware_info() - if len(ApplicationMetadata.DEPENDENCY_INFO) > 0: - Logger.debug("Using Conan managed dependencies: " + ", ".join( - [dep["recipe"]["id"] for dep in ApplicationMetadata.DEPENDENCY_INFO["installed"] if dep["recipe"]["version"] != "latest"])) - else: - Logger.warning("Could not find conan_install_info.json") + Logger.debug("Using conan dependencies: {}", str(self.conanInstalls)) + Logger.debug("Using python dependencies: {}", str(self.pythonInstalls)) Logger.log("i", "Initializing machine error checker") self._machine_error_checker = MachineErrorChecker(self) @@ -2130,3 +2130,11 @@ class CuraApplication(QtApplication): @pyqtProperty(bool, constant=True) def isEnterprise(self) -> bool: return ApplicationMetadata.IsEnterpriseVersion + + @pyqtProperty("QVariant", constant=True) + def conanInstalls(self) -> Dict[str, Dict[str, str]]: + return self._conan_installs + + @pyqtProperty("QVariant", constant=True) + def pythonInstalls(self) -> Dict[str, Dict[str, str]]: + return self._python_installs diff --git a/resources/qml/Dialogs/AboutDialog.qml b/resources/qml/Dialogs/AboutDialog.qml index b0cd9d2ad3..bbd7c45b8d 100644 --- a/resources/qml/Dialogs/AboutDialog.qml +++ b/resources/qml/Dialogs/AboutDialog.qml @@ -1,19 +1,22 @@ -// Copyright (c) 2022 UltiMaker +// Copyright (c) 2023 UltiMaker // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.4 import QtQuick.Controls 2.9 +import QtQuick.Layouts 1.3 import UM 1.6 as UM -import Cura 1.5 as Cura +import Cura 1.6 as Cura UM.Dialog { id: base - //: About dialog title title: catalog.i18nc("@title:window The argument is the application name.", "About %1").arg(CuraApplication.applicationDisplayName) + // Flag to toggle between main dependencies information and extensive dependencies information + property bool showDefaultDependencies: true + minimumWidth: 500 * screenScaleFactor minimumHeight: 700 * screenScaleFactor width: minimumWidth @@ -21,186 +24,241 @@ UM.Dialog backgroundColor: UM.Theme.getColor("main_background") - - Rectangle + headerComponent: Rectangle { - id: header - width: parent.width + 2 * margin // margin from Dialog.qml - height: childrenRect.height + topPadding - - anchors.top: parent.top - anchors.topMargin: -margin - anchors.horizontalCenter: parent.horizontalCenter - - property real topPadding: UM.Theme.getSize("wide_margin").height - + width: parent.width + height: logo.height + 2 * UM.Theme.getSize("wide_margin").height color: UM.Theme.getColor("main_window_header_background") Image { id: logo - width: (base.minimumWidth * 0.85) | 0 - height: (width * (UM.Theme.getSize("logo").height / UM.Theme.getSize("logo").width)) | 0 + width: Math.floor(base.width * 0.85) + height: Math.floor(width * UM.Theme.getSize("logo").height / UM.Theme.getSize("logo").width) source: UM.Theme.getImage("logo") - sourceSize.width: width - sourceSize.height: height fillMode: Image.PreserveAspectFit - anchors.top: parent.top - anchors.topMargin: parent.topPadding - anchors.horizontalCenter: parent.horizontalCenter + anchors.centerIn: parent - UM.I18nCatalog{id: catalog; name: "cura"} - MouseArea - { - anchors.fill: parent - onClicked: - { - projectsList.visible = !projectsList.visible; - projectBuildInfoList.visible = !projectBuildInfoList.visible; - } - } + UM.I18nCatalog{ id: catalog; name: "cura" } } UM.Label { id: version - text: catalog.i18nc("@label","version: %1").arg(UM.Application.version) font: UM.Theme.getFont("large_bold") color: UM.Theme.getColor("button_text") anchors.right : logo.right anchors.top: logo.bottom - anchors.topMargin: (UM.Theme.getSize("default_margin").height / 2) | 0 + } + + MouseArea + { + anchors.fill: parent + onDoubleClicked: showDefaultDependencies = !showDefaultDependencies } } - UM.Label + // Reusable component to display a dependency + readonly property Component dependency_row: RowLayout { - id: description - width: parent.width + spacing: UM.Theme.getSize("narrow_margin").width - //: About dialog application description - text: catalog.i18nc("@label","End-to-end solution for fused filament 3D printing.") - font: UM.Theme.getFont("system") - wrapMode: Text.WordWrap - anchors.top: header.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - } - - UM.Label - { - id: creditsNotes - width: parent.width - - //: About dialog application author note - text: catalog.i18nc("@info:credit","Cura is developed by UltiMaker in cooperation with the community.\nCura proudly uses the following open source projects:") - font: UM.Theme.getFont("system") - wrapMode: Text.WordWrap - anchors.top: description.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - } - - ListView - { - id: projectsList - anchors.top: creditsNotes.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - height: base.height - y - (2 * UM.Theme.getSize("default_margin").height + closeButton.height) - - ScrollBar.vertical: UM.ScrollBar + UM.Label { - id: projectsListScrollBar + text: { + if (typeof(url) !== "undefined" && url !== "") { + return "" + name + ""; + } else { + return name; + } + } + visible: text !== "" + Layout.fillWidth: true + Layout.preferredWidth: 2 + elide: Text.ElideRight } - delegate: Row + UM.Label { + text: description + visible: text !== "" + Layout.fillWidth: true + Layout.preferredWidth: 3 + elide: Text.ElideRight + } + + UM.Label + { + text: license + visible: text !== "" + Layout.fillWidth: true + Layout.preferredWidth: 2 + elide: Text.ElideRight + } + + UM.Label + { + text: version + visible: text !== "" + Layout.fillWidth: true + Layout.preferredWidth: 2 + elide: Text.ElideRight + } + } + + Flickable + { + anchors.fill: parent + ScrollBar.vertical: UM.ScrollBar { + visible: contentHeight > height + } + contentHeight: content.height + clip: true + + Column + { + id: content spacing: UM.Theme.getSize("narrow_margin").width + width: parent.width + UM.Label { - text: "%2".arg(model.url).arg(model.name) - width: (projectsList.width * 0.25) | 0 - elide: Text.ElideRight - onLinkActivated: Qt.openUrlExternally(link) + text: catalog.i18nc("@label", "End-to-end solution for fused filament 3D printing.") + font: UM.Theme.getFont("system") + wrapMode: Text.WordWrap } + UM.Label { - text: model.description - elide: Text.ElideRight - width: ((projectsList.width * 0.6) | 0) - parent.spacing * 2 - projectsListScrollBar.width + text: catalog.i18nc("@info:credit", "Cura is developed by UltiMaker in cooperation with the community.\nCura proudly uses the following open source projects:") + font: UM.Theme.getFont("system") + wrapMode: Text.WordWrap } + + Column + { + visible: showDefaultDependencies + width: parent.width + + Repeater + { + width: parent.width + + delegate: Loader { + sourceComponent: dependency_row + width: parent.width + property string name: model.name + property string description: model.description + property string license: model.license + property string url: model.url + } + + model: ListModel + { + id: projectsModel + } + Component.onCompleted: + { + //Do NOT add dependencies of our dependencies here, nor CI-dependencies! + //UltiMaker's own projects and forks. + projectsModel.append({ name: "Cura", description: catalog.i18nc("@label Description for application component", "Graphical user interface"), license: "LGPLv3", url: "https://github.com/Ultimaker/Cura" }); + projectsModel.append({ name: "Uranium", description: catalog.i18nc("@label Description for application component", "Application framework"), license: "LGPLv3", url: "https://github.com/Ultimaker/Uranium" }); + projectsModel.append({ name: "CuraEngine", description: catalog.i18nc("@label Description for application component", "G-code generator"), license: "AGPLv3", url: "https://github.com/Ultimaker/CuraEngine" }); + projectsModel.append({ name: "libArcus", description: catalog.i18nc("@label Description for application component", "Interprocess communication library"), license: "LGPLv3", url: "https://github.com/Ultimaker/libArcus" }); + projectsModel.append({ name: "pynest2d", description: catalog.i18nc("@label Description for application component", "Python bindings for libnest2d"), license: "LGPL", url: "https://github.com/Ultimaker/pynest2d" }); + projectsModel.append({ name: "libnest2d", description: catalog.i18nc("@label Description for application component", "Polygon packing library, developed by Prusa Research"), license: "LGPL", url: "https://github.com/tamasmeszaros/libnest2d" }); + projectsModel.append({ name: "libSavitar", description: catalog.i18nc("@label Description for application component", "Support library for handling 3MF files"), license: "LGPLv3", url: "https://github.com/ultimaker/libsavitar" }); + projectsModel.append({ name: "libCharon", description: catalog.i18nc("@label Description for application component", "Support library for file metadata and streaming"), license: "LGPLv3", url: "https://github.com/ultimaker/libcharon" }); + + //Direct dependencies of the front-end. + projectsModel.append({ name: "Python", description: catalog.i18nc("@label Description for application dependency", "Programming language"), license: "Python", url: "http://python.org/" }); + projectsModel.append({ name: "Qt6", description: catalog.i18nc("@label Description for application dependency", "GUI framework"), license: "LGPLv3", url: "https://www.qt.io/" }); + projectsModel.append({ name: "PyQt", description: catalog.i18nc("@label Description for application dependency", "GUI framework bindings"), license: "GPL", url: "https://riverbankcomputing.com/software/pyqt" }); + projectsModel.append({ name: "SIP", description: catalog.i18nc("@label Description for application dependency", "C/C++ Binding library"), license: "GPL", url: "https://riverbankcomputing.com/software/sip" }); + projectsModel.append({ name: "Protobuf", description: catalog.i18nc("@label Description for application dependency", "Data interchange format"), license: "BSD", url: "https://developers.google.com/protocol-buffers" }); + projectsModel.append({ name: "Noto Sans", description: catalog.i18nc("@label", "Font"), license: "Apache 2.0", url: "https://www.google.com/get/noto/" }); + + //CuraEngine's dependencies. + projectsModel.append({ name: "Clipper", description: catalog.i18nc("@label Description for application dependency", "Polygon clipping library"), license: "Boost", url: "http://www.angusj.com/delphi/clipper.php" }); + projectsModel.append({ name: "RapidJSON", description: catalog.i18nc("@label Description for application dependency", "JSON parser"), license: "MIT", url: "https://rapidjson.org/" }); + projectsModel.append({ name: "STB", description: catalog.i18nc("@label Description for application dependency", "Utility functions, including an image loader"), license: "Public Domain", url: "https://github.com/nothings/stb" }); + projectsModel.append({ name: "Boost", description: catalog.i18nc("@label Description for application dependency", "Utility library, including Voronoi generation"), license: "Boost", url: "https://www.boost.org/" }); + + //Python modules. + projectsModel.append({ name: "Certifi", description: catalog.i18nc("@label Description for application dependency", "Root Certificates for validating SSL trustworthiness"), license: "MPL", url: "https://github.com/certifi/python-certifi" }); + projectsModel.append({ name: "Cryptography", description: catalog.i18nc("@label Description for application dependency", "Root Certificates for validating SSL trustworthiness"), license: "APACHE and BSD", url: "https://cryptography.io/" }); + projectsModel.append({ name: "Future", description: catalog.i18nc("@label Description for application dependency", "Compatibility between Python 2 and 3"), license: "MIT", url: "https://python-future.org/" }); + projectsModel.append({ name: "keyring", description: catalog.i18nc("@label Description for application dependency", "Support library for system keyring access"), license: "MIT", url: "https://github.com/jaraco/keyring" }); + projectsModel.append({ name: "NumPy", description: catalog.i18nc("@label Description for application dependency", "Support library for faster math"), license: "BSD", url: "http://www.numpy.org/" }); + projectsModel.append({ name: "NumPy-STL", description: catalog.i18nc("@label Description for application dependency", "Support library for handling STL files"), license: "BSD", url: "https://github.com/WoLpH/numpy-stl" }); + projectsModel.append({ name: "PyClipper", description: catalog.i18nc("@label Description for application dependency", "Python bindings for Clipper"), license: "MIT", url: "https://github.com/fonttools/pyclipper" }); + projectsModel.append({ name: "PySerial", description: catalog.i18nc("@label Description for application dependency", "Serial communication library"), license: "Python", url: "http://pyserial.sourceforge.net/" }); + projectsModel.append({ name: "SciPy", description: catalog.i18nc("@label Description for application dependency", "Support library for scientific computing"), license: "BSD-new", url: "https://www.scipy.org/" }); + projectsModel.append({ name: "Sentry", description: catalog.i18nc("@Label Description for application dependency", "Python Error tracking library"), license: "BSD 2-Clause 'Simplified'", url: "https://sentry.io/for/python/" }); + projectsModel.append({ name: "Trimesh", description: catalog.i18nc("@label Description for application dependency", "Support library for handling triangular meshes"), license: "MIT", url: "https://trimsh.org" }); + projectsModel.append({ name: "python-zeroconf", description: catalog.i18nc("@label Description for application dependency", "ZeroConf discovery library"), license: "LGPL", url: "https://github.com/jstasiak/python-zeroconf" }); + + //Building/packaging. + projectsModel.append({ name: "CMake", description: catalog.i18nc("@label Description for development tool", "Universal build system configuration"), license: "BSD 3-Clause", url: "https://cmake.org/" }); + projectsModel.append({ name: "Conan", description: catalog.i18nc("@label Description for development tool", "Dependency and package manager"), license: "MIT", url: "https://conan.io/" }); + projectsModel.append({ name: "Pyinstaller", description: catalog.i18nc("@label Description for development tool", "Packaging Python-applications"), license: "GPLv2", url: "https://pyinstaller.org/" }); + projectsModel.append({ name: "AppImageKit", description: catalog.i18nc("@label Description for development tool", "Linux cross-distribution application deployment"), license: "MIT", url: "https://github.com/AppImage/AppImageKit" }); + projectsModel.append({ name: "NSIS", description: catalog.i18nc("@label Description for development tool", "Generating Windows installers"), license: "Zlib", url: "https://nsis.sourceforge.io/" }); + } + } + } + UM.Label { - text: model.license - elide: Text.ElideRight - width: (projectsList.width * 0.15) | 0 + visible: !showDefaultDependencies + text: "Conan Installs" + font: UM.Theme.getFont("large_bold") + } + + Column + { + visible: !showDefaultDependencies + width: parent.width + + Repeater + { + width: parent.width + model: Object.entries(CuraApplication.conanInstalls).map(function (item) { return { name: item[0], version: item[1].version } }) + delegate: Loader { + sourceComponent: dependency_row + width: parent.width + property string name: modelData.name + property string version: modelData.version + } + } + } + + UM.Label + { + visible: !showDefaultDependencies + text: "Python Installs" + font: UM.Theme.getFont("large_bold") + } + + Column + { + width: parent.width + visible: !showDefaultDependencies + Repeater + { + delegate: Loader { + sourceComponent: dependency_row + width: parent.width + property string name: modelData.name + property string version: modelData.version + } + width: parent.width + model: Object.entries(CuraApplication.pythonInstalls).map(function (item) { return { name: item[0], version: item[1].version } }) + } } } - model: ListModel - { - id: projectsModel - } - Component.onCompleted: - { - //Do NOT add dependencies of our dependencies here, nor CI-dependencies! - //UltiMaker's own projects and forks. - projectsModel.append({ name: "Cura", description: catalog.i18nc("@label Description for application component", "Graphical user interface"), license: "LGPLv3", url: "https://github.com/Ultimaker/Cura" }); - projectsModel.append({ name: "Uranium", description: catalog.i18nc("@label Description for application component", "Application framework"), license: "LGPLv3", url: "https://github.com/Ultimaker/Uranium" }); - projectsModel.append({ name: "CuraEngine", description: catalog.i18nc("@label Description for application component", "G-code generator"), license: "AGPLv3", url: "https://github.com/Ultimaker/CuraEngine" }); - projectsModel.append({ name: "libArcus", description: catalog.i18nc("@label Description for application component", "Interprocess communication library"), license: "LGPLv3", url: "https://github.com/Ultimaker/libArcus" }); - projectsModel.append({ name: "pynest2d", description: catalog.i18nc("@label Description for application component", "Python bindings for libnest2d"), license: "LGPL", url: "https://github.com/Ultimaker/pynest2d" }); - projectsModel.append({ name: "libnest2d", description: catalog.i18nc("@label Description for application component", "Polygon packing library, developed by Prusa Research"), license: "LGPL", url: "https://github.com/tamasmeszaros/libnest2d" }); - projectsModel.append({ name: "libSavitar", description: catalog.i18nc("@label Description for application component", "Support library for handling 3MF files"), license: "LGPLv3", url: "https://github.com/ultimaker/libsavitar" }); - projectsModel.append({ name: "libCharon", description: catalog.i18nc("@label Description for application component", "Support library for file metadata and streaming"), license: "LGPLv3", url: "https://github.com/ultimaker/libcharon" }); - - //Direct dependencies of the front-end. - projectsModel.append({ name: "Python", description: catalog.i18nc("@label Description for application dependency", "Programming language"), license: "Python", url: "http://python.org/" }); - projectsModel.append({ name: "Qt6", description: catalog.i18nc("@label Description for application dependency", "GUI framework"), license: "LGPLv3", url: "https://www.qt.io/" }); - projectsModel.append({ name: "PyQt", description: catalog.i18nc("@label Description for application dependency", "GUI framework bindings"), license: "GPL", url: "https://riverbankcomputing.com/software/pyqt" }); - projectsModel.append({ name: "SIP", description: catalog.i18nc("@label Description for application dependency", "C/C++ Binding library"), license: "GPL", url: "https://riverbankcomputing.com/software/sip" }); - projectsModel.append({ name: "Protobuf", description: catalog.i18nc("@label Description for application dependency", "Data interchange format"), license: "BSD", url: "https://developers.google.com/protocol-buffers" }); - projectsModel.append({ name: "Noto Sans", description: catalog.i18nc("@label", "Font"), license: "Apache 2.0", url: "https://www.google.com/get/noto/" }); - - //CuraEngine's dependencies. - projectsModel.append({ name: "Clipper", description: catalog.i18nc("@label Description for application dependency", "Polygon clipping library"), license: "Boost", url: "http://www.angusj.com/delphi/clipper.php" }); - projectsModel.append({ name: "RapidJSON", description: catalog.i18nc("@label Description for application dependency", "JSON parser"), license: "MIT", url: "https://rapidjson.org/" }); - projectsModel.append({ name: "STB", description: catalog.i18nc("@label Description for application dependency", "Utility functions, including an image loader"), license: "Public Domain", url: "https://github.com/nothings/stb" }); - projectsModel.append({ name: "Boost", description: catalog.i18nc("@label Description for application dependency", "Utility library, including Voronoi generation"), license: "Boost", url: "https://www.boost.org/" }); - - //Python modules. - projectsModel.append({ name: "Certifi", description: catalog.i18nc("@label Description for application dependency", "Root Certificates for validating SSL trustworthiness"), license: "MPL", url: "https://github.com/certifi/python-certifi" }); - projectsModel.append({ name: "Cryptography", description: catalog.i18nc("@label Description for application dependency", "Root Certificates for validating SSL trustworthiness"), license: "APACHE and BSD", url: "https://cryptography.io/" }); - projectsModel.append({ name: "Future", description: catalog.i18nc("@label Description for application dependency", "Compatibility between Python 2 and 3"), license: "MIT", url: "https://python-future.org/" }); - projectsModel.append({ name: "keyring", description: catalog.i18nc("@label Description for application dependency", "Support library for system keyring access"), license: "MIT", url: "https://github.com/jaraco/keyring" }); - projectsModel.append({ name: "NumPy", description: catalog.i18nc("@label Description for application dependency", "Support library for faster math"), license: "BSD", url: "http://www.numpy.org/" }); - projectsModel.append({ name: "NumPy-STL", description: catalog.i18nc("@label Description for application dependency", "Support library for handling STL files"), license: "BSD", url: "https://github.com/WoLpH/numpy-stl" }); - projectsModel.append({ name: "PyClipper", description: catalog.i18nc("@label Description for application dependency", "Python bindings for Clipper"), license: "MIT", url: "https://github.com/fonttools/pyclipper" }); - projectsModel.append({ name: "PySerial", description: catalog.i18nc("@label Description for application dependency", "Serial communication library"), license: "Python", url: "http://pyserial.sourceforge.net/" }); - projectsModel.append({ name: "SciPy", description: catalog.i18nc("@label Description for application dependency", "Support library for scientific computing"), license: "BSD-new", url: "https://www.scipy.org/" }); - projectsModel.append({ name: "Sentry", description: catalog.i18nc("@Label Description for application dependency", "Python Error tracking library"), license: "BSD 2-Clause 'Simplified'", url: "https://sentry.io/for/python/" }); - projectsModel.append({ name: "Trimesh", description: catalog.i18nc("@label Description for application dependency", "Support library for handling triangular meshes"), license: "MIT", url: "https://trimsh.org" }); - projectsModel.append({ name: "python-zeroconf", description: catalog.i18nc("@label Description for application dependency", "ZeroConf discovery library"), license: "LGPL", url: "https://github.com/jstasiak/python-zeroconf" }); - - //Building/packaging. - projectsModel.append({ name: "CMake", description: catalog.i18nc("@label Description for development tool", "Universal build system configuration"), license: "BSD 3-Clause", url: "https://cmake.org/" }); - projectsModel.append({ name: "Conan", description: catalog.i18nc("@label Description for development tool", "Dependency and package manager"), license: "MIT", url: "https://conan.io/" }); - projectsModel.append({ name: "Pyinstaller", description: catalog.i18nc("@label Description for development tool", "Packaging Python-applications"), license: "GPLv2", url: "https://pyinstaller.org/" }); - projectsModel.append({ name: "AppImageKit", description: catalog.i18nc("@label Description for development tool", "Linux cross-distribution application deployment"), license: "MIT", url: "https://github.com/AppImage/AppImageKit" }); - projectsModel.append({ name: "NSIS", description: catalog.i18nc("@label Description for development tool", "Generating Windows installers"), license: "Zlib", url: "https://nsis.sourceforge.io/" }); - } - } - - AboutDialogVersionsList{ - id: projectBuildInfoList - - } - - - onVisibleChanged: - { - projectsList.visible = true; - projectBuildInfoList.visible = false; } rightButtons: Cura.TertiaryButton From a588b8d44ab14ed1a1aa4f017421123a21869d70 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 19 Oct 2023 18:48:55 +0200 Subject: [PATCH 02/42] Fix urls in about page CURA-10561 --- resources/qml/Dialogs/AboutDialog.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/Dialogs/AboutDialog.qml b/resources/qml/Dialogs/AboutDialog.qml index bbd7c45b8d..5678920196 100644 --- a/resources/qml/Dialogs/AboutDialog.qml +++ b/resources/qml/Dialogs/AboutDialog.qml @@ -77,6 +77,7 @@ UM.Dialog visible: text !== "" Layout.fillWidth: true Layout.preferredWidth: 2 + onLinkActivated: Qt.openUrlExternally(url) elide: Text.ElideRight } From cdc3f910d383a32a15cd618d1a61ce767163f8ee Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 19 Oct 2023 18:50:52 +0200 Subject: [PATCH 03/42] Add basic makerbot writer CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 236 +++++++++++++++++++++++ plugins/MakerbotWriter/__init__.py | 28 +++ plugins/MakerbotWriter/plugin.json | 13 ++ 3 files changed, 277 insertions(+) create mode 100644 plugins/MakerbotWriter/MakerbotWriter.py create mode 100644 plugins/MakerbotWriter/__init__.py create mode 100644 plugins/MakerbotWriter/plugin.json diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py new file mode 100644 index 0000000000..521db04b5b --- /dev/null +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -0,0 +1,236 @@ +# Copyright (c) 2023 UltiMaker +# Cura is released under the terms of the LGPLv3 or higher. + +from io import StringIO, BufferedIOBase +import json +from typing import cast, List, Optional, Dict +from zipfile import BadZipFile, ZipFile, ZIP_DEFLATED + +from PyQt6.QtCore import QBuffer + +from UM.Logger import Logger +from UM.Math.AxisAlignedBox import AxisAlignedBox +from UM.Mesh.MeshWriter import MeshWriter +from UM.PluginRegistry import PluginRegistry +from UM.Scene.SceneNode import SceneNode +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.i18n import i18nCatalog + +from cura.CuraApplication import CuraApplication +from cura.Snapshot import Snapshot +from cura.Utils.Threading import call_on_qt_thread +from cura.CuraVersion import ConanInstalls + +catalog = i18nCatalog("cura") + + +class MakerbotWriter(MeshWriter): + """A file writer that writes '.makerbot' files.""" + + def __init__(self) -> None: + super().__init__(add_to_recent_files=False) + + _PNG_FORMATS = [ + {"prefix": "isometric_thumbnail", "width": 120, "height": 120}, + {"prefix": "isometric_thumbnail", "width": 320, "height": 320}, + {"prefix": "isometric_thumbnail", "width": 640, "height": 640}, + {"prefix": "thumbnail", "width": 140, "height": 106}, + {"prefix": "thumbnail", "width": 212, "height": 300}, + {"prefix": "thumbnail", "width": 960, "height": 1460}, + {"prefix": "thumbnail", "width": 90, "height": 90}, + ] + _META_VERSION = "3.0.0" + _PRINT_NAME_MAP = { + "Makerbot Method": "fire_e", + "Makerbot Method X": "lava_f", + "Makerbot Method XL": "magma_10", + } + _EXTRUDER_NAME_MAP = { + "1XA": "mk14_hot", + "2XA": "mk14_hot_s", + "1C": "mk14_c", + "1A": "mk14", + "2A": "mk14_s", + } + + # must be called from the main thread because of OpenGL + @staticmethod + @call_on_qt_thread + def _createThumbnail(width: int, height: int) -> Optional[QBuffer]: + if not CuraApplication.getInstance().isVisible: + Logger.warning("Can't create snapshot when renderer not initialized.") + return + try: + snapshot = Snapshot.snapshot(width, height) + except: + Logger.logException("w", "Failed to create snapshot image") + return + + thumbnail_buffer = QBuffer() + thumbnail_buffer.open(QBuffer.OpenModeFlag.ReadWrite) + + snapshot.save(thumbnail_buffer, "PNG") + + return thumbnail_buffer + + def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode=MeshWriter.OutputMode.BinaryMode) -> bool: + if mode != MeshWriter.OutputMode.BinaryMode: + Logger.log("e", "MakerbotWriter does not support text mode.") + self.setInformation(catalog.i18nc("@error:not supported", "MakerbotWriter does not support text mode.")) + return False + + # The GCodeWriter plugin is bundled, so it must at least exist. (What happens if people disable that plugin?) + gcode_writer = PluginRegistry.getInstance().getPluginObject("GCodeWriter") + + if gcode_writer is None: + Logger.log("e", "Could not find the GCodeWriter plugin, is it disabled?.") + self.setInformation( + catalog.i18nc("@error:load", "Could not load GCodeWriter plugin. Try to re-enable the plugin.")) + return False + + gcode_writer = cast(MeshWriter, gcode_writer) + + gcode_text_io = StringIO() + success = gcode_writer.write(gcode_text_io, None) + + # TODO convert gcode_text_io to json + + # Writing the g-code failed. Then I can also not write the gzipped g-code. + if not success: + self.setInformation(gcode_writer.getInformation()) + return False + + metadata = self._getMeta(nodes) + + png_files = [] + for png_format in self._PNG_FORMATS: + width, height, prefix = png_format["width"], png_format["height"], png_format["prefix"] + thumbnail_buffer = self._createThumbnail(width, height) + if thumbnail_buffer is None: + Logger.warning(f"Could not create thumbnail of size {width}x{height}.") + continue + png_files.append({ + "file": f"{prefix}_{width}x{height}.png", + "data": thumbnail_buffer.data(), + }) + + try: + with ZipFile(stream, "w", compression=ZIP_DEFLATED) as zip_stream: + zip_stream.writestr("meta.json", json.dumps(metadata, indent=4)) + for png_file in png_files: + file, data = png_file["file"], png_file["data"] + zip_stream.writestr(file, data) + except (IOError, OSError, BadZipFile) as ex: + Logger.log("e", f"Could not write to (.makerbot) file because: '{ex}'.") + self.setInformation(catalog.i18nc("@error", "MakerbotWriter could not save to the designated path.")) + return False + + return True + + def _getMeta(self, root_nodes: List[SceneNode]) -> Dict[str, any]: + application = CuraApplication.getInstance() + machine_manager = application.getMachineManager() + global_stack = machine_manager.activeMachine + extruders = global_stack.extruderList + + nodes = [] + for root_node in root_nodes: + for node in DepthFirstIterator(root_node): + if not getattr(node, "_outside_buildarea", False): + if node.callDecoration( + "isSliceable") and node.getMeshData() and node.isVisible() and not node.callDecoration( + "isNonThumbnailVisibleMesh"): + nodes.append(node) + + meta = dict() + + meta["bot_type"] = MakerbotWriter._PRINT_NAME_MAP.get((name := global_stack.name), name) + + bounds: Optional[AxisAlignedBox] = None + for node in nodes: + node_bounds = node.getBoundingBox() + if node_bounds is None: + continue + if bounds is None: + bounds = node_bounds + else: + bounds += node_bounds + + if bounds is not None: + meta["bounding_box"] = { + "x_min": bounds.left, + "x_max": bounds.right, + "y_min": bounds.back, + "y_max": bounds.front, + "z_min": bounds.bottom, + "z_max": bounds.top, + } + + material_bed_temperature = global_stack.getProperty("material_bed_temperature", "value") + meta["build_plane_temperature"] = material_bed_temperature + + print_information = application.getPrintInformation() + meta["commanded_duration_s"] = print_information.currentPrintTime.seconds + meta["duration_s"] = print_information.currentPrintTime.seconds + + material_lengths = list(map(meter_to_millimeter, print_information.materialLengths)) + meta["extrusion_distance_mm"] = material_lengths[0] + meta["extrusion_distances_mm"] = material_lengths + + meta["extrusion_mass_g"] = print_information.materialWeights[0] + meta["extrusion_masses_g"] = print_information.materialWeights + + meta["uuid"] = print_information.slice_uuid + + materials = [extruder.material.getMetaData().get("material") for extruder in extruders] + meta["material"] = materials[0] + meta["materials"] = materials + + materials_temps = [extruder.getProperty("default_material_print_temperature", "value") for extruder in + extruders] + meta["extruder_temperature"] = materials_temps[0] + meta["extruder_temperatures"] = materials_temps + + meta["model_counts"] = [{"count": 1, "name": node.getName()} for node in nodes] + + tool_types = [MakerbotWriter._EXTRUDER_NAME_MAP.get((name := extruder.variant.getName()), name) for extruder in + extruders] + meta["tool_type"] = tool_types[0] + meta["tool_types"] = tool_types + + meta["version"] = MakerbotWriter._META_VERSION + + meta["preferences"] = dict() + for node in nodes: + bound = node.getBoundingBox() + meta["preferences"][str(node.getName())] = { + "machineBounds": [bounds.right, bounds.back, bounds.left, bounds.front] if bounds is not None else None, + "printMode": CuraApplication.getInstance().getIntentManager().currentIntentCategory, + } + + cura_engine_info = ConanInstalls.get("curaengine", {"version": "unknown", "revision": "unknown"}) + meta["curaengine_version"] = cura_engine_info["version"] + meta["curaengine_commit_hash"] = cura_engine_info["revision"] + + meta["makerbot_writer_version"] = self.getVersion() + # meta["makerbot_writer_commit_hash"] = self.getRevision() + + for name, package_info in ConanInstalls.items(): + if not name.startswith("curaengine_ "): + continue + meta[f"{name}_version"] = package_info["version"] + meta[f"{name}_commit_hash"] = package_info["revision"] + + # TODO add the following instructions + # num_tool_changes + # num_z_layers + # num_z_transitions + # platform_temperature + # total_commands + + return meta + + +def meter_to_millimeter(value: float) -> float: + """Converts a value in meters to millimeters.""" + return value * 1000.0 diff --git a/plugins/MakerbotWriter/__init__.py b/plugins/MakerbotWriter/__init__.py new file mode 100644 index 0000000000..ede2435c4f --- /dev/null +++ b/plugins/MakerbotWriter/__init__.py @@ -0,0 +1,28 @@ +# Copyright (c) 2023 UltiMaker +# Cura is released under the terms of the LGPLv3 or higher. + +from UM.i18n import i18nCatalog + +from . import MakerbotWriter + +catalog = i18nCatalog("cura") + + +def getMetaData(): + file_extension = "makerbot" + return { + "mesh_writer": { + "output": [{ + "extension": file_extension, + "description": catalog.i18nc("@item:inlistbox", "Makerbot Printfile"), + "mime_type": "application/x-makerbot", + "mode": MakerbotWriter.MakerbotWriter.OutputMode.BinaryMode, + }], + } + } + + +def register(app): + return { + "mesh_writer": MakerbotWriter.MakerbotWriter(), + } diff --git a/plugins/MakerbotWriter/plugin.json b/plugins/MakerbotWriter/plugin.json new file mode 100644 index 0000000000..f2b875bb54 --- /dev/null +++ b/plugins/MakerbotWriter/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "Makerbot Printfile Writer", + "author": "UltiMaker", + "version": "0.1.0", + "description": "Provides support for writing MakerBot Format Packages.", + "api": 8, + "supported_sdk_versions": [ + "8.0.0", + "8.1.0", + "8.2.0" + ], + "i18n-catalog": "cura" +} From c7eee660decbee7a5e154addcecc8d0b5880dedc Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 19 Oct 2023 19:59:48 +0200 Subject: [PATCH 04/42] Cleanup about page CURA-10561 --- resources/qml/Dialogs/AboutDialog.qml | 28 ++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/resources/qml/Dialogs/AboutDialog.qml b/resources/qml/Dialogs/AboutDialog.qml index 5678920196..f448a32f1e 100644 --- a/resources/qml/Dialogs/AboutDialog.qml +++ b/resources/qml/Dialogs/AboutDialog.qml @@ -10,6 +10,8 @@ import Cura 1.6 as Cura UM.Dialog { + readonly property UM.I18nCatalog catalog: UM.I18nCatalog { name: "cura" } + id: base title: catalog.i18nc("@title:window The argument is the application name.", "About %1").arg(CuraApplication.applicationDisplayName) @@ -39,8 +41,6 @@ UM.Dialog fillMode: Image.PreserveAspectFit anchors.centerIn: parent - - UM.I18nCatalog{ id: catalog; name: "cura" } } UM.Label @@ -69,7 +69,7 @@ UM.Dialog { text: { if (typeof(url) !== "undefined" && url !== "") { - return "" + name + ""; + return `${name}`; } else { return name; } @@ -78,7 +78,6 @@ UM.Dialog Layout.fillWidth: true Layout.preferredWidth: 2 onLinkActivated: Qt.openUrlExternally(url) - elide: Text.ElideRight } UM.Label @@ -87,7 +86,6 @@ UM.Dialog visible: text !== "" Layout.fillWidth: true Layout.preferredWidth: 3 - elide: Text.ElideRight } UM.Label @@ -96,7 +94,6 @@ UM.Dialog visible: text !== "" Layout.fillWidth: true Layout.preferredWidth: 2 - elide: Text.ElideRight } UM.Label @@ -105,7 +102,6 @@ UM.Dialog visible: text !== "" Layout.fillWidth: true Layout.preferredWidth: 2 - elide: Text.ElideRight } } @@ -147,7 +143,8 @@ UM.Dialog { width: parent.width - delegate: Loader { + delegate: Loader + { sourceComponent: dependency_row width: parent.width property string name: model.name @@ -226,8 +223,11 @@ UM.Dialog Repeater { width: parent.width - model: Object.entries(CuraApplication.conanInstalls).map(function (item) { return { name: item[0], version: item[1].version } }) - delegate: Loader { + model: Object + .entries(CuraApplication.conanInstalls) + .map(([name, { version }]) => ({ name, version })) + delegate: Loader + { sourceComponent: dependency_row width: parent.width property string name: modelData.name @@ -249,14 +249,17 @@ UM.Dialog visible: !showDefaultDependencies Repeater { - delegate: Loader { + delegate: Loader + { sourceComponent: dependency_row width: parent.width property string name: modelData.name property string version: modelData.version } width: parent.width - model: Object.entries(CuraApplication.pythonInstalls).map(function (item) { return { name: item[0], version: item[1].version } }) + model: Object + .entries(CuraApplication.pythonInstalls) + .map(([name, { version }]) => ({ name, version })) } } } @@ -264,7 +267,6 @@ UM.Dialog rightButtons: Cura.TertiaryButton { - //: Close about dialog button id: closeButton text: catalog.i18nc("@action:button", "Close") onClicked: reject() From e7188c2f9f92b6c9a695d850a987aab0bed5de08 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 19 Oct 2023 21:54:50 +0200 Subject: [PATCH 05/42] Find python dependencies directly in python CURA-10561 --- CuraVersion.py.jinja | 4 +++- conanfile.py | 20 -------------------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/CuraVersion.py.jinja b/CuraVersion.py.jinja index 515293b8af..690a1386d3 100644 --- a/CuraVersion.py.jinja +++ b/CuraVersion.py.jinja @@ -1,6 +1,8 @@ # Copyright (c) 2023 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. +from pkg_resources import working_set + CuraAppName = "{{ cura_app_name }}" CuraAppDisplayName = "{{ cura_app_display_name }}" CuraVersion = "{{ cura_version }}" @@ -14,4 +16,4 @@ CuraDigitalFactoryURL = "{{ cura_digital_factory_url }}" CuraLatestURL = "{{ cura_latest_url }}" ConanInstalls = {{ conan_installs }} -PythonInstalls = {{ python_installs }} +PythonInstalls = {package.key: {'version': package.version} for package in working_set} \ No newline at end of file diff --git a/conanfile.py b/conanfile.py index fc56d5033c..f99fa772dd 100644 --- a/conanfile.py +++ b/conanfile.py @@ -148,25 +148,6 @@ class CuraConan(ConanFile): } return conan_installs - def _python_installs(self): - python_installs = {} - - # list of python installs - outer = '"' if self.settings.os == "Windows" else "'" - inner = "'" if self.settings.os == "Windows" else '"' - python_ins_cmd = f"python -c {outer}import pkg_resources; print({inner};{inner}.join([(s.key+{inner},{inner}+ s.version) for s in pkg_resources.working_set])){outer}" - from six import StringIO - buffer = StringIO() - self.run(python_ins_cmd, run_environment= True, env = "conanrun", output=buffer) - - packages = str(buffer.getvalue()).split("-----------------\n") - packages = packages[1].strip('\r\n').split(";") - for package in packages: - name, version = package.split(",") - python_installs[name] = {"version": version} - - return python_installs - def _generate_cura_version(self, location): with open(os.path.join(self.recipe_folder, "CuraVersion.py.jinja"), "r") as f: cura_version_py = Template(f.read()) @@ -193,7 +174,6 @@ class CuraConan(ConanFile): cura_digital_factory_url = self.conan_data["urls"][self._urls]["digital_factory_url"], cura_latest_url=self.conan_data["urls"][self._urls]["cura_latest_url"], conan_installs=self._conan_installs(), - python_installs=self._python_installs(), )) def export_sources(self): From bdb7444afac4e7021b65842b6a8d75d2df4baad4 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 19 Oct 2023 22:06:40 +0200 Subject: [PATCH 06/42] Resolve qt warnings CURA-10561 --- resources/qml/Dialogs/AboutDialog.qml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/resources/qml/Dialogs/AboutDialog.qml b/resources/qml/Dialogs/AboutDialog.qml index f448a32f1e..154281958e 100644 --- a/resources/qml/Dialogs/AboutDialog.qml +++ b/resources/qml/Dialogs/AboutDialog.qml @@ -68,7 +68,7 @@ UM.Dialog UM.Label { text: { - if (typeof(url) !== "undefined" && url !== "") { + if (url !== "") { return `${name}`; } else { return name; @@ -151,6 +151,7 @@ UM.Dialog property string description: model.description property string license: model.license property string url: model.url + property string version: "" } model: ListModel @@ -232,6 +233,9 @@ UM.Dialog width: parent.width property string name: modelData.name property string version: modelData.version + property string license: "" + property string url: "" + property string description: "" } } } @@ -255,6 +259,9 @@ UM.Dialog width: parent.width property string name: modelData.name property string version: modelData.version + property string license: "" + property string url: "" + property string description: "" } width: parent.width model: Object From 366004cdc1ed12d769584793c5394d1cba1988b4 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 19 Oct 2023 22:44:27 +0200 Subject: [PATCH 07/42] Change margins CURA-10561 --- resources/qml/Dialogs/AboutDialog.qml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/resources/qml/Dialogs/AboutDialog.qml b/resources/qml/Dialogs/AboutDialog.qml index 154281958e..d687f4a25d 100644 --- a/resources/qml/Dialogs/AboutDialog.qml +++ b/resources/qml/Dialogs/AboutDialog.qml @@ -63,7 +63,7 @@ UM.Dialog // Reusable component to display a dependency readonly property Component dependency_row: RowLayout { - spacing: UM.Theme.getSize("narrow_margin").width + spacing: UM.Theme.getSize("default_margin").width UM.Label { @@ -76,7 +76,7 @@ UM.Dialog } visible: text !== "" Layout.fillWidth: true - Layout.preferredWidth: 2 + Layout.preferredWidth: 1 onLinkActivated: Qt.openUrlExternally(url) } @@ -85,7 +85,7 @@ UM.Dialog text: description visible: text !== "" Layout.fillWidth: true - Layout.preferredWidth: 3 + Layout.preferredWidth: 2 } UM.Label @@ -93,7 +93,7 @@ UM.Dialog text: license visible: text !== "" Layout.fillWidth: true - Layout.preferredWidth: 2 + Layout.preferredWidth: 1 } UM.Label @@ -101,7 +101,7 @@ UM.Dialog text: version visible: text !== "" Layout.fillWidth: true - Layout.preferredWidth: 2 + Layout.preferredWidth: 1 } } @@ -117,7 +117,7 @@ UM.Dialog Column { id: content - spacing: UM.Theme.getSize("narrow_margin").width + spacing: UM.Theme.getSize("default_margin").height width: parent.width UM.Label @@ -251,6 +251,7 @@ UM.Dialog { width: parent.width visible: !showDefaultDependencies + Repeater { delegate: Loader From 1425dd01d5b692679b5d7937d4613fab9c1d2cde Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 20 Oct 2023 22:49:26 +0200 Subject: [PATCH 08/42] Fix bug in create bounds CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index 521db04b5b..2a6243828c 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -154,7 +154,7 @@ class MakerbotWriter(MeshWriter): if bounds is None: bounds = node_bounds else: - bounds += node_bounds + bounds = bounds + node_bounds if bounds is not None: meta["bounding_box"] = { From fe4790fe0695739c8e8d41f97c61d1c05d73967f Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 20 Oct 2023 23:14:58 +0200 Subject: [PATCH 09/42] Add iso view to snapshot --- cura/Snapshot.py | 90 +++++++++++++++++++++++- plugins/MakerbotWriter/MakerbotWriter.py | 2 +- 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/cura/Snapshot.py b/cura/Snapshot.py index 1266d3dcb1..53090a5fec 100644 --- a/cura/Snapshot.py +++ b/cura/Snapshot.py @@ -1,7 +1,9 @@ -# Copyright (c) 2021 Ultimaker B.V. +# Copyright (c) 2023 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. import numpy +from typing import Optional + from PyQt6 import QtCore from PyQt6.QtCore import QCoreApplication from PyQt6.QtGui import QImage @@ -10,11 +12,13 @@ from UM.Logger import Logger from cura.PreviewPass import PreviewPass from UM.Application import Application +from UM.Math.AxisAlignedBox import AxisAlignedBox from UM.Math.Matrix import Matrix from UM.Math.Vector import Vector from UM.Scene.Camera import Camera from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator - +from UM.Scene.SceneNode import SceneNode +from UM.Qt.QtRenderer import QtRenderer class Snapshot: @staticmethod @@ -32,6 +36,88 @@ class Snapshot: return min_x, max_x, min_y, max_y + @staticmethod + def isometric_snapshot(width: int = 300, height: int = 300, *, root: Optional[SceneNode] = None) -> Optional[ + QImage]: + """Create an isometric snapshot of the scene.""" + + root = Application.getInstance().getController().getScene().getRoot() if root is None else root + + # the direction the camera is looking at to create the isometric view + iso_view_dir = Vector(-1, -1, -1).normalized() + + bounds = Snapshot.node_bounds(root) + if bounds is None: + Logger.log("w", "There appears to be nothing to render") + return None + + camera = Camera("snapshot") + + # find local x and y directional vectors of the camera + s = iso_view_dir.cross(Vector.Unit_Y).normalized() + u = s.cross(iso_view_dir).normalized() + + # find extreme screen space coords of the scene + x_points = [p.dot(s) for p in bounds.points] + y_points = [p.dot(u) for p in bounds.points] + min_x = min(x_points) + max_x = max(x_points) + min_y = min(y_points) + max_y = max(y_points) + camera_width = max_x - min_x + camera_height = max_y - min_y + + if camera_width == 0 or camera_height == 0: + Logger.log("w", "There appears to be nothing to render") + return None + + # increase either width or height to match the aspect ratio of the image + if camera_width / camera_height > width / height: + camera_height = camera_width * height / width + else: + camera_width = camera_height * width / height + + # Configure camera for isometric view + ortho_matrix = Matrix() + ortho_matrix.setOrtho( + -camera_width / 2, + camera_width / 2, + -camera_height / 2, + camera_height / 2, + -10000, + 10000 + ) + camera.setPerspective(False) + camera.setProjectionMatrix(ortho_matrix) + camera.setPosition(bounds.center) + camera.lookAt(bounds.center + iso_view_dir) + + # Render the scene + renderer = QtRenderer() + render_pass = PreviewPass(width, height) + renderer.setViewportSize(width, height) + renderer.setWindowSize(width, height) + render_pass.setCamera(camera) + renderer.addRenderPass(render_pass) + renderer.beginRendering() + renderer.render() + + return render_pass.getOutput() + + @staticmethod + def node_bounds(root_node: SceneNode) -> Optional[AxisAlignedBox]: + axis_aligned_box = None + for node in DepthFirstIterator(root_node): + if not getattr(node, "_outside_buildarea", False): + if node.callDecoration( + "isSliceable") and node.getMeshData() and node.isVisible() and not node.callDecoration( + "isNonThumbnailVisibleMesh"): + if axis_aligned_box is None: + axis_aligned_box = node.getBoundingBox() + else: + axis_aligned_box = axis_aligned_box + node.getBoundingBox() + return axis_aligned_box + @staticmethod def snapshot(width = 300, height = 300): """Return a QImage of the scene diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index 2a6243828c..18fb435490 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -61,7 +61,7 @@ class MakerbotWriter(MeshWriter): Logger.warning("Can't create snapshot when renderer not initialized.") return try: - snapshot = Snapshot.snapshot(width, height) + snapshot = Snapshot.isometric_snapshot(width, height) except: Logger.logException("w", "Failed to create snapshot image") return From 513454075142d5e4074252027810cc9bd87f2948 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 23 Oct 2023 11:19:04 +0200 Subject: [PATCH 10/42] Remove unused extra argument --- cura/Snapshot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cura/Snapshot.py b/cura/Snapshot.py index 53090a5fec..8b162403f8 100644 --- a/cura/Snapshot.py +++ b/cura/Snapshot.py @@ -37,11 +37,10 @@ class Snapshot: return min_x, max_x, min_y, max_y @staticmethod - def isometric_snapshot(width: int = 300, height: int = 300, *, root: Optional[SceneNode] = None) -> Optional[ - QImage]: + def isometric_snapshot(width: int = 300, height: int = 300) -> Optional[QImage]: """Create an isometric snapshot of the scene.""" - root = Application.getInstance().getController().getScene().getRoot() if root is None else root + root = Application.getInstance().getController().getScene().getRoot() # the direction the camera is looking at to create the isometric view iso_view_dir = Vector(-1, -1, -1).normalized() From 6c2a468c1896fcb4f82e95a186806a604691e986 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 23 Oct 2023 11:19:28 +0200 Subject: [PATCH 11/42] Reuse `node_bounds` utility function --- cura/Snapshot.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/cura/Snapshot.py b/cura/Snapshot.py index 8b162403f8..4fd8f57b94 100644 --- a/cura/Snapshot.py +++ b/cura/Snapshot.py @@ -140,14 +140,7 @@ class Snapshot: camera = Camera("snapshot", root) # determine zoom and look at - bbox = None - for node in DepthFirstIterator(root): - if not getattr(node, "_outside_buildarea", False): - if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible() and not node.callDecoration("isNonThumbnailVisibleMesh"): - if bbox is None: - bbox = node.getBoundingBox() - else: - bbox = bbox + node.getBoundingBox() + bbox = Snapshot.node_bounds(root) # If there is no bounding box, it means that there is no model in the buildplate if bbox is None: Logger.log("w", "Unable to create snapshot as we seem to have an empty buildplate") From 603d69451299324c120d705bc1c423bacca68369 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Wed, 25 Oct 2023 16:27:36 +0200 Subject: [PATCH 12/42] Fix duration in makerbot metadata CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index 2a6243828c..87206a8aaa 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -170,8 +170,9 @@ class MakerbotWriter(MeshWriter): meta["build_plane_temperature"] = material_bed_temperature print_information = application.getPrintInformation() - meta["commanded_duration_s"] = print_information.currentPrintTime.seconds - meta["duration_s"] = print_information.currentPrintTime.seconds + + meta["commanded_duration_s"] = int(print_information.currentPrintTime) + meta["duration_s"] = int(print_information.currentPrintTime) material_lengths = list(map(meter_to_millimeter, print_information.materialLengths)) meta["extrusion_distance_mm"] = material_lengths[0] @@ -202,7 +203,7 @@ class MakerbotWriter(MeshWriter): meta["preferences"] = dict() for node in nodes: - bound = node.getBoundingBox() + bounds = node.getBoundingBox() meta["preferences"][str(node.getName())] = { "machineBounds": [bounds.right, bounds.back, bounds.left, bounds.front] if bounds is not None else None, "printMode": CuraApplication.getInstance().getIntentManager().currentIntentCategory, From 251f247147eb43b6fb40a99c71067e3bc1eef6cc Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Wed, 25 Oct 2023 17:52:09 +0200 Subject: [PATCH 13/42] Update `platform_temperature` and `build_plane_temperature` CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index 4aa8120b67..059dbd185d 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -167,7 +167,10 @@ class MakerbotWriter(MeshWriter): } material_bed_temperature = global_stack.getProperty("material_bed_temperature", "value") - meta["build_plane_temperature"] = material_bed_temperature + meta["platform_temperature"] = material_bed_temperature + + build_volume_temperature = global_stack.getProperty("build_volume_temperature", "value") + meta["build_plane_temperature"] = build_volume_temperature print_information = application.getPrintInformation() From b2ced7c0bade3814773f7bccbad0f911e238d9cc Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 26 Oct 2023 16:09:38 +0200 Subject: [PATCH 14/42] Install pyDulcificum Contributes to CURA-10561 --- conandata.yml | 1 + conanfile.py | 3 +++ plugins/MakerbotWriter/MakerbotWriter.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/conandata.yml b/conandata.yml index c5ca663f91..c027bde567 100644 --- a/conandata.yml +++ b/conandata.yml @@ -86,6 +86,7 @@ pyinstaller: hiddenimports: - "pySavitar" - "pyArcus" + - "pyDulcificum" - "pynest2d" - "PyQt6" - "PyQt6.QtNetwork" diff --git a/conanfile.py b/conanfile.py index a9173e6614..27736d9daa 100644 --- a/conanfile.py +++ b/conanfile.py @@ -192,6 +192,7 @@ class CuraConan(ConanFile): self.options["pyarcus"].shared = True self.options["pysavitar"].shared = True self.options["pynest2d"].shared = True + self.options["dulcificum"].shared = True self.options["cpython"].shared = True self.options["boost"].header_only = True if self.settings.os == "Linux": @@ -204,9 +205,11 @@ class CuraConan(ConanFile): def requirements(self): self.requires("boost/1.82.0") + self.requires("fmt/9.0.0") self.requires("curaengine_grpc_definitions/(latest)@ultimaker/testing") self.requires("zlib/1.2.13") self.requires("pyarcus/5.3.0") + self.requires("dulcificum/(latest)@ultimaker/cura_10561") self.requires("curaengine/(latest)@ultimaker/testing") self.requires("pysavitar/5.3.0") self.requires("pynest2d/5.3.0") diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index 059dbd185d..db4a8b7797 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -5,6 +5,7 @@ from io import StringIO, BufferedIOBase import json from typing import cast, List, Optional, Dict from zipfile import BadZipFile, ZipFile, ZIP_DEFLATED +import pyDulcificum as du from PyQt6.QtCore import QBuffer @@ -29,6 +30,7 @@ class MakerbotWriter(MeshWriter): def __init__(self) -> None: super().__init__(add_to_recent_files=False) + Logger.info(f"Using PyDulcificum: {du.__version__}") _PNG_FORMATS = [ {"prefix": "isometric_thumbnail", "width": 120, "height": 120}, From 494543a7d782dd1117b13c568aa1c3d2caf879d0 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 26 Oct 2023 16:23:04 +0200 Subject: [PATCH 15/42] Use CURA-10561 Uranium Contributes to CURA-10561 --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 27736d9daa..31ad2108de 100644 --- a/conanfile.py +++ b/conanfile.py @@ -214,7 +214,7 @@ class CuraConan(ConanFile): self.requires("pysavitar/5.3.0") self.requires("pynest2d/5.3.0") self.requires("curaengine_plugin_gradual_flow/0.1.0") - self.requires("uranium/(latest)@ultimaker/testing") + self.requires("uranium/(latest)@ultimaker/cura_10561") self.requires("cura_binary_data/(latest)@ultimaker/testing") self.requires("cpython/3.10.4") if self.options.internal: From 0ce4a6bd6752e7097c6c4049e6df7296c2febc04 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 26 Oct 2023 17:01:26 +0200 Subject: [PATCH 16/42] Add material map CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index db4a8b7797..71620d8457 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -54,6 +54,21 @@ class MakerbotWriter(MeshWriter): "1A": "mk14", "2A": "mk14_s", } + _MATERIAL_MAP = { + '2780b345-577b-4a24-a2c5-12e6aad3e690': 'abs', + '88c8919c-6a09-471a-b7b6-e801263d862d': 'abs-wss1', + '416eead4-0d8e-4f0b-8bfc-a91a519befa5': 'asa', + '85bbae0e-938d-46fb-989f-c9b3689dc4f0': 'nylon-cf', + '283d439a-3490-4481-920c-c51d8cdecf9c': 'nylon', + '62414577-94d1-490d-b1e4-7ef3ec40db02': 'pc', + '69386c85-5b6c-421a-bec5-aeb1fb33f060': 'pet', # PETG + '0ff92885-617b-4144-a03c-9989872454bc': 'pla', + 'a4255da2-cb2a-4042-be49-4a83957a2f9a': 'pva', + 'a140ef8f-4f26-4e73-abe0-cfc29d6d1024': 'wss1', + '77873465-83a9-4283-bc44-4e542b8eb3eb': 'sr30', + '96fca5d9-0371-4516-9e96-8e8182677f3c': 'im-pla', + '19baa6a9-94ff-478b-b4a1-8157b74358d2': 'tpu', + } # must be called from the main thread because of OpenGL @staticmethod @@ -188,7 +203,13 @@ class MakerbotWriter(MeshWriter): meta["uuid"] = print_information.slice_uuid - materials = [extruder.material.getMetaData().get("material") for extruder in extruders] + materials = [] + for extruder in extruders: + guid = extruder.material.getMetaData().get("GUID") + material_name = extruder.material.getMetaData().get("material") + material = self._MATERIAL_MAP.get(guid, material_name) + materials.append(material) + meta["material"] = materials[0] meta["materials"] = materials From cdb581a80b3312d056f97023a2495f6b96ef5523 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 26 Oct 2023 17:31:20 +0200 Subject: [PATCH 17/42] Use CURA-10561 private data branch Contributes to CURA-10561 --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 31ad2108de..938ffd13fd 100644 --- a/conanfile.py +++ b/conanfile.py @@ -218,7 +218,7 @@ class CuraConan(ConanFile): self.requires("cura_binary_data/(latest)@ultimaker/testing") self.requires("cpython/3.10.4") if self.options.internal: - self.requires("cura_private_data/(latest)@ultimaker/testing") + self.requires("cura_private_data/(latest)@ultimaker/cura_10561") self.requires("fdm_materials/(latest)@internal/testing") else: self.requires("fdm_materials/(latest)@ultimaker/testing") From 089ee6c04b816dc0bad57faee2810489a3533676 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 26 Oct 2023 17:36:57 +0200 Subject: [PATCH 18/42] Use pyDulcificum Contributes to CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index 71620d8457..6d91043e3e 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -110,13 +110,12 @@ class MakerbotWriter(MeshWriter): gcode_text_io = StringIO() success = gcode_writer.write(gcode_text_io, None) - # TODO convert gcode_text_io to json - # Writing the g-code failed. Then I can also not write the gzipped g-code. if not success: self.setInformation(gcode_writer.getInformation()) return False + json_toolpaths = du.gcode_2_miracle_jtp(gcode_text_io.getvalue()) metadata = self._getMeta(nodes) png_files = [] @@ -134,6 +133,7 @@ class MakerbotWriter(MeshWriter): try: with ZipFile(stream, "w", compression=ZIP_DEFLATED) as zip_stream: zip_stream.writestr("meta.json", json.dumps(metadata, indent=4)) + zip_stream.writestr("print.jsontoolpath", json_toolpaths) for png_file in png_files: file, data = png_file["file"], png_file["data"] zip_stream.writestr(file, data) From aec5ec884444f29e6aa3bdb5c94e7d3741059ddb Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 27 Oct 2023 13:19:22 +0200 Subject: [PATCH 19/42] Add gaggle CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index 6d91043e3e..cd69161838 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -115,7 +115,7 @@ class MakerbotWriter(MeshWriter): self.setInformation(gcode_writer.getInformation()) return False - json_toolpaths = du.gcode_2_miracle_jtp(gcode_text_io.getvalue()) + json_toolpaths = convert(gcode_text_io.getvalue()) metadata = self._getMeta(nodes) png_files = [] @@ -235,6 +235,8 @@ class MakerbotWriter(MeshWriter): "printMode": CuraApplication.getInstance().getIntentManager().currentIntentCategory, } + meta["miracle_config"] = {"gaggles": {str(node.getName()): {} for node in nodes}} + cura_engine_info = ConanInstalls.get("curaengine", {"version": "unknown", "revision": "unknown"}) meta["curaengine_version"] = cura_engine_info["version"] meta["curaengine_commit_hash"] = cura_engine_info["revision"] From b0c63c35c649813c12e8f16fb026ad5fb1573a95 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 27 Oct 2023 13:43:26 +0200 Subject: [PATCH 20/42] Use `pyDulcificum` again CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index cd69161838..af9b271671 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -115,7 +115,7 @@ class MakerbotWriter(MeshWriter): self.setInformation(gcode_writer.getInformation()) return False - json_toolpaths = convert(gcode_text_io.getvalue()) + json_toolpaths = du.gcode_2_miracle_jtp(gcode_text_io.getvalue()) metadata = self._getMeta(nodes) png_files = [] @@ -237,6 +237,18 @@ class MakerbotWriter(MeshWriter): meta["miracle_config"] = {"gaggles": {str(node.getName()): {} for node in nodes}} + meta["purge_routins"] = [ + [ + ["move", [54, 9, 0, 0, 0], 100, [False, False, False, True, True]], + ["purge_move", [72, 9, 0, 100, 0], 3, [False, False, False, True, True]], + ["move", [63, 0, 0, 0, 0], 30, [False, False, False, True, True]] + ], [ + ["move", [54, 9, 0, 0, 0], 100, [False, False, False, True, True]], + ["purge_move", [72, 9, 0, 0, 100], 1, [False, False, False, True, True]], + ["move", [63, 0, 0, 0, 0], 30, [False, False, False, True, True]] + ] + ] + cura_engine_info = ConanInstalls.get("curaengine", {"version": "unknown", "revision": "unknown"}) meta["curaengine_version"] = cura_engine_info["version"] meta["curaengine_commit_hash"] = cura_engine_info["revision"] From b60a3b04ad567ecc0555cd1f581c63b5d7ee50a2 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 27 Oct 2023 13:54:53 +0200 Subject: [PATCH 21/42] Add versions CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index af9b271671..a574d15bbd 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -237,25 +237,19 @@ class MakerbotWriter(MeshWriter): meta["miracle_config"] = {"gaggles": {str(node.getName()): {} for node in nodes}} - meta["purge_routins"] = [ - [ - ["move", [54, 9, 0, 0, 0], 100, [False, False, False, True, True]], - ["purge_move", [72, 9, 0, 100, 0], 3, [False, False, False, True, True]], - ["move", [63, 0, 0, 0, 0], 30, [False, False, False, True, True]] - ], [ - ["move", [54, 9, 0, 0, 0], 100, [False, False, False, True, True]], - ["purge_move", [72, 9, 0, 0, 100], 1, [False, False, False, True, True]], - ["move", [63, 0, 0, 0, 0], 30, [False, False, False, True, True]] - ] - ] - cura_engine_info = ConanInstalls.get("curaengine", {"version": "unknown", "revision": "unknown"}) meta["curaengine_version"] = cura_engine_info["version"] meta["curaengine_commit_hash"] = cura_engine_info["revision"] + dulcificum_info = ConanInstalls.get("dulcificum", {"version": "unknown", "revision": "unknown"}) + meta["dulcificum_version"] = dulcificum_info["version"] + meta["dulcificum_commit_hash"] = dulcificum_info["revision"] + meta["makerbot_writer_version"] = self.getVersion() # meta["makerbot_writer_commit_hash"] = self.getRevision() + meta["pyDulcificum"] = du.__version__ + for name, package_info in ConanInstalls.items(): if not name.startswith("curaengine_ "): continue From 5915994d7f6da026927516a76e3d6d00099932f4 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 27 Oct 2023 14:08:54 +0200 Subject: [PATCH 22/42] codecleanup CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index a574d15bbd..04dcec34d4 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -246,12 +246,11 @@ class MakerbotWriter(MeshWriter): meta["dulcificum_commit_hash"] = dulcificum_info["revision"] meta["makerbot_writer_version"] = self.getVersion() - # meta["makerbot_writer_commit_hash"] = self.getRevision() - meta["pyDulcificum"] = du.__version__ + # Add engine plugin information to the metadata for name, package_info in ConanInstalls.items(): - if not name.startswith("curaengine_ "): + if not name.startswith("curaengine_"): continue meta[f"{name}_version"] = package_info["version"] meta[f"{name}_commit_hash"] = package_info["revision"] From 64f5f5491997fe0bc8a8d5b5ebc15fe61deb83a6 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Sat, 28 Oct 2023 12:59:18 +0200 Subject: [PATCH 23/42] Fix missing pyinstaller spec generation Contributes to CURA-10561 --- conanfile.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/conanfile.py b/conanfile.py index e046ba806d..3104cddcc5 100644 --- a/conanfile.py +++ b/conanfile.py @@ -176,6 +176,89 @@ class CuraConan(ConanFile): conan_installs=self._conan_installs(), )) + def _generate_pyinstaller_spec(self, location, entrypoint_location, icon_path, entitlements_file): + pyinstaller_metadata = self.conan_data["pyinstaller"] + datas = [] + 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: + src_path = os.path.join(self.package_folder, data["src"]) + else: + src_path = os.path.join(self.source_folder, data["src"]) + else: + src_path = os.path.join(self.deps_cpp_info[data["package"]].rootpath, data["src"]) + elif "root" in data: # get the paths relative from the install folder + src_path = os.path.join(self.install_folder, data["root"], data["src"]) + else: + continue + if Path(src_path).exists(): + datas.append((str(src_path), data["dst"])) + + binaries = [] + for binary in pyinstaller_metadata["binaries"].values(): + if "package" in binary: # get the paths from conan package + src_path = os.path.join(self.deps_cpp_info[binary["package"]].rootpath, binary["src"]) + elif "root" in binary: # get the paths relative from the sourcefolder + src_path = str(self.source_path.joinpath(binary["root"], binary["src"])) + if self.settings.os == "Windows": + src_path = src_path.replace("\\", "\\\\") + else: + continue + if not Path(src_path).exists(): + self.output.warning(f"Source path for binary {binary['binary']} does not exist") + continue + + for bin in Path(src_path).glob(binary["binary"] + "*[.exe|.dll|.so|.dylib|.so.]*"): + binaries.append((str(bin), binary["dst"])) + for bin in Path(src_path).glob(binary["binary"]): + binaries.append((str(bin), binary["dst"])) + + # Make sure all Conan dependencies which are shared are added to the binary list for pyinstaller + for _, dependency in self.dependencies.host.items(): + for bin_paths in dependency.cpp_info.bindirs: + binaries.extend([(f"{p}", ".") for p in Path(bin_paths).glob("**/*.dll")]) + for lib_paths in dependency.cpp_info.libdirs: + binaries.extend([(f"{p}", ".") for p in Path(lib_paths).glob("**/*.so*")]) + binaries.extend([(f"{p}", ".") for p in Path(lib_paths).glob("**/*.dylib*")]) + + # Copy dynamic libs from lib path + binaries.extend([(f"{p}", ".") for p in Path(self._base_dir.joinpath("lib")).glob("**/*.dylib*")]) + binaries.extend([(f"{p}", ".") for p in Path(self._base_dir.joinpath("lib")).glob("**/*.so*")]) + + # Collect all dll's from PyQt6 and place them in the root + binaries.extend([(f"{p}", ".") for p in Path(self._site_packages, "PyQt6", "Qt6").glob("**/*.dll")]) + + with open(os.path.join(self.recipe_folder, "UltiMaker-Cura.spec.jinja"), "r") as f: + pyinstaller = Template(f.read()) + + version = self.conf_info.get("user.cura:version", default = self.version, check_type = str) + cura_version = Version(version) + + with open(os.path.join(location, "UltiMaker-Cura.spec"), "w") as f: + f.write(pyinstaller.render( + name = str(self.options.display_name).replace(" ", "-"), + display_name = self._app_name, + entrypoint = entrypoint_location, + datas = datas, + binaries = binaries, + venv_script_path = str(self._script_dir), + hiddenimports = pyinstaller_metadata["hiddenimports"], + collect_all = pyinstaller_metadata["collect_all"], + icon = icon_path, + entitlements_file = entitlements_file, + osx_bundle_identifier = "'nl.ultimaker.cura'" if self.settings.os == "Macos" else "None", + upx = str(self.settings.os == "Windows"), + strip = False, # This should be possible on Linux and MacOS but, it can also cause issues on some distributions. Safest is to disable it for now + target_arch = self._pyinstaller_spec_arch, + macos = self.settings.os == "Macos", + version = f"'{version}'", + short_version = f"'{cura_version.major}.{cura_version.minor}.{cura_version.patch}'", + )) + def export_sources(self): copy(self, "*", os.path.join(self.recipe_folder, "plugins"), os.path.join(self.export_sources_folder, "plugins")) copy(self, "*", os.path.join(self.recipe_folder, "resources"), os.path.join(self.export_sources_folder, "resources"), excludes = "*.mo") @@ -300,9 +383,9 @@ class CuraConan(ConanFile): vb.generate() # # FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement - # cpp_info = self.dependencies["gettext"].cpp_info - # pot = self.python_requires["translationextractor"].module.ExtractTranslations(self, cpp_info.bindirs[0]) - # pot.generate() + cpp_info = self.dependencies["gettext"].cpp_info + pot = self.python_requires["translationextractor"].module.ExtractTranslations(self, cpp_info.bindirs[0]) + pot.generate() def build(self): if self.options.devtools: From bb6f8fa554887e810675d629c7811fd25bde24df Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Sat, 28 Oct 2023 14:37:54 +0200 Subject: [PATCH 24/42] Fix incorrect embedded quotes Contributes to CURA-10561 --- .github/workflows/linux.yml | 4 ++-- .github/workflows/macos.yml | 4 ++-- .github/workflows/windows.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7941b13328..1cabf719bd 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -216,7 +216,7 @@ jobs: f.writelines("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n") f.writelines("## Conan packages:\n") for dep_name, dep_info in ConanDependencies.items(): - f.writelines(f"`{dep_name} {dep_info["version"]} {dep_info["revision"]}`\n") + f.writelines(f"`{dep_name} {dep_info['version']} {dep_info['revision']}`\n") - name: Summarize the used Python modules shell: python @@ -236,7 +236,7 @@ jobs: f.write(content) f.writelines("## Python modules:\n") for dep_name, dep_info in ConanDependencies.items(): - f.writelines(f"`{dep_name} {dep_info["version"]}`\n") + f.writelines(f"`{dep_name} {dep_info['version']}`\n") - name: Create the Linux AppImage (Bash) run: | diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 9f2d44bf80..53fb119520 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -228,7 +228,7 @@ jobs: f.writelines("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n") f.writelines("## Conan packages:\n") for dep_name, dep_info in ConanDependencies.items(): - f.writelines(f"`{dep_name} {dep_info["version"]} {dep_info["revision"]}`\n") + f.writelines(f"`{dep_name} {dep_info['version']} {dep_info['revision']}`\n") - name: Summarize the used Python modules shell: python @@ -248,7 +248,7 @@ jobs: f.write(content) f.writelines("## Python modules:\n") for dep_name, dep_info in ConanDependencies.items(): - f.writelines(f"`{dep_name} {dep_info["version"]}`\n") + f.writelines(f"`{dep_name} {dep_info['version']}`\n") - name: Create the Macos dmg (Bash) run: python ../cura_inst/packaging/MacOS/build_macos.py --source_path ../cura_inst --dist_path . --cura_conan_version $CURA_CONAN_VERSION --filename "${{ steps.filename.outputs.INSTALLER_FILENAME }}" --build_dmg --build_pkg --app_name "$CURA_APP_NAME" diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 28790dd05a..cd5e843878 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -187,7 +187,7 @@ jobs: f.writelines("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n") f.writelines("## Conan packages:\n") for dep_name, dep_info in ConanDependencies.items(): - f.writelines(f"`{dep_name} {dep_info["version"]} {dep_info["revision"]}`\n") + f.writelines(f"`{dep_name} {dep_info['version']} {dep_info['revision']}`\n") - name: Summarize the used Python modules shell: python @@ -207,7 +207,7 @@ jobs: f.write(content) f.writelines("## Python modules:\n") for dep_name, dep_info in ConanDependencies.items(): - f.writelines(f"`{dep_name} {dep_info["version"]}`\n") + f.writelines(f"`{dep_name} {dep_info['version']}`\n") - name: Create PFX certificate from BASE64_PFX_CONTENT secret id: create-pfx From 91be79fbf3621c16c405c01fcb6223e2579a40e4 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Sat, 28 Oct 2023 18:05:09 +0200 Subject: [PATCH 25/42] Fix incorrect deps workflow summary Contributes to CURA-10561 --- .github/workflows/linux.yml | 26 ++++---------------------- .github/workflows/macos.yml | 26 ++++---------------------- .github/workflows/windows.yml | 26 ++++---------------------- 3 files changed, 12 insertions(+), 66 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1cabf719bd..81228e5f2d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -196,14 +196,12 @@ jobs: f.write(content) f.writelines(f"INSTALLER_FILENAME={installer_filename}\n") - - name: Summarize the used Conan dependencies + - name: Summarize the used dependencies shell: python run: | import os - import json - from pathlib import Path - from cura.CuraVersion import ConanInstalls + from cura.CuraVersion import ConanInstalls, PythonInstalls summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" @@ -215,27 +213,11 @@ jobs: f.write(content) f.writelines("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n") f.writelines("## Conan packages:\n") - for dep_name, dep_info in ConanDependencies.items(): + for dep_name, dep_info in ConanInstalls.items(): f.writelines(f"`{dep_name} {dep_info['version']} {dep_info['revision']}`\n") - - name: Summarize the used Python modules - shell: python - run: | - import os - import pkg_resources - - from cura.CuraVersion import ConanDependencies - - summary_env = os.environ["GITHUB_STEP_SUMMARY"] - content = "" - if os.path.exists(summary_env): - with open(summary_env, "r") as f: - content = f.read() - - with open(summary_env, "w") as f: - f.write(content) f.writelines("## Python modules:\n") - for dep_name, dep_info in ConanDependencies.items(): + for dep_name, dep_info in PythonInstalls.items(): f.writelines(f"`{dep_name} {dep_info['version']}`\n") - name: Create the Linux AppImage (Bash) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 53fb119520..ef4e7e4a12 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -208,14 +208,12 @@ jobs: f.write(content) f.writelines(f"INSTALLER_FILENAME={installer_filename}\n") - - name: Summarize the used Conan dependencies + - name: Summarize the used dependencies shell: python run: | import os - import json - from pathlib import Path - from cura.CuraVersion import ConanInstalls + from cura.CuraVersion import ConanInstalls, PythonInstalls summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" @@ -227,27 +225,11 @@ jobs: f.write(content) f.writelines("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n") f.writelines("## Conan packages:\n") - for dep_name, dep_info in ConanDependencies.items(): + for dep_name, dep_info in ConanInstalls.items(): f.writelines(f"`{dep_name} {dep_info['version']} {dep_info['revision']}`\n") - - name: Summarize the used Python modules - shell: python - run: | - import os - import pkg_resources - - from cura.CuraVersion import PythonInstalls - - summary_env = os.environ["GITHUB_STEP_SUMMARY"] - content = "" - if os.path.exists(summary_env): - with open(summary_env, "r") as f: - content = f.read() - - with open(summary_env, "w") as f: - f.write(content) f.writelines("## Python modules:\n") - for dep_name, dep_info in ConanDependencies.items(): + for dep_name, dep_info in PythonInstalls.items(): f.writelines(f"`{dep_name} {dep_info['version']}`\n") - name: Create the Macos dmg (Bash) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index cd5e843878..176b4196c0 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -167,14 +167,12 @@ jobs: f.write(content) f.writelines(f"INSTALLER_FILENAME={installer_filename}\n") - - name: Summarize the used Conan dependencies + - name: Summarize the used dependencies shell: python run: | import os - import json - from pathlib import Path - from cura.CuraVersion import ConanInstalls + from cura.CuraVersion import ConanInstalls, PythonInstalls summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" @@ -186,27 +184,11 @@ jobs: f.write(content) f.writelines("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n") f.writelines("## Conan packages:\n") - for dep_name, dep_info in ConanDependencies.items(): + for dep_name, dep_info in ConanInstalls.items(): f.writelines(f"`{dep_name} {dep_info['version']} {dep_info['revision']}`\n") - - name: Summarize the used Python modules - shell: python - run: | - import os - import pkg_resources - - from cura.CuraVersion import PythonInstalls - - summary_env = os.environ["GITHUB_STEP_SUMMARY"] - content = "" - if os.path.exists(summary_env): - with open(summary_env, "r") as f: - content = f.read() - - with open(summary_env, "w") as f: - f.write(content) f.writelines("## Python modules:\n") - for dep_name, dep_info in ConanDependencies.items(): + for dep_name, dep_info in PythonInstalls.items(): f.writelines(f"`{dep_name} {dep_info['version']}`\n") - name: Create PFX certificate from BASE64_PFX_CONTENT secret From 4b7cefa891e11df431a2b5076d832b62227941f0 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Sat, 28 Oct 2023 18:14:42 +0200 Subject: [PATCH 26/42] No need to build unit tests for installer Contributes to CURA-10561 --- .github/workflows/linux.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/windows.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 81228e5f2d..1dee7a237f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -147,7 +147,7 @@ jobs: run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache" - name: Create the Packages (Bash) - run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING + run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING -c tools.build:skip_test=True - name: Remove internal packages before uploading run: | diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index ef4e7e4a12..3bde4f1b00 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -156,7 +156,7 @@ jobs: run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache" - name: Create the Packages (Bash) - run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING" + run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING -c tools.build:skip_test=True - name: Remove internal packages before uploading run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 176b4196c0..ad946d9d99 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -122,7 +122,7 @@ jobs: run: conan config set storage.download_cache="C:\Users\runneradmin\.conan\conan_download_cache" - name: Create the Packages (Powershell) - run: conan install $Env:CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$Env:ENTERPRISE -o cura:staging=$Env:STAGING + run: conan install $Env:CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$Env:ENTERPRISE -o cura:staging=$Env:STAGING -c tools.build:skip_test=True - name: Remove internal packages before uploading run: | From 872ce27d8a48e00ad85a9f7d82c394552ae10a16 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Sun, 29 Oct 2023 11:51:56 +0100 Subject: [PATCH 27/42] Give translations their own option Only available on bash/zsh environments Contributes to CURA-10561 - CURA-11117 --- conanfile.py | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/conanfile.py b/conanfile.py index 3104cddcc5..18a3275f20 100644 --- a/conanfile.py +++ b/conanfile.py @@ -34,7 +34,8 @@ class CuraConan(ConanFile): "cloud_api_version": "ANY", "display_name": "ANY", # TODO: should this be an option?? "cura_debug_mode": [True, False], # FIXME: Use profiles - "internal": [True, False] + "internal": [True, False], + "enable_i18n": [True, False], } default_options = { "enterprise": "False", @@ -44,6 +45,7 @@ class CuraConan(ConanFile): "display_name": "UltiMaker Cura", "cura_debug_mode": False, # Not yet implemented "internal": False, + "enable_i18n": False, } def set_version(self): @@ -271,6 +273,10 @@ class CuraConan(ConanFile): copy(self, "requirements-ultimaker.txt", self.recipe_folder, self.export_sources_folder) copy(self, "cura_app.py", self.recipe_folder, self.export_sources_folder) + def config_options(self): + if self.settings.os == "Windows" and not self.conf.get("tools.microsoft.bash:path", check_type=str): + del self.options.enable_i18n + def configure(self): self.options["pyarcus"].shared = True self.options["pysavitar"].shared = True @@ -307,10 +313,8 @@ class CuraConan(ConanFile): self.requires("fdm_materials/(latest)@ultimaker/testing") def build_requirements(self): - if self.options.devtools: - if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type = str): - # FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement - self.tool_requires("gettext/0.21@ultimaker/testing", force_host_context = True) + if self.options.get_safe("enable_i18n", False): + self.tool_requires("gettext/0.21@ultimaker/testing", force_host_context = True) def layout(self): self.folders.source = "." @@ -377,26 +381,24 @@ class CuraConan(ConanFile): icon_path = "'{}'".format(os.path.join(self.source_folder, "packaging", self.conan_data["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"), entitlements_file = entitlements_file if self.settings.os == "Macos" else "None") + if self.options.get_safe("enable_i18n", False): # Update the po and pot files - if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type=str): - vb = VirtualBuildEnv(self) - vb.generate() + vb = VirtualBuildEnv(self) + vb.generate() - # # FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement - cpp_info = self.dependencies["gettext"].cpp_info - pot = self.python_requires["translationextractor"].module.ExtractTranslations(self, cpp_info.bindirs[0]) - pot.generate() + # # FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement + cpp_info = self.dependencies["gettext"].cpp_info + pot = self.python_requires["translationextractor"].module.ExtractTranslations(self, cpp_info.bindirs[0]) + pot.generate() def build(self): - if self.options.devtools: - if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type = str): - # FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement - for po_file in self.source_path.joinpath("resources", "i18n").glob("**/*.po"): - mo_file = Path(self.build_folder, po_file.with_suffix('.mo').relative_to(self.source_path)) - mo_file = mo_file.parent.joinpath("LC_MESSAGES", mo_file.name) - mkdir(self, str(unix_path(self, Path(mo_file).parent))) - cpp_info = self.dependencies["gettext"].cpp_info - self.run(f"{cpp_info.bindirs[0]}/msgfmt {po_file} -o {mo_file} -f", env="conanbuild", ignore_errors=True) + if self.options.get_safe("enable_i18n", False): + for po_file in self.source_path.joinpath("resources", "i18n").glob("**/*.po"): + mo_file = Path(self.build_folder, po_file.with_suffix('.mo').relative_to(self.source_path)) + mo_file = mo_file.parent.joinpath("LC_MESSAGES", mo_file.name) + mkdir(self, str(unix_path(self, Path(mo_file).parent))) + cpp_info = self.dependencies["gettext"].cpp_info + self.run(f"{cpp_info.bindirs[0]}/msgfmt {po_file} -o {mo_file} -f", env="conanbuild", ignore_errors=True) def deploy(self): copy(self, "*", os.path.join(self.package_folder, self.cpp.package.resdirs[2]), os.path.join(self.install_folder, "packaging"), keep_path = True) From 94eb9e1a21825af84161bb0544aaf772e9cddf48 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Sun, 29 Oct 2023 11:52:27 +0100 Subject: [PATCH 28/42] Build dulcificum static on Windows Contributes to CURA-10561 --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 18a3275f20..b47c5789d9 100644 --- a/conanfile.py +++ b/conanfile.py @@ -281,7 +281,7 @@ class CuraConan(ConanFile): self.options["pyarcus"].shared = True self.options["pysavitar"].shared = True self.options["pynest2d"].shared = True - self.options["dulcificum"].shared = True + self.options["dulcificum"].shared = self.settings.os != "Windows" self.options["cpython"].shared = True self.options["boost"].header_only = True if self.settings.os == "Linux": From 16249e490ed3c8a2a8227969a7ebc779605c2188 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Sun, 29 Oct 2023 14:25:06 +0100 Subject: [PATCH 29/42] Allow sideloading of resources via env `CURA_DATA_ROOT` and `URANIUM_DATA_ROOT` This allows the conanfile.py to point to `venv/share/cura` This functionality is disabled in sys.frozen Contributes to CURA-10561 --- conanfile.py | 2 ++ cura/CuraApplication.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/conanfile.py b/conanfile.py index b47c5789d9..31b741a970 100644 --- a/conanfile.py +++ b/conanfile.py @@ -67,6 +67,8 @@ class CuraConan(ConanFile): self._cura_env = Environment() self._cura_env.define("QML2_IMPORT_PATH", str(self._site_packages.joinpath("PyQt6", "Qt6", "qml"))) self._cura_env.define("QT_PLUGIN_PATH", str(self._site_packages.joinpath("PyQt6", "Qt6", "plugins"))) + if not self.in_local_cache: + self._cura_env.define( "CURA_DATA_ROOT", str(self._share_dir.joinpath("cura"))) if self.settings.os == "Linux": self._cura_env.define("QT_QPA_FONTDIR", "/usr/share/fonts") diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index b51fbd9d82..722a728863 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -6,6 +6,7 @@ import sys import tempfile import time import platform +from pathlib import Path from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict import numpy @@ -366,6 +367,10 @@ class CuraApplication(QtApplication): Resources.addSecureSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources")) if not hasattr(sys, "frozen"): + cura_data_root = os.environ.get('CURA_DATA_ROOT', None) + if cura_data_root: + Resources.addSearchPath(str(Path(cura_data_root).joinpath("resources"))) + Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")) # local Conan cache From 2660933d6ea9c29b9d76f96d2c07f335481dd546 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 30 Oct 2023 13:37:50 +0100 Subject: [PATCH 30/42] Add mimetype for .makerbot files CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index 04dcec34d4..ecb0006690 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -12,6 +12,7 @@ from PyQt6.QtCore import QBuffer from UM.Logger import Logger from UM.Math.AxisAlignedBox import AxisAlignedBox from UM.Mesh.MeshWriter import MeshWriter +from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.PluginRegistry import PluginRegistry from UM.Scene.SceneNode import SceneNode from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator @@ -31,6 +32,13 @@ class MakerbotWriter(MeshWriter): def __init__(self) -> None: super().__init__(add_to_recent_files=False) Logger.info(f"Using PyDulcificum: {du.__version__}") + MimeTypeDatabase.addMimeType( + MimeType( + name="application/x-makerbot", + comment="Makerbot Toolpath Package", + suffixes=["makerbot"] + ) + ) _PNG_FORMATS = [ {"prefix": "isometric_thumbnail", "width": 120, "height": 120}, From 71ada85966ae53d776e7fa36ab78692c18b5ee70 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 30 Oct 2023 15:41:54 +0100 Subject: [PATCH 31/42] Use camercase for `isometricSnapshot` def CURA-10561 --- cura/Snapshot.py | 2 +- plugins/MakerbotWriter/MakerbotWriter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/Snapshot.py b/cura/Snapshot.py index 4fd8f57b94..0aeacbc1bb 100644 --- a/cura/Snapshot.py +++ b/cura/Snapshot.py @@ -37,7 +37,7 @@ class Snapshot: return min_x, max_x, min_y, max_y @staticmethod - def isometric_snapshot(width: int = 300, height: int = 300) -> Optional[QImage]: + def isometricSnapshot(width: int = 300, height: int = 300) -> Optional[QImage]: """Create an isometric snapshot of the scene.""" root = Application.getInstance().getController().getScene().getRoot() diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index ecb0006690..e0b5b0eb2b 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -86,7 +86,7 @@ class MakerbotWriter(MeshWriter): Logger.warning("Can't create snapshot when renderer not initialized.") return try: - snapshot = Snapshot.isometric_snapshot(width, height) + snapshot = Snapshot.isometricSnapshot(width, height) except: Logger.logException("w", "Failed to create snapshot image") return From 7e91750f584e4fa96089f9425991c91bad9754c1 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 30 Oct 2023 15:43:02 +0100 Subject: [PATCH 32/42] Use camercase for `nodeBounds` def CURA-10561 --- cura/Snapshot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/Snapshot.py b/cura/Snapshot.py index 0aeacbc1bb..138777d812 100644 --- a/cura/Snapshot.py +++ b/cura/Snapshot.py @@ -45,7 +45,7 @@ class Snapshot: # the direction the camera is looking at to create the isometric view iso_view_dir = Vector(-1, -1, -1).normalized() - bounds = Snapshot.node_bounds(root) + bounds = Snapshot.nodeBounds(root) if bounds is None: Logger.log("w", "There appears to be nothing to render") return None @@ -104,7 +104,7 @@ class Snapshot: return render_pass.getOutput() @staticmethod - def node_bounds(root_node: SceneNode) -> Optional[AxisAlignedBox]: + def nodeBounds(root_node: SceneNode) -> Optional[AxisAlignedBox]: axis_aligned_box = None for node in DepthFirstIterator(root_node): if not getattr(node, "_outside_buildarea", False): @@ -140,7 +140,7 @@ class Snapshot: camera = Camera("snapshot", root) # determine zoom and look at - bbox = Snapshot.node_bounds(root) + bbox = Snapshot.nodeBounds(root) # If there is no bounding box, it means that there is no model in the buildplate if bbox is None: Logger.log("w", "Unable to create snapshot as we seem to have an empty buildplate") From c68a4b1bb75c83963785cdb388a71c8bb00271ea Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 30 Oct 2023 15:44:42 +0100 Subject: [PATCH 33/42] Write correct key for `pyDulcificum_version` metadata CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index e0b5b0eb2b..666d77a537 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -254,7 +254,7 @@ class MakerbotWriter(MeshWriter): meta["dulcificum_commit_hash"] = dulcificum_info["revision"] meta["makerbot_writer_version"] = self.getVersion() - meta["pyDulcificum"] = du.__version__ + meta["pyDulcificum_version"] = du.__version__ # Add engine plugin information to the metadata for name, package_info in ConanInstalls.items(): From 4f649e57d13b41c7e8565beeb35987568bcd666e Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 30 Oct 2023 16:10:13 +0100 Subject: [PATCH 34/42] Write `meterToMillimeter` as camelcase CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index 666d77a537..2ed8bb3dd9 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -202,7 +202,7 @@ class MakerbotWriter(MeshWriter): meta["commanded_duration_s"] = int(print_information.currentPrintTime) meta["duration_s"] = int(print_information.currentPrintTime) - material_lengths = list(map(meter_to_millimeter, print_information.materialLengths)) + material_lengths = list(map(meterToMillimeter, print_information.materialLengths)) meta["extrusion_distance_mm"] = material_lengths[0] meta["extrusion_distances_mm"] = material_lengths @@ -273,6 +273,6 @@ class MakerbotWriter(MeshWriter): return meta -def meter_to_millimeter(value: float) -> float: +def meterToMillimeter(value: float) -> float: """Converts a value in meters to millimeters.""" return value * 1000.0 From f8bb30b0252809c4a2032dfad2cf5ea0960e9e2f Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 30 Oct 2023 16:31:32 +0100 Subject: [PATCH 35/42] Use better variable naming CURA-10561 --- cura/Snapshot.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cura/Snapshot.py b/cura/Snapshot.py index 138777d812..e493f0e79c 100644 --- a/cura/Snapshot.py +++ b/cura/Snapshot.py @@ -53,12 +53,12 @@ class Snapshot: camera = Camera("snapshot") # find local x and y directional vectors of the camera - s = iso_view_dir.cross(Vector.Unit_Y).normalized() - u = s.cross(iso_view_dir).normalized() + tangent_space_x_direction = iso_view_dir.cross(Vector.Unit_Y).normalized() + tangent_space_y_direction = tangent_space_x_direction.cross(iso_view_dir).normalized() # find extreme screen space coords of the scene - x_points = [p.dot(s) for p in bounds.points] - y_points = [p.dot(u) for p in bounds.points] + x_points = [p.dot(tangent_space_x_direction) for p in bounds.points] + y_points = [p.dot(tangent_space_y_direction) for p in bounds.points] min_x = min(x_points) max_x = max(x_points) min_y = min(y_points) From 0c8496b7ab7008a0a4688263f760f4606db3f941 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 30 Oct 2023 17:03:59 +0100 Subject: [PATCH 36/42] Set write flag for thumbnail CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index 2ed8bb3dd9..aac1b3495c 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -92,7 +92,7 @@ class MakerbotWriter(MeshWriter): return thumbnail_buffer = QBuffer() - thumbnail_buffer.open(QBuffer.OpenModeFlag.ReadWrite) + thumbnail_buffer.open(QBuffer.OpenModeFlag.WriteOnly) snapshot.save(thumbnail_buffer, "PNG") From 20719ea5b590963f3a68c7ae3d9e5061b56b2ce0 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 30 Oct 2023 17:55:06 +0100 Subject: [PATCH 37/42] Use definition name rather then display name of the machine CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index aac1b3495c..f90cef1639 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -169,7 +169,7 @@ class MakerbotWriter(MeshWriter): meta = dict() - meta["bot_type"] = MakerbotWriter._PRINT_NAME_MAP.get((name := global_stack.name), name) + meta["bot_type"] = MakerbotWriter._PRINT_NAME_MAP.get((name := global_stack.definition.name), name) bounds: Optional[AxisAlignedBox] = None for node in nodes: From 9e0def310cda0c33e05661f93a415edd79938e23 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 31 Oct 2023 10:20:44 +0100 Subject: [PATCH 38/42] Update Material GUID LUT Contributes to CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 55 +++++++++++++++++------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index f90cef1639..7009ad650c 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -62,21 +62,46 @@ class MakerbotWriter(MeshWriter): "1A": "mk14", "2A": "mk14_s", } - _MATERIAL_MAP = { - '2780b345-577b-4a24-a2c5-12e6aad3e690': 'abs', - '88c8919c-6a09-471a-b7b6-e801263d862d': 'abs-wss1', - '416eead4-0d8e-4f0b-8bfc-a91a519befa5': 'asa', - '85bbae0e-938d-46fb-989f-c9b3689dc4f0': 'nylon-cf', - '283d439a-3490-4481-920c-c51d8cdecf9c': 'nylon', - '62414577-94d1-490d-b1e4-7ef3ec40db02': 'pc', - '69386c85-5b6c-421a-bec5-aeb1fb33f060': 'pet', # PETG - '0ff92885-617b-4144-a03c-9989872454bc': 'pla', - 'a4255da2-cb2a-4042-be49-4a83957a2f9a': 'pva', - 'a140ef8f-4f26-4e73-abe0-cfc29d6d1024': 'wss1', - '77873465-83a9-4283-bc44-4e542b8eb3eb': 'sr30', - '96fca5d9-0371-4516-9e96-8e8182677f3c': 'im-pla', - '19baa6a9-94ff-478b-b4a1-8157b74358d2': 'tpu', - } + _MATERIAL_MAP = {"2780b345-577b-4a24-a2c5-12e6aad3e690": "abs", + "88c8919c-6a09-471a-b7b6-e801263d862d": "abs-wss1", + "416eead4-0d8e-4f0b-8bfc-a91a519befa5": "asa", + "85bbae0e-938d-46fb-989f-c9b3689dc4f0": "nylon-cf", + "283d439a-3490-4481-920c-c51d8cdecf9c": "nylon", + "62414577-94d1-490d-b1e4-7ef3ec40db02": "pc", + "69386c85-5b6c-421a-bec5-aeb1fb33f060": "petg", + "0ff92885-617b-4144-a03c-9989872454bc": "pla", + "a4255da2-cb2a-4042-be49-4a83957a2f9a": "pva", + "a140ef8f-4f26-4e73-abe0-cfc29d6d1024": "wss1", + "77873465-83a9-4283-bc44-4e542b8eb3eb": "sr30", + "96fca5d9-0371-4516-9e96-8e8182677f3c": "im-pla", + "9f52c514-bb53-46a6-8c0c-d507cd6ee742": "abs", + "0f9a2a91-f9d6-4b6b-bd9b-a120a29391be": "abs", + "d3e972f2-68c0-4d2f-8cfd-91028dfc3381": "abs", + "cb76bd6e-91fd-480c-a191-12301712ec77": "abs-wss1", + "a017777e-3f37-4d89-a96c-dc71219aac77": "abs-wss1", + "4d96000d-66de-4d54-a580-91827dcfd28f": "abs-wss1", + "0ecb0e1a-6a66-49fb-b9ea-61a8924e0cf5": "asa", + "efebc2ea-2381-4937-926f-e824524524a5": "asa", + "b0199512-5714-4951-af85-be19693430f8": "asa", + "b9f55a0a-a2b6-4b8d-8d48-07802c575bd1": "pla", + "c439d884-9cdc-4296-a12c-1bacae01003f": "pla", + "16a723e3-44df-49f4-82ec-2a1173c1e7d9": "pla", + "74d0f5c2-fdfd-4c56-baf1-ff5fa92d177e": "pla", + "64dcb783-470d-4400-91b1-7001652f20da": "pla", + "3a1b479b-899c-46eb-a2ea-67050d1a4937": "pla", + "4708ac49-5dde-4cc2-8c0a-87425a92c2b3": "pla", + "4b560eda-1719-407f-b085-1c2c1fc8ffc1": "pla", + "e10a287d-0067-4a58-9083-b7054f479991": "im-pla", + "01a6b5b0-fab1-420c-a5d9-31713cbeb404": "im-pla", + "f65df4ad-a027-4a48-a51d-975cc8b87041": "im-pla", + "f48739f8-6d96-4a3d-9a2e-8505a47e2e35": "im-pla", + "5c7d7672-e885-4452-9a78-8ba90ec79937": "petg", + "91e05a6e-2f5b-4964-b973-d83b5afe6db4": "petg", + "bdc7dd03-bf38-48ee-aeca-c3e11cee799e": "petg", + "54f66c89-998d-4070-aa60-1cb0fd887518": "nylon", + "002c84b3-84ac-4b5a-b57d-fe1f555a6351": "pva", + "e4da5fcb-f62d-48a2-aaef-0b645aa6973b": "wss1", + "77f06146-6569-437d-8380-9edb0d635a32": "sr30"} # must be called from the main thread because of OpenGL @staticmethod From 625fb06267401b73e0a3a67bef0f9fa66a6066b9 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Tue, 31 Oct 2023 11:30:23 +0100 Subject: [PATCH 39/42] Add makerbotwriter to cura.json CURA-10561 --- resources/bundled_packages/cura.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/resources/bundled_packages/cura.json b/resources/bundled_packages/cura.json index b9667eea13..12ab219f30 100644 --- a/resources/bundled_packages/cura.json +++ b/resources/bundled_packages/cura.json @@ -305,6 +305,23 @@ } } }, + "MakerbotWriter": { + "package_info": { + "package_id": "MakerbotWriter", + "package_type": "plugin", + "display_name": "Makerbot Printfile Writer", + "description": "Provides support for writing MakerBot Format Packages.", + "package_version": "1.0.1", + "sdk_version": "8.5.0", + "website": "https://ultimaker.com", + "author": { + "author_id": "UltimakerPackages", + "display_name": "UltiMaker", + "email": "plugins@ultimaker.com", + "website": "https://ultimaker.com" + } + } + }, "ModelChecker": { "package_info": { "package_id": "ModelChecker", From 6a07c346d6a6d6299756e36a7c9b3086ed60944e Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Tue, 31 Oct 2023 11:31:10 +0100 Subject: [PATCH 40/42] Update comment CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index 7009ad650c..122f4b8e91 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -129,7 +129,7 @@ class MakerbotWriter(MeshWriter): self.setInformation(catalog.i18nc("@error:not supported", "MakerbotWriter does not support text mode.")) return False - # The GCodeWriter plugin is bundled, so it must at least exist. (What happens if people disable that plugin?) + # The GCodeWriter plugin is always available since it is in the "required" list of plugins. gcode_writer = PluginRegistry.getInstance().getPluginObject("GCodeWriter") if gcode_writer is None: From f9517ada4421b230c89bde4c2c64376993690c2f Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 31 Oct 2023 15:33:05 +0100 Subject: [PATCH 41/42] Add ABS-CF to material map Contributes to CURA-10561 --- plugins/MakerbotWriter/MakerbotWriter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/MakerbotWriter/MakerbotWriter.py b/plugins/MakerbotWriter/MakerbotWriter.py index 122f4b8e91..dde1541cfe 100644 --- a/plugins/MakerbotWriter/MakerbotWriter.py +++ b/plugins/MakerbotWriter/MakerbotWriter.py @@ -77,6 +77,7 @@ class MakerbotWriter(MeshWriter): "9f52c514-bb53-46a6-8c0c-d507cd6ee742": "abs", "0f9a2a91-f9d6-4b6b-bd9b-a120a29391be": "abs", "d3e972f2-68c0-4d2f-8cfd-91028dfc3381": "abs", + "495a0ce5-9daf-4a16-b7b2-06856d82394d": "abs-cf", "cb76bd6e-91fd-480c-a191-12301712ec77": "abs-wss1", "a017777e-3f37-4d89-a96c-dc71219aac77": "abs-wss1", "4d96000d-66de-4d54-a580-91827dcfd28f": "abs-wss1", From 6a5e01a5faeff8fb960cbfcf2e43ce1aa3ddc565 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Tue, 31 Oct 2023 17:33:02 +0100 Subject: [PATCH 42/42] Uncouple dependencies CURA-10561 --- conanfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conanfile.py b/conanfile.py index 31b741a970..1ce2b1d369 100644 --- a/conanfile.py +++ b/conanfile.py @@ -300,12 +300,12 @@ class CuraConan(ConanFile): self.requires("curaengine_grpc_definitions/(latest)@ultimaker/testing") self.requires("zlib/1.2.13") self.requires("pyarcus/5.3.0") - self.requires("dulcificum/(latest)@ultimaker/cura_10561") + self.requires("dulcificum/(latest)@ultimaker/testing") self.requires("curaengine/(latest)@ultimaker/testing") self.requires("pysavitar/5.3.0") self.requires("pynest2d/5.3.0") self.requires("curaengine_plugin_gradual_flow/0.1.0") - self.requires("uranium/(latest)@ultimaker/cura_10561") + self.requires("uranium/(latest)@ultimaker/testing") self.requires("cura_binary_data/(latest)@ultimaker/testing") self.requires("cpython/3.10.4") if self.options.internal: