Merge branch 'main' into Makerbot-profiles-update

This commit is contained in:
Jelle Spijker 2023-10-31 18:43:35 +01:00 committed by GitHub
commit e1e443eadf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 775 additions and 370 deletions

View File

@ -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 --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 -c tools.build:skip_test=True
- name: Remove internal packages before uploading
run: |
@ -196,19 +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
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, PythonInstalls
summary_env = os.environ["GITHUB_STEP_SUMMARY"]
content = ""
@ -220,25 +213,12 @@ 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 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
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 package in pkg_resources.working_set:
f.writelines(f"`{package.key}/{package.version}`\n")
for dep_name, dep_info in PythonInstalls.items():
f.writelines(f"`{dep_name} {dep_info['version']}`\n")
- name: Create the Linux AppImage (Bash)
run: |

View File

@ -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 --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 -c tools.build:skip_test=True
- name: Remove internal packages before uploading
run: |
@ -208,19 +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
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, PythonInstalls
summary_env = os.environ["GITHUB_STEP_SUMMARY"]
content = ""
@ -232,25 +225,12 @@ 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 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
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 package in pkg_resources.working_set:
f.writelines(f"`{package.key}/{package.version}`\n")
for dep_name, dep_info in PythonInstalls.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"

View File

@ -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 -c tools.build:skip_test=True
- name: Remove internal packages before uploading
run: |
@ -167,19 +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
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, PythonInstalls
summary_env = os.environ["GITHUB_STEP_SUMMARY"]
content = ""
@ -191,25 +184,12 @@ 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 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
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 package in pkg_resources.working_set:
f.writelines(f"`{package.key}/{package.version}`\n")
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
id: create-pfx

1
.gitignore vendored
View File

@ -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

View File

@ -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 : "<H1>Conan Installs</H1>", 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 : "<H1>Python Installs</H1>", version : '' });
for (var n in python_installs)
{
developerInfo.append({ name : python_installs[n][0], version : python_installs[n][1] });
}
}
}

View File

@ -1,6 +1,8 @@
# Copyright (c) 2022 UltiMaker
# 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 }}"
@ -12,3 +14,6 @@ CuraCloudAccountAPIRoot = "{{ cura_cloud_account_api_root }}"
CuraMarketplaceRoot = "{{ cura_marketplace_root }}"
CuraDigitalFactoryURL = "{{ cura_digital_factory_url }}"
CuraLatestURL = "{{ cura_latest_url }}"
ConanInstalls = {{ conan_installs }}
PythonInstalls = {package.key: {'version': package.version} for package in working_set}

View File

@ -86,6 +86,7 @@ pyinstaller:
hiddenimports:
- "pySavitar"
- "pyArcus"
- "pyDulcificum"
- "pynest2d"
- "PyQt6"
- "PyQt6.QtNetwork"

View File

@ -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):
@ -65,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")
@ -137,36 +141,16 @@ 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())
conan_installs = []
python_installs = []
# list of conan installs
for _, dependency in self.dependencies.host.items():
conan_installs.append([dependency.ref.name,dependency.ref.version])
#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")
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
))
def _conan_installs(self):
conan_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
def _generate_cura_version(self, location):
with open(os.path.join(self.recipe_folder, "CuraVersion.py.jinja"), "r") as f:
@ -192,11 +176,13 @@ 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"]))
cura_latest_url=self.conan_data["urls"][self._urls]["cura_latest_url"],
conan_installs=self._conan_installs(),
))
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")), ".")]
datas = []
for data in pyinstaller_metadata["datas"].values():
if not self.options.internal and data.get("internal", False):
continue
@ -289,10 +275,15 @@ 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
self.options["pynest2d"].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":
@ -305,9 +296,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/testing")
self.requires("curaengine/(latest)@ultimaker/testing")
self.requires("pysavitar/5.3.0")
self.requires("pynest2d/5.3.0")
@ -316,16 +309,14 @@ 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)@internal/testing")
self.requires("cura_private_data/(latest)@internal/cura_10561")
self.requires("fdm_materials/(latest)@internal/testing")
else:
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 = "."
@ -346,7 +337,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
@ -393,26 +383,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)
@ -451,7 +439,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,

