Merge branch 'PP-245-high-speed-profiles' of github.com:Ultimaker/Cura into CURA-10953_introduce_any_color_material

This commit is contained in:
Jaime van Kessel 2023-09-07 15:03:27 +02:00
commit e899b9987c
No known key found for this signature in database
GPG key ID: C85F7A3AF1BAA7C4
236 changed files with 9064 additions and 1321 deletions

View file

@ -119,18 +119,12 @@ jobs:
sudo apt upgrade
sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config flex bison -y
- name: Install GCC-12 on ubuntu-22.04
if: ${{ startsWith(inputs.runs_on, 'ubuntu-22.04') }}
- name: Install GCC-132 on ubuntu
if: ${{ startsWith(inputs.runs_on, 'ubuntu') }}
run: |
sudo apt install g++-12 gcc-12 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
- name: Use GCC-10 on ubuntu-20.04
if: ${{ startsWith(inputs.runs_on, 'ubuntu-20.04') }}
run: |
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 10
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 10
sudo apt install g++-13 gcc-13 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13
- name: Create the default Conan profile
run: conan profile new default --detect
@ -140,14 +134,15 @@ jobs:
run: conan config install https://github.com/Ultimaker/conan-config.git -a "-b ${{ inputs.conan_config_branch }}"
- name: Get Conan configuration
if: ${{ inputs.conan_config_branch == '' }}
run: conan config install https://github.com/Ultimaker/conan-config.git
run: |
conan config install https://github.com/Ultimaker/conan-config.git
conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}"
- name: Add Cura private Artifactory remote
run: conan remote add cura-private https://ultimaker.jfrog.io/artifactory/api/conan/cura-private True
- name: Create the Packages
run: conan install ${{ inputs.recipe_id_full }} --build=missing --update
run: conan install ${{ inputs.recipe_id_full }} --build=missing --update -c tools.build:skip_test=True
- name: Upload the Package(s)
if: ${{ always() && inputs.conan_upload_community }}

View file

@ -57,7 +57,7 @@ env:
CONAN_LOGGING_LEVEL: ${{ inputs.conan_logging_level }}
CONAN_NON_INTERACTIVE: 1
permissions: {}
permissions: { }
jobs:
conan-recipe-version:
permissions:
@ -103,18 +103,23 @@ jobs:
sudo apt update
sudo apt upgrade
sudo apt install efibootmgr build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config flex bison g++-12 gcc-12 -y
sudo apt install g++-12 gcc-12 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
- name: Install GCC-13
run: |
sudo apt install g++-13 gcc-13 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13
- name: Create the default Conan profile
run: conan profile new default --detect --force
- name: Get Conan configuration
run: conan config install https://github.com/Ultimaker/conan-config.git
run: |
conan config install https://github.com/Ultimaker/conan-config.git
conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}"
- name: Create the Packages
run: conan create . ${{ needs.conan-recipe-version.outputs.recipe_id_full }} --build=missing --update -o ${{ needs.conan-recipe-version.outputs.project_name }}:devtools=True
run: conan create . ${{ needs.conan-recipe-version.outputs.recipe_id_full }} --build=missing --update -o ${{ needs.conan-recipe-version.outputs.project_name }}:devtools=True -c tools.build:skip_test=True
- name: Create the latest alias
if: always()

View file

@ -75,15 +75,16 @@ jobs:
run: conan config install https://github.com/Ultimaker/conan-config.git -a "-b ${{ inputs.conan_config_branch }}"
- name: Get Conan configuration
if: ${{ inputs.conan_config_branch == '' }}
run: conan config install https://github.com/Ultimaker/conan-config.git
run: |
conan config install https://github.com/Ultimaker/conan-config.git
conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}"
- name: Add Cura private Artifactory remote
run: conan remote add cura-private https://ultimaker.jfrog.io/artifactory/api/conan/cura-private True
- name: Export the Package (binaries)
if: ${{ inputs.conan_export_binaries }}
run: conan create . ${{ inputs.recipe_id_full }} --build=missing --update
run: conan create . ${{ inputs.recipe_id_full }} --build=missing --update -c tools.build:skip_test=True
- name: Export the Package
if: ${{ !inputs.conan_export_binaries }}

View file