View File

@ -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 = {}

View File

@ -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
@ -269,6 +270,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
@ -363,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
@ -851,11 +859,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 +2135,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

View File

@ -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,87 @@ class Snapshot:
return min_x, max_x, min_y, max_y
@staticmethod
def isometricSnapshot(width: int = 300, height: int = 300) -> Optional[QImage]:
"""Create an isometric snapshot of the scene."""
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()
bounds = Snapshot.nodeBounds(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
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(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)
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 nodeBounds(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
@ -55,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.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")

View File

@ -0,0 +1,304 @@
# 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
import pyDulcificum as du
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
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)
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},
{"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",
}
_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",
"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",
"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
@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.isometricSnapshot(width, height)
except:
Logger.logException("w", "Failed to create snapshot image")
return
thumbnail_buffer = QBuffer()
thumbnail_buffer.open(QBuffer.OpenModeFlag.WriteOnly)
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 always available since it is in the "required" list of plugins.
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)
# 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 = []
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))
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)
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.definition.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 = 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["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()
meta["commanded_duration_s"] = int(print_information.currentPrintTime)
meta["duration_s"] = int(print_information.currentPrintTime)
material_lengths = list(map(meterToMillimeter, 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 = []
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
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:
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,
}
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"]
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["pyDulcificum_version"] = du.__version__
# Add engine plugin information to the metadata
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 meterToMillimeter(value: float) -> float:
"""Converts a value in meters to millimeters."""
return value * 1000.0

View File

@ -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(),
}

View File

@ -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"
}

View File

@ -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",

View File

@ -1,19 +1,24 @@
// 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
{
readonly property UM.I18nCatalog catalog: UM.I18nCatalog { name: "cura" }
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,191 +26,255 @@ 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
UM.I18nCatalog{id: catalog; name: "cura"}
MouseArea
{
anchors.fill: parent
onClicked:
{
projectsList.visible = !projectsList.visible;
projectBuildInfoList.visible = !projectBuildInfoList.visible;
}
}
anchors.centerIn: parent
}
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("default_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 (url !== "") {
return `<a href="${url}">${name}</a>`;
} else {
return name;
}
}
visible: text !== ""
Layout.fillWidth: true
Layout.preferredWidth: 1
onLinkActivated: Qt.openUrlExternally(url)
}
delegate: Row
UM.Label
{
spacing: UM.Theme.getSize("narrow_margin").width
text: description
visible: text !== ""
Layout.fillWidth: true
Layout.preferredWidth: 2
}
UM.Label
{
text: license
visible: text !== ""
Layout.fillWidth: true
Layout.preferredWidth: 1
}
UM.Label
{
text: version
visible: text !== ""
Layout.fillWidth: true
Layout.preferredWidth: 1
}
}
Flickable
{
anchors.fill: parent
ScrollBar.vertical: UM.ScrollBar {
visible: contentHeight > height
}
contentHeight: content.height
clip: true
Column
{
id: content
spacing: UM.Theme.getSize("default_margin").height
width: parent.width
UM.Label
{
text: "<a href='%1' title='%2'>%2</a>".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
property string 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/" });
}
}
}
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(([name, { version }]) => ({ name, version }))
delegate: Loader
{
sourceComponent: dependency_row
width: parent.width
property string name: modelData.name
property string version: modelData.version
property string license: ""
property string url: ""
property string description: ""
}
}
}
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
property string license: ""
property string url: ""
property string description: ""
}
width: parent.width
model: Object
.entries(CuraApplication.pythonInstalls)
.map(([name, { version }]) => ({ name, 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
{
//: Close about dialog button
id: closeButton
text: catalog.i18nc("@action:button", "Close")
onClicked: reject()