@ -1,151 +0,0 @@
name: Cura All Installers
run-name: ${{ inputs.cura_conan_version }} for exe ${{ inputs.build_windows_exe }}, msi ${{ inputs.build_windows_msi }}, dmg ${{ inputs.build_macos }}, pkg ${{ inputs.build_macos_installer }}, appimage ${{ inputs.build_linux }} - enterprise ${{ inputs.enterprise }}
on:
workflow_dispatch:
inputs:
cura_conan_version:
description: 'Cura Conan Version'
default: 'cura/latest@ultimaker/testing'
required: true
type: string
conan_args:
description: 'Conan args: eq.: --require-override'
default: ''
required: false
type: string
conan_config:
description: 'Conan config branch to use'
default: ''
required: false
type: string
enterprise:
description: 'Build Cura as an Enterprise edition'
default: false
required: true
type: boolean
staging:
description: 'Use staging API'
default: false
required: true
type: boolean
installer:
description: 'Create the installer'
default: true
required: true
type: boolean
build_windows_exe:
description: 'Build for Windows exe'
default: false
required: true
type: boolean
build_windows_msi:
description: 'Build for msi+pkg'
default: true
required: true
type: boolean
build_linux:
description: 'Build for Linux'
default: true
required: true
type: boolean
build_macos:
description: 'Build dmg for MacOS'
default: true
required: true
type: boolean
# Run the nightly at 3:25 UTC on working days
schedule:
- cron: '25 3 * * 1-5'
jobs:
windows-installer-create-exe:
if: ${{ inputs.build_windows_exe }}
uses: ./.github/workflows/cura-installer.yml
with:
platform: 'windows-2022'
os_name: 'win64'
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
conan_config: ${{ inputs.conan_config }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
installer: ${{ inputs.installer }}
msi_installer: false
secrets: inherit
windows-installer-create-msi:
if: ${{ inputs.build_windows_msi }}
uses: ./.github/workflows/cura-installer.yml
with:
platform: 'windows-2022'
os_name: 'win64'
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
conan_config: ${{ inputs.conan_config }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
installer: ${{ inputs.installer }}
msi_installer: true
secrets: inherit
linux-installer-create:
if: ${{ inputs.build_linux }}
uses: ./.github/workflows/cura-installer.yml
with:
platform: 'ubuntu-20.04'
os_name: 'linux'
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
conan_config: ${{ inputs.conan_config }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
installer: ${{ inputs.installer }}
msi_installer: false
secrets: inherit
linux-modern-installer-create:
if: ${{ inputs.build_linux }}
uses: ./.github/workflows/cura-installer.yml
with:
platform: 'ubuntu-22.04'
os_name: 'linux-modern'
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
conan_config: ${{ inputs.conan_config }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
installer: ${{ inputs.installer }}
msi_installer: false
secrets: inherit
macos-dmg-create:
if: ${{ inputs.build_macos }}
uses: ./.github/workflows/cura-installer.yml
with:
platform: 'macos-11'
os_name: 'mac'
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
conan_config: ${{ inputs.conan_config }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
installer: ${{ inputs.installer }}
msi_installer: false
secrets: inherit
macos-installer-create:
if: ${{ inputs.build_macos }}
uses: ./.github/workflows/cura-installer.yml
with:
platform: 'macos-11'
os_name: 'mac'
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
conan_config: ${{ inputs.conan_config }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
installer: ${{ inputs.installer }}
msi_installer: true
secrets: inherit

View file

@ -1,390 +0,0 @@
name: Cura Installer
run-name: ${{ inputs.cura_conan_version }} for ${{ inputs.platform }} by @${{ github.actor }}
on:
workflow_call:
inputs:
platform:
description: 'Selected Installer OS'
default: 'ubuntu-20.04'
required: true
type: string
os_name:
description: 'OS Friendly Name'
default: 'linux'
required: true
type: string
cura_conan_version:
description: 'Cura Conan Version'
default: 'cura/latest@ultimaker/testing'
required: true
type: string
conan_args:
description: 'Conan args: eq.: --require-override'
default: ''
required: false
type: string
conan_config:
description: 'Conan config branch to use'
default: ''
required: false
type: string
enterprise:
description: 'Build Cura as an Enterprise edition'
default: false
required: true
type: boolean
staging:
description: 'Use staging API'
default: false
required: true
type: boolean
installer:
description: 'Create the installer'
default: true
required: true
type: boolean
msi_installer:
description: 'Create the msi'
default: false
required: true
type: boolean
env:
CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }}
CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }}
CONAN_LOGIN_USERNAME_CURA_CE: ${{ secrets.CONAN_USER }}
CONAN_PASSWORD_CURA_CE: ${{ secrets.CONAN_PASS }}
CONAN_LOG_RUN_TO_OUTPUT: 1
CONAN_LOGGING_LEVEL: ${{ inputs.conan_logging_level }}
CONAN_NON_INTERACTIVE: 1
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
MAC_NOTARIZE_USER: ${{ secrets.MAC_NOTARIZE_USER }}
MAC_NOTARIZE_PASS: ${{ secrets.MAC_NOTARIZE_PASS }}
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
MACOS_CERT_INSTALLER_P12: ${{ secrets.MACOS_CERT_INSTALLER_P12 }}
MACOS_CERT_USER: ${{ secrets.MACOS_CERT_USER }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
MACOS_CERT_PASSPHRASE: ${{ secrets.MACOS_CERT_PASSPHRASE }}
WIN_CERT_INSTALLER_CER: ${{ secrets.WIN_CERT_INSTALLER_CER }}
WIN_CERT_INSTALLER_CER_PASS: ${{ secrets.WIN_CERT_INSTALLER_CER_PASS }}
CURA_CONAN_VERSION: ${{ inputs.cura_conan_version }}
ENTERPRISE: ${{ inputs.enterprise }}
STAGING: ${{ inputs.staging }}
jobs:
cura-installer-create:
runs-on: ${{ inputs.platform }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Python and pip
uses: actions/setup-python@v4
with:
python-version: '3.10.x'
cache: 'pip'
cache-dependency-path: .github/workflows/requirements-conan-package.txt
- name: Install Python requirements for runner
run: pip install -r https://raw.githubusercontent.com/Ultimaker/Cura/main/.github/workflows/requirements-conan-package.txt
# Note the runner requirements are always installed from the main branch in the Ultimaker/Cura repo
- name: Use Conan download cache (Bash)
if: ${{ runner.os != 'Windows' }}
run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache"
- name: Use Conan download cache (Powershell)
if: ${{ runner.os == 'Windows' }}
run: conan config set storage.download_cache="C:\Users\runneradmin\.conan\conan_download_cache"
- name: Cache Conan local repository packages (Bash)
uses: actions/cache@v3
if: ${{ runner.os != 'Windows' }}
with:
path: |
$HOME/.conan/data
$HOME/.conan/conan_download_cache
key: conan-${{ runner.os }}-${{ runner.arch }}-installer-cache
- name: Cache Conan local repository packages (Powershell)
uses: actions/cache@v3
if: ${{ runner.os == 'Windows' }}
with:
path: |
C:\Users\runneradmin\.conan\data
C:\.conan
C:\Users\runneradmin\.conan\conan_download_cache
key: conan-${{ runner.os }}-${{ runner.arch }}-installer-cache
- name: Install MacOS system requirements
if: ${{ runner.os == 'Macos' }}
run: brew install autoconf automake ninja create-dmg # Delete create-dmg when deprecating dmg
- name: Hack needed specifically for ubuntu-22.04 from mid-Feb 2023 onwards
if: ${{ runner.os == 'Linux' && startsWith(inputs.platform, 'ubuntu-22.04') }}
run: sudo apt remove libodbc2 libodbcinst2 unixodbc-common -y
# NOTE: Due to what are probably github issues, we have to remove the cache and reconfigure before the rest.
# This is maybe because grub caches the disk it uses last time, which is recreated each time.
- name: Install Linux system requirements
if: ${{ runner.os == 'Linux' }}
run: |
sudo rm /var/cache/debconf/config.dat
sudo dpkg --configure -a
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
sudo apt update
sudo apt upgrade
sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config -y
wget --no-check-certificate --quiet https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O $GITHUB_WORKSPACE/appimagetool
chmod +x $GITHUB_WORKSPACE/appimagetool
echo "APPIMAGETOOL_LOCATION=$GITHUB_WORKSPACE/appimagetool" >> $GITHUB_ENV
- name: Install GCC-12 on ubuntu-22.04
if: ${{ startsWith(inputs.platform, 'ubuntu-22.04') }}
run: |
sudo apt install g++-12 gcc-12 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
- name: Use GCC-10 on ubuntu-20.04
if: ${{ startsWith(inputs.platform, 'ubuntu-20.04') }}
run: |
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 10
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 10
- name: Create the default Conan profile
run: conan profile new default --detect
- name: Configure GPG Key Linux (Bash)
if: ${{ runner.os == 'Linux' }}
run: echo -n "$GPG_PRIVATE_KEY" | base64 --decode | gpg --import
- name: Configure Macos keychain Developer Cert(Bash)
id: macos-keychain-developer-cert
if: ${{ runner.os == 'Macos' }}
uses: apple-actions/import-codesign-certs@v1
with:
keychain-password: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
p12-file-base64: ${{ secrets.MACOS_CERT_P12 }}
p12-password: ${{ secrets.MACOS_CERT_PASSPHRASE }}
- name: Configure Macos keychain Installer Cert (Bash)
id: macos-keychain-installer-cert
if: ${{ runner.os == 'Macos' }}
uses: apple-actions/import-codesign-certs@v1
with:
keychain-password: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
create-keychain: false # keychain is created in previous use of action.
p12-file-base64: ${{ secrets.MACOS_CERT_INSTALLER_P12 }}
p12-password: ${{ secrets.MACOS_CERT_PASSPHRASE }}
- name: Create PFX certificate from BASE64_PFX_CONTENT secret
if: ${{ runner.os == 'Windows' }}
id: create-pfx
env:
PFX_CONTENT: ${{ secrets.WIN_CERT_INSTALLER_CER }}
run: |
$pfxPath = Join-Path -Path $env:RUNNER_TEMP -ChildPath "cert.pfx";
$encodedBytes = [System.Convert]::FromBase64String($env:PFX_CONTENT);
Set-Content $pfxPath -Value $encodedBytes -AsByteStream;
echo "PFX_PATH=$pfxPath" >> $env:GITHUB_OUTPUT;
- name: Get Conan configuration from branch
if: ${{ inputs.conan_config != '' }}
run: conan config install https://github.com/Ultimaker/conan-config.git -a "-b ${{ inputs.conan_config }}"
- name: Get Conan configuration
if: ${{ inputs.conan_config == '' }}
run: conan config install https://github.com/Ultimaker/conan-config.git
- name: Create the Packages (Bash)
if: ${{ runner.os != 'Windows' }}
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"
- name: Create the Packages (Powershell)
if: ${{ runner.os == 'Windows' }}
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"
- name: Set Environment variables for Cura (bash)
if: ${{ runner.os != 'Windows' }}
run: |
. ./cura_inst/bin/activate_github_actions_env.sh
. ./cura_inst/bin/activate_github_actions_version_env.sh
- name: Set Environment variables for Cura (Powershell)
if: ${{ runner.os == 'Windows' }}
run: |
echo "${Env:WIX}\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
.\cura_inst\Scripts\activate_github_actions_env.ps1
.\cura_inst\Scripts\activate_github_actions_version_env.ps1
- name: Unlock Macos keychain (Bash)
if: ${{ runner.os == 'Macos' }}
run: security unlock -p $TEMP_KEYCHAIN_PASSWORD signing_temp.keychain
env:
TEMP_KEYCHAIN_PASSWORD: ${{ steps.macos-keychain-developer-cert.outputs.keychain-password }}
# FIXME: This is a workaround to ensure that we use and pack a shared library for OpenSSL 1.1.1l. We currently compile
# OpenSSL statically for CPython, but our Python Dependenies (such as PyQt6) require a shared library.
# Because Conan won't allow for building the same library with two different options (easily) we need to install it explicitly
# and do a manual copy to the VirtualEnv, such that Pyinstaller can find it.
- name: Install OpenSSL shared
run: conan install openssl/1.1.1l@_/_ --build=missing --update -o openssl:shared=True -g deploy
- name: Copy OpenSSL shared (Bash)
if: ${{ runner.os != 'Windows' }}
run: |
cp ./openssl/lib/*.so* ./cura_inst/bin/ || true
cp ./openssl/lib/*.dylib* ./cura_inst/bin/ || true
- name: Copy OpenSSL shared (Powershell)
if: ${{ runner.os == 'Windows' }}
run: |
cp openssl/bin/*.dll ./cura_inst/Scripts/
cp openssl/lib/*.lib ./cura_inst/Lib/
- name: Create the Cura dist
run: pyinstaller ./cura_inst/UltiMaker-Cura.spec
- name: Output the name file name and extension
id: filename
shell: python
run: |
import os
enterprise = "-Enterprise" if "${{ inputs.enterprise }}" == "true" else ""
installer_filename = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-${{ inputs.os_name }}"
if "${{ runner.os }}" == "Windows":
installer_ext = "msi" if "${{ inputs.msi_installer }}" == "true" else "exe"
elif "${{ runner.os }}" == "macOS":
installer_ext = "pkg" if "${{ inputs.msi_installer }}" == "true" else "dmg"
else:
installer_ext = "AppImage"
output_env = os.environ["GITHUB_OUTPUT"]
content = ""
if os.path.exists(output_env):
with open(output_env, "r") as f:
content = f.read()
with open(output_env, "w") as f:
f.write(content)
f.writelines(f"INSTALLER_FILENAME={installer_filename}\n")
f.writelines(f"INSTALLER_EXT={installer_ext}\n")
f.writelines(f"FULL_INSTALLER_FILENAME={installer_filename}.{installer_ext}\n")
- name: Summarize the used Conan 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"]])
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("# ${{ steps.filename.outputs.FULL_INSTALLER_FILENAME }}\n")
f.writelines("## Conan packages:\n")
for dep in sorted_deps:
f.writelines(f"`{dep}`\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")
- name: Archive the artifacts (bash)
if: ${{ !inputs.installer && runner.os != 'Windows' }}
run: tar -zcf "./${{ steps.filename.outputs.INSTALLER_FILENAME }}.tar.gz" "./UltiMaker-Cura/"
working-directory: dist
- name: Archive the artifacts (Powershell)
if: ${{ !inputs.installer && runner.os == 'Windows' }}
run: Compress-Archive -Path ".\UltiMaker-Cura" -DestinationPath ".\${{ steps.filename.outputs.INSTALLER_FILENAME }}.zip"
working-directory: dist
- name: Create the Windows exe installer (Powershell)
if: ${{ inputs.installer && runner.os == 'Windows' && !inputs.msi_installer }}
run: |
python ..\cura_inst\packaging\NSIS\create_windows_installer.py ../cura_inst . "${{ steps.filename.outputs.FULL_INSTALLER_FILENAME }}"
working-directory: dist
- name: Create the Windows msi installer (Powershell)
if: ${{ inputs.installer && runner.os == 'Windows' && inputs.msi_installer }}
run: |
python ..\cura_inst\packaging\msi\create_windows_msi.py ..\cura_inst .\UltiMaker-Cura "${{ steps.filename.outputs.FULL_INSTALLER_FILENAME }}" "$Env:CURA_APP_NAME"
working-directory: dist
- name: Sign the Windows exe installer (Powershell)
if: ${{ inputs.installer && runner.os == 'Windows' && !inputs.msi_installer }}
env:
PFX_PATH: ${{ steps.create-pfx.outputs.PFX_PATH }}
run: |
& "C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe" sign /f $Env:PFX_PATH /p "$Env:WIN_CERT_INSTALLER_CER_PASS" /fd SHA256 /t http://timestamp.digicert.com "${{ steps.filename.outputs.FULL_INSTALLER_FILENAME }}"
working-directory: dist
- name: Sign the Windows msi installer (Powershell)
if: ${{ inputs.installer && runner.os == 'Windows' && inputs.msi_installer }}
env:
PFX_PATH: ${{ steps.create-pfx.outputs.PFX_PATH }}
run: |
& "C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe" sign /f $Env:PFX_PATH /p "$Env:WIN_CERT_INSTALLER_CER_PASS" /fd SHA256 /t http://timestamp.digicert.com "${{ steps.filename.outputs.FULL_INSTALLER_FILENAME }}"
working-directory: dist
- name: Create the Linux AppImage (Bash)
if: ${{ inputs.installer && runner.os == 'Linux' }}
run: python ../cura_inst/packaging/AppImage/create_appimage.py ./UltiMaker-Cura $CURA_VERSION_FULL "${{ steps.filename.outputs.FULL_INSTALLER_FILENAME }}"
working-directory: dist
- name: Create the MacOS dmg and/or pkg (Bash)
if: ${{ github.event.inputs.installer == 'true' && runner.os == 'Macos' }}
run: python ../cura_inst/packaging/MacOS/build_macos.py ../cura_inst . $CURA_CONAN_VERSION "${{ steps.filename.outputs.FULL_INSTALLER_FILENAME }}" "$CURA_APP_NAME"
working-directory: dist
- name: Upload the artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ steps.filename.outputs.INSTALLER_FILENAME }}-${{ steps.filename.outputs.INSTALLER_EXT }}
path: |
dist/*.tar.gz
dist/*.zip
dist/${{ steps.filename.outputs.FULL_INSTALLER_FILENAME }}
dist/*.asc
retention-days: 5
notify-export:
if: ${{ always() }}
needs: [ cura-installer-create ]
uses: ultimaker/cura/.github/workflows/notify.yml@main
with:
success: ${{ contains(join(needs.*.result, ','), 'success') }}
success_title: "Create the Cura distributions"
success_body: "Installers for ${{ inputs.cura_conan_version }}"
failure_title: "Failed to create the Cura distributions"
failure_body: "Failed to create at least 1 installer for ${{ inputs.cura_conan_version }}"
secrets: inherit

82
.github/workflows/installers.yml vendored Normal file
View file

@ -0,0 +1,82 @@
name: All installers
run-name: ${{ inputs.cura_conan_version }} by @${{ github.actor }}
on:
workflow_dispatch:
inputs:
cura_conan_version:
description: 'Cura Conan Version'
default: 'cura/latest@ultimaker/testing'
required: true
type: string
conan_args:
description: 'Conan args: eq.: --require-override'
default: ''
required: false
type: string
enterprise:
description: 'Build Cura as an Enterprise edition'
default: false
required: true
type: boolean
staging:
description: 'Use staging API'
default: false
required: true
type: boolean
jobs:
windows-installer:
uses: ./.github/workflows/windows.yml
with:
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
architecture: X64
operating_system: windows-2022
secrets: inherit
linux-modern-installer:
uses: ./.github/workflows/linux.yml
with:
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
architecture: X64
operating_system: ubuntu-22.04
secrets: inherit
linux-legacy-installer:
uses: ./.github/workflows/linux.yml
with:
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
architecture: X64
operating_system: ubuntu-20.04
secrets: inherit
macos-installer:
uses: ./.github/workflows/macos.yml
with:
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
architecture: X64
operating_system: macos-11.0
secrets: inherit
macos-arm-installer:
uses: ./.github/workflows/macos.yml
with:
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
architecture: ARM64
operating_system: self-hosted
secrets: inherit

264
.github/workflows/linux.yml vendored Normal file
View file

@ -0,0 +1,264 @@
name: Linux Installer
run-name: ${{ inputs.cura_conan_version }} for Linux-${{ inputs.architecture }} by @${{ github.actor }}
on:
workflow_dispatch:
inputs:
cura_conan_version:
description: 'Cura Conan Version'
default: 'cura/latest@ultimaker/testing'
required: true
type: string
conan_args:
description: 'Conan args: eq.: --require-override'
default: ''
required: false
type: string
enterprise:
description: 'Build Cura as an Enterprise edition'
default: false
required: true
type: boolean
staging:
description: 'Use staging API'
default: false
required: true
type: boolean
architecture:
description: 'Architecture'
required: true
default: 'X64'
type: choice
options:
- X64
operating_system:
description: 'OS'
required: true
default: 'ubuntu-22.04'
type: choice
options:
- ubuntu-22.04
- ubuntu-20.04
workflow_call:
inputs:
cura_conan_version:
description: 'Cura Conan Version'
default: 'cura/latest@ultimaker/testing'
required: true
type: string
conan_args:
description: 'Conan args: eq.: --require-override'
default: ''
required: false
type: string
enterprise:
description: 'Build Cura as an Enterprise edition'
default: false
required: true
type: boolean
staging:
description: 'Use staging API'
default: false
required: true
type: boolean
architecture:
description: 'Architecture'
required: true
default: 'X64'
type: string
operating_system:
description: 'OS'
required: true
default: 'ubuntu-22.04'
type: string
env:
CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }}
CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
CURA_CONAN_VERSION: ${{ inputs.cura_conan_version }}
ENTERPRISE: ${{ inputs.enterprise }}
STAGING: ${{ inputs.staging }}
jobs:
cura-installer-create:
runs-on: ${{ inputs.operating_system }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Python and pip
uses: actions/setup-python@v4
with:
python-version: '3.10.x'
cache: 'pip'
cache-dependency-path: .github/workflows/requirements-conan-package.txt
- name: Install Python requirements for runner
run: pip install -r .github/workflows/requirements-conan-package.txt
- name: Cache Conan local repository packages (Bash)
uses: actions/cache@v3
with:
path: |
$HOME/.conan/data
$HOME/.conan/conan_download_cache
key: conan-${{ runner.os }}-${{ runner.arch }}-installer-cache
- name: Hack needed specifically for ubuntu-22.04 from mid-Feb 2023 onwards
if: ${{ startsWith(inputs.operating_system, 'ubuntu-22.04') }}
run: sudo apt remove libodbc2 libodbcinst2 unixodbc-common -y
# NOTE: Due to what are probably github issues, we have to remove the cache and reconfigure before the rest.
# This is maybe because grub caches the disk it uses last time, which is recreated each time.
- name: Install Linux system requirements
run: |
sudo rm /var/cache/debconf/config.dat
sudo dpkg --configure -a
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
sudo apt update
sudo apt upgrade
sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config -y
wget --no-check-certificate --quiet https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O $GITHUB_WORKSPACE/appimagetool
chmod +x $GITHUB_WORKSPACE/appimagetool
echo "APPIMAGETOOL_LOCATION=$GITHUB_WORKSPACE/appimagetool" >> $GITHUB_ENV
- name: Install GCC-13
run: |
sudo apt install g++-13 gcc-13 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13
- name: Create the default Conan profile
run: conan profile new default --detect --force
- name: Configure GPG Key Linux (Bash)
run: echo -n "$GPG_PRIVATE_KEY" | base64 --decode | gpg --import
- name: Get Conan configuration
run: |
conan config install https://github.com/Ultimaker/conan-config.git
conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}"
- name: Use Conan download cache (Bash)
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"
- name: Upload the Package(s)
if: always()
run: |
conan upload "*" -r cura --all -c
- name: Set Environment variables for Cura (bash)
run: |
. ./cura_inst/bin/activate_github_actions_env.sh
. ./cura_inst/bin/activate_github_actions_version_env.sh
# FIXME: This is a workaround to ensure that we use and pack a shared library for OpenSSL 1.1.1l. We currently compile
# OpenSSL statically for CPython, but our Python Dependenies (such as PyQt6) require a shared library.
# Because Conan won't allow for building the same library with two different options (easily) we need to install it explicitly
# and do a manual copy to the VirtualEnv, such that Pyinstaller can find it.
- name: Install OpenSSL shared
run: conan install openssl/1.1.1l@_/_ --build=missing --update -o openssl:shared=True -g deploy
- name: Copy OpenSSL shared (Bash)
run: |
cp ./openssl/lib/*.so* ./cura_inst/bin/ || true
cp ./openssl/lib/*.dylib* ./cura_inst/bin/ || true
- name: Create the Cura dist
run: pyinstaller ./cura_inst/UltiMaker-Cura.spec
- name: Output the name file name and extension
id: filename
shell: python
run: |
import os
enterprise = "-Enterprise" if "${{ inputs.enterprise }}" == "true" else ""
if "${{ inputs.operating_system }}" == "ubuntu-22.04":
installer_filename = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-linux-modern-${{ inputs.architecture }}"
else:
installer_filename = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-linux-${{ inputs.architecture }}"
output_env = os.environ["GITHUB_OUTPUT"]
content = ""
if os.path.exists(output_env):
with open(output_env, "r") as f:
content = f.read()
with open(output_env, "w") as f:
f.write(content)
f.writelines(f"INSTALLER_FILENAME={installer_filename}\n")
- name: Summarize the used Conan 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"]])
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("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n")
f.writelines("## Conan packages:\n")
for dep in sorted_deps:
f.writelines(f"`{dep}`\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")
- name: Create the Linux AppImage (Bash)
run: |
python ../cura_inst/packaging/AppImage/create_appimage.py ./UltiMaker-Cura $CURA_VERSION_FULL "${{ steps.filename.outputs.INSTALLER_FILENAME }}.AppImage"
chmod +x "${{ steps.filename.outputs.INSTALLER_FILENAME }}.AppImage"
working-directory: dist
- name: Upload the AppImage
uses: actions/upload-artifact@v3
with:
name: ${{ steps.filename.outputs.INSTALLER_FILENAME }}-AppImage
path: |
dist/${{ steps.filename.outputs.INSTALLER_FILENAME }}.AppImage
retention-days: 5
notify-export:
if: ${{ always() }}
needs: [ cura-installer-create ]
uses: ultimaker/cura/.github/workflows/notify.yml@main
with:
success: ${{ contains(join(needs.*.result, ','), 'success') }}
success_title: "Create the Cura distributions"
success_body: "Installers for ${{ inputs.cura_conan_version }}"
failure_title: "Failed to create the Cura distributions"
failure_body: "Failed to create at least 1 installer for ${{ inputs.cura_conan_version }}"
secrets: inherit

279
.github/workflows/macos.yml vendored Normal file
View file

@ -0,0 +1,279 @@
name: Macos Installer
run-name: ${{ inputs.cura_conan_version }} for Macos-${{ inputs.architecture }} by @${{ github.actor }}
on:
workflow_dispatch:
inputs:
cura_conan_version:
description: 'Cura Conan Version'
default: 'cura/latest@ultimaker/testing'
required: true
type: string
conan_args:
description: 'Conan args: eq.: --require-override'
default: ''
required: false
type: string
enterprise:
description: 'Build Cura as an Enterprise edition'
default: false
required: true
type: boolean
staging:
description: 'Use staging API'
default: false
required: true
type: boolean
architecture:
description: 'Architecture'
required: true
default: 'X64'
type: choice
options:
- X64
- ARM64
operating_system:
description: 'OS'
required: true
default: 'macos-11'
type: choice
options:
- self-hosted
- macos-11
- macos-12
workflow_call:
inputs:
cura_conan_version:
description: 'Cura Conan Version'
default: 'cura/latest@ultimaker/testing'
required: true
type: string
conan_args:
description: 'Conan args: eq.: --require-override'
default: ''
required: false
type: string
enterprise:
description: 'Build Cura as an Enterprise edition'
default: false
required: true
type: boolean
staging:
description: 'Use staging API'
default: false
required: true
type: boolean
architecture:
description: 'Architecture'
required: true
default: 'X64'
type: string
operating_system:
description: 'OS'
required: true
default: 'macos-11'
type: string
env:
CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }}
CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }}
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
MAC_NOTARIZE_USER: ${{ secrets.MAC_NOTARIZE_USER }}
MAC_NOTARIZE_PASS: ${{ secrets.MAC_NOTARIZE_PASS }}
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
MACOS_CERT_INSTALLER_P12: ${{ secrets.MACOS_CERT_INSTALLER_P12 }}
MACOS_CERT_USER: ${{ secrets.MACOS_CERT_USER }}
MACOS_CERT_PASSPHRASE: ${{ secrets.MACOS_CERT_PASSPHRASE }}
CURA_CONAN_VERSION: ${{ inputs.cura_conan_version }}
ENTERPRISE: ${{ inputs.enterprise }}
STAGING: ${{ inputs.staging }}
jobs:
cura-installer-create:
runs-on: ${{ inputs.operating_system }}
outputs:
INSTALLER_FILENAME: ${{ steps.filename.outputs.INSTALLER_FILENAME }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Python and pip
uses: actions/setup-python@v4
with:
python-version: '3.10.x'
cache: 'pip'
cache-dependency-path: .github/workflows/requirements-conan-package.txt
- name: Install Python requirements for runner
run: pip install -r .github/workflows/requirements-conan-package.txt
- name: Cache Conan local repository packages (Bash)
uses: actions/cache@v3
with:
path: |
$HOME/.conan/data
$HOME/.conan/conan_download_cache
key: conan-${{ runner.os }}-${{ runner.arch }}-installer-cache
- name: Install MacOS system requirements
run: brew install cmake autoconf automake ninja create-dmg
- name: Create the default Conan profile
run: conan profile new default --detect --force
- name: Remove Macos keychain (Bash)
run: security delete-keychain signing_temp.keychain || true
- name: Configure Macos keychain Developer Cert(Bash)
id: macos-keychain-developer-cert
uses: apple-actions/import-codesign-certs@v1
with:
keychain-password: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
p12-file-base64: ${{ secrets.MACOS_CERT_P12 }}
p12-password: ${{ secrets.MACOS_CERT_PASSPHRASE }}
- name: Configure Macos keychain Installer Cert (Bash)
id: macos-keychain-installer-cert
uses: apple-actions/import-codesign-certs@v1
with:
keychain-password: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
create-keychain: false # keychain is created in previous use of action.
p12-file-base64: ${{ secrets.MACOS_CERT_INSTALLER_P12 }}
p12-password: ${{ secrets.MACOS_CERT_PASSPHRASE }}
- name: Get Conan configuration
run: |
conan config install https://github.com/Ultimaker/conan-config.git
conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}"
- name: Use Conan download cache (Bash)
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"
- name: Upload the Package(s)
if: always()
run: |
conan upload "*" -r cura --all -c
- name: Set Environment variables for Cura (bash)
run: |
. ./cura_inst/bin/activate_github_actions_env.sh
. ./cura_inst/bin/activate_github_actions_version_env.sh
- name: Unlock Macos keychain (Bash)
run: security unlock -p $TEMP_KEYCHAIN_PASSWORD signing_temp.keychain
env:
TEMP_KEYCHAIN_PASSWORD: ${{ steps.macos-keychain-developer-cert.outputs.keychain-password }}
# FIXME: This is a workaround to ensure that we use and pack a shared library for OpenSSL 1.1.1l. We currently compile
# OpenSSL statically for CPython, but our Python Dependenies (such as PyQt6) require a shared library.
# Because Conan won't allow for building the same library with two different options (easily) we need to install it explicitly
# and do a manual copy to the VirtualEnv, such that Pyinstaller can find it.
- name: Install OpenSSL shared
run: conan install openssl/1.1.1l@_/_ --build=missing --update -o openssl:shared=True -g deploy
- name: Copy OpenSSL shared (Bash)
run: |
cp ./openssl/lib/*.so* ./cura_inst/bin/ || true
cp ./openssl/lib/*.dylib* ./cura_inst/bin/ || true
- name: Create the Cura dist
run: pyinstaller ./cura_inst/UltiMaker-Cura.spec
- name: Output the name file name and extension
id: filename
shell: python
run: |
import os
enterprise = "-Enterprise" if "${{ inputs.enterprise }}" == "true" else ""
installer_filename = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-macos-${{ inputs.architecture }}"
output_env = os.environ["GITHUB_OUTPUT"]
content = ""
if os.path.exists(output_env):
with open(output_env, "r") as f:
content = f.read()
with open(output_env, "w") as f:
f.write(content)
f.writelines(f"INSTALLER_FILENAME={installer_filename}\n")
- name: Summarize the used Conan 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"]])
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("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n")
f.writelines("## Conan packages:\n")
for dep in sorted_deps:
f.writelines(f"`{dep}`\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")
- 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"
working-directory: dist
- name: Upload the dmg
uses: actions/upload-artifact@v3
with:
name: ${{ steps.filename.outputs.INSTALLER_FILENAME }}-dmg
path: |
dist/${{ steps.filename.outputs.INSTALLER_FILENAME }}.dmg
retention-days: 5
- name: Upload the pkg
uses: actions/upload-artifact@v3
with:
name: ${{ steps.filename.outputs.INSTALLER_FILENAME }}-pkg
path: |
dist/${{ steps.filename.outputs.INSTALLER_FILENAME }}.pkg
retention-days: 5
notify-export:
if: ${{ always() }}
needs: [ cura-installer-create ]
uses: ultimaker/cura/.github/workflows/notify.yml@main
with:
success: ${{ contains(join(needs.*.result, ','), 'success') }}
success_title: "Create the Cura distributions"
success_body: "Installers for ${{ inputs.cura_conan_version }}"
failure_title: "Failed to create the Cura distributions"
failure_body: "Failed to create at least 1 installer for ${{ inputs.cura_conan_version }}"
secrets: inherit

View file

@ -1,2 +1,2 @@
conan==1.56.0
conan>=1.60.2,<2.0.0
sip

View file

@ -115,14 +115,16 @@ jobs:
sudo apt upgrade
sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config -y
- name: Install GCC-12 on ubuntu-22.04
- name: Install GCC-13
run: |
sudo apt install g++-12 gcc-12 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
sudo apt install g++-13 gcc-13 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13
- name: Get Conan configuration
run: conan config install https://github.com/Ultimaker/conan-config.git
run: |
conan config install https://github.com/Ultimaker/conan-config.git
conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}"
- name: Get Conan profile
run: conan profile new default --detect --force

View file

@ -44,7 +44,7 @@ jobs:
- name: Setup Python and pip
uses: actions/setup-python@v4
with:
python-version: 3.10.x
python-version: 3.11.x
cache: pip
cache-dependency-path: .github/workflows/requirements-conan-package.txt
@ -62,15 +62,20 @@ jobs:
sudo apt update
sudo apt upgrade
sudo apt install efibootmgr build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config flex bison g++-12 gcc-12 -y
sudo apt install g++-12 gcc-12 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
- name: Install GCC-13
run: |
sudo apt install g++-13 gcc-13 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13
- name: Create the default Conan profile
run: conan profile new default --detect --force
- name: Get Conan configuration
run: conan config install https://github.com/Ultimaker/conan-config.git
run: |
conan config install https://github.com/Ultimaker/conan-config.git
conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}"
- name: generate the files using Conan install
run: conan install . --build=missing --update -o cura:devtools=True

270
.github/workflows/windows.yml vendored Normal file
View file

@ -0,0 +1,270 @@
name: Windows Installer
run-name: ${{ inputs.cura_conan_version }} for Windows-${{ inputs.architecture }} by @${{ github.actor }}
on:
workflow_dispatch:
inputs:
cura_conan_version:
description: 'Cura Conan Version'
default: 'cura/latest@ultimaker/testing'
required: true
type: string
conan_args:
description: 'Conan args: eq.: --require-override'
default: ''
required: false
type: string
enterprise:
description: 'Build Cura as an Enterprise edition'
default: false
required: true
type: boolean
staging:
description: 'Use staging API'
default: false
required: true
type: boolean
architecture:
description: 'Architecture'
required: true
default: 'X64'
type: choice
options:
- X64
operating_system:
description: 'OS'
required: true
default: 'windows-2022'
type: choice
options:
- windows-2022
workflow_call:
inputs:
cura_conan_version:
description: 'Cura Conan Version'
default: 'cura/latest@ultimaker/testing'
required: true
type: string
conan_args:
description: 'Conan args: eq.: --require-override'
default: ''
required: false
type: string
enterprise:
description: 'Build Cura as an Enterprise edition'
default: false
required: true
type: boolean
staging:
description: 'Use staging API'
default: false
required: true
type: boolean
architecture:
description: 'Architecture'
required: true
default: 'X64'
type: string
operating_system:
description: 'OS'
required: true
default: 'windows-2022'
type: string
env:
CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }}
CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }}
WIN_CERT_INSTALLER_CER: ${{ secrets.WIN_CERT_INSTALLER_CER }}
WIN_CERT_INSTALLER_CER_PASS: ${{ secrets.WIN_CERT_INSTALLER_CER_PASS }}
CURA_CONAN_VERSION: ${{ inputs.cura_conan_version }}
ENTERPRISE: ${{ inputs.enterprise }}
STAGING: ${{ inputs.staging }}
jobs:
cura-installer-create:
runs-on: ${{ inputs.operating_system }}
outputs:
INSTALLER_FILENAME: ${{ steps.filename.outputs.INSTALLER_FILENAME }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Python and pip
uses: actions/setup-python@v4
with:
python-version: '3.10.x'
cache: 'pip'
cache-dependency-path: .github/workflows/requirements-conan-package.txt
- name: Install Python requirements for runner
run: pip install -r .github/workflows/requirements-conan-package.txt
- name: Cache Conan local repository packages (Powershell)
uses: actions/cache@v3
with:
path: |
C:\Users\runneradmin\.conan\data
C:\.conan
C:\Users\runneradmin\.conan\conan_download_cache
key: conan-${{ runner.os }}-${{ runner.arch }}-installer-cache
- name: Create the default Conan profile
run: conan profile new default --detect --force
- name: Get Conan configuration
run: |
conan config install https://github.com/Ultimaker/conan-config.git
conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}"
- name: Use Conan download cache (Powershell)
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"
- name: Upload the Package(s)
if: always()
run: |
conan upload "*" -r cura --all -c
- name: Set Environment variables for Cura (Powershell)
run: |
echo "${Env:WIX}\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
.\cura_inst\Scripts\activate_github_actions_env.ps1
.\cura_inst\Scripts\activate_github_actions_version_env.ps1
- name: Install OpenSSL shared
run: conan install openssl/1.1.1l@_/_ --build=missing --update -o openssl:shared=True -g deploy
- name: Copy OpenSSL shared (Powershell)
run: |
cp openssl/bin/*.dll ./cura_inst/Scripts/
cp openssl/lib/*.lib ./cura_inst/Lib/
- name: Create the Cura dist
run: pyinstaller ./cura_inst/UltiMaker-Cura.spec
- name: Output the name file name and extension
id: filename
shell: python
run: |
import os
enterprise = "-Enterprise" if "${{ inputs.enterprise }}" == "true" else ""
installer_filename = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-win64-${{ inputs.architecture }}"
output_env = os.environ["GITHUB_OUTPUT"]
content = ""
if os.path.exists(output_env):
with open(output_env, "r") as f:
content = f.read()
with open(output_env, "w") as f:
f.write(content)
f.writelines(f"INSTALLER_FILENAME={installer_filename}\n")
- name: Summarize the used Conan 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"]])
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("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n")
f.writelines("## Conan packages:\n")
for dep in sorted_deps:
f.writelines(f"`{dep}`\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")
- name: Create PFX certificate from BASE64_PFX_CONTENT secret
id: create-pfx
env:
PFX_CONTENT: ${{ secrets.WIN_CERT_INSTALLER_CER }}
run: |
$pfxPath = Join-Path -Path $env:RUNNER_TEMP -ChildPath "cert.pfx";
$encodedBytes = [System.Convert]::FromBase64String($env:PFX_CONTENT);
Set-Content $pfxPath -Value $encodedBytes -AsByteStream;
echo "PFX_PATH=$pfxPath" >> $env:GITHUB_OUTPUT;
- name: Create the Windows msi installer (Powershell)
run: |
python ..\cura_inst\packaging\msi\create_windows_msi.py ..\cura_inst .\UltiMaker-Cura "${{steps.filename.outputs.INSTALLER_FILENAME }}.msi" "$Env:CURA_APP_NAME"
working-directory: dist
- name: Sign the Windows msi installer (Powershell)
env:
PFX_PATH: ${{ steps.create-pfx.outputs.PFX_PATH }}
run: |
& "C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe" sign /f $Env:PFX_PATH /p "$Env:WIN_CERT_INSTALLER_CER_PASS" /fd SHA256 /t http://timestamp.digicert.com "${{steps.filename.outputs.INSTALLER_FILENAME }}.msi"
working-directory: dist
- name: Create the Windows exe installer (Powershell)
run: |
python ..\cura_inst\packaging\NSIS\create_windows_installer.py ../cura_inst . "${{steps.filename.outputs.INSTALLER_FILENAME }}.exe"
working-directory: dist
- name: Sign the Windows exe installer (Powershell)
env:
PFX_PATH: ${{ steps.create-pfx.outputs.PFX_PATH }}
run: |
& "C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe" sign /f $Env:PFX_PATH /p "$Env:WIN_CERT_INSTALLER_CER_PASS" /fd SHA256 /t http://timestamp.digicert.com "${{steps.filename.outputs.INSTALLER_FILENAME }}.exe"
working-directory: dist
- name: Upload the msi
uses: actions/upload-artifact@v3
with:
name: ${{steps.filename.outputs.INSTALLER_FILENAME }}-msi
path: |
dist/${{steps.filename.outputs.INSTALLER_FILENAME }}.msi
retention-days: 5
- name: Upload the exe
uses: actions/upload-artifact@v3
with:
name: ${{steps.filename.outputs.INSTALLER_FILENAME }}-exe
path: |
dist/${{steps.filename.outputs.INSTALLER_FILENAME }}.exe
retention-days: 5
notify-export:
if: ${{ always() }}
needs: [ cura-installer-create ]
uses: ultimaker/cura/.github/workflows/notify.yml@main
with:
success: ${{ contains(join(needs.*.result, ','), 'success') }}
success_title: "Create the Cura distributions"
success_body: "Installers for ${{ inputs.cura_conan_version }}"
failure_title: "Failed to create the Cura distributions"
failure_body: "Failed to create at least 1 installer for ${{ inputs.cura_conan_version }}"
secrets: inherit

View file

@ -18,8 +18,8 @@ url: "https://ultimaker.com/software/ultimaker-cura"
repository-code: "https://github.com/Ultimaker/Cura"
license: LGPL-3.0
license-url: "https://github.com/Ultimaker/Cura/blob/main/LICENSE"
version: 5.2.1
date-released: "2022-10-19"
version: 5.4.0
date-released: "2023-07-04"
keywords:
- Ultimaker
- Cura

1
FUNDING.yml Normal file
View file

@ -0,0 +1 @@
github: [ultimaker]

View file

@ -10,7 +10,7 @@ from conan.tools.env import VirtualRunEnv, Environment, VirtualBuildEnv
from conan.tools.scm import Version
from conan.errors import ConanInvalidConfiguration, ConanException
required_conan_version = ">=1.54 <=1.56.0 || >=1.58.0 <2.0.0"
required_conan_version = ">=1.58.0 <2.0.0"
class CuraConan(ConanFile):
@ -293,6 +293,7 @@ class CuraConan(ConanFile):
self.options["pysavitar"].shared = True
self.options["pynest2d"].shared = True
self.options["cpython"].shared = True
self.options["boost"].header_only = True
def validate(self):
version = self.conf_info.get("user.cura:version", default = self.version, check_type = str)
@ -300,10 +301,11 @@ class CuraConan(ConanFile):
raise ConanInvalidConfiguration("Only versions 5+ are support")
def requirements(self):
self.requires("pyarcus/5.2.2")
self.requires("boost/1.82.0")
self.requires("pyarcus/(latest)@ultimaker/cura_10951")
self.requires("curaengine/(latest)@ultimaker/testing")
self.requires("pysavitar/5.2.2")
self.requires("pynest2d/5.2.2")
self.requires("pysavitar/(latest)@ultimaker/cura_10951")
self.requires("pynest2d/(latest)@ultimaker/cura_10951")
self.requires("uranium/(latest)@ultimaker/testing")
self.requires("cura_binary_data/(latest)@ultimaker/testing")
self.requires("cpython/3.10.4")
@ -338,7 +340,38 @@ 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
curaengine = self.dependencies["curaengine"].cpp_info
copy(self, "CuraEngine.exe", curaengine.bindirs[0], self.source_folder, keep_path = False)
copy(self, "CuraEngine", curaengine.bindirs[0], self.source_folder, keep_path = False)
# Copy resources of cura_binary_data
cura_binary_data = self.dependencies["cura_binary_data"].cpp_info
copy(self, "*", cura_binary_data.resdirs[0], str(self._share_dir.joinpath("cura")), keep_path = True)
copy(self, "*", cura_binary_data.resdirs[1], str(self._share_dir.joinpath("uranium")), keep_path = True)
if self.settings.os == "Windows":
copy(self, "*", cura_binary_data.resdirs[2], str(self._share_dir.joinpath("windows")), keep_path = True)
for dependency in self.dependencies.host.values():
for bindir in dependency.cpp_info.bindirs:
copy(self, "*.dll", bindir, str(self._site_packages), keep_path = False)
for libdir in dependency.cpp_info.libdirs:
copy(self, "*.pyd", libdir, str(self._site_packages), keep_path = False)
copy(self, "*.pyi", libdir, str(self._site_packages), keep_path = False)
copy(self, "*.dylib", libdir, str(self._base_dir.joinpath("lib")), keep_path = False)
# Copy materials (flat)
rmdir(self, os.path.join(self.source_folder, "resources", "materials"))
fdm_materials = self.dependencies["fdm_materials"].cpp_info
copy(self, "*", fdm_materials.resdirs[0], self.source_folder)
# Copy internal resources
if self.options.internal:
cura_private_data = self.dependencies["cura_private_data"].cpp_info
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"))
@ -357,8 +390,6 @@ class CuraConan(ConanFile):
pot = self.python_requires["translationextractor"].module.ExtractTranslations(self, cpp_info.bindirs[0])
pot.generate()
self._generate_about_versions(os.path.join(self.source_folder, "resources","qml", "Dialogs"))
def build(self):
if self.options.devtools:
if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type = str):
@ -370,29 +401,6 @@ class CuraConan(ConanFile):
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 imports(self):
self.copy("CuraEngine.exe", root_package = "curaengine", src = "@bindirs", dst = "", keep_path = False)
self.copy("CuraEngine", root_package = "curaengine", src = "@bindirs", dst = "", keep_path = False)
rmdir(self, os.path.join(self.source_folder, "resources", "materials"))
self.copy("*.fdm_material", root_package = "fdm_materials", src = "@resdirs", dst = "resources/materials", keep_path = False)
self.copy("*.sig", root_package = "fdm_materials", src = "@resdirs", dst = "resources/materials", keep_path = False)
if self.options.internal:
self.copy("*", root_package = "cura_private_data", src = self.deps_cpp_info["cura_private_data"].resdirs[0],
dst = self._share_dir.joinpath("cura", "resources"), keep_path = True)
# Copy resources of cura_binary_data
self.copy("*", root_package = "cura_binary_data", src = self.deps_cpp_info["cura_binary_data"].resdirs[0],
dst = self._share_dir.joinpath("cura", "resources"), keep_path = True)
self.copy("*", root_package = "cura_binary_data", src = self.deps_cpp_info["cura_binary_data"].resdirs[1],
dst =self._share_dir.joinpath("uranium", "resources"), keep_path = True)
self.copy("*.dll", src = "@bindirs", dst = self._site_packages)
self.copy("*.pyd", src = "@libdirs", dst = self._site_packages)
self.copy("*.pyi", src = "@libdirs", dst = self._site_packages)
self.copy("*.dylib", src = "@libdirs", dst = self._script_dir)
def deploy(self):
# Copy CuraEngine.exe to bindirs of Virtual Python Environment
curaengine = self.dependencies["curaengine"].cpp_info

View file

@ -8,17 +8,20 @@ from UM.Logger import Logger
from UM.Message import Message
from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog
from cura.Arranging.Nest2DArrange import arrange
from cura.Arranging.GridArrange import GridArrange
from cura.Arranging.Nest2DArrange import Nest2DArrange
i18n_catalog = i18nCatalog("cura")
class ArrangeObjectsJob(Job):
def __init__(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode], min_offset = 8) -> None:
def __init__(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode], min_offset = 8,
*, grid_arrange: bool = False) -> None:
super().__init__()
self._nodes = nodes
self._fixed_nodes = fixed_nodes
self._min_offset = min_offset
self._grid_arrange = grid_arrange
def run(self):
found_solution_for_all = False
@ -29,10 +32,18 @@ class ArrangeObjectsJob(Job):
title = i18n_catalog.i18nc("@info:title", "Finding Location"))
status_message.show()
if self._grid_arrange:
arranger = GridArrange(self._nodes, Application.getInstance().getBuildVolume(), self._fixed_nodes)
else:
arranger = Nest2DArrange(self._nodes, Application.getInstance().getBuildVolume(), self._fixed_nodes,
factor=1000)
found_solution_for_all = False
try:
found_solution_for_all = arrange(self._nodes, Application.getInstance().getBuildVolume(), self._fixed_nodes)
found_solution_for_all = arranger.arrange()
except: # If the thread crashes, the message should still close
Logger.logException("e", "Unable to arrange the objects on the buildplate. The arrange algorithm has crashed.")
Logger.logException("e",
"Unable to arrange the objects on the buildplate. The arrange algorithm has crashed.")
status_message.hide()

View file

@ -0,0 +1,28 @@
from typing import List, TYPE_CHECKING, Optional, Tuple, Set
if TYPE_CHECKING:
from UM.Operations.GroupedOperation import GroupedOperation
class Arranger:
def createGroupOperationForArrange(self, *, add_new_nodes_in_scene: bool = False) -> Tuple["GroupedOperation", int]:
"""
Find placement for a set of scene nodes, but don't actually move them just yet.
:param add_new_nodes_in_scene: Whether to create new scene nodes before applying the transformations and rotations
:return: tuple (found_solution_for_all, node_items)
WHERE
found_solution_for_all: Whether the algorithm found a place on the buildplate for all the objects
node_items: A list of the nodes return by libnest2d, which contain the new positions on the buildplate
"""
raise NotImplementedError
def arrange(self, *, add_new_nodes_in_scene: bool = False) -> bool:
"""
Find placement for a set of scene nodes, and move them by using a single grouped operation.
:param add_new_nodes_in_scene: Whether to create new scene nodes before applying the transformations and rotations
:return: found_solution_for_all: Whether the algorithm found a place on the buildplate for all the objects
"""
grouped_operation, not_fit_count = self.createGroupOperationForArrange(
add_new_nodes_in_scene=add_new_nodes_in_scene)
grouped_operation.push()
return not_fit_count == 0

View file

@ -0,0 +1,331 @@
import math
from typing import List, TYPE_CHECKING, Tuple, Set
if TYPE_CHECKING:
from UM.Scene.SceneNode import SceneNode
from cura.BuildVolume import BuildVolume
from UM.Application import Application
from UM.Math.Vector import Vector
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.TranslateOperation import TranslateOperation
from cura.Arranging.Arranger import Arranger
class GridArrange(Arranger):
def __init__(self, nodes_to_arrange: List["SceneNode"], build_volume: "BuildVolume", fixed_nodes: List["SceneNode"] = None):
if fixed_nodes is None:
fixed_nodes = []
self._nodes_to_arrange = nodes_to_arrange
self._build_volume = build_volume
self._build_volume_bounding_box = build_volume.getBoundingBox()
self._fixed_nodes = fixed_nodes
self._margin_x: float = 1
self._margin_y: float = 1
self._grid_width = 0
self._grid_height = 0
for node in self._nodes_to_arrange:
bounding_box = node.getBoundingBox()
self._grid_width = max(self._grid_width, bounding_box.width)
self._grid_height = max(self._grid_height, bounding_box.depth)
self._grid_width += self._margin_x
self._grid_height += self._margin_y
# Round up the grid size to the nearest cm
grid_precision = 10 # 1cm
self._grid_width = math.ceil(self._grid_width / grid_precision) * grid_precision
self._grid_height = math.ceil(self._grid_height / grid_precision) * grid_precision
self._offset_x = 0
self._offset_y = 0
self._findOptimalGridOffset()
coord_initial_leftover_x = self._build_volume_bounding_box.right + 2 * self._grid_width
coord_initial_leftover_y = (self._build_volume_bounding_box.back + self._build_volume_bounding_box.front) * 0.5
self._initial_leftover_grid_x, self._initial_leftover_grid_y = self._coordSpaceToGridSpace(
coord_initial_leftover_x, coord_initial_leftover_y)
self._initial_leftover_grid_x = math.floor(self._initial_leftover_grid_x)
self._initial_leftover_grid_y = math.floor(self._initial_leftover_grid_y)
# Find grid indexes that intersect with fixed objects
self._fixed_nodes_grid_ids = set()
for node in self._fixed_nodes:
self._fixed_nodes_grid_ids = self._fixed_nodes_grid_ids.union(
self._intersectingGridIdxInclusive(node.getBoundingBox()))
#grid indexes that are in disallowed area
for polygon in self._build_volume.getDisallowedAreas():
self._fixed_nodes_grid_ids = self._fixed_nodes_grid_ids.union(
self._getIntersectingGridIdForPolygon(polygon))
self._build_plate_grid_ids = self._intersectingGridIdxExclusive(self._build_volume_bounding_box)
# Filter out the corner grid squares if the build plate shape is elliptic
if self._build_volume.getShape() == "elliptic":
self._build_plate_grid_ids = set(
filter(lambda grid_id: self._checkGridUnderDiscSpace(grid_id[0], grid_id[1]),
self._build_plate_grid_ids))
self._allowed_grid_idx = self._build_plate_grid_ids.difference(self._fixed_nodes_grid_ids)
def createGroupOperationForArrange(self, *, add_new_nodes_in_scene: bool = False) -> Tuple[GroupedOperation, int]:
# Find the sequence in which items are placed
coord_build_plate_center_x = self._build_volume_bounding_box.width * 0.5 + self._build_volume_bounding_box.left
coord_build_plate_center_y = self._build_volume_bounding_box.depth * 0.5 + self._build_volume_bounding_box.back
grid_build_plate_center_x, grid_build_plate_center_y = self._coordSpaceToGridSpace(coord_build_plate_center_x,
coord_build_plate_center_y)
sequence: List[Tuple[int, int]] = list(self._allowed_grid_idx)
sequence.sort(key=lambda grid_id: (grid_build_plate_center_x - grid_id[0]) ** 2 + (
grid_build_plate_center_y - grid_id[1]) ** 2)
scene_root = Application.getInstance().getController().getScene().getRoot()
grouped_operation = GroupedOperation()
for grid_id, node in zip(sequence, self._nodes_to_arrange):
if add_new_nodes_in_scene:
grouped_operation.addOperation(AddSceneNodeOperation(node, scene_root))
grid_x, grid_y = grid_id
operation = self._moveNodeOnGrid(node, grid_x, grid_y)
grouped_operation.addOperation(operation)
leftover_nodes = self._nodes_to_arrange[len(sequence):]
left_over_grid_y = self._initial_leftover_grid_y
for node in leftover_nodes:
if add_new_nodes_in_scene:
grouped_operation.addOperation(AddSceneNodeOperation(node, scene_root))
# find the first next grid position that isn't occupied by a fixed node
while (self._initial_leftover_grid_x, left_over_grid_y) in self._fixed_nodes_grid_ids:
left_over_grid_y = left_over_grid_y - 1
operation = self._moveNodeOnGrid(node, self._initial_leftover_grid_x, left_over_grid_y)
grouped_operation.addOperation(operation)
left_over_grid_y = left_over_grid_y - 1
return grouped_operation, len(leftover_nodes)
def _findOptimalGridOffset(self):
if len(self._fixed_nodes) == 0:
self._offset_x = 0
self._offset_y = 0
return
if len(self._fixed_nodes) == 1:
center_grid_x = 0.5 * self._grid_width + self._build_volume_bounding_box.left
center_grid_y = 0.5 * self._grid_height + self._build_volume_bounding_box.back
bounding_box = self._fixed_nodes[0].getBoundingBox()
center_node_x = (bounding_box.left + bounding_box.right) * 0.5
center_node_y = (bounding_box.back + bounding_box.front) * 0.5
self._offset_x = center_node_x - center_grid_x
self._offset_y = center_node_y - center_grid_y
return
# If there are multiple fixed nodes, an optimal solution is not always possible
# We will try to find an offset that minimizes the number of grid intersections
# with fixed nodes. The algorithm below achieves this by utilizing a scanline
# algorithm. In this algorithm each axis is solved separately as offsetting
# is completely independent in each axis. The comments explaining the algorithm
# below are for the x-axis, but the same applies for the y-axis.
#
# Each node either occupies ceil((node.right - node.right) / grid_width) or
# ceil((node.right - node.right) / grid_width) + 1 grid squares. We will call
# these the node's "footprint".
#
# ┌────────────────┐
# minimum foot-print │ NODE │
# └────────────────┘
# │ grid 1 │ grid 2 │ grid 3 │ grid 4 | grid 5 |
# ┌────────────────┐
# maximum foot-print │ NODE │
# └────────────────┘
#
# The algorithm will find the grid offset such that the number of nodes with
# a _minimal_ footprint is _maximized_.
# The scanline algorithm works as follows, we create events for both end points
# of each node's footprint. The event have two properties,
# - the coordinate: the amount the endpoint can move to the
# left before it crosses a grid line
# - the change: either +1 or -1, indicating whether crossing the grid line
# would result in a minimal footprint node becoming a maximal footprint
class Event:
def __init__(self, coord: float, change: float):
self.coord = coord
self.change = change
# create events for both the horizontal and vertical axis
events_horizontal: List[Event] = []
events_vertical: List[Event] = []
for node in self._fixed_nodes:
bounding_box = node.getBoundingBox()
left = bounding_box.left - self._build_volume_bounding_box.left
right = bounding_box.right - self._build_volume_bounding_box.left
back = bounding_box.back - self._build_volume_bounding_box.back
front = bounding_box.front - self._build_volume_bounding_box.back
value_left = math.ceil(left / self._grid_width) * self._grid_width - left
value_right = math.ceil(right / self._grid_width) * self._grid_width - right
value_back = math.ceil(back / self._grid_height) * self._grid_height - back
value_front = math.ceil(front / self._grid_height) * self._grid_height - front
# give nodes a weight according to their size. This
# weight is heuristically chosen to be proportional to
# the number of grid squares the node-boundary occupies
weight = bounding_box.width + bounding_box.depth
events_horizontal.append(Event(value_left, weight))
events_horizontal.append(Event(value_right, -weight))
events_vertical.append(Event(value_back, weight))
events_vertical.append(Event(value_front, -weight))
events_horizontal.sort(key=lambda event: event.coord)
events_vertical.sort(key=lambda event: event.coord)
def findOptimalShiftAxis(events: List[Event], interval: float) -> float:
# executing the actual scanline algorithm
# iteratively go through events (left to right) and keep track of the
# current footprint. The optimal location is the one with the minimal
# footprint. If there are multiple locations with the same minimal
# footprint, the optimal location is the one with the largest range
# between the left and right endpoint of the footprint.
prev_offset = events[-1].coord - interval
current_minimal_footprint_count = 0
best_minimal_footprint_count = float('inf')
best_offset_span = float('-inf')
best_offset = 0.0
for event in events:
offset_span = event.coord - prev_offset
if current_minimal_footprint_count < best_minimal_footprint_count or (
current_minimal_footprint_count == best_minimal_footprint_count and offset_span > best_offset_span):
best_minimal_footprint_count = current_minimal_footprint_count
best_offset_span = offset_span
best_offset = event.coord
current_minimal_footprint_count += event.change
prev_offset = event.coord
return best_offset - best_offset_span * 0.5
center_grid_x = 0.5 * self._grid_width
center_grid_y = 0.5 * self._grid_height
optimal_center_x = self._grid_width - findOptimalShiftAxis(events_horizontal, self._grid_width)
optimal_center_y = self._grid_height - findOptimalShiftAxis(events_vertical, self._grid_height)
self._offset_x = optimal_center_x - center_grid_x
self._offset_y = optimal_center_y - center_grid_y
def _moveNodeOnGrid(self, node: "SceneNode", grid_x: int, grid_y: int) -> "Operation.Operation":
coord_grid_x, coord_grid_y = self._gridSpaceToCoordSpace(grid_x, grid_y)
center_grid_x = coord_grid_x + (0.5 * self._grid_width)
center_grid_y = coord_grid_y + (0.5 * self._grid_height)
bounding_box = node.getBoundingBox()
center_node_x = (bounding_box.left + bounding_box.right) * 0.5
center_node_y = (bounding_box.back + bounding_box.front) * 0.5
delta_x = center_grid_x - center_node_x
delta_y = center_grid_y - center_node_y
return TranslateOperation(node, Vector(delta_x, 0, delta_y))
def _getGridCornerPoints(self, bounding_box: "BoundingVolume") -> Tuple[float, float, float, float]:
coord_x1 = bounding_box.left
coord_x2 = bounding_box.right
coord_y1 = bounding_box.back
coord_y2 = bounding_box.front
grid_x1, grid_y1 = self._coordSpaceToGridSpace(coord_x1, coord_y1)
grid_x2, grid_y2 = self._coordSpaceToGridSpace(coord_x2, coord_y2)
return grid_x1, grid_y1, grid_x2, grid_y2
def _getIntersectingGridIdForPolygon(self, polygon)-> Set[Tuple[int, int]]:
# (x0, y0)
# |
# v
# ┌─────────────┐
# │ │
# │ │
# └─────────────┘ < (x1, y1)
x0 = float('inf')
y0 = float('inf')
x1 = float('-inf')
y1 = float('-inf')
grid_idx = set()
for [x, y] in polygon.getPoints():
x0 = min(x0, x)
y0 = min(y0, y)
x1 = max(x1, x)
y1 = max(y1, y)
grid_x1, grid_y1 = self._coordSpaceToGridSpace(x0, y0)
grid_x2, grid_y2 = self._coordSpaceToGridSpace(x1, y1)
for grid_x in range(math.floor(grid_x1), math.ceil(grid_x2)):
for grid_y in range(math.floor(grid_y1), math.ceil(grid_y2)):
grid_idx.add((grid_x, grid_y))
return grid_idx
def _intersectingGridIdxInclusive(self, bounding_box: "BoundingVolume") -> Set[Tuple[int, int]]:
grid_x1, grid_y1, grid_x2, grid_y2 = self._getGridCornerPoints(bounding_box)
grid_idx = set()
for grid_x in range(math.floor(grid_x1), math.ceil(grid_x2)):
for grid_y in range(math.floor(grid_y1), math.ceil(grid_y2)):
grid_idx.add((grid_x, grid_y))
return grid_idx
def _intersectingGridIdxExclusive(self, bounding_box: "BoundingVolume") -> Set[Tuple[int, int]]:
grid_x1, grid_y1, grid_x2, grid_y2 = self._getGridCornerPoints(bounding_box)
grid_idx = set()
for grid_x in range(math.ceil(grid_x1), math.floor(grid_x2)):
for grid_y in range(math.ceil(grid_y1), math.floor(grid_y2)):
grid_idx.add((grid_x, grid_y))
return grid_idx
def _gridSpaceToCoordSpace(self, x: float, y: float) -> Tuple[float, float]:
grid_x = x * self._grid_width + self._build_volume_bounding_box.left + self._offset_x
grid_y = y * self._grid_height + self._build_volume_bounding_box.back + self._offset_y
return grid_x, grid_y
def _coordSpaceToGridSpace(self, grid_x: float, grid_y: float) -> Tuple[float, float]:
coord_x = (grid_x - self._build_volume_bounding_box.left - self._offset_x) / self._grid_width
coord_y = (grid_y - self._build_volume_bounding_box.back - self._offset_y) / self._grid_height
return coord_x, coord_y
def _checkGridUnderDiscSpace(self, grid_x: int, grid_y: int) -> bool:
left, back = self._gridSpaceToCoordSpace(grid_x, grid_y)
right, front = self._gridSpaceToCoordSpace(grid_x + 1, grid_y + 1)
corners = [(left, back), (right, back), (right, front), (left, front)]
return all([self._checkPointUnderDiscSpace(x, y) for x, y in corners])
def _checkPointUnderDiscSpace(self, x: float, y: float) -> bool:
disc_x, disc_y = self._coordSpaceToDiscSpace(x, y)
distance_to_center_squared = disc_x ** 2 + disc_y ** 2
return distance_to_center_squared <= 1.0
def _coordSpaceToDiscSpace(self, x: float, y: float) -> Tuple[float, float]:
# Transform coordinate system to
#
# coord_build_plate_left = -1
# | coord_build_plate_right = 1
# v (0,1) v
# ┌───────┬───────┐ < coord_build_plate_back = -1
# │ │ │
# │ │(0,0) │
# (-1,0)├───────o───────┤(1,0)
# │ │ │
# │ │ │
# └───────┴───────┘ < coord_build_plate_front = +1
# (0,-1)
disc_x = ((x - self._build_volume_bounding_box.left) / self._build_volume_bounding_box.width) * 2.0 - 1.0
disc_y = ((y - self._build_volume_bounding_box.back) / self._build_volume_bounding_box.depth) * 2.0 - 1.0
return disc_x, disc_y

View file

@ -15,46 +15,56 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.RotateOperation import RotateOperation
from UM.Operations.TranslateOperation import TranslateOperation
from cura.Arranging.Arranger import Arranger
if TYPE_CHECKING:
from UM.Scene.SceneNode import SceneNode
from cura.BuildVolume import BuildVolume
def findNodePlacement(nodes_to_arrange: List["SceneNode"], build_volume: "BuildVolume", fixed_nodes: Optional[List["SceneNode"]] = None, factor = 10000) -> Tuple[bool, List[Item]]:
class Nest2DArrange(Arranger):
def __init__(self,
nodes_to_arrange: List["SceneNode"],
build_volume: "BuildVolume",
fixed_nodes: Optional[List["SceneNode"]] = None,
*,
factor: int = 10000,
lock_rotation: bool = False):
"""
Find placement for a set of scene nodes, but don't actually move them just yet.
:param nodes_to_arrange: The list of nodes that need to be moved.
:param build_volume: The build volume that we want to place the nodes in. It gets size & disallowed areas from this.
:param fixed_nodes: List of nods that should not be moved, but should be used when deciding where the others nodes
are placed.
:param factor: The library that we use is int based. This factor defines how accurate we want it to be.
:return: tuple (found_solution_for_all, node_items)
WHERE
found_solution_for_all: Whether the algorithm found a place on the buildplate for all the objects
node_items: A list of the nodes return by libnest2d, which contain the new positions on the buildplate
:param factor: The library that we use is int based. This factor defines how accuracte we want it to be.
:param lock_rotation: If set to true the orientation of the object will remain the same
"""
spacing = int(1.5 * factor) # 1.5mm spacing.
super().__init__()
self._nodes_to_arrange = nodes_to_arrange
self._build_volume = build_volume
self._fixed_nodes = fixed_nodes
self._factor = factor
self._lock_rotation = lock_rotation
machine_width = build_volume.getWidth()
machine_depth = build_volume.getDepth()
build_plate_bounding_box = Box(int(machine_width * factor), int(machine_depth * factor))
def findNodePlacement(self) -> Tuple[bool, List[Item]]:
spacing = int(1.5 * self._factor) # 1.5mm spacing.
if fixed_nodes is None:
fixed_nodes = []
machine_width = self._build_volume.getWidth()
machine_depth = self._build_volume.getDepth()
build_plate_bounding_box = Box(int(machine_width * self._factor), int(machine_depth * self._factor))
if self._fixed_nodes is None:
self._fixed_nodes = []
# Add all the items we want to arrange
node_items = []
for node in nodes_to_arrange:
for node in self._nodes_to_arrange:
hull_polygon = node.callDecoration("getConvexHull")
if not hull_polygon or hull_polygon.getPoints is None:
Logger.log("w", "Object {} cannot be arranged because it has no convex hull.".format(node.getName()))
continue
converted_points = []
for point in hull_polygon.getPoints():
converted_points.append(Point(int(point[0] * factor), int(point[1] * factor)))
converted_points.append(Point(int(point[0] * self._factor), int(point[1] * self._factor)))
item = Item(converted_points)
node_items.append(item)
@ -68,7 +78,7 @@ def findNodePlacement(nodes_to_arrange: List["SceneNode"], build_volume: "BuildV
[half_machine_width, half_machine_depth]
], numpy.float32))
disallowed_areas = build_volume.getDisallowedAreas()
disallowed_areas = self._build_volume.getDisallowedAreas()
num_disallowed_areas_added = 0
for area in disallowed_areas:
converted_points = []
@ -76,22 +86,24 @@ def findNodePlacement(nodes_to_arrange: List["SceneNode"], build_volume: "BuildV
# Clip the disallowed areas so that they don't overlap the bounding box (The arranger chokes otherwise)
clipped_area = area.intersectionConvexHulls(build_plate_polygon)
if clipped_area.getPoints() is not None and len(clipped_area.getPoints()) > 2: # numpy array has to be explicitly checked against None
if clipped_area.getPoints() is not None and len(
clipped_area.getPoints()) > 2: # numpy array has to be explicitly checked against None
for point in clipped_area.getPoints():
converted_points.append(Point(int(point[0] * factor), int(point[1] * factor)))
converted_points.append(Point(int(point[0] * self._factor), int(point[1] * self._factor)))
disallowed_area = Item(converted_points)
disallowed_area.markAsDisallowedAreaInBin(0)
node_items.append(disallowed_area)
num_disallowed_areas_added += 1
for node in fixed_nodes:
for node in self._fixed_nodes:
converted_points = []
hull_polygon = node.callDecoration("getConvexHull")
if hull_polygon is not None and hull_polygon.getPoints() is not None and len(hull_polygon.getPoints()) > 2: # numpy array has to be explicitly checked against None
if hull_polygon is not None and hull_polygon.getPoints() is not None and len(
hull_polygon.getPoints()) > 2: # numpy array has to be explicitly checked against None
for point in hull_polygon.getPoints():
converted_points.append(Point(int(point[0] * factor), int(point[1] * factor)))
converted_points.append(Point(int(point[0] * self._factor), int(point[1] * self._factor)))
item = Item(converted_points)
item.markAsFixedInBin(0)
node_items.append(item)
@ -100,6 +112,8 @@ def findNodePlacement(nodes_to_arrange: List["SceneNode"], build_volume: "BuildV
config = NfpConfig()
config.accuracy = 1.0
config.alignment = NfpConfig.Alignment.DONT_ALIGN
if self._lock_rotation:
config.rotations = [0.0]
num_bins = nest(node_items, build_plate_bounding_box, spacing, config)
@ -110,18 +124,13 @@ def findNodePlacement(nodes_to_arrange: List["SceneNode"], build_volume: "BuildV
return found_solution_for_all, node_items
def createGroupOperationForArrange(nodes_to_arrange: List["SceneNode"],
build_volume: "BuildVolume",
fixed_nodes: Optional[List["SceneNode"]] = None,
factor = 10000,
add_new_nodes_in_scene: bool = False) -> Tuple[GroupedOperation, int]:
def createGroupOperationForArrange(self, *, add_new_nodes_in_scene: bool = False) -> Tuple[GroupedOperation, int]:
scene_root = Application.getInstance().getController().getScene().getRoot()
found_solution_for_all, node_items = findNodePlacement(nodes_to_arrange, build_volume, fixed_nodes, factor)
found_solution_for_all, node_items = self.findNodePlacement()
not_fit_count = 0
grouped_operation = GroupedOperation()
for node, node_item in zip(nodes_to_arrange, node_items):
for node, node_item in zip(self._nodes_to_arrange, node_items):
if add_new_nodes_in_scene:
grouped_operation.addOperation(AddSceneNodeOperation(node, scene_root))
@ -130,8 +139,9 @@ def createGroupOperationForArrange(nodes_to_arrange: List["SceneNode"],
rotation_matrix = Matrix()
rotation_matrix.setByRotationAxis(node_item.rotation(), Vector(0, -1, 0))
grouped_operation.addOperation(RotateOperation(node, Quaternion.fromMatrix(rotation_matrix)))
grouped_operation.addOperation(TranslateOperation(node, Vector(node_item.translation().x() / factor, 0,
node_item.translation().y() / factor)))
grouped_operation.addOperation(
TranslateOperation(node, Vector(node_item.translation().x() / self._factor, 0,
node_item.translation().y() / self._factor)))
else:
# We didn't find a spot
grouped_operation.addOperation(
@ -139,25 +149,3 @@ def createGroupOperationForArrange(nodes_to_arrange: List["SceneNode"],
not_fit_count += 1
return grouped_operation, not_fit_count
def arrange(nodes_to_arrange: List["SceneNode"],
build_volume: "BuildVolume",
fixed_nodes: Optional[List["SceneNode"]] = None,
factor = 10000,
add_new_nodes_in_scene: bool = False) -> bool:
"""
Find placement for a set of scene nodes, and move them by using a single grouped operation.
:param nodes_to_arrange: The list of nodes that need to be moved.
:param build_volume: The build volume that we want to place the nodes in. It gets size & disallowed areas from this.
:param fixed_nodes: List of nods that should not be moved, but should be used when deciding where the others nodes
are placed.
:param factor: The library that we use is int based. This factor defines how accuracte we want it to be.
:param add_new_nodes_in_scene: Whether to create new scene nodes before applying the transformations and rotations
:return: found_solution_for_all: Whether the algorithm found a place on the buildplate for all the objects
"""
grouped_operation, not_fit_count = createGroupOperationForArrange(nodes_to_arrange, build_volume, fixed_nodes, factor, add_new_nodes_in_scene)
grouped_operation.push()
return not_fit_count == 0

View file

@ -203,6 +203,9 @@ class BuildVolume(SceneNode):
if shape:
self._shape = shape
def getShape(self) -> str:
return self._shape
def getDiagonalSize(self) -> float:
"""Get the length of the 3D diagonal through the build volume.

View file

@ -22,7 +22,10 @@ from cura.Operations.SetParentOperation import SetParentOperation
from cura.MultiplyObjectsJob import MultiplyObjectsJob
from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Arranging.Nest2DArrange import createGroupOperationForArrange
from cura.Arranging.GridArrange import GridArrange
from cura.Arranging.Nest2DArrange import Nest2DArrange
from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOperation
@ -82,16 +85,25 @@ class CuraActions(QObject):
center_operation = TranslateOperation(current_node, Vector(0, center_y, 0), set_position = True)
operation.addOperation(center_operation)
operation.push()
@pyqtSlot(int)
def multiplySelection(self, count: int) -> None:
"""Multiply all objects in the selection
:param count: The number of times to multiply the selection.
"""
min_offset = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset = max(min_offset, 8))
job.start()
@pyqtSlot(int)
def multiplySelectionToGrid(self, count: int) -> None:
"""Multiply all objects in the selection
:param count: The number of times to multiply the selection.
"""
min_offset = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset = max(min_offset, 8))
job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset=max(min_offset, 8),
grid_arrange=True)
job.start()
@pyqtSlot()
@ -229,9 +241,9 @@ class CuraActions(QObject):
if node.callDecoration("isSliceable"):
fixed_nodes.append(node)
# Add the new nodes to the scene, and arrange them
group_operation, not_fit_count = createGroupOperationForArrange(nodes, application.getBuildVolume(),
fixed_nodes, factor=10000,
add_new_nodes_in_scene=True)
arranger = GridArrange(nodes, application.getBuildVolume(), fixed_nodes)
group_operation, not_fit_count = arranger.createGroupOperationForArrange(add_new_nodes_in_scene = True)
group_operation.push()
# deselect currently selected nodes, and select the new nodes

View file

@ -54,7 +54,6 @@ from cura import ApplicationMetadata
from cura.API import CuraAPI
from cura.API.Account import Account
from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob
from cura.Arranging.Nest2DArrange import arrange
from cura.Machines.MachineErrorChecker import MachineErrorChecker
from cura.Machines.Models.BuildPlateModel import BuildPlateModel
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
@ -115,6 +114,7 @@ from . import CameraAnimation
from . import CuraActions
from . import PlatformPhysics
from . import PrintJobPreviewImageProvider
from .Arranging.Nest2DArrange import Nest2DArrange
from .AutoSave import AutoSave
from .Machines.Models.CompatibleMachineModel import CompatibleMachineModel
from .Machines.Models.MachineListModel import MachineListModel
@ -409,7 +409,9 @@ class CuraApplication(QtApplication):
SettingFunction.registerOperator("extruderValue", self._cura_formula_functions.getValueInExtruder)
SettingFunction.registerOperator("extruderValues", self._cura_formula_functions.getValuesInAllExtruders)
SettingFunction.registerOperator("anyExtruderNrWithOrDefault", self._cura_formula_functions.getAnyExtruderPositionWithOrDefault)
SettingFunction.registerOperator("anyExtruderWithMaterial", self._cura_formula_functions.getExtruderPositionWithMaterial)
SettingFunction.registerOperator("anyExtruderNrWithOrDefault",
self._cura_formula_functions.getAnyExtruderPositionWithOrDefault)
SettingFunction.registerOperator("resolveOrValue", self._cura_formula_functions.getResolveOrValue)
SettingFunction.registerOperator("defaultExtruderPosition", self._cura_formula_functions.getDefaultExtruderPosition)
SettingFunction.registerOperator("valueFromContainer", self._cura_formula_functions.getValueFromContainerAtIndex)
@ -495,6 +497,36 @@ class CuraApplication(QtApplication):
def startSplashWindowPhase(self) -> None:
"""Runs preparations that needs to be done before the starting process."""
self.setRequiredPlugins([
# Misc.:
"ConsoleLogger", # You want to be able to read the log if something goes wrong.
"CuraEngineBackend", # Cura is useless without this one since you can't slice.
"FileLogger", # You want to be able to read the log if something goes wrong.
"XmlMaterialProfile", # Cura crashes without this one.
"Marketplace",
# This contains the interface to enable/disable plug-ins, so if you disable it you can't enable it back.
"PrepareStage", # Cura is useless without this one since you can't load models.
"PreviewStage", # This shows the list of the plugin views that are installed in Cura.
"MonitorStage", # Major part of Cura's functionality.
"LocalFileOutputDevice", # Major part of Cura's functionality.
"LocalContainerProvider", # Cura is useless without any profiles or setting definitions.
# Views:
"SimpleView", # Dependency of SolidView.
"SolidView", # Displays models. Cura is useless without it.
# Readers & Writers:
"GCodeWriter", # Cura is useless if it can't write its output.
"STLReader", # Most common model format, so disabling this makes Cura 90% useless.
"3MFWriter", # Required for writing project files.
# Tools:
"CameraTool", # Needed to see the scene. Cura is useless without it.
"SelectionTool", # Dependency of the rest of the tools.
"TranslateTool", # You'll need this for almost every print.
])
# Plugins need to be set here, since in the super the check is done if they are actually loaded.
super().startSplashWindowPhase()
if not self.getIsHeadLess():
@ -503,33 +535,7 @@ class CuraApplication(QtApplication):
except FileNotFoundError:
Logger.log("w", "Unable to find the window icon.")
self.setRequiredPlugins([
# Misc.:
"ConsoleLogger", #You want to be able to read the log if something goes wrong.
"CuraEngineBackend", #Cura is useless without this one since you can't slice.
"FileLogger", #You want to be able to read the log if something goes wrong.
"XmlMaterialProfile", #Cura crashes without this one.
"Marketplace", #This contains the interface to enable/disable plug-ins, so if you disable it you can't enable it back.
"PrepareStage", #Cura is useless without this one since you can't load models.
"PreviewStage", #This shows the list of the plugin views that are installed in Cura.
"MonitorStage", #Major part of Cura's functionality.
"LocalFileOutputDevice", #Major part of Cura's functionality.
"LocalContainerProvider", #Cura is useless without any profiles or setting definitions.
# Views:
"SimpleView", #Dependency of SolidView.
"SolidView", #Displays models. Cura is useless without it.
# Readers & Writers:
"GCodeWriter", #Cura is useless if it can't write its output.
"STLReader", #Most common model format, so disabling this makes Cura 90% useless.
"3MFWriter", #Required for writing project files.
# Tools:
"CameraTool", #Needed to see the scene. Cura is useless without it.
"SelectionTool", #Dependency of the rest of the tools.
"TranslateTool", #You'll need this for almost every print.
])
self._i18n_catalog = i18nCatalog("cura")
self._update_platform_activity_timer = QTimer()
@ -1438,6 +1444,13 @@ class CuraApplication(QtApplication):
# Single build plate
@pyqtSlot()
def arrangeAll(self) -> None:
self._arrangeAll(grid_arrangement = False)
@pyqtSlot()
def arrangeAllInGrid(self) -> None:
self._arrangeAll(grid_arrangement = True)
def _arrangeAll(self, *, grid_arrangement: bool) -> None:
nodes_to_arrange = []
active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
locked_nodes = []
@ -1467,17 +1480,17 @@ class CuraApplication(QtApplication):
locked_nodes.append(node)
else:
nodes_to_arrange.append(node)
self.arrange(nodes_to_arrange, locked_nodes)
self.arrange(nodes_to_arrange, locked_nodes, grid_arrangement = grid_arrangement)
def arrange(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode]) -> None:
def arrange(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode], *, grid_arrangement: bool = False) -> None:
"""Arrange a set of nodes given a set of fixed nodes
:param nodes: nodes that we have to place
:param fixed_nodes: nodes that are placed in the arranger before finding spots for nodes
:param grid_arrangement: If set to true if objects are to be placed in a grid
"""
min_offset = self.getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
job = ArrangeObjectsJob(nodes, fixed_nodes, min_offset = max(min_offset, 8))
job = ArrangeObjectsJob(nodes, fixed_nodes, min_offset = max(min_offset, 8), grid_arrange = grid_arrangement)
job.start()
@pyqtSlot()
@ -1964,7 +1977,8 @@ class CuraApplication(QtApplication):
if select_models_on_load:
Selection.add(node)
try:
arrange(nodes_to_arrange, self.getBuildVolume(), fixed_nodes)
arranger = Nest2DArrange(nodes_to_arrange, self.getBuildVolume(), fixed_nodes)
arranger.arrange()
except:
Logger.logException("e", "Failed to arrange the models")

View file

@ -14,17 +14,19 @@ from UM.Operations.TranslateOperation import TranslateOperation
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog
from cura.Arranging.Nest2DArrange import arrange, createGroupOperationForArrange
from cura.Arranging.GridArrange import GridArrange
from cura.Arranging.Nest2DArrange import Nest2DArrange
i18n_catalog = i18nCatalog("cura")
class MultiplyObjectsJob(Job):
def __init__(self, objects, count, min_offset = 8):
def __init__(self, objects, count: int, min_offset: int = 8 ,* , grid_arrange: bool = False):
super().__init__()
self._objects = objects
self._count = count
self._min_offset = min_offset
self._count: int = count
self._min_offset: int = min_offset
self._grid_arrange: bool = grid_arrange
def run(self) -> None:
status_message = Message(i18n_catalog.i18nc("@info:status", "Multiplying and placing objects"), lifetime = 0,
@ -39,7 +41,7 @@ class MultiplyObjectsJob(Job):
root = scene.getRoot()
processed_nodes = [] # type: List[SceneNode]
processed_nodes: List[SceneNode] = []
nodes = []
fixed_nodes = []
@ -76,12 +78,12 @@ class MultiplyObjectsJob(Job):
found_solution_for_all = True
group_operation = GroupedOperation()
if nodes:
group_operation, not_fit_count = createGroupOperationForArrange(nodes,
Application.getInstance().getBuildVolume(),
fixed_nodes,
factor = 10000,
add_new_nodes_in_scene = True)
found_solution_for_all = not_fit_count == 0
if self._grid_arrange:
arranger = GridArrange(nodes, Application.getInstance().getBuildVolume(), fixed_nodes)
else:
arranger = Nest2DArrange(nodes, Application.getInstance().getBuildVolume(), fixed_nodes, factor=1000)
group_operation, not_fit_count = arranger.createGroupOperationForArrange(add_new_nodes_in_scene=True)
if nodes_to_add_without_arrange:
for nested_node in nodes_to_add_without_arrange:

View file

@ -111,11 +111,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
# Parent can be None if node is just loaded.
if self._isSingularOneAtATimeNode():
hull = self.getConvexHullHeadFull()
if hull is None:
return None
hull = self._add2DAdhesionMargin(hull)
return hull
return self.getConvexHullHeadFull()
return self._compute2DConvexHull()
@ -323,6 +319,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
def _compute2DConvexHeadFull(self) -> Optional[Polygon]:
convex_hull = self._compute2DConvexHull()
convex_hull = self._add2DAdhesionMargin(convex_hull)
if convex_hull:
return convex_hull.getMinkowskiHull(self._getHeadAndFans())
return None

View file

@ -105,6 +105,15 @@ class CuraFormulaFunctions:
if value is None or not value:
continue
return str(extruder.position)
# Get the first extruder with material that adheres to a specific (boolean) property, like 'material_is_support_material'.
def getExtruderPositionWithMaterial(self, filter_key: str,
context: Optional["PropertyEvaluationContext"] = None) -> str:
for extruder in self._getActiveExtruders(context):
material_container = extruder.material
value = material_container.getProperty(filter_key, "value", context)
if value is not None:
return str(extruder.position)
return self.getDefaultExtruderPosition()
# Get the resolve value or value for a given key.

View file

@ -54,7 +54,10 @@ There are also a few extra things that can be used in these expressions:
* The function `extruderValue(extruder, key)` will evaluate a particular setting for a particular extruder.
* The function `resolveOrValue(key)` will perform the full setting evaluation as described in this document for the current context (so if this setting is being evaluated for the second extruder it would perform it as if coming from the second extruder).
* The function `defaultExtruderPosition()` will get the first extruder that is not disabled. For instance, if a printer has three extruders but the first is disabled, this would return `1` to indicate the second extruder (0-indexed).
* The function `anyExtruderNrWithOrDefault(key)` will filter the list of extruders on the key, and then give the first index for which it is true, or if none of them are, the default one as specified by the 'default extruder position' function above.
* The function `anyExtruderNrWithOrDefault(key)` will filter the list of extruders on the key, and then give the first
index for which it is true, or if none of them are, the default one as specified by the 'default extruder position'
function above.
* The function `anyExtruderWithMaterial(key)` will filter the list of extruders on the key of material quality, and then give the first index for which it is true, or if none of them are, the default one as specified by the 'default extruder position' function above.
* The function `valueFromContainer(key, index)` will get a setting value from the global stack, but skip the first few containers in that stack. It will skip until it reaches a particular index in the container stack.
* The function `extruderValueFromContainer(key, index)` will get a setting value from the current extruder stack, but skip the first few containers in that stack. It will skip until it reaches a particular index in the container stack.

View file

@ -21,6 +21,7 @@ def build_dmg(source_path: str, dist_path: str, filename: str, app_name: str) ->
"--icon", app_name, "169", "272",
"--eula", f"{source_path}/packaging/cura_license.txt",
"--background", f"{source_path}/packaging/MacOs/cura_background_dmg.png",
"--hdiutil-quiet",
f"{dist_path}/{filename}",
f"{dist_path}/{app_name}"]
@ -138,18 +139,20 @@ def create_dmg(filename: str, dist_path: str, source_path: str, app_name: str) -
if __name__ == "__main__":
parser = argparse.ArgumentParser(description = "Create installer for Cura.")
parser.add_argument("source_path", type = str, help = "Path to Pyinstaller source folder")
parser.add_argument("dist_path", type = str, help = "Path to Pyinstaller dist folder")
parser.add_argument("cura_conan_version", type = str, help="The version of cura")
parser.add_argument("filename", type = str, help = "Filename of the pkg/dmg (e.g. 'UltiMaker-Cura-5.1.0-beta-Macos-X64.pkg' or 'UltiMaker-Cura-5.1.0-beta-Macos-X64.dmg')")
parser.add_argument("app_name", type = str, help = "Filename of the .app that will be contained within the dmg/pkg")
parser.add_argument("--source_path", required = True, type = str, help = "Path to Pyinstaller source folder")
parser.add_argument("--dist_path", required = True, type = str, help = "Path to Pyinstaller dist folder")
parser.add_argument("--cura_conan_version", required = True, type = str, help = "The version of cura")
parser.add_argument("--filename", required = True, type = str, help = "Filename of the pkg/dmg (e.g. 'UltiMaker-Cura-5.5.0-Macos-X64' or 'UltiMaker-Cura-5.5.0-beta.1-Macos-ARM64')")
parser.add_argument("--build_pkg", action="store_true", default = False, help = "build the pkg")
parser.add_argument("--build_dmg", action="store_true", default = True, help = "build the dmg")
parser.add_argument("--app_name", required = True, type = str, help = "Filename of the .app that will be contained within the dmg/pkg")
args = parser.parse_args()
cura_version = args.cura_conan_version.split("/")[-1]
app_name = f"{args.app_name}.app"
if Path(args.filename).suffix == ".pkg":
create_pkg_installer(args.filename, args.dist_path, cura_version, app_name)
else:
create_dmg(args.filename, args.dist_path, args.source_path, app_name)
if args.build_pkg:
create_pkg_installer(f"{args.filename}.pkg", args.dist_path, cura_version, app_name)
if args.build_dmg:
create_dmg(f"{args.filename}.dmg", args.dist_path, args.source_path, app_name)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 KiB

After

Width:  |  Height:  |  Size: 417 KiB

Before After
Before After

View file

@ -1,5 +1,5 @@
# Copyright (c) 2018 Jaime van Kessel, Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
# The PostProcessingPlugin is released under the terms of the LGPLv3 or higher.
import configparser # The script lists are stored in metadata as serialised config files.
import importlib.util

View file

@ -1,5 +1,5 @@
// Copyright (c) 2022 Jaime van Kessel, Ultimaker B.V.
// The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
// The PostProcessingPlugin is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 2.15

View file

@ -1,6 +1,6 @@
# Copyright (c) 2015 Jaime van Kessel
# Copyright (c) 2018 Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
# The PostProcessingPlugin is released under the terms of the LGPLv3 or higher.
from typing import Optional, Any, Dict, TYPE_CHECKING, List
from UM.Signal import Signal, signalemitter

View file

@ -1,7 +1,7 @@
# ChangeAtZ script - Change printing parameters at a given height
# This script is the successor of the TweakAtZ plugin for legacy Cura.
# It contains code from the TweakAtZ plugin V1.0-V4.x and from the ExampleScript by Jaime van Kessel, Ultimaker B.V.
# It runs with the PostProcessingPlugin which is released under the terms of the AGPLv3 or higher.
# It runs with the PostProcessingPlugin which is released under the terms of the LGPLv3 or higher.
# This script is licensed under the Creative Commons - Attribution - Share Alike (CC BY-SA) terms
# Authors of the ChangeAtZ plugin / script:

View file

@ -1,6 +1,6 @@
# ColorMix script - 2-1 extruder color mix and blending
# This script is specific for the Geeetech A10M dual extruder but should work with other Marlin printers.
# It runs with the PostProcessingPlugin which is released under the terms of the AGPLv3 or higher.
# It runs with the PostProcessingPlugin which is released under the terms of the LGPLv3 or higher.
# This script is licensed under the Creative Commons - Attribution - Share Alike (CC BY-SA) terms
#Authors of the 2-1 ColorMix plug-in / script:

View file

@ -1,5 +1,5 @@
# Copyright (c) 2023 Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
# The PostProcessingPlugin is released under the terms of the LGPLv3 or higher.
# Modification 06.09.2020
# add checkbox, now you can choose and use configuration from the firmware itself.

View file

@ -1,5 +1,5 @@
# Copyright (c) 2023 UltiMaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
# The PostProcessingPlugin is released under the terms of the LGPLv3 or higher.
from ..Script import Script

View file

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

View file

@ -1,4 +1,4 @@
# This PostProcessingPlugin script is released under the terms of the AGPLv3 or higher.
# This PostProcessingPlugin script is released under the terms of the LGPLv3 or higher.
"""
Copyright (c) 2017 Christophe Baribaud 2017
Python implementation of https://github.com/electrocbd/post_stretch

View file

@ -1,6 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
import os.path
from UM.Application import Application
@ -143,38 +144,44 @@ class RemovableDriveOutputDevice(OutputDevice):
def _onFinished(self, job):
if self._stream:
# Explicitly closing the stream flushes the write-buffer
error = job.getError()
try:
# Explicitly closing the stream flushes the write-buffer
self._stream.close()
self._stream = None
except:
Logger.logException("w", "An exception occurred while trying to write to removable drive.")
message = Message(catalog.i18nc("@info:status", "Could not save to removable drive {0}: {1}").format(self.getName(),str(job.getError())),
title = catalog.i18nc("@info:title", "Error"),
message_type = Message.MessageType.ERROR)
message.show()
self.writeError.emit(self)
return
except Exception as e:
if not error:
# Only log new error if there was no previous one
error = e
self._stream = None
self._writing = False
self.writeFinished.emit(self)
if job.getResult():
message = Message(catalog.i18nc("@info:status", "Saved to Removable Drive {0} as {1}").format(self.getName(), os.path.basename(job.getFileName())),
title = catalog.i18nc("@info:title", "File Saved"),
message_type = Message.MessageType.POSITIVE)
message.addAction("eject", catalog.i18nc("@action:button", "Eject"), "eject", catalog.i18nc("@action", "Eject removable device {0}").format(self.getName()))
if not error:
message = Message(
catalog.i18nc("@info:status", "Saved to Removable Drive {0} as {1}").format(self.getName(),
os.path.basename(
job.getFileName())),
title=catalog.i18nc("@info:title", "File Saved"),
message_type=Message.MessageType.POSITIVE)
message.addAction("eject", catalog.i18nc("@action:button", "Eject"), "eject",
catalog.i18nc("@action", "Eject removable device {0}").format(self.getName()))
message.actionTriggered.connect(self._onActionTriggered)
message.show()
self.writeSuccess.emit(self)
else:
try:
os.remove(job.getFileName())
except Exception as e:
Logger.logException("e", "Exception when trying to remove incomplete exported file %s",
str(job.getFileName()))
message = Message(catalog.i18nc("@info:status",
"Could not save to removable drive {0}: {1}").format(self.getName(),
str(job.getError())),
title = catalog.i18nc("@info:title", "Error"),
message_type = Message.MessageType.ERROR)
title=catalog.i18nc("@info:title", "Error"),
message_type=Message.MessageType.ERROR)
message.show()
self.writeError.emit(self)
job.getStream().close()
def _onActionTriggered(self, message, action):
if action == "eject":

View file

@ -37,24 +37,13 @@ class NewPrinterDetectedMessage(Message):
def finalize(self, new_devices_added, new_output_devices):
self.setProgress(None)
num_devices_added = len(new_devices_added)
max_disp_devices = 3
if num_devices_added > max_disp_devices:
num_hidden = num_devices_added - max_disp_devices
device_name_list = ["<li>{} ({})</li>".format(device.name, device.printerTypeName) for device in
new_output_devices[0: max_disp_devices]]
device_name_list.append(
"<li>" + self.i18n_catalog.i18ncp("info:{0} gets replaced by a number of printers", "... and {0} other",
"... and {0} others", num_hidden) + "</li>")
device_names = "".join(device_name_list)
else:
device_names = "".join(
["<li>{} ({})</li>".format(device.name, device.printerTypeName) for device in new_devices_added])
if new_devices_added:
message_text = self.i18n_catalog.i18nc("info:status",
"Printers added from Digital Factory:") + f"<ul>{device_names}</ul>"
device_names = ""
for device in new_devices_added:
device_names = device_names + "<li>{} ({})</li>".format(device.name, device.printerTypeName)
message_title = self.i18n_catalog.i18nc("info:status", "Printers added from Digital Factory:")
message_text = f"{message_title}<ul>{device_names}</ul>"
self.setText(message_text)
else:
self.hide()

View file

@ -59,7 +59,7 @@
"machine_width": { "default_value": 280 },
"material_diameter": { "default_value": 1.75 },
"material_initial_print_temperature": { "value": "material_print_temperature" },
"prime_tower_min_volume": { "value": "((reveOrValue('layer_height'))/2" },
"prime_tower_min_volume": { "value": "((resolveOrValue('layer_height'))/2" },
"prime_tower_position_x": { "value": "240" },
"prime_tower_position_y": { "value": "190" },
"prime_tower_size": { "value": "30" },

View file

@ -69,7 +69,7 @@
"material_print_temperature_layer_0": { "value": "210 if material_print_temperature < 210 else material_print_temperature" },
"min_infill_area": { "value": "5" },
"minimum_interface_area": { "default_value": 10 },
"minimum_support_area": { "default_value": 3 },
"minimum_support_area": { "value": "3 if support_structure == 'normal' else 0" },
"optimize_wall_printing_order": { "default_value": true },
"prime_tower_brim_enable": { "default_value": true },
"prime_tower_min_volume": { "value": "(layer_height) * (prime_tower_size / 2)**2 * 3 * 0.5 " },

View file

@ -1664,17 +1664,27 @@
"small_skin_width":
{
"label": "Small Top/Bottom Width",
"description": "Small top/bottom regions are filled with walls instead of the default top/bottom pattern. This helps to avoids jerky motions.",
"value": "0",
"default_value": 0,
"description": "Small top/bottom regions are filled with walls instead of the default top/bottom pattern. This helps to avoids jerky motions. Off for the topmost (air-exposed) layer by default (see 'Small Top/Bottom On Surface').",
"value": "skin_line_width * 2",
"default_value": 1,
"minimum_value": "0",
"maximum_value_warning": "skin_line_width * 10",
"maximum_value_warning": "skin_line_width * 3",
"type": "float",
"enabled": false,
"enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern != 'concentric'",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true,
"unit": "mm",
"comment": "Disabled for 5.4.x, as we're worried about micro-segments in the infill. Also disabled in the engine, so forcing this > 0 will not do anything at the moment."
"unit": "mm"
},
"small_skin_on_surface":
{
"label": "Small Top/Bottom On Surface",
"description": "Enable small (up to 'Small Top/Bottom Width') regions on the topmost skinned layer (exposed to air) to be filled with walls instead of the default pattern.",
"value": "False",
"default_value": false,
"type": "bool",
"enabled": "small_skin_width > 0 and top_layers > 0",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
},
"skin_no_small_gaps_heuristic":
{
@ -4490,7 +4500,7 @@
"type": "extruder",
"default_value": "0",
"enabled": "(support_enable or support_meshes_present) and extruders_enabled_count > 1",
"value": "int(defaultExtruderPosition())",
"value": "int(anyExtruderWithMaterial('material_is_support_material'))",
"settable_per_mesh": false,
"settable_per_extruder": false,
"children":
@ -4570,6 +4580,7 @@
},
"enabled": "support_enable",
"default_value": "normal",
"resolve": "extruderValue(support_extruder_nr, 'support_structure')",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@ -5504,7 +5515,9 @@
"unit": "mm",
"type": "float",
"default_value": 0.0,
"maximum_value": "extruderValue(support_extruder_nr, 'support_offset')",
"maximum_value": "extruderValue(support_extruder_nr, 'support_offset') if support_structure == 'normal' else None",
"minimum_value_warning": "-1 * machine_nozzle_size",
"maximum_value_warning": "10 * machine_nozzle_size",
"limit_to_extruder": "support_interface_extruder_nr",
"enabled": "support_interface_enable and (support_enable or support_meshes_present)",
"settable_per_mesh": false,
@ -5519,7 +5532,9 @@
"type": "float",
"default_value": 0.0,
"value": "extruderValue(support_roof_extruder_nr, 'support_interface_offset')",
"maximum_value": "extruderValue(support_extruder_nr, 'support_offset')",
"maximum_value": "extruderValue(support_extruder_nr, 'support_offset') if support_structure == 'normal' else None",
"minimum_value_warning": "-1 * machine_nozzle_size",
"maximum_value_warning": "10 * machine_nozzle_size",
"limit_to_extruder": "support_roof_extruder_nr",
"enabled": "support_roof_enable and (support_enable or support_meshes_present)",
"settable_per_mesh": false,
@ -5533,7 +5548,9 @@
"type": "float",
"default_value": 0.0,
"value": "extruderValue(support_bottom_extruder_nr, 'support_interface_offset')",
"maximum_value": "extruderValue(support_extruder_nr, 'support_offset')",
"maximum_value": "extruderValue(support_extruder_nr, 'support_offset') if support_structure == 'normal' else None",
"minimum_value_warning": "-1 * machine_nozzle_size",
"maximum_value_warning": "10 * machine_nozzle_size",
"limit_to_extruder": "support_bottom_extruder_nr",
"enabled": "support_bottom_enable and (support_enable or support_meshes_present)",
"settable_per_mesh": false,
@ -6805,6 +6822,48 @@
"minimum_value_warning": "500",
"maximum_value_warning": "100000",
"settable_per_mesh": true
},
"meshfix_fluid_motion_enabled":
{
"label": "Enable Fluid Motion",
"description": "When enabled tool paths are corrected for printers with smooth motion planners. Small movements that deviate from the general tool path direction are smoothed to improve fluid motions.",
"type": "bool",
"default_value": true
},
"meshfix_fluid_motion_shift_distance":
{
"label": "Fluid Motion Shift Distance",
"description": "Distance points are shifted to smooth the path",
"enabled": "meshfix_fluid_motion_enabled",
"type": "float",
"unit": "mm",
"default_value": 0.1,
"minimum_value": "0.01",
"maximum_value": "1"
},
"meshfix_fluid_motion_small_distance":
{
"label": "Fluid Motion Small Distance",
"description": "Distance points are shifted to smooth the path",
"enabled": "meshfix_fluid_motion_enabled",
"unit": "mm",
"type": "float",
"default_value": 0.01,
"minimum_value": "0.01",
"maximum_value": "0.1"
},
"meshfix_fluid_motion_angle":
{
"label": "Fluid Motion Angle",
"description": "If a toolpath-segment deviates more than this angle from the general motion it is smoothed.",
"enabled": "meshfix_fluid_motion_enabled",
"type": "float",
"unit": "\u00b0",
"default_value": 15,
"maximum_value": "90",
"minimum_value": "0",
"minimum_value_warning": "1",
"maximum_value_warning": "35"
}
}
},

View file

@ -5,13 +5,14 @@
"metadata":
{
"visible": true,
"author": "willuhmjs",
"platform": "kingroon_kp3s.stl",
"quality_definition": "kingroon_base",
"author": "willuhmjs"
"quality_definition": "kingroon_base"
},
"overrides":
{
"machine_acceleration": { "value": 1000 },
"machine_depth": { "default_value": 200 },
"machine_height": { "default_value": 200 },
"machine_max_acceleration_e": { "value": 1000 },
"machine_max_acceleration_x": { "value": 1000 },
@ -23,7 +24,7 @@
"machine_max_feedrate_z": { "value": 4 },
"machine_max_jerk_xy": { "value": 15 },
"machine_max_jerk_z": { "value": 0.4 },
"machine_name": { "default_value": "Kingroon KP3S" },
"machine_name": { "default_value": "Kingroon KP3S Pro" },
"machine_steps_per_mm_e": { "value": 764 },
"machine_steps_per_mm_x": { "value": 160 },
"machine_steps_per_mm_y": { "value": 160 },

View file

@ -0,0 +1,60 @@
{
"version": 2,
"name": "Pulse XE E-444M",
"inherits": "fdmprinter",
"metadata":
{
"visible": true,
"author": "Zwitch Guitars",
"manufacturer": "MatterHackers",
"file_formats": "text/x-gcode",
"first_start_actions": [ "MachineSettingsAction" ],
"has_machine_quality": false,
"has_materials": true,
"has_variants": false,
"machine_extruder_trains": { "0": "matterhackers_extruder" },
"preferred_material": "generic_pla",
"preferred_quality_type": "normal"
},
"overrides":
{
"adhesion_type": { "value": "skirt" },
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
"cool_min_layer_time": { "value": 10 },
"gantry_height": { "value": 23 },
"machine_acceleration": { "value": 1300 },
"machine_depth": { "default_value": 220 },
"machine_end_gcode": { "default_value": "M104 S0 ; turn off extruder\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y{machine_depth}; home X axis and push Y forward\nG28 Z0\nM84 ; disable motors" },
"machine_gcode_flavor": { "default_value": "Marlin" },
"machine_head_with_fans_polygon":
{
"default_value": [
[-28, 45],
[-28, -18],
[40, 45],
[40, -18]
]
},
"machine_heated_bed": { "default_value": true },
"machine_height": { "default_value": 215 },
"machine_max_feedrate_e": { "value": 75 },
"machine_max_feedrate_x": { "value": 300 },
"machine_max_feedrate_y": { "value": 300 },
"machine_max_feedrate_z": { "value": 30 },
"machine_name": { "default_value": "Pulse XE E-444M" },
"machine_start_gcode": { "default_value": "G21 ; set units to millimeters\nG90 ; use absolute positioning\nM82 ; absolute extrusion mode\nG28 ; home axes\nM104 S{material_print_temperature_layer_0} ; set extruder temp\nM140 S{material_bed_temperature_layer_0} ; set bed temp\nM190 S{material_bed_temperature_layer_0} ; wait for bed temp\nM109 S{material_print_temperature_layer_0} ; wait for extruder temp\nG29 ; mesh bed leveling\n\nG92 E0\nG1 X5 Y5 Z0.8 F1800\nG1 X100 Z0.3 E25 F900\nG92 E0\nG1 E-2 F2400" },
"machine_steps_per_mm_e": { "value": 415 },
"machine_steps_per_mm_x": { "value": 80 },
"machine_steps_per_mm_y": { "value": 80 },
"machine_steps_per_mm_z": { "value": 400 },
"machine_width": { "default_value": 250 },
"material_diameter": { "value": 1.75 },
"optimize_wall_printing_order": { "value": true },
"speed_layer_0": { "value": 20.0 },
"speed_print": { "value": 50 },
"top_bottom_thickness": { "value": "layer_height_0 + layer_height * 3" },
"travel_retract_before_outer_wall": { "value": true },
"wall_thickness": { "value": "line_width * 2" },
"z_seam_type": { "value": "back" }
}
}

View file

@ -57,7 +57,7 @@
[25, 49],
[25, -49],
[-25, -49],
[25, 49]
[-25, 49]
]
},
"machine_heated_bed": { "default_value": true },

View file

@ -54,7 +54,7 @@
[25, 49],
[25, -49],
[-25, -49],
[25, 49]
[-25, 49]
]
},
"machine_heated_bed": { "default_value": true },

View file

@ -51,7 +51,7 @@
[25, 49],
[25, -49],
[-25, -49],
[25, 49]
[-25, 49]
]
},
"machine_heated_bed": { "default_value": true },

View file

@ -111,7 +111,7 @@
"skin_angles": { "value": "[] if infill_pattern not in ['cross', 'cross_3d'] else [20, 110]" },
"skin_edge_support_thickness": { "value": "4 * layer_height if infill_sparse_density < 30 else 0" },
"skin_material_flow": { "value": "0.95 * material_flow" },
"skin_material_flow_layer_0": { "value": "0.9 * material_flow_layer_0" },
"skin_material_flow_layer_0": { "value": "95" },
"skin_monotonic": { "value": "roofing_layer_count == 0" },
"skin_overlap": { "value": "20" },
"speed_equalize_flow_width_factor": { "value": "110.0" },

View file

@ -41,7 +41,7 @@
0
],
"platform_texture": "UltimakerS3backplate.png",
"preferred_quality_type": "fast",
"preferred_quality_type": "draft",
"preferred_variant_name": "AA 0.4",
"supported_actions": [ "DiscoverUM3Action" ],
"supports_material_export": true,

View file

@ -38,7 +38,7 @@
-10
],
"platform_texture": "UltimakerS5backplate.png",
"preferred_quality_type": "fast",
"preferred_quality_type": "draft",
"preferred_variant_buildplate_name": "Glass",
"preferred_variant_name": "AA 0.4",
"supported_actions": [ "DiscoverUM3Action" ],

View file

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

View file

@ -0,0 +1,17 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_abs
quality_type = high
setting_version = 22
type = intent
variant = AA 0.4
[values]
speed_infill = 50
top_bottom_thickness = 1.05

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_abs
quality_type = fast
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_abs
quality_type = fast
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
acceleration_print = 2500
acceleration_wall_0 = 1000
inset_direction = inside_out
jerk_wall_0 = 20
speed_print = 50
speed_roofing = =math.ceil(speed_wall*(35/50))
speed_wall_0 = =math.ceil(speed_wall*(25/50))
top_bottom_thickness = =max(1.2 , layer_height * 6)

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_abs
quality_type = normal
setting_version = 22
type = intent
variant = AA 0.4
[values]
jerk_print = 30
speed_infill = =speed_print
speed_print = 30
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,17 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_abs
quality_type = normal
setting_version = 22
type = intent
variant = AA 0.4
[values]
speed_infill = 50
top_bottom_thickness = 1.05

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_abs
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,27 @@
[general]
definition = ultimaker_s3
name = Quick
version = 4
[metadata]
intent_category = quick
material = ultimaker_abs
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__gradual_flow_enabled = False
acceleration_wall_0 = 2000
gradual_infill_step_height = =4 * layer_height
gradual_infill_steps = 3
infill_sparse_density = 40
jerk_print = 30
jerk_wall_0 = 30
speed_print = 150
speed_wall = =speed_print
speed_wall_0 = 40
top_bottom_thickness = = 4 * layer_height
wall_thickness = =2 * line_width

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_abs
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
acceleration_print = 2500
acceleration_wall_0 = 1000
inset_direction = inside_out
jerk_wall_0 = 20
speed_print = 50
speed_roofing = =math.ceil(speed_wall*(35/50))
speed_wall_0 = =math.ceil(speed_wall*(25/50))
top_bottom_thickness = =max(1.2 , layer_height * 6)

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_petg
quality_type = fast
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_petg
quality_type = normal
setting_version = 22
type = intent
variant = AA 0.4
[values]
jerk_print = 30
speed_infill = =speed_print
speed_print = 30
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_petg
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,27 @@
[general]
definition = ultimaker_s3
name = Quick
version = 4
[metadata]
intent_category = quick
material = ultimaker_petg
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__gradual_flow_enabled = False
acceleration_wall_0 = 2000
gradual_infill_step_height = =4 * layer_height
gradual_infill_steps = 3
infill_sparse_density = 40
jerk_print = 30
jerk_wall_0 = 30
speed_print = 150
speed_wall = =speed_print
speed_wall_0 = 40
top_bottom_thickness = = 4 * layer_height
wall_thickness = =2 * line_width

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_petg
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
acceleration_print = 2500
acceleration_wall_0 = 1000
inset_direction = inside_out
jerk_wall_0 = 20
speed_print = 50
speed_roofing = =math.ceil(speed_wall*(35/50))
speed_wall_0 = =math.ceil(speed_wall*(25/50))
top_bottom_thickness = =max(1.2 , layer_height * 6)

View file

@ -0,0 +1,17 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_pla
quality_type = high
setting_version = 22
type = intent
variant = AA 0.4
[values]
speed_infill = 50
top_bottom_thickness = 1.05

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_pla
quality_type = fast
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_pla
quality_type = fast
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
acceleration_print = 2500
acceleration_wall_0 = 1000
inset_direction = inside_out
jerk_wall_0 = 20
speed_print = 50
speed_roofing = =math.ceil(speed_wall*(35/50))
speed_wall_0 = =math.ceil(speed_wall*(25/50))
top_bottom_thickness = =max(1.2 , layer_height * 6)

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_pla
quality_type = normal
setting_version = 22
type = intent
variant = AA 0.4
[values]
jerk_print = 30
speed_infill = =speed_print
speed_print = 30
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,17 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_pla
quality_type = normal
setting_version = 22
type = intent
variant = AA 0.4
[values]
speed_infill = 50
top_bottom_thickness = 1.05

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_pla
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,27 @@
[general]
definition = ultimaker_s3
name = Quick
version = 4
[metadata]
intent_category = quick
material = ultimaker_pla
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__gradual_flow_enabled = False
acceleration_wall_0 = 2000
gradual_infill_step_height = =4 * layer_height
gradual_infill_steps = 3
infill_sparse_density = 40
jerk_print = 30
jerk_wall_0 = 30
speed_print = 150
speed_wall = =speed_print
speed_wall_0 = 40
top_bottom_thickness = = 4 * layer_height
wall_thickness = =2 * line_width

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_pla
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
acceleration_print = 2500
acceleration_wall_0 = 1000
inset_direction = inside_out
jerk_wall_0 = 20
speed_print = 50
speed_roofing = =math.ceil(speed_wall*(35/50))
speed_wall_0 = =math.ceil(speed_wall*(25/50))
top_bottom_thickness = =max(1.2 , layer_height * 6)

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Quick
version = 4
[metadata]
intent_category = quick
is_experimental = True
material = ultimaker_pla
quality_type = verydraft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__gradual_flow_enabled = False
acceleration_wall_0 = 2000
gradual_infill_step_height = =4 * layer_height
gradual_infill_steps = 3
infill_sparse_density = 40
jerk_print = 30
jerk_wall_0 = 30
speed_print = 150
speed_wall = =speed_print
speed_wall_0 = 40
top_bottom_thickness = = 4 * layer_height
wall_thickness = =2 * line_width

View file

@ -0,0 +1,17 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_tough_pla
quality_type = high
setting_version = 22
type = intent
variant = AA 0.4
[values]
speed_infill = 50
top_bottom_thickness = 1.05

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_tough_pla
quality_type = fast
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_tough_pla
quality_type = fast
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
acceleration_print = 2500
acceleration_wall_0 = 1000
inset_direction = inside_out
jerk_wall_0 = 20
speed_print = 50
speed_roofing = =math.ceil(speed_wall*(35/50))
speed_wall_0 = =math.ceil(speed_wall*(25/50))
top_bottom_thickness = =max(1.2 , layer_height * 6)

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_tough_pla
quality_type = normal
setting_version = 22
type = intent
variant = AA 0.4
[values]
jerk_print = 30
speed_infill = =speed_print
speed_print = 30
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,17 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_tough_pla
quality_type = normal
setting_version = 22
type = intent
variant = AA 0.4
[values]
speed_infill = 50
top_bottom_thickness = 1.05

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_tough_pla
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,27 @@
[general]
definition = ultimaker_s3
name = Quick
version = 4
[metadata]
intent_category = quick
material = ultimaker_tough_pla
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__gradual_flow_enabled = False
acceleration_wall_0 = 2000
gradual_infill_step_height = =4 * layer_height
gradual_infill_steps = 3
infill_sparse_density = 40
jerk_print = 30
jerk_wall_0 = 30
speed_print = 150
speed_wall = =speed_print
speed_wall_0 = 40
top_bottom_thickness = = 4 * layer_height
wall_thickness = =2 * line_width

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_tough_pla
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
acceleration_print = 2500
acceleration_wall_0 = 1000
inset_direction = inside_out
jerk_wall_0 = 20
speed_print = 50
speed_roofing = =math.ceil(speed_wall*(35/50))
speed_wall_0 = =math.ceil(speed_wall*(25/50))
top_bottom_thickness = =max(1.2 , layer_height * 6)

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Quick
version = 4
[metadata]
intent_category = quick
is_experimental = True
material = ultimaker_tough_pla
quality_type = verydraft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__gradual_flow_enabled = False
acceleration_wall_0 = 2000
gradual_infill_step_height = =4 * layer_height
gradual_infill_steps = 3
infill_sparse_density = 40
jerk_print = 30
jerk_wall_0 = 30
speed_print = 150
speed_wall = =speed_print
speed_wall_0 = 40
top_bottom_thickness = = 4 * layer_height
wall_thickness = =2 * line_width

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_abs
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.8
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,26 @@
[general]
definition = ultimaker_s3
name = Quick
version = 4
[metadata]
intent_category = quick
material = ultimaker_abs
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.8
[values]
_plugin__curaenginegradualflow__0_1_0__gradual_flow_enabled = False
acceleration_wall_0 = 2000
gradual_infill_step_height = =4 * layer_height
gradual_infill_steps = 3
infill_sparse_density = 40
jerk_print = 30
jerk_wall_0 = 30
speed_wall = =speed_print
speed_wall_0 = 40
top_bottom_thickness = = 4 * layer_height
wall_thickness = =wall_line_width_0

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_abs
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.8
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
acceleration_print = 2500
acceleration_wall_0 = 1000
inset_direction = inside_out
jerk_wall_0 = 20
speed_print = 50
speed_roofing = =math.ceil(speed_wall*(35/50))
speed_wall_0 = =math.ceil(speed_wall*(25/50))
top_bottom_thickness = =max(1.2 , layer_height * 6)

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_petg
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.8
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,26 @@
[general]
definition = ultimaker_s3
name = Quick
version = 4
[metadata]
intent_category = quick
material = ultimaker_petg
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.8
[values]
_plugin__curaenginegradualflow__0_1_0__gradual_flow_enabled = False
acceleration_wall_0 = 2000
gradual_infill_step_height = =4 * layer_height
gradual_infill_steps = 3
infill_sparse_density = 40
jerk_print = 30
jerk_wall_0 = 30
speed_wall = =speed_print
speed_wall_0 = 40
top_bottom_thickness = = 4 * layer_height
wall_thickness = =wall_line_width_0

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_petg
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.8
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
acceleration_print = 2500
acceleration_wall_0 = 1000
inset_direction = inside_out
jerk_wall_0 = 20
speed_print = 50
speed_roofing = =math.ceil(speed_wall*(35/50))
speed_wall_0 = =math.ceil(speed_wall*(25/50))
top_bottom_thickness = =max(1.2 , layer_height * 6)

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_pla
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.8
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,26 @@
[general]
definition = ultimaker_s3
name = Quick
version = 4
[metadata]
intent_category = quick
material = ultimaker_pla
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.8
[values]
_plugin__curaenginegradualflow__0_1_0__gradual_flow_enabled = False
acceleration_wall_0 = 2000
gradual_infill_step_height = =4 * layer_height
gradual_infill_steps = 3
infill_sparse_density = 40
jerk_print = 30
jerk_wall_0 = 30
speed_wall = =speed_print
speed_wall_0 = 40
top_bottom_thickness = = 4 * layer_height
wall_thickness = =wall_line_width_0

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_pla
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.8
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
acceleration_print = 2500
acceleration_wall_0 = 1000
inset_direction = inside_out
jerk_wall_0 = 20
speed_print = 50
speed_roofing = =math.ceil(speed_wall*(35/50))
speed_wall_0 = =math.ceil(speed_wall*(25/50))
top_bottom_thickness = =max(1.2 , layer_height * 6)

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s3
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_tough_pla
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.8
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,26 @@
[general]
definition = ultimaker_s3
name = Quick
version = 4
[metadata]
intent_category = quick
material = ultimaker_tough_pla
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.8
[values]
_plugin__curaenginegradualflow__0_1_0__gradual_flow_enabled = False
acceleration_wall_0 = 2000
gradual_infill_step_height = =4 * layer_height
gradual_infill_steps = 3
infill_sparse_density = 40
jerk_print = 30
jerk_wall_0 = 30
speed_wall = =speed_print
speed_wall_0 = 40
top_bottom_thickness = = 4 * layer_height
wall_thickness = =wall_line_width_0

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s3
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_tough_pla
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.8
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
acceleration_print = 2500
acceleration_wall_0 = 1000
inset_direction = inside_out
jerk_wall_0 = 20
speed_print = 50
speed_roofing = =math.ceil(speed_wall*(35/50))
speed_wall_0 = =math.ceil(speed_wall*(25/50))
top_bottom_thickness = =max(1.2 , layer_height * 6)

View file

@ -0,0 +1,17 @@
[general]
definition = ultimaker_s5
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_abs
quality_type = high
setting_version = 22
type = intent
variant = AA 0.4
[values]
speed_infill = 50
top_bottom_thickness = 1.05

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s5
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_abs
quality_type = fast
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s5
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_abs
quality_type = fast
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
acceleration_print = 2500
acceleration_wall_0 = 1000
inset_direction = inside_out
jerk_wall_0 = 20
speed_print = 50
speed_roofing = =math.ceil(speed_wall*(35/50))
speed_wall_0 = =math.ceil(speed_wall*(25/50))
top_bottom_thickness = =max(1.2 , layer_height * 6)

View file

@ -0,0 +1,24 @@
[general]
definition = ultimaker_s5
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_abs
quality_type = normal
setting_version = 22
type = intent
variant = AA 0.4
[values]
jerk_print = 30
speed_infill = =speed_print
speed_print = 30
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

View file

@ -0,0 +1,17 @@
[general]
definition = ultimaker_s5
name = Visual
version = 4
[metadata]
intent_category = visual
material = ultimaker_abs
quality_type = normal
setting_version = 22
type = intent
variant = AA 0.4
[values]
speed_infill = 50
top_bottom_thickness = 1.05

View file

@ -0,0 +1,28 @@
[general]
definition = ultimaker_s5
name = Accurate
version = 4
[metadata]
intent_category = engineering
material = ultimaker_abs
quality_type = draft
setting_version = 22
type = intent
variant = AA 0.4
[values]
_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration = 1
infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'triangles'
infill_sparse_density = 20
jerk_print = 30
speed_infill = =speed_print
speed_print = 35
speed_roofing = =speed_topbottom
speed_topbottom = =speed_print
speed_wall = =speed_print
speed_wall_0 = =speed_wall
speed_wall_x = =speed_wall
top_bottom_thickness = =wall_thickness
wall_thickness = =line_width * 3

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