Merge branch 'main' into object-reload-refactor
23
.github/workflows/conan-package-create.yml
vendored
@ -119,18 +119,12 @@ jobs:
|
|||||||
sudo apt upgrade
|
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
|
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
|
- name: Install GCC-13 on ubuntu
|
||||||
if: ${{ startsWith(inputs.runs_on, 'ubuntu-22.04') }}
|
if: ${{ startsWith(inputs.runs_on, 'ubuntu') }}
|
||||||
run: |
|
run: |
|
||||||
sudo apt install g++-12 gcc-12 -y
|
sudo apt install g++-13 gcc-13 -y
|
||||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12
|
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13
|
||||||
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
|
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
- name: Create the default Conan profile
|
- name: Create the default Conan profile
|
||||||
run: conan profile new default --detect
|
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 }}"
|
run: conan config install https://github.com/Ultimaker/conan-config.git -a "-b ${{ inputs.conan_config_branch }}"
|
||||||
|
|
||||||
- name: Get Conan configuration
|
- name: Get Conan configuration
|
||||||
if: ${{ inputs.conan_config_branch == '' }}
|
run: |
|
||||||
run: conan config install https://github.com/Ultimaker/conan-config.git
|
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
|
- name: Add Cura private Artifactory remote
|
||||||
run: conan remote add cura-private https://ultimaker.jfrog.io/artifactory/api/conan/cura-private True
|
run: conan remote add cura-private https://ultimaker.jfrog.io/artifactory/api/conan/cura-private True
|
||||||
|
|
||||||
- name: Create the Packages
|
- 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)
|
- name: Upload the Package(s)
|
||||||
if: ${{ always() && inputs.conan_upload_community }}
|
if: ${{ always() && inputs.conan_upload_community }}
|
||||||
|
17
.github/workflows/conan-package.yml
vendored
@ -57,7 +57,7 @@ env:
|
|||||||
CONAN_LOGGING_LEVEL: ${{ inputs.conan_logging_level }}
|
CONAN_LOGGING_LEVEL: ${{ inputs.conan_logging_level }}
|
||||||
CONAN_NON_INTERACTIVE: 1
|
CONAN_NON_INTERACTIVE: 1
|
||||||
|
|
||||||
permissions: {}
|
permissions: { }
|
||||||
jobs:
|
jobs:
|
||||||
conan-recipe-version:
|
conan-recipe-version:
|
||||||
permissions:
|
permissions:
|
||||||
@ -103,18 +103,23 @@ jobs:
|
|||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt upgrade
|
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 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
|
- name: Install GCC-13
|
||||||
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
|
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
|
- name: Create the default Conan profile
|
||||||
run: conan profile new default --detect --force
|
run: conan profile new default --detect --force
|
||||||
|
|
||||||
- name: Get Conan configuration
|
- 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
|
- 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
|
- name: Create the latest alias
|
||||||
if: always()
|
if: always()
|
||||||
|
7
.github/workflows/conan-recipe-export.yml
vendored
@ -75,15 +75,16 @@ jobs:
|
|||||||
run: conan config install https://github.com/Ultimaker/conan-config.git -a "-b ${{ inputs.conan_config_branch }}"
|
run: conan config install https://github.com/Ultimaker/conan-config.git -a "-b ${{ inputs.conan_config_branch }}"
|
||||||
|
|
||||||
- name: Get Conan configuration
|
- name: Get Conan configuration
|
||||||
if: ${{ inputs.conan_config_branch == '' }}
|
run: |
|
||||||
run: conan config install https://github.com/Ultimaker/conan-config.git
|
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
|
- name: Add Cura private Artifactory remote
|
||||||
run: conan remote add cura-private https://ultimaker.jfrog.io/artifactory/api/conan/cura-private True
|
run: conan remote add cura-private https://ultimaker.jfrog.io/artifactory/api/conan/cura-private True
|
||||||
|
|
||||||
- name: Export the Package (binaries)
|
- name: Export the Package (binaries)
|
||||||
if: ${{ inputs.conan_export_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
|
- name: Export the Package
|
||||||
if: ${{ !inputs.conan_export_binaries }}
|
if: ${{ !inputs.conan_export_binaries }}
|
||||||
|
243
.github/workflows/installers.yml
vendored
@ -24,59 +24,244 @@ on:
|
|||||||
default: false
|
default: false
|
||||||
required: true
|
required: true
|
||||||
type: boolean
|
type: boolean
|
||||||
|
nightly:
|
||||||
|
description: 'Upload to nightly release'
|
||||||
|
default: false
|
||||||
|
required: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
schedule:
|
||||||
|
# Daily at 5:15 CET
|
||||||
|
- cron: '15 3 * * *'
|
||||||
|
|
||||||
|
env:
|
||||||
|
CURA_CONAN_VERSION: ${{ inputs.cura_conan_version || 'cura/latest@ultimaker/testing' }}
|
||||||
|
CONAN_ARGS: ${{ inputs.conan_args || '' }}
|
||||||
|
ENTERPRISE: ${{ inputs.enterprise || false }}
|
||||||
|
STAGING: ${{ inputs.staging || false }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
default-values:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
cura_conan_version: ${{ steps.default.outputs.cura_conan_version }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Output default values
|
||||||
|
id: default
|
||||||
|
shell: python
|
||||||
|
run: |
|
||||||
|
import os
|
||||||
|
cura_conan_version = "cura/latest@ultimaker/testing" if "${{ github.event.inputs.cura_conan_version }}" == "" else "${{ github.event.inputs.cura_conan_version }}"
|
||||||
|
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"cura_conan_version={cura_conan_version}\n")
|
||||||
|
|
||||||
windows-installer:
|
windows-installer:
|
||||||
uses: ./.github/workflows/windows.yml
|
uses: ./.github/workflows/windows.yml
|
||||||
|
needs: [ default-values ]
|
||||||
with:
|
with:
|
||||||
cura_conan_version: ${{ inputs.cura_conan_version }}
|
cura_conan_version: ${{ needs.default-values.outputs.cura_conan_version }}
|
||||||
conan_args: ${{ inputs.conan_args }}
|
conan_args: ${{ github.event.inputs.conan_args }}
|
||||||
enterprise: ${{ inputs.enterprise }}
|
enterprise: ${{ github.event.inputs.enterprise == 'true' }}
|
||||||
staging: ${{ inputs.staging }}
|
staging: ${{ github.event.inputs.staging == 'true' }}
|
||||||
architecture: X64
|
architecture: X64
|
||||||
operating_system: windows-2022
|
operating_system: windows-2022
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
linux-modern-installer:
|
linux-installer:
|
||||||
uses: ./.github/workflows/linux.yml
|
uses: ./.github/workflows/linux.yml
|
||||||
|
needs: [ default-values ]
|
||||||
with:
|
with:
|
||||||
cura_conan_version: ${{ inputs.cura_conan_version }}
|
cura_conan_version: ${{ needs.default-values.outputs.cura_conan_version }}
|
||||||
conan_args: ${{ inputs.conan_args }}
|
conan_args: ${{ github.event.inputs.conan_args }}
|
||||||
enterprise: ${{ inputs.enterprise }}
|
enterprise: ${{ github.event.inputs.enterprise == 'true' }}
|
||||||
staging: ${{ inputs.staging }}
|
staging: ${{ github.event.inputs.staging == 'true' }}
|
||||||
architecture: X64
|
architecture: X64
|
||||||
operating_system: ubuntu-22.04
|
operating_system: ubuntu-22.04
|
||||||
secrets: inherit
|
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:
|
macos-installer:
|
||||||
uses: ./.github/workflows/macos.yml
|
uses: ./.github/workflows/macos.yml
|
||||||
|
needs: [ default-values ]
|
||||||
with:
|
with:
|
||||||
cura_conan_version: ${{ inputs.cura_conan_version }}
|
cura_conan_version: ${{ needs.default-values.outputs.cura_conan_version }}
|
||||||
conan_args: ${{ inputs.conan_args }}
|
conan_args: ${{ github.event.inputs.conan_args }}
|
||||||
enterprise: ${{ inputs.enterprise }}
|
enterprise: ${{ github.event.inputs.enterprise == 'true' }}
|
||||||
staging: ${{ inputs.staging }}
|
staging: ${{ github.event.inputs.staging == 'true' }}
|
||||||
architecture: X64
|
architecture: X64
|
||||||
operating_system: macos-11.0
|
operating_system: macos-12
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
macos-arm-installer:
|
macos-arm-installer:
|
||||||
uses: ./.github/workflows/macos.yml
|
uses: ./.github/workflows/macos.yml
|
||||||
|
needs: [ default-values ]
|
||||||
with:
|
with:
|
||||||
cura_conan_version: ${{ inputs.cura_conan_version }}
|
cura_conan_version: ${{ needs.default-values.outputs.cura_conan_version }}
|
||||||
conan_args: ${{ inputs.conan_args }}
|
conan_args: ${{ github.event.inputs.conan_args }}
|
||||||
enterprise: ${{ inputs.enterprise }}
|
enterprise: ${{ github.event.inputs.enterprise == 'true' }}
|
||||||
staging: ${{ inputs.staging }}
|
staging: ${{ github.event.inputs.staging == 'true' }}
|
||||||
architecture: ARM64
|
architecture: ARM64
|
||||||
operating_system: self-hosted
|
operating_system: self-hosted
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
|
# Run and update nightly release when the nightly input is set to true or if the schedule is triggered
|
||||||
|
update-nightly-release:
|
||||||
|
if: ${{ inputs.nightly || github.event_name == 'schedule' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ windows-installer, linux-installer, macos-installer, macos-arm-installer ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# It's not necessary to download all three, but it does make sure we have at least one if an OS is skipped.
|
||||||
|
|
||||||
|
- name: Download the run info
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: linux-run-info
|
||||||
|
|
||||||
|
- name: Set the run info as environment variables
|
||||||
|
run: |
|
||||||
|
. run_info.sh
|
||||||
|
|
||||||
|
- name: Output the name file name and extension
|
||||||
|
id: filename
|
||||||
|
shell: python
|
||||||
|
run: |
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
enterprise = "-Enterprise" if "${{ github.event.inputs.enterprise }}" == "true" else ""
|
||||||
|
linux = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-linux-X64"
|
||||||
|
mac_x64_dmg = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-macos-X64"
|
||||||
|
mac_x64_pkg = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-macos-X64"
|
||||||
|
mac_arm_dmg = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-macos-ARM64"
|
||||||
|
mac_arm_pkg = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-macos-ARM64"
|
||||||
|
win_msi = installer_filename = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-win64-X64"
|
||||||
|
win_exe = installer_filename = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-win64-X64"
|
||||||
|
nightly_name = "UltiMaker-Cura-" + os.getenv('CURA_VERSION_FULL').split("+")[0]
|
||||||
|
nightly_creation_time = str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
|
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"LINUX={linux}\n")
|
||||||
|
f.writelines(f"MAC_X64_DMG={mac_x64_dmg}\n")
|
||||||
|
f.writelines(f"MAC_X64_PKG={mac_x64_pkg}\n")
|
||||||
|
f.writelines(f"MAC_ARM_DMG={mac_arm_dmg}\n")
|
||||||
|
f.writelines(f"MAC_ARM_PKG={mac_arm_pkg}\n")
|
||||||
|
f.writelines(f"WIN_MSI={win_msi}\n")
|
||||||
|
f.writelines(f"WIN_EXE={win_exe}\n")
|
||||||
|
f.writelines(f"NIGHTLY_NAME={nightly_name}\n")
|
||||||
|
f.writelines(f"NIGHTLY_TIME={nightly_creation_time}\n")
|
||||||
|
|
||||||
|
- name: Download linux installer jobs artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ${{ steps.filename.outputs.LINUX }}-AppImage
|
||||||
|
path: installers
|
||||||
|
|
||||||
|
- name: Download linux installer jobs asc artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ${{ steps.filename.outputs.LINUX }}-asc
|
||||||
|
path: installers
|
||||||
|
|
||||||
|
- name: Rename Linux installer to nightlies
|
||||||
|
run: |
|
||||||
|
mv installers/${{ steps.filename.outputs.LINUX }}.AppImage installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-linux-X64.AppImage
|
||||||
|
mv installers/${{ steps.filename.outputs.LINUX }}.AppImage.asc installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-linux-X64.AppImage.asc
|
||||||
|
|
||||||
|
- name: Update nightly release for Linux
|
||||||
|
run: |
|
||||||
|
gh release upload nightly installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-linux-X64.AppImage --clobber
|
||||||
|
gh release upload nightly installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-linux-X64.AppImage.asc --clobber
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Download win msi installer jobs artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ${{ steps.filename.outputs.WIN_MSI }}-msi
|
||||||
|
path: installers
|
||||||
|
|
||||||
|
- name: Download win exe installer jobs artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ${{ steps.filename.outputs.WIN_EXE }}-exe
|
||||||
|
path: installers
|
||||||
|
|
||||||
|
- name: Rename Windows installers to nightlies
|
||||||
|
run: |
|
||||||
|
mv installers/${{ steps.filename.outputs.WIN_MSI }}.msi installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-win64-X64.msi
|
||||||
|
mv installers/${{ steps.filename.outputs.WIN_EXE }}.exe installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-win64-X64.exe
|
||||||
|
|
||||||
|
- name: Update nightly release for Windows
|
||||||
|
run: |
|
||||||
|
gh release upload nightly installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-win64-X64.msi --clobber
|
||||||
|
gh release upload nightly installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-win64-X64.exe --clobber
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Download MacOS (X64) dmg installer jobs artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ${{ steps.filename.outputs.MAC_X64_DMG }}-dmg
|
||||||
|
path: installers
|
||||||
|
|
||||||
|
- name: Download MacOS (X64) pkg installer jobs artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ${{ steps.filename.outputs.MAC_X64_PKG }}-pkg
|
||||||
|
path: installers
|
||||||
|
|
||||||
|
- name: Rename MacOS (X64) installers to nightlies
|
||||||
|
run: |
|
||||||
|
mv installers/${{ steps.filename.outputs.MAC_X64_DMG }}.dmg installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-macos-X64.dmg
|
||||||
|
mv installers/${{ steps.filename.outputs.MAC_X64_PKG }}.pkg installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-macos-X64.pkg
|
||||||
|
|
||||||
|
- name: Update nightly release for MacOS (X64)
|
||||||
|
run: |
|
||||||
|
gh release upload nightly installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-macos-X64.dmg --clobber
|
||||||
|
gh release upload nightly installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-macos-X64.pkg --clobber
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Download MacOS (ARM-64) dmg installer jobs artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ${{ steps.filename.outputs.MAC_ARM_DMG }}-dmg
|
||||||
|
path: installers
|
||||||
|
|
||||||
|
- name: Download MacOS (ARM-64) pkg installer jobs artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ${{ steps.filename.outputs.MAC_ARM_PKG }}-pkg
|
||||||
|
path: installers
|
||||||
|
|
||||||
|
- name: Rename MacOS (ARM-64) installers to nightlies
|
||||||
|
run: |
|
||||||
|
mv installers/${{ steps.filename.outputs.MAC_ARM_DMG }}.dmg installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-macos-ARM64.dmg
|
||||||
|
mv installers/${{ steps.filename.outputs.MAC_ARM_PKG }}.pkg installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-macos-ARM64.pkg
|
||||||
|
|
||||||
|
- name: Update nightly release for MacOS (ARM-64)
|
||||||
|
run: |
|
||||||
|
gh release upload nightly installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-macos-ARM64.dmg --clobber
|
||||||
|
gh release upload nightly installers/${{ steps.filename.outputs.NIGHTLY_NAME }}-macos-ARM64.pkg --clobber
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Update nightly release description (with date)
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
gh release edit nightly --title "${{ steps.filename.outputs.NIGHTLY_NAME }}" --notes "Nightly release created on: ${{ steps.filename.outputs.NIGHTLY_TIME }}"
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
54
.github/workflows/linux.yml
vendored
@ -38,7 +38,7 @@ on:
|
|||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
- ubuntu-22.04
|
- ubuntu-22.04
|
||||||
- ubuntu-20.04
|
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
cura_conan_version:
|
cura_conan_version:
|
||||||
@ -119,23 +119,26 @@ jobs:
|
|||||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt upgrade
|
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
|
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 binutils coreutils desktop-file-utils fakeroot fuse libgdk-pixbuf2.0-dev patchelf squashfs-tools strace util-linux zsync -y
|
||||||
|
|
||||||
|
# Get the AppImage tool
|
||||||
wget --no-check-certificate --quiet https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O $GITHUB_WORKSPACE/appimagetool
|
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
|
chmod +x $GITHUB_WORKSPACE/appimagetool
|
||||||
echo "APPIMAGETOOL_LOCATION=$GITHUB_WORKSPACE/appimagetool" >> $GITHUB_ENV
|
echo "APPIMAGETOOL_LOCATION=$GITHUB_WORKSPACE/appimagetool" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Install GCC-12 on ubuntu-22.04
|
# Get the AppImage builder
|
||||||
if: ${{ startsWith(inputs.operating_system, 'ubuntu-22.04') }}
|
wget --no-check-certificate --quiet -O $GITHUB_WORKSPACE/appimage-builder-x86_64.AppImage https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage
|
||||||
run: |
|
chmod +x appimage-builder-x86_64.AppImage
|
||||||
sudo apt install g++-12 gcc-12 -y
|
echo "APPIMAGEBUILDER_LOCATION=$GITHUB_WORKSPACE/appimage-builder-x86_64.AppImage" >> $GITHUB_ENV
|
||||||
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
|
# Make sure these tools can be found on the path
|
||||||
if: ${{ startsWith(inputs.operating_system, 'ubuntu-20.04') }}
|
echo "$GITHUB_WORKSPACE" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Install GCC-13
|
||||||
run: |
|
run: |
|
||||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 10
|
sudo apt install g++-13 gcc-13 -y
|
||||||
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 10
|
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
|
- name: Create the default Conan profile
|
||||||
run: conan profile new default --detect --force
|
run: conan profile new default --detect --force
|
||||||
@ -186,9 +189,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
import os
|
import os
|
||||||
enterprise = "-Enterprise" if "${{ inputs.enterprise }}" == "true" else ""
|
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 }}"
|
installer_filename = f"UltiMaker-Cura-{os.getenv('CURA_VERSION_FULL')}{enterprise}-linux-${{ inputs.architecture }}"
|
||||||
output_env = os.environ["GITHUB_OUTPUT"]
|
output_env = os.environ["GITHUB_OUTPUT"]
|
||||||
content = ""
|
content = ""
|
||||||
@ -245,7 +245,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create the Linux AppImage (Bash)
|
- name: Create the Linux AppImage (Bash)
|
||||||
run: |
|
run: |
|
||||||
python ../cura_inst/packaging/AppImage/create_appimage.py ./UltiMaker-Cura $CURA_VERSION_FULL "${{ steps.filename.outputs.INSTALLER_FILENAME }}.AppImage"
|
python ../cura_inst/packaging/AppImage-builder/create_appimage.py ./UltiMaker-Cura $CURA_VERSION_FULL "${{ steps.filename.outputs.INSTALLER_FILENAME }}.AppImage"
|
||||||
chmod +x "${{ steps.filename.outputs.INSTALLER_FILENAME }}.AppImage"
|
chmod +x "${{ steps.filename.outputs.INSTALLER_FILENAME }}.AppImage"
|
||||||
working-directory: dist
|
working-directory: dist
|
||||||
|
|
||||||
@ -257,6 +257,28 @@ jobs:
|
|||||||
dist/${{ steps.filename.outputs.INSTALLER_FILENAME }}.AppImage
|
dist/${{ steps.filename.outputs.INSTALLER_FILENAME }}.AppImage
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
|
- name: Upload the asc
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ steps.filename.outputs.INSTALLER_FILENAME }}-asc
|
||||||
|
path: |
|
||||||
|
dist/${{ steps.filename.outputs.INSTALLER_FILENAME }}.AppImage.asc
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
|
- name: Write the run info
|
||||||
|
shell: python
|
||||||
|
run: |
|
||||||
|
import os
|
||||||
|
with open("run_info.sh", "w") as f:
|
||||||
|
f.writelines(f'echo "CURA_VERSION_FULL={os.environ["CURA_VERSION_FULL"]}" >> $GITHUB_ENV\n')
|
||||||
|
- name: Upload the run info
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: linux-run-info
|
||||||
|
path: |
|
||||||
|
run_info.sh
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
notify-export:
|
notify-export:
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
needs: [ cura-installer-create ]
|
needs: [ cura-installer-create ]
|
||||||
|
20
.github/workflows/macos.yml
vendored
@ -143,6 +143,9 @@ jobs:
|
|||||||
p12-file-base64: ${{ secrets.MACOS_CERT_INSTALLER_P12 }}
|
p12-file-base64: ${{ secrets.MACOS_CERT_INSTALLER_P12 }}
|
||||||
p12-password: ${{ secrets.MACOS_CERT_PASSPHRASE }}
|
p12-password: ${{ secrets.MACOS_CERT_PASSPHRASE }}
|
||||||
|
|
||||||
|
- name: Remove private Artifactory
|
||||||
|
run: conan remote remove cura-conan-private || true
|
||||||
|
|
||||||
- name: Get Conan configuration
|
- name: Get Conan configuration
|
||||||
run: |
|
run: |
|
||||||
conan config install https://github.com/Ultimaker/conan-config.git
|
conan config install https://github.com/Ultimaker/conan-config.git
|
||||||
@ -155,7 +158,7 @@ jobs:
|
|||||||
run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING --json "cura_inst/conan_install_info.json"
|
run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING --json "cura_inst/conan_install_info.json"
|
||||||
|
|
||||||
- name: Upload the Package(s)
|
- name: Upload the Package(s)
|
||||||
if: always()
|
if: ${{ inputs.operating_system != 'self-hosted' }}
|
||||||
run: |
|
run: |
|
||||||
conan upload "*" -r cura --all -c
|
conan upload "*" -r cura --all -c
|
||||||
|
|
||||||
@ -264,6 +267,21 @@ jobs:
|
|||||||
dist/${{ steps.filename.outputs.INSTALLER_FILENAME }}.pkg
|
dist/${{ steps.filename.outputs.INSTALLER_FILENAME }}.pkg
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
|
- name: Write the run info
|
||||||
|
shell: python
|
||||||
|
run: |
|
||||||
|
import os
|
||||||
|
with open("run_info.sh", "w") as f:
|
||||||
|
f.writelines(f'echo "CURA_VERSION_FULL={os.environ["CURA_VERSION_FULL"]}" >> $GITHUB_ENV\n')
|
||||||
|
|
||||||
|
- name: Upload the run info
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: macos-run-info
|
||||||
|
path: |
|
||||||
|
run_info.sh
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
|
|
||||||
notify-export:
|
notify-export:
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
conan==1.60.2
|
conan>=1.60.2,<2.0.0
|
||||||
sip
|
sip
|
||||||
|
12
.github/workflows/unit-test.yml
vendored
@ -115,14 +115,16 @@ jobs:
|
|||||||
sudo apt upgrade
|
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
|
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: |
|
run: |
|
||||||
sudo apt install g++-12 gcc-12 -y
|
sudo apt install g++-13 gcc-13 -y
|
||||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12
|
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13
|
||||||
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
|
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13
|
||||||
|
|
||||||
- name: Get Conan configuration
|
- 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
|
- name: Get Conan profile
|
||||||
run: conan profile new default --detect --force
|
run: conan profile new default --detect --force
|
||||||
|
15
.github/workflows/update-translation.yml
vendored
@ -44,7 +44,7 @@ jobs:
|
|||||||
- name: Setup Python and pip
|
- name: Setup Python and pip
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.10.x
|
python-version: 3.11.x
|
||||||
cache: pip
|
cache: pip
|
||||||
cache-dependency-path: .github/workflows/requirements-conan-package.txt
|
cache-dependency-path: .github/workflows/requirements-conan-package.txt
|
||||||
|
|
||||||
@ -62,15 +62,20 @@ jobs:
|
|||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt upgrade
|
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 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
|
- name: Install GCC-13
|
||||||
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
|
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
|
- name: Create the default Conan profile
|
||||||
run: conan profile new default --detect --force
|
run: conan profile new default --detect --force
|
||||||
|
|
||||||
- name: Get Conan configuration
|
- 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
|
- name: generate the files using Conan install
|
||||||
run: conan install . --build=missing --update -o cura:devtools=True
|
run: conan install . --build=missing --update -o cura:devtools=True
|
||||||
|
17
.github/workflows/windows.yml
vendored
@ -256,6 +256,23 @@ jobs:
|
|||||||
dist/${{steps.filename.outputs.INSTALLER_FILENAME }}.exe
|
dist/${{steps.filename.outputs.INSTALLER_FILENAME }}.exe
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
|
# NOTE: The extension is .sh, since this isn't going to build-environment, so not on the Win build image.
|
||||||
|
- name: Write the run info
|
||||||
|
shell: python
|
||||||
|
run: |
|
||||||
|
import os
|
||||||
|
with open("run_info.sh", "w") as f:
|
||||||
|
f.writelines(f'echo "CURA_VERSION_FULL={os.environ["CURA_VERSION_FULL"]}" >> $GITHUB_ENV\n')
|
||||||
|
|
||||||
|
# NOTE: The extension is .sh, since this isn't going to build-environment, so not on the Win build image.
|
||||||
|
- name: Upload the run info
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: windows-run-info
|
||||||
|
path: |
|
||||||
|
run_info.sh
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
notify-export:
|
notify-export:
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
needs: [ cura-installer-create ]
|
needs: [ cura-installer-create ]
|
||||||
|
4
.gitignore
vendored
@ -102,3 +102,7 @@ Ultimaker-Cura.spec
|
|||||||
.run/
|
.run/
|
||||||
/printer-linter/src/printerlinter.egg-info/
|
/printer-linter/src/printerlinter.egg-info/
|
||||||
/resources/qml/Dialogs/AboutDialogVersionsList.qml
|
/resources/qml/Dialogs/AboutDialogVersionsList.qml
|
||||||
|
/plugins/CuraEngineGradualFlow
|
||||||
|
/resources/bundled_packages/bundled_*.json
|
||||||
|
curaengine_plugin_gradual_flow
|
||||||
|
curaengine_plugin_gradual_flow.exe
|
||||||
|
@ -2,6 +2,7 @@ checks:
|
|||||||
diagnostic-mesh-file-extension: true
|
diagnostic-mesh-file-extension: true
|
||||||
diagnostic-mesh-file-size: true
|
diagnostic-mesh-file-size: true
|
||||||
diagnostic-definition-redundant-override: true
|
diagnostic-definition-redundant-override: true
|
||||||
|
diagnostic-resources-macos-app-directory-name: true
|
||||||
fixes:
|
fixes:
|
||||||
diagnostic-definition-redundant-override: true
|
diagnostic-definition-redundant-override: true
|
||||||
format:
|
format:
|
||||||
|
@ -59,10 +59,10 @@
|
|||||||
[Contributors]: https://github.com/Ultimaker/Cura/graphs/contributors
|
[Contributors]: https://github.com/Ultimaker/Cura/graphs/contributors
|
||||||
[PullRequests]: https://github.com/Ultimaker/Cura/pulls
|
[PullRequests]: https://github.com/Ultimaker/Cura/pulls
|
||||||
[Machines]: https://github.com/Ultimaker/Cura/wiki/Adding-new-machine-profiles-to-Cura
|
[Machines]: https://github.com/Ultimaker/Cura/wiki/Adding-new-machine-profiles-to-Cura
|
||||||
[Building]: https://github.com/Ultimaker/Cura/wiki/Running-Cura-from-Source
|
[Building]: https://github.com/Ultimaker/Cura/wiki/Getting-Started
|
||||||
[Localize]: https://github.com/Ultimaker/Cura/wiki/Translating-Cura
|
[Localize]: https://github.com/Ultimaker/Cura/wiki/Translating-Cura
|
||||||
[Settings]: https://github.com/Ultimaker/Cura/wiki/Cura-Settings
|
[Settings]: https://github.com/Ultimaker/Cura/wiki/Profiles-&-Settings
|
||||||
[Plugins]: https://github.com/Ultimaker/Cura/wiki/Plugin-Directory
|
[Plugins]: https://github.com/Ultimaker/Cura/wiki/Plugins-And-Packages
|
||||||
[Closed]: https://github.com/Ultimaker/Cura/issues?q=is%3Aissue+is%3Aclosed
|
[Closed]: https://github.com/Ultimaker/Cura/issues?q=is%3Aissue+is%3Aclosed
|
||||||
[Issues]: https://github.com/Ultimaker/Cura/issues
|
[Issues]: https://github.com/Ultimaker/Cura/issues
|
||||||
[Conan]: https://github.com/Ultimaker/Cura/actions/workflows/conan-package.yml
|
[Conan]: https://github.com/Ultimaker/Cura/actions/workflows/conan-package.yml
|
||||||
@ -90,7 +90,7 @@
|
|||||||
<!---------------------------------[ Buttons ]--------------------------------->
|
<!---------------------------------[ Buttons ]--------------------------------->
|
||||||
|
|
||||||
[Button Localize]: https://img.shields.io/badge/Help_Localize-e2467d?style=for-the-badge&logoColor=white&logo=GoogleTranslate
|
[Button Localize]: https://img.shields.io/badge/Help_Localize-e2467d?style=for-the-badge&logoColor=white&logo=GoogleTranslate
|
||||||
[Button Machines]: https://img.shields.io/badge/Adding_Machines-yellow?style=for-the-badge&logoColor=white&logo=CloudFoundry
|
[Button Machines]: https://img.shields.io/badge/Adding_Printers-yellow?style=for-the-badge&logoColor=white&logo=CloudFoundry
|
||||||
[Button Settings]: https://img.shields.io/badge/Configuration-00979D?style=for-the-badge&logoColor=white&logo=CodeReview
|
[Button Settings]: https://img.shields.io/badge/Configuration-00979D?style=for-the-badge&logoColor=white&logo=CodeReview
|
||||||
[Button Building]: https://img.shields.io/badge/Building_Cura-blue?style=for-the-badge&logoColor=white&logo=GitBook
|
[Button Building]: https://img.shields.io/badge/Building_Cura-blue?style=for-the-badge&logoColor=white&logo=GitBook
|
||||||
[Button Plugins]: https://img.shields.io/badge/Plugin_Usage-569A31?style=for-the-badge&logoColor=white&logo=ROS
|
[Button Plugins]: https://img.shields.io/badge/Plugin_Usage-569A31?style=for-the-badge&logoColor=white&logo=ROS
|
||||||
|
@ -19,6 +19,14 @@ pyinstaller:
|
|||||||
package: "cura"
|
package: "cura"
|
||||||
src: "plugins"
|
src: "plugins"
|
||||||
dst: "share/cura/plugins"
|
dst: "share/cura/plugins"
|
||||||
|
curaengine_gradual_flow_plugin:
|
||||||
|
package: "curaengine_plugin_gradual_flow"
|
||||||
|
src: "res/plugins/CuraEngineGradualFlow"
|
||||||
|
dst: "share/cura/plugins/CuraEngineGradualFlow"
|
||||||
|
curaengine_gradual_flow_plugin_bundled:
|
||||||
|
package: "curaengine_plugin_gradual_flow"
|
||||||
|
src: "res/bundled_packages"
|
||||||
|
dst: "share/cura/resources/bundled_packages"
|
||||||
cura_resources:
|
cura_resources:
|
||||||
package: "cura"
|
package: "cura"
|
||||||
src: "resources"
|
src: "resources"
|
||||||
@ -70,6 +78,11 @@ pyinstaller:
|
|||||||
src: "bin"
|
src: "bin"
|
||||||
dst: "."
|
dst: "."
|
||||||
binary: "CuraEngine"
|
binary: "CuraEngine"
|
||||||
|
curaengine_gradual_flow_plugin_service:
|
||||||
|
package: "curaengine_plugin_gradual_flow"
|
||||||
|
src: "bin"
|
||||||
|
dst: "."
|
||||||
|
binary: "curaengine_plugin_gradual_flow"
|
||||||
hiddenimports:
|
hiddenimports:
|
||||||
- "pySavitar"
|
- "pySavitar"
|
||||||
- "pyArcus"
|
- "pyArcus"
|
||||||
|
91
conanfile.py
@ -4,7 +4,7 @@ from pathlib import Path
|
|||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
|
||||||
from conan import ConanFile
|
from conan import ConanFile
|
||||||
from conan.tools.files import copy, rmdir, save, mkdir
|
from conan.tools.files import copy, rmdir, save, mkdir, rm
|
||||||
from conan.tools.microsoft import unix_path
|
from conan.tools.microsoft import unix_path
|
||||||
from conan.tools.env import VirtualRunEnv, Environment, VirtualBuildEnv
|
from conan.tools.env import VirtualRunEnv, Environment, VirtualBuildEnv
|
||||||
from conan.tools.scm import Version
|
from conan.tools.scm import Version
|
||||||
@ -21,12 +21,11 @@ class CuraConan(ConanFile):
|
|||||||
description = "3D printer / slicing GUI built on top of the Uranium framework"
|
description = "3D printer / slicing GUI built on top of the Uranium framework"
|
||||||
topics = ("conan", "python", "pyqt6", "qt", "qml", "3d-printing", "slicer")
|
topics = ("conan", "python", "pyqt6", "qt", "qml", "3d-printing", "slicer")
|
||||||
build_policy = "missing"
|
build_policy = "missing"
|
||||||
exports = "LICENSE*", "UltiMaker-Cura.spec.jinja", "CuraVersion.py.jinja", "AboutDialogVersionsList.qml.jinja"
|
exports = "LICENSE*", "*.jinja"
|
||||||
settings = "os", "compiler", "build_type", "arch"
|
settings = "os", "compiler", "build_type", "arch"
|
||||||
|
|
||||||
# FIXME: Remove specific branch once merged to main
|
# FIXME: Remove specific branch once merged to main
|
||||||
python_requires = "umbase/[>=0.1.7]@ultimaker/stable", "translationextractor/[>=2.1.1]@ultimaker/stable"
|
python_requires = "translationextractor/[>=2.1.1]@ultimaker/stable"
|
||||||
python_requires_extend = "umbase.UMBaseConanfile"
|
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
"enterprise": ["True", "False", "true", "false"], # Workaround for GH Action passing boolean as lowercase string
|
"enterprise": ["True", "False", "true", "false"], # Workaround for GH Action passing boolean as lowercase string
|
||||||
@ -49,7 +48,7 @@ class CuraConan(ConanFile):
|
|||||||
|
|
||||||
def set_version(self):
|
def set_version(self):
|
||||||
if not self.version:
|
if not self.version:
|
||||||
self.version = "5.5.0-alpha"
|
self.version = "5.6.0-alpha"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _pycharm_targets(self):
|
def _pycharm_targets(self):
|
||||||
@ -210,8 +209,8 @@ class CuraConan(ConanFile):
|
|||||||
src_path = os.path.join(self.source_folder, data["src"])
|
src_path = os.path.join(self.source_folder, data["src"])
|
||||||
else:
|
else:
|
||||||
src_path = os.path.join(self.deps_cpp_info[data["package"]].rootpath, data["src"])
|
src_path = os.path.join(self.deps_cpp_info[data["package"]].rootpath, data["src"])
|
||||||
elif "root" in data: # get the paths relative from the sourcefolder
|
elif "root" in data: # get the paths relative from the install folder
|
||||||
src_path = os.path.join(self.source_folder, data["root"], data["src"])
|
src_path = os.path.join(self.install_folder, data["root"], data["src"])
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
if Path(src_path).exists():
|
if Path(src_path).exists():
|
||||||
@ -222,7 +221,9 @@ class CuraConan(ConanFile):
|
|||||||
if "package" in binary: # get the paths from conan package
|
if "package" in binary: # get the paths from conan package
|
||||||
src_path = os.path.join(self.deps_cpp_info[binary["package"]].rootpath, binary["src"])
|
src_path = os.path.join(self.deps_cpp_info[binary["package"]].rootpath, binary["src"])
|
||||||
elif "root" in binary: # get the paths relative from the sourcefolder
|
elif "root" in binary: # get the paths relative from the sourcefolder
|
||||||
src_path = os.path.join(self.source_folder, binary["root"], binary["src"])
|
src_path = str(self.source_path.joinpath(binary["root"], binary["src"]))
|
||||||
|
if self.settings.os == "Windows":
|
||||||
|
src_path = src_path.replace("\\", "\\\\")
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
if not Path(src_path).exists():
|
if not Path(src_path).exists():
|
||||||
@ -294,6 +295,8 @@ class CuraConan(ConanFile):
|
|||||||
self.options["pynest2d"].shared = True
|
self.options["pynest2d"].shared = True
|
||||||
self.options["cpython"].shared = True
|
self.options["cpython"].shared = True
|
||||||
self.options["boost"].header_only = True
|
self.options["boost"].header_only = True
|
||||||
|
if self.settings.os == "Linux":
|
||||||
|
self.options["curaengine_grpc_definitions"].shared = True
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
version = self.conf_info.get("user.cura:version", default = self.version, check_type = str)
|
version = self.conf_info.get("user.cura:version", default = self.version, check_type = str)
|
||||||
@ -302,10 +305,13 @@ class CuraConan(ConanFile):
|
|||||||
|
|
||||||
def requirements(self):
|
def requirements(self):
|
||||||
self.requires("boost/1.82.0")
|
self.requires("boost/1.82.0")
|
||||||
self.requires("pyarcus/(latest)@ultimaker/cura_10951")
|
self.requires("curaengine_grpc_definitions/(latest)@ultimaker/testing")
|
||||||
|
self.requires("zlib/1.2.13")
|
||||||
|
self.requires("pyarcus/5.3.0")
|
||||||
self.requires("curaengine/(latest)@ultimaker/testing")
|
self.requires("curaengine/(latest)@ultimaker/testing")
|
||||||
self.requires("pysavitar/(latest)@ultimaker/cura_10951")
|
self.requires("pysavitar/5.3.0")
|
||||||
self.requires("pynest2d/(latest)@ultimaker/cura_10951")
|
self.requires("pynest2d/5.3.0")
|
||||||
|
self.requires("curaengine_plugin_gradual_flow/(latest)@ultimaker/testing")
|
||||||
self.requires("uranium/(latest)@ultimaker/testing")
|
self.requires("uranium/(latest)@ultimaker/testing")
|
||||||
self.requires("cura_binary_data/(latest)@ultimaker/testing")
|
self.requires("cura_binary_data/(latest)@ultimaker/testing")
|
||||||
self.requires("cpython/3.10.4")
|
self.requires("cpython/3.10.4")
|
||||||
@ -348,6 +354,13 @@ class CuraConan(ConanFile):
|
|||||||
copy(self, "CuraEngine.exe", curaengine.bindirs[0], self.source_folder, keep_path = False)
|
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(self, "CuraEngine", curaengine.bindirs[0], self.source_folder, keep_path = False)
|
||||||
|
|
||||||
|
# Copy the external plugins that we want to bundle with Cura
|
||||||
|
rmdir(self,str(self.source_path.joinpath("plugins", "CuraEngineGradualFlow")))
|
||||||
|
curaengine_plugin_gradual_flow = self.dependencies["curaengine_plugin_gradual_flow"].cpp_info
|
||||||
|
copy(self, "*", curaengine_plugin_gradual_flow.resdirs[0], str(self.source_path.joinpath("plugins", "CuraEngineGradualFlow")), keep_path = True)
|
||||||
|
copy(self, "*", curaengine_plugin_gradual_flow.bindirs[0], self.source_folder, keep_path = False)
|
||||||
|
copy(self, "bundled_*.json", curaengine_plugin_gradual_flow.resdirs[1], str(self.source_path.joinpath("resources", "bundled_packages")), keep_path = False)
|
||||||
|
|
||||||
# Copy resources of cura_binary_data
|
# Copy resources of cura_binary_data
|
||||||
cura_binary_data = self.dependencies["cura_binary_data"].cpp_info
|
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[0], str(self._share_dir.joinpath("cura")), keep_path = True)
|
||||||
@ -385,10 +398,10 @@ class CuraConan(ConanFile):
|
|||||||
vb = VirtualBuildEnv(self)
|
vb = VirtualBuildEnv(self)
|
||||||
vb.generate()
|
vb.generate()
|
||||||
|
|
||||||
# FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement
|
# # FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement
|
||||||
cpp_info = self.dependencies["gettext"].cpp_info
|
# cpp_info = self.dependencies["gettext"].cpp_info
|
||||||
pot = self.python_requires["translationextractor"].module.ExtractTranslations(self, cpp_info.bindirs[0])
|
# pot = self.python_requires["translationextractor"].module.ExtractTranslations(self, cpp_info.bindirs[0])
|
||||||
pot.generate()
|
# pot.generate()
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
if self.options.devtools:
|
if self.options.devtools:
|
||||||
@ -402,56 +415,20 @@ class CuraConan(ConanFile):
|
|||||||
self.run(f"{cpp_info.bindirs[0]}/msgfmt {po_file} -o {mo_file} -f", env="conanbuild", ignore_errors=True)
|
self.run(f"{cpp_info.bindirs[0]}/msgfmt {po_file} -o {mo_file} -f", env="conanbuild", ignore_errors=True)
|
||||||
|
|
||||||
def deploy(self):
|
def deploy(self):
|
||||||
# Copy CuraEngine.exe to bindirs of Virtual Python Environment
|
copy(self, "*", os.path.join(self.package_folder, self.cpp.package.resdirs[2]), os.path.join(self.install_folder, "packaging"), keep_path = True)
|
||||||
curaengine = self.dependencies["curaengine"].cpp_info
|
|
||||||
copy(self, "CuraEngine.exe", curaengine.bindirs[0], str(self._base_dir), keep_path = False)
|
|
||||||
copy(self, "CuraEngine", curaengine.bindirs[0], str(self._base_dir), keep_path = False)
|
|
||||||
|
|
||||||
# Copy resources of Cura (keep folder structure)
|
# Copy resources of Cura (keep folder structure) needed by pyinstaller to determine the module structure
|
||||||
copy(self, "*", os.path.join(self.package_folder, self.cpp_info.bindirs[0]), str(self._base_dir), keep_path = False)
|
copy(self, "*", os.path.join(self.package_folder, self.cpp_info.bindirs[0]), str(self._base_dir), keep_path = False)
|
||||||
copy(self, "*", os.path.join(self.package_folder, self.cpp_info.libdirs[0]), str(self._site_packages.joinpath("cura")), keep_path = True)
|
copy(self, "*", os.path.join(self.package_folder, self.cpp_info.libdirs[0]), str(self._site_packages.joinpath("cura")), keep_path = True)
|
||||||
copy(self, "*", os.path.join(self.package_folder, self.cpp_info.resdirs[0]), str(self._share_dir.joinpath("cura", "resources")), keep_path = True)
|
copy(self, "*", os.path.join(self.package_folder, self.cpp_info.resdirs[0]), str(self._share_dir.joinpath("cura", "resources")), keep_path = True)
|
||||||
copy(self, "*", os.path.join(self.package_folder, self.cpp_info.resdirs[1]), str(self._share_dir.joinpath("cura", "plugins")), keep_path = True)
|
copy(self, "*", os.path.join(self.package_folder, self.cpp_info.resdirs[1]), str(self._share_dir.joinpath("cura", "plugins")), keep_path = True)
|
||||||
|
|
||||||
# Copy materials (flat)
|
|
||||||
fdm_materials = self.dependencies["fdm_materials"].cpp_info
|
|
||||||
copy(self, "*", fdm_materials.resdirs[0], str(self._share_dir.joinpath("cura")))
|
|
||||||
|
|
||||||
# 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")))
|
|
||||||
|
|
||||||
# Copy resources of Uranium (keep folder structure)
|
# Copy resources of Uranium (keep folder structure)
|
||||||
uranium = self.dependencies["uranium"].cpp_info
|
uranium = self.dependencies["uranium"].cpp_info
|
||||||
copy(self, "*", uranium.resdirs[0], str(self._share_dir.joinpath("uranium", "resources")), keep_path = True)
|
copy(self, "*", uranium.resdirs[0], str(self._share_dir.joinpath("uranium", "resources")), keep_path = True)
|
||||||
copy(self, "*", uranium.resdirs[1], str(self._share_dir.joinpath("uranium", "plugins")), keep_path = True)
|
copy(self, "*", uranium.resdirs[1], str(self._share_dir.joinpath("uranium", "plugins")), keep_path = True)
|
||||||
copy(self, "*", uranium.libdirs[0], str(self._site_packages.joinpath("UM")), keep_path = True)
|
copy(self, "*", uranium.libdirs[0], str(self._site_packages.joinpath("UM")), keep_path = True)
|
||||||
|
|
||||||
# TODO: figure out if this is still needed
|
|
||||||
copy(self, "*", os.path.join(uranium.libdirs[0], "Qt", "qml", "UM"), str(self._site_packages.joinpath("PyQt6", "Qt6", "qml", "UM")), keep_path = True)
|
|
||||||
|
|
||||||
# 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 packaging scripts
|
|
||||||
copy(self, "*", os.path.join(self.package_folder, self.cpp_info.resdirs[2]), str(self._base_dir.joinpath("packaging")), keep_path = True)
|
|
||||||
|
|
||||||
# Copy requirements.txt's
|
|
||||||
copy(self, "*.txt", os.path.join(self.package_folder, self.cpp_info.resdirs[-1]), str(self._base_dir.joinpath("pip_requirements")), keep_path = False)
|
|
||||||
|
|
||||||
# Generate the GitHub Action version info Environment
|
# Generate the GitHub Action version info Environment
|
||||||
version = self.conf_info.get("user.cura:version", default = self.version, check_type = str)
|
version = self.conf_info.get("user.cura:version", default = self.version, check_type = str)
|
||||||
cura_version = Version(version)
|
cura_version = Version(version)
|
||||||
@ -482,7 +459,6 @@ echo "CURA_APP_NAME={{ cura_app_name }}" >> ${{ env_prefix }}GITHUB_ENV
|
|||||||
icon_path = "'{}'".format(os.path.join(self.package_folder, self.cpp_info.resdirs[2], self.conan_data["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"),
|
icon_path = "'{}'".format(os.path.join(self.package_folder, self.cpp_info.resdirs[2], self.conan_data["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"),
|
||||||
entitlements_file = entitlements_file if self.settings.os == "Macos" else "None")
|
entitlements_file = entitlements_file if self.settings.os == "Macos" else "None")
|
||||||
|
|
||||||
|
|
||||||
def package(self):
|
def package(self):
|
||||||
copy(self, "cura_app.py", src = self.source_folder, dst = os.path.join(self.package_folder, self.cpp.package.bindirs[0]))
|
copy(self, "cura_app.py", src = self.source_folder, dst = os.path.join(self.package_folder, self.cpp.package.bindirs[0]))
|
||||||
copy(self, "*", src = os.path.join(self.source_folder, "cura"), dst = os.path.join(self.package_folder, self.cpp.package.libdirs[0]))
|
copy(self, "*", src = os.path.join(self.source_folder, "cura"), dst = os.path.join(self.package_folder, self.cpp.package.libdirs[0]))
|
||||||
@ -492,6 +468,13 @@ echo "CURA_APP_NAME={{ cura_app_name }}" >> ${{ env_prefix }}GITHUB_ENV
|
|||||||
copy(self, "requirement*.txt", src = self.source_folder, dst = os.path.join(self.package_folder, self.cpp.package.resdirs[-1]))
|
copy(self, "requirement*.txt", src = self.source_folder, dst = os.path.join(self.package_folder, self.cpp.package.resdirs[-1]))
|
||||||
copy(self, "*", src = os.path.join(self.source_folder, "packaging"), dst = os.path.join(self.package_folder, self.cpp.package.resdirs[2]))
|
copy(self, "*", src = os.path.join(self.source_folder, "packaging"), dst = os.path.join(self.package_folder, self.cpp.package.resdirs[2]))
|
||||||
|
|
||||||
|
# Remove the CuraEngineGradualFlow plugin from the package
|
||||||
|
rmdir(self, os.path.join(self.package_folder, self.cpp.package.resdirs[1], "CuraEngineGradualFlow"))
|
||||||
|
rm(self, "bundled_*.json", os.path.join(self.package_folder, self.cpp.package.resdirs[0], "bundled_packages"), recursive = False)
|
||||||
|
|
||||||
|
# Remove the fdm_materials from the package
|
||||||
|
rmdir(self, os.path.join(self.package_folder, self.cpp.package.resdirs[0], "materials"))
|
||||||
|
|
||||||
def package_info(self):
|
def package_info(self):
|
||||||
self.user_info.pip_requirements = "requirements.txt"
|
self.user_info.pip_requirements = "requirements.txt"
|
||||||
self.user_info.pip_requirements_git = "requirements-ultimaker.txt"
|
self.user_info.pip_requirements_git = "requirements-ultimaker.txt"
|
||||||
|
@ -14,7 +14,7 @@ DEFAULT_CURA_LATEST_URL = "https://software.ultimaker.com/latest.json"
|
|||||||
# Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for
|
# Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for
|
||||||
# example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the
|
# example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the
|
||||||
# CuraVersion.py.in template.
|
# CuraVersion.py.in template.
|
||||||
CuraSDKVersion = "8.4.0"
|
CuraSDKVersion = "8.5.0"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cura.CuraVersion import CuraLatestURL
|
from cura.CuraVersion import CuraLatestURL
|
||||||
|
@ -8,17 +8,20 @@ from UM.Logger import Logger
|
|||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.i18n import i18nCatalog
|
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")
|
i18n_catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
class ArrangeObjectsJob(Job):
|
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__()
|
super().__init__()
|
||||||
self._nodes = nodes
|
self._nodes = nodes
|
||||||
self._fixed_nodes = fixed_nodes
|
self._fixed_nodes = fixed_nodes
|
||||||
self._min_offset = min_offset
|
self._min_offset = min_offset
|
||||||
|
self._grid_arrange = grid_arrange
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
found_solution_for_all = False
|
found_solution_for_all = False
|
||||||
@ -29,10 +32,18 @@ class ArrangeObjectsJob(Job):
|
|||||||
title = i18n_catalog.i18nc("@info:title", "Finding Location"))
|
title = i18n_catalog.i18nc("@info:title", "Finding Location"))
|
||||||
status_message.show()
|
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:
|
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
|
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()
|
status_message.hide()
|
||||||
|
|
||||||
|
27
cura/Arranging/Arranger.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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)
|
||||||
|
grouped_operation.push()
|
||||||
|
return not_fit_count == 0
|
347
cura/Arranging/GridArrange.py
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
import math
|
||||||
|
from typing import List, TYPE_CHECKING, Tuple, Set, Union
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
from cura.BuildVolume import BuildVolume
|
||||||
|
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||||
|
from UM.Math.Polygon import Polygon
|
||||||
|
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, this assures that new objects will
|
||||||
|
# be placed on integer offsets from each other
|
||||||
|
grid_precision = 10 # 1cm
|
||||||
|
rounded_grid_width = math.ceil(self._grid_width / grid_precision) * grid_precision
|
||||||
|
rounded_grid_height = math.ceil(self._grid_height / grid_precision) * grid_precision
|
||||||
|
|
||||||
|
# The space added by the "grid precision rounding up" of the grid size
|
||||||
|
self._grid_round_margin_x = rounded_grid_width - self._grid_width
|
||||||
|
self._grid_round_margin_y = rounded_grid_height - self._grid_height
|
||||||
|
|
||||||
|
self._grid_width = rounded_grid_width
|
||||||
|
self._grid_height = rounded_grid_height
|
||||||
|
|
||||||
|
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._intersectingGridIdxInclusive(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,
|
||||||
|
bounds: Union[AxisAlignedBox, Polygon],
|
||||||
|
*,
|
||||||
|
margin_x: float = 0.0,
|
||||||
|
margin_y: float = 0.0
|
||||||
|
) -> Tuple[float, float, float, float]:
|
||||||
|
if isinstance(bounds, AxisAlignedBox):
|
||||||
|
coord_x1 = bounds.left - margin_x
|
||||||
|
coord_x2 = bounds.right + margin_x
|
||||||
|
coord_y1 = bounds.back - margin_y
|
||||||
|
coord_y2 = bounds.front + margin_y
|
||||||
|
elif isinstance(bounds, Polygon):
|
||||||
|
coord_x1 = float('inf')
|
||||||
|
coord_y1 = float('inf')
|
||||||
|
coord_x2 = float('-inf')
|
||||||
|
coord_y2 = float('-inf')
|
||||||
|
for x, y in bounds.getPoints():
|
||||||
|
coord_x1 = min(coord_x1, x)
|
||||||
|
coord_y1 = min(coord_y1, y)
|
||||||
|
coord_x2 = max(coord_x2, x)
|
||||||
|
coord_y2 = max(coord_y2, y)
|
||||||
|
else:
|
||||||
|
raise TypeError("bounds must be either an AxisAlignedBox or a Polygon")
|
||||||
|
|
||||||
|
coord_x1 -= margin_x
|
||||||
|
coord_x2 += margin_x
|
||||||
|
coord_y1 -= margin_y
|
||||||
|
coord_y2 += margin_y
|
||||||
|
|
||||||
|
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 _intersectingGridIdxInclusive(self, bounds: Union[AxisAlignedBox, Polygon]) -> Set[Tuple[int, int]]:
|
||||||
|
grid_x1, grid_y1, grid_x2, grid_y2 = self._getGridCornerPoints(
|
||||||
|
bounds,
|
||||||
|
margin_x=-(self._margin_x + self._grid_round_margin_x) * 0.5,
|
||||||
|
margin_y=-(self._margin_y + self._grid_round_margin_y) * 0.5,
|
||||||
|
)
|
||||||
|
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, bounds: Union[AxisAlignedBox, Polygon]) -> Set[Tuple[int, int]]:
|
||||||
|
grid_x1, grid_y1, grid_x2, grid_y2 = self._getGridCornerPoints(
|
||||||
|
bounds,
|
||||||
|
margin_x=(self._margin_x + self._grid_round_margin_x) * 0.5,
|
||||||
|
margin_y=(self._margin_y + self._grid_round_margin_y) * 0.5,
|
||||||
|
)
|
||||||
|
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
|
@ -6,6 +6,7 @@ from pynest2d import Point, Box, Item, NfpConfig, nest
|
|||||||
from typing import List, TYPE_CHECKING, Optional, Tuple
|
from typing import List, TYPE_CHECKING, Optional, Tuple
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
from UM.Decorators import deprecated
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Math.Matrix import Matrix
|
from UM.Math.Matrix import Matrix
|
||||||
from UM.Math.Polygon import Polygon
|
from UM.Math.Polygon import Polygon
|
||||||
@ -15,46 +16,56 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
|||||||
from UM.Operations.GroupedOperation import GroupedOperation
|
from UM.Operations.GroupedOperation import GroupedOperation
|
||||||
from UM.Operations.RotateOperation import RotateOperation
|
from UM.Operations.RotateOperation import RotateOperation
|
||||||
from UM.Operations.TranslateOperation import TranslateOperation
|
from UM.Operations.TranslateOperation import TranslateOperation
|
||||||
|
from cura.Arranging.Arranger import Arranger
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from cura.BuildVolume import BuildVolume
|
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 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 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
|
:param fixed_nodes: List of nods that should not be moved, but should be used when deciding where the others nodes
|
||||||
are placed.
|
are placed.
|
||||||
:param factor: The library that we use is int based. This factor defines how accurate we want it to be.
|
: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
|
||||||
: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
|
|
||||||
"""
|
"""
|
||||||
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()
|
def findNodePlacement(self) -> Tuple[bool, List[Item]]:
|
||||||
machine_depth = build_volume.getDepth()
|
spacing = int(1.5 * self._factor) # 1.5mm spacing.
|
||||||
build_plate_bounding_box = Box(int(machine_width * factor), int(machine_depth * factor))
|
|
||||||
|
|
||||||
if fixed_nodes is None:
|
machine_width = self._build_volume.getWidth()
|
||||||
fixed_nodes = []
|
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
|
# Add all the items we want to arrange
|
||||||
node_items = []
|
node_items = []
|
||||||
for node in nodes_to_arrange:
|
for node in self._nodes_to_arrange:
|
||||||
hull_polygon = node.callDecoration("getConvexHull")
|
hull_polygon = node.callDecoration("getConvexHull")
|
||||||
if not hull_polygon or hull_polygon.getPoints is None:
|
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()))
|
Logger.log("w", "Object {} cannot be arranged because it has no convex hull.".format(node.getName()))
|
||||||
continue
|
continue
|
||||||
converted_points = []
|
converted_points = []
|
||||||
for point in hull_polygon.getPoints():
|
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 = Item(converted_points)
|
||||||
node_items.append(item)
|
node_items.append(item)
|
||||||
|
|
||||||
@ -68,7 +79,7 @@ def findNodePlacement(nodes_to_arrange: List["SceneNode"], build_volume: "BuildV
|
|||||||
[half_machine_width, half_machine_depth]
|
[half_machine_width, half_machine_depth]
|
||||||
], numpy.float32))
|
], numpy.float32))
|
||||||
|
|
||||||
disallowed_areas = build_volume.getDisallowedAreas()
|
disallowed_areas = self._build_volume.getDisallowedAreas()
|
||||||
num_disallowed_areas_added = 0
|
num_disallowed_areas_added = 0
|
||||||
for area in disallowed_areas:
|
for area in disallowed_areas:
|
||||||
converted_points = []
|
converted_points = []
|
||||||
@ -76,22 +87,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)
|
# Clip the disallowed areas so that they don't overlap the bounding box (The arranger chokes otherwise)
|
||||||
clipped_area = area.intersectionConvexHulls(build_plate_polygon)
|
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():
|
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 = Item(converted_points)
|
||||||
disallowed_area.markAsDisallowedAreaInBin(0)
|
disallowed_area.markAsDisallowedAreaInBin(0)
|
||||||
node_items.append(disallowed_area)
|
node_items.append(disallowed_area)
|
||||||
num_disallowed_areas_added += 1
|
num_disallowed_areas_added += 1
|
||||||
|
|
||||||
for node in fixed_nodes:
|
for node in self._fixed_nodes:
|
||||||
converted_points = []
|
converted_points = []
|
||||||
hull_polygon = node.callDecoration("getConvexHull")
|
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():
|
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 = Item(converted_points)
|
||||||
item.markAsFixedInBin(0)
|
item.markAsFixedInBin(0)
|
||||||
node_items.append(item)
|
node_items.append(item)
|
||||||
@ -100,6 +113,8 @@ def findNodePlacement(nodes_to_arrange: List["SceneNode"], build_volume: "BuildV
|
|||||||
config = NfpConfig()
|
config = NfpConfig()
|
||||||
config.accuracy = 1.0
|
config.accuracy = 1.0
|
||||||
config.alignment = NfpConfig.Alignment.DONT_ALIGN
|
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)
|
num_bins = nest(node_items, build_plate_bounding_box, spacing, config)
|
||||||
|
|
||||||
@ -110,18 +125,13 @@ def findNodePlacement(nodes_to_arrange: List["SceneNode"], build_volume: "BuildV
|
|||||||
|
|
||||||
return found_solution_for_all, node_items
|
return found_solution_for_all, node_items
|
||||||
|
|
||||||
|
def createGroupOperationForArrange(self, add_new_nodes_in_scene: bool = False) -> Tuple[GroupedOperation, int]:
|
||||||
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]:
|
|
||||||
scene_root = Application.getInstance().getController().getScene().getRoot()
|
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
|
not_fit_count = 0
|
||||||
grouped_operation = GroupedOperation()
|
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:
|
if add_new_nodes_in_scene:
|
||||||
grouped_operation.addOperation(AddSceneNodeOperation(node, scene_root))
|
grouped_operation.addOperation(AddSceneNodeOperation(node, scene_root))
|
||||||
|
|
||||||
@ -130,8 +140,9 @@ def createGroupOperationForArrange(nodes_to_arrange: List["SceneNode"],
|
|||||||
rotation_matrix = Matrix()
|
rotation_matrix = Matrix()
|
||||||
rotation_matrix.setByRotationAxis(node_item.rotation(), Vector(0, -1, 0))
|
rotation_matrix.setByRotationAxis(node_item.rotation(), Vector(0, -1, 0))
|
||||||
grouped_operation.addOperation(RotateOperation(node, Quaternion.fromMatrix(rotation_matrix)))
|
grouped_operation.addOperation(RotateOperation(node, Quaternion.fromMatrix(rotation_matrix)))
|
||||||
grouped_operation.addOperation(TranslateOperation(node, Vector(node_item.translation().x() / factor, 0,
|
grouped_operation.addOperation(
|
||||||
node_item.translation().y() / factor)))
|
TranslateOperation(node, Vector(node_item.translation().x() / self._factor, 0,
|
||||||
|
node_item.translation().y() / self._factor)))
|
||||||
else:
|
else:
|
||||||
# We didn't find a spot
|
# We didn't find a spot
|
||||||
grouped_operation.addOperation(
|
grouped_operation.addOperation(
|
||||||
@ -141,23 +152,28 @@ def createGroupOperationForArrange(nodes_to_arrange: List["SceneNode"],
|
|||||||
return grouped_operation, not_fit_count
|
return grouped_operation, not_fit_count
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated("Use the Nest2DArrange class instead")
|
||||||
|
def findNodePlacement(nodes_to_arrange: List["SceneNode"], build_volume: "BuildVolume",
|
||||||
|
fixed_nodes: Optional[List["SceneNode"]] = None, factor=10000) -> Tuple[bool, List[Item]]:
|
||||||
|
arranger = Nest2DArrange(nodes_to_arrange, build_volume, fixed_nodes, factor=factor)
|
||||||
|
return arranger.findNodePlacement()
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated("Use the Nest2DArrange class instead")
|
||||||
|
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]:
|
||||||
|
arranger = Nest2DArrange(nodes_to_arrange, build_volume, fixed_nodes, factor=factor)
|
||||||
|
return arranger.createGroupOperationForArrange(add_new_nodes_in_scene=add_new_nodes_in_scene)
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated("Use the Nest2DArrange class instead")
|
||||||
def arrange(nodes_to_arrange: List["SceneNode"],
|
def arrange(nodes_to_arrange: List["SceneNode"],
|
||||||
build_volume: "BuildVolume",
|
build_volume: "BuildVolume",
|
||||||
fixed_nodes: Optional[List["SceneNode"]] = None,
|
fixed_nodes: Optional[List["SceneNode"]] = None,
|
||||||
factor = 10000,
|
factor=10000,
|
||||||
add_new_nodes_in_scene: bool = False) -> bool:
|
add_new_nodes_in_scene: bool = False) -> bool:
|
||||||
"""
|
arranger = Nest2DArrange(nodes_to_arrange, build_volume, fixed_nodes, factor=factor)
|
||||||
Find placement for a set of scene nodes, and move them by using a single grouped operation.
|
return arranger.arrange(add_new_nodes_in_scene=add_new_nodes_in_scene)
|
||||||
: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
|
|
||||||
|
141
cura/BackendPlugin.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# Copyright (c) 2023 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
import socket
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Message import Message
|
||||||
|
from UM.Settings.AdditionalSettingDefinitionsAppender import AdditionalSettingDefinitionsAppender
|
||||||
|
from UM.PluginObject import PluginObject
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
from UM.Platform import Platform
|
||||||
|
from UM.Resources import Resources
|
||||||
|
|
||||||
|
|
||||||
|
class BackendPlugin(AdditionalSettingDefinitionsAppender, PluginObject):
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
settings_catalog = i18nCatalog("fdmprinter.def.json")
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__(self.settings_catalog)
|
||||||
|
self.__port: int = 0
|
||||||
|
self._plugin_address: str = "127.0.0.1"
|
||||||
|
self._plugin_command: Optional[List[str]] = None
|
||||||
|
self._process = None
|
||||||
|
self._is_running = False
|
||||||
|
self._supported_slots: List[int] = []
|
||||||
|
self._use_plugin = True
|
||||||
|
|
||||||
|
def usePlugin(self) -> bool:
|
||||||
|
return self._use_plugin
|
||||||
|
|
||||||
|
def getSupportedSlots(self) -> List[int]:
|
||||||
|
return self._supported_slots
|
||||||
|
|
||||||
|
def isRunning(self):
|
||||||
|
return self._is_running
|
||||||
|
|
||||||
|
def setPort(self, port: int) -> None:
|
||||||
|
self.__port = port
|
||||||
|
|
||||||
|
def getPort(self) -> int:
|
||||||
|
return self.__port
|
||||||
|
|
||||||
|
def getAddress(self) -> str:
|
||||||
|
return self._plugin_address
|
||||||
|
|
||||||
|
def setAvailablePort(self) -> None:
|
||||||
|
"""
|
||||||
|
Sets the port to a random available port.
|
||||||
|
"""
|
||||||
|
sock = socket.socket()
|
||||||
|
sock.bind((self.getAddress(), 0))
|
||||||
|
port = sock.getsockname()[1]
|
||||||
|
self.setPort(port)
|
||||||
|
|
||||||
|
def _validatePluginCommand(self) -> list[str]:
|
||||||
|
"""
|
||||||
|
Validate the plugin command and add the port parameter if it is missing.
|
||||||
|
|
||||||
|
:return: A list of strings containing the validated plugin command.
|
||||||
|
"""
|
||||||
|
if not self._plugin_command or "--port" in self._plugin_command:
|
||||||
|
return self._plugin_command or []
|
||||||
|
|
||||||
|
return self._plugin_command + ["--address", self.getAddress(), "--port", str(self.__port)]
|
||||||
|
|
||||||
|
def start(self) -> bool:
|
||||||
|
"""
|
||||||
|
Starts the backend_plugin process.
|
||||||
|
|
||||||
|
:return: True if the plugin process started successfully, False otherwise.
|
||||||
|
"""
|
||||||
|
if not self.usePlugin():
|
||||||
|
return False
|
||||||
|
Logger.info(f"Starting backend_plugin [{self._plugin_id}] with command: {self._validatePluginCommand()}")
|
||||||
|
plugin_log_path = os.path.join(Resources.getDataStoragePath(), f"{self.getPluginId()}.log")
|
||||||
|
if os.path.exists(plugin_log_path):
|
||||||
|
try:
|
||||||
|
os.remove(plugin_log_path)
|
||||||
|
except:
|
||||||
|
pass # removing is only done such that it doesn't grow out of proportions, if it fails once or twice that is okay
|
||||||
|
Logger.info(f"Logging plugin output to: {plugin_log_path}")
|
||||||
|
try:
|
||||||
|
# STDIN needs to be None because we provide no input, but communicate via a local socket instead.
|
||||||
|
# The NUL device sometimes doesn't exist on some computers.
|
||||||
|
with open(plugin_log_path, 'a') as f:
|
||||||
|
popen_kwargs = {
|
||||||
|
"stdin": None,
|
||||||
|
"stdout": f, # Redirect output to file
|
||||||
|
"stderr": subprocess.STDOUT, # Combine stderr and stdout
|
||||||
|
}
|
||||||
|
if Platform.isWindows():
|
||||||
|
popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
|
||||||
|
self._process = subprocess.Popen(self._validatePluginCommand(), **popen_kwargs)
|
||||||
|
self._is_running = True
|
||||||
|
return True
|
||||||
|
except PermissionError:
|
||||||
|
Logger.log("e", f"Couldn't start EnginePlugin: {self._plugin_id} No permission to execute process.")
|
||||||
|
self._showMessage(self.catalog.i18nc("@info:plugin_failed",
|
||||||
|
f"Couldn't start EnginePlugin: {self._plugin_id}\nNo permission to execute process."),
|
||||||
|
message_type = Message.MessageType.ERROR)
|
||||||
|
except FileNotFoundError:
|
||||||
|
Logger.logException("e", f"Unable to find local EnginePlugin server executable for: {self._plugin_id}")
|
||||||
|
self._showMessage(self.catalog.i18nc("@info:plugin_failed",
|
||||||
|
f"Unable to find local EnginePlugin server executable for: {self._plugin_id}"),
|
||||||
|
message_type = Message.MessageType.ERROR)
|
||||||
|
except BlockingIOError:
|
||||||
|
Logger.logException("e", f"Couldn't start EnginePlugin: {self._plugin_id} Resource is temporarily unavailable")
|
||||||
|
self._showMessage(self.catalog.i18nc("@info:plugin_failed",
|
||||||
|
f"Couldn't start EnginePlugin: {self._plugin_id}\nResource is temporarily unavailable"),
|
||||||
|
message_type = Message.MessageType.ERROR)
|
||||||
|
except OSError as e:
|
||||||
|
Logger.logException("e", f"Couldn't start EnginePlugin {self._plugin_id} Operating system is blocking it (antivirus?)")
|
||||||
|
self._showMessage(self.catalog.i18nc("@info:plugin_failed",
|
||||||
|
f"Couldn't start EnginePlugin: {self._plugin_id}\nOperating system is blocking it (antivirus?)"),
|
||||||
|
message_type = Message.MessageType.ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def stop(self) -> bool:
|
||||||
|
if not self._process:
|
||||||
|
self._is_running = False
|
||||||
|
return True # Nothing to stop
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._process.terminate()
|
||||||
|
return_code = self._process.wait()
|
||||||
|
self._is_running = False
|
||||||
|
Logger.log("d", f"EnginePlugin: {self._plugin_id} was killed. Received return code {return_code}")
|
||||||
|
return True
|
||||||
|
except PermissionError:
|
||||||
|
Logger.log("e", f"Unable to kill running EnginePlugin: {self._plugin_id} Access is denied.")
|
||||||
|
self._showMessage(self.catalog.i18nc("@info:plugin_failed",
|
||||||
|
f"Unable to kill running EnginePlugin: {self._plugin_id}\nAccess is denied."),
|
||||||
|
message_type = Message.MessageType.ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _showMessage(self, message: str, message_type: Message.MessageType = Message.MessageType.ERROR) -> None:
|
||||||
|
Message(message, title=self.catalog.i18nc("@info:title", "EnginePlugin"), message_type = message_type).show()
|
||||||
|
|
@ -203,6 +203,9 @@ class BuildVolume(SceneNode):
|
|||||||
if shape:
|
if shape:
|
||||||
self._shape = shape
|
self._shape = shape
|
||||||
|
|
||||||
|
def getShape(self) -> str:
|
||||||
|
return self._shape
|
||||||
|
|
||||||
def getDiagonalSize(self) -> float:
|
def getDiagonalSize(self) -> float:
|
||||||
"""Get the length of the 3D diagonal through the build volume.
|
"""Get the length of the 3D diagonal through the build volume.
|
||||||
|
|
||||||
|
@ -22,7 +22,10 @@ from cura.Operations.SetParentOperation import SetParentOperation
|
|||||||
from cura.MultiplyObjectsJob import MultiplyObjectsJob
|
from cura.MultiplyObjectsJob import MultiplyObjectsJob
|
||||||
from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
|
from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
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
|
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)
|
center_operation = TranslateOperation(current_node, Vector(0, center_y, 0), set_position = True)
|
||||||
operation.addOperation(center_operation)
|
operation.addOperation(center_operation)
|
||||||
operation.push()
|
operation.push()
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def multiplySelection(self, count: int) -> None:
|
def multiplySelection(self, count: int) -> None:
|
||||||
"""Multiply all objects in the selection
|
"""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.
|
:param count: The number of times to multiply the selection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
min_offset = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
|
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()
|
job.start()
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@ -229,9 +241,9 @@ class CuraActions(QObject):
|
|||||||
if node.callDecoration("isSliceable"):
|
if node.callDecoration("isSliceable"):
|
||||||
fixed_nodes.append(node)
|
fixed_nodes.append(node)
|
||||||
# Add the new nodes to the scene, and arrange them
|
# Add the new nodes to the scene, and arrange them
|
||||||
group_operation, not_fit_count = createGroupOperationForArrange(nodes, application.getBuildVolume(),
|
|
||||||
fixed_nodes, factor=10000,
|
arranger = GridArrange(nodes, application.getBuildVolume(), fixed_nodes)
|
||||||
add_new_nodes_in_scene=True)
|
group_operation, not_fit_count = arranger.createGroupOperationForArrange(add_new_nodes_in_scene = True)
|
||||||
group_operation.push()
|
group_operation.push()
|
||||||
|
|
||||||
# deselect currently selected nodes, and select the new nodes
|
# deselect currently selected nodes, and select the new nodes
|
||||||
|
@ -50,11 +50,11 @@ from UM.Settings.Validator import Validator
|
|||||||
from UM.View.SelectionPass import SelectionPass # For typing.
|
from UM.View.SelectionPass import SelectionPass # For typing.
|
||||||
from UM.Workspace.WorkspaceReader import WorkspaceReader
|
from UM.Workspace.WorkspaceReader import WorkspaceReader
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
from UM.Version import Version
|
||||||
from cura import ApplicationMetadata
|
from cura import ApplicationMetadata
|
||||||
from cura.API import CuraAPI
|
from cura.API import CuraAPI
|
||||||
from cura.API.Account import Account
|
from cura.API.Account import Account
|
||||||
from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob
|
from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob
|
||||||
from cura.Arranging.Nest2DArrange import arrange
|
|
||||||
from cura.Machines.MachineErrorChecker import MachineErrorChecker
|
from cura.Machines.MachineErrorChecker import MachineErrorChecker
|
||||||
from cura.Machines.Models.BuildPlateModel import BuildPlateModel
|
from cura.Machines.Models.BuildPlateModel import BuildPlateModel
|
||||||
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
|
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
|
||||||
@ -115,6 +115,7 @@ from . import CameraAnimation
|
|||||||
from . import CuraActions
|
from . import CuraActions
|
||||||
from . import PlatformPhysics
|
from . import PlatformPhysics
|
||||||
from . import PrintJobPreviewImageProvider
|
from . import PrintJobPreviewImageProvider
|
||||||
|
from .Arranging.Nest2DArrange import Nest2DArrange
|
||||||
from .AutoSave import AutoSave
|
from .AutoSave import AutoSave
|
||||||
from .Machines.Models.CompatibleMachineModel import CompatibleMachineModel
|
from .Machines.Models.CompatibleMachineModel import CompatibleMachineModel
|
||||||
from .Machines.Models.MachineListModel import MachineListModel
|
from .Machines.Models.MachineListModel import MachineListModel
|
||||||
@ -206,6 +207,8 @@ class CuraApplication(QtApplication):
|
|||||||
self._cura_scene_controller = None
|
self._cura_scene_controller = None
|
||||||
self._machine_error_checker = None
|
self._machine_error_checker = None
|
||||||
|
|
||||||
|
self._backend_plugins: List[BackendPlugin] = []
|
||||||
|
|
||||||
self._machine_settings_manager = MachineSettingsManager(self, parent = self)
|
self._machine_settings_manager = MachineSettingsManager(self, parent = self)
|
||||||
self._material_management_model = None
|
self._material_management_model = None
|
||||||
self._quality_management_model = None
|
self._quality_management_model = None
|
||||||
@ -616,6 +619,16 @@ class CuraApplication(QtApplication):
|
|||||||
|
|
||||||
def _onEngineCreated(self):
|
def _onEngineCreated(self):
|
||||||
self._qml_engine.addImageProvider("print_job_preview", PrintJobPreviewImageProvider.PrintJobPreviewImageProvider())
|
self._qml_engine.addImageProvider("print_job_preview", PrintJobPreviewImageProvider.PrintJobPreviewImageProvider())
|
||||||
|
version = Version(self.getVersion())
|
||||||
|
if hasattr(sys, "frozen") and version.hasPostFix() and "beta" not in version.getPostfixType():
|
||||||
|
self._qml_engine.rootObjects()[0].setTitle(f"{ApplicationMetadata.CuraAppDisplayName} {ApplicationMetadata.CuraVersion}")
|
||||||
|
message = Message(
|
||||||
|
self._i18n_catalog.i18nc("@info:warning",
|
||||||
|
f"This version is not intended for production use. If you encounter any issues, please report them on our GitHub page, mentioning the full version {self.getVersion()}"),
|
||||||
|
lifetime = 0,
|
||||||
|
title = self._i18n_catalog.i18nc("@info:title", "Nightly build"),
|
||||||
|
message_type = Message.MessageType.WARNING)
|
||||||
|
message.show()
|
||||||
|
|
||||||
@pyqtProperty(bool)
|
@pyqtProperty(bool)
|
||||||
def needToShowUserAgreement(self) -> bool:
|
def needToShowUserAgreement(self) -> bool:
|
||||||
@ -799,6 +812,7 @@ class CuraApplication(QtApplication):
|
|||||||
|
|
||||||
self._plugin_registry.addType("profile_reader", self._addProfileReader)
|
self._plugin_registry.addType("profile_reader", self._addProfileReader)
|
||||||
self._plugin_registry.addType("profile_writer", self._addProfileWriter)
|
self._plugin_registry.addType("profile_writer", self._addProfileWriter)
|
||||||
|
self._plugin_registry.addType("backend_plugin", self._addBackendPlugin)
|
||||||
|
|
||||||
if Platform.isLinux():
|
if Platform.isLinux():
|
||||||
lib_suffixes = {"", "64", "32", "x32"} # A few common ones on different distributions.
|
lib_suffixes = {"", "64", "32", "x32"} # A few common ones on different distributions.
|
||||||
@ -1444,6 +1458,13 @@ class CuraApplication(QtApplication):
|
|||||||
# Single build plate
|
# Single build plate
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def arrangeAll(self) -> None:
|
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 = []
|
nodes_to_arrange = []
|
||||||
active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
|
||||||
locked_nodes = []
|
locked_nodes = []
|
||||||
@ -1473,17 +1494,17 @@ class CuraApplication(QtApplication):
|
|||||||
locked_nodes.append(node)
|
locked_nodes.append(node)
|
||||||
else:
|
else:
|
||||||
nodes_to_arrange.append(node)
|
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
|
"""Arrange a set of nodes given a set of fixed nodes
|
||||||
|
|
||||||
:param nodes: nodes that we have to place
|
:param nodes: nodes that we have to place
|
||||||
:param fixed_nodes: nodes that are placed in the arranger before finding spots for nodes
|
: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
|
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()
|
job.start()
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@ -1763,6 +1784,13 @@ class CuraApplication(QtApplication):
|
|||||||
def _addProfileWriter(self, profile_writer):
|
def _addProfileWriter(self, profile_writer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _addBackendPlugin(self, backend_plugin: "BackendPlugin") -> None:
|
||||||
|
self._container_registry.addAdditionalSettingDefinitionsAppender(backend_plugin)
|
||||||
|
self._backend_plugins.append(backend_plugin)
|
||||||
|
|
||||||
|
def getBackendPlugins(self) -> List["BackendPlugin"]:
|
||||||
|
return self._backend_plugins
|
||||||
|
|
||||||
@pyqtSlot("QSize")
|
@pyqtSlot("QSize")
|
||||||
def setMinimumWindowSize(self, size):
|
def setMinimumWindowSize(self, size):
|
||||||
main_window = self.getMainWindow()
|
main_window = self.getMainWindow()
|
||||||
@ -1987,7 +2015,8 @@ class CuraApplication(QtApplication):
|
|||||||
if select_models_on_load:
|
if select_models_on_load:
|
||||||
Selection.add(node)
|
Selection.add(node)
|
||||||
try:
|
try:
|
||||||
arrange(nodes_to_arrange, self.getBuildVolume(), fixed_nodes)
|
arranger = Nest2DArrange(nodes_to_arrange, self.getBuildVolume(), fixed_nodes)
|
||||||
|
arranger.arrange()
|
||||||
except:
|
except:
|
||||||
Logger.logException("e", "Failed to arrange the models")
|
Logger.logException("e", "Failed to arrange the models")
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ class MaterialNode(ContainerNode):
|
|||||||
my_metadata = container_registry.findContainersMetadata(id = container_id)[0]
|
my_metadata = container_registry.findContainersMetadata(id = container_id)[0]
|
||||||
self.base_file = my_metadata["base_file"]
|
self.base_file = my_metadata["base_file"]
|
||||||
self.material_type = my_metadata["material"]
|
self.material_type = my_metadata["material"]
|
||||||
|
self.brand = my_metadata["brand"]
|
||||||
self.guid = my_metadata["GUID"]
|
self.guid = my_metadata["GUID"]
|
||||||
self._loadAll()
|
self._loadAll()
|
||||||
container_registry.containerRemoved.connect(self._onRemoved)
|
container_registry.containerRemoved.connect(self._onRemoved)
|
||||||
@ -80,6 +81,7 @@ class MaterialNode(ContainerNode):
|
|||||||
# such as "generic_pla_ultimaker_s5_AA_0.4". So we search with the "base_file" which is the material_root_id.
|
# such as "generic_pla_ultimaker_s5_AA_0.4". So we search with the "base_file" which is the material_root_id.
|
||||||
else:
|
else:
|
||||||
qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, material = self.base_file)
|
qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, material = self.base_file)
|
||||||
|
|
||||||
if not qualities:
|
if not qualities:
|
||||||
my_material_type = self.material_type
|
my_material_type = self.material_type
|
||||||
if self.variant.machine.has_variants:
|
if self.variant.machine.has_variants:
|
||||||
@ -89,9 +91,22 @@ class MaterialNode(ContainerNode):
|
|||||||
else:
|
else:
|
||||||
qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition)
|
qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition)
|
||||||
|
|
||||||
all_material_base_files = {material_metadata["base_file"] for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", material = my_material_type)}
|
# First we attempt to find materials that have the same brand but not the right color
|
||||||
|
all_material_base_files_right_brand = {material_metadata["base_file"] for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", material = my_material_type, brand = self.brand)}
|
||||||
|
|
||||||
qualities.extend((quality for quality in qualities_any_material if quality.get("material") in all_material_base_files))
|
right_brand_no_color_qualities = [quality for quality in qualities_any_material if quality.get("material") in all_material_base_files_right_brand]
|
||||||
|
|
||||||
|
if right_brand_no_color_qualities:
|
||||||
|
# We found qualties for materials with the right brand but not with the right color. Use those.
|
||||||
|
qualities.extend(right_brand_no_color_qualities)
|
||||||
|
else:
|
||||||
|
# Fall back to generic
|
||||||
|
all_material_base_files = {material_metadata["base_file"] for material_metadata in
|
||||||
|
container_registry.findInstanceContainersMetadata(type="material",
|
||||||
|
material=my_material_type)}
|
||||||
|
no_brand_no_color_qualities = (quality for quality in qualities_any_material if
|
||||||
|
quality.get("material") in all_material_base_files)
|
||||||
|
qualities.extend(no_brand_no_color_qualities)
|
||||||
|
|
||||||
if not qualities: # No quality profiles found. Go by GUID then.
|
if not qualities: # No quality profiles found. Go by GUID then.
|
||||||
my_guid = self.guid
|
my_guid = self.guid
|
||||||
|
@ -39,7 +39,9 @@ class IntentCategoryModel(ListModel):
|
|||||||
"""
|
"""
|
||||||
if len(cls._translations) == 0:
|
if len(cls._translations) == 0:
|
||||||
cls._translations["default"] = {
|
cls._translations["default"] = {
|
||||||
"name": catalog.i18nc("@label", "Default")
|
"name": catalog.i18nc("@label", "Balanced"),
|
||||||
|
"description": catalog.i18nc("@text",
|
||||||
|
"The balanced profile is designed to strike a balance between productivity, surface quality, mechanical properties and dimensional accuracy.")
|
||||||
}
|
}
|
||||||
cls._translations["visual"] = {
|
cls._translations["visual"] = {
|
||||||
"name": catalog.i18nc("@label", "Visual"),
|
"name": catalog.i18nc("@label", "Visual"),
|
||||||
|
@ -8,7 +8,9 @@ catalog = i18nCatalog("cura")
|
|||||||
|
|
||||||
intent_translations = collections.OrderedDict() # type: collections.OrderedDict[str, Dict[str, Optional[str]]]
|
intent_translations = collections.OrderedDict() # type: collections.OrderedDict[str, Dict[str, Optional[str]]]
|
||||||
intent_translations["default"] = {
|
intent_translations["default"] = {
|
||||||
"name": catalog.i18nc("@label", "Default")
|
"name": catalog.i18nc("@label", "Balanced"),
|
||||||
|
"description": catalog.i18nc("@text",
|
||||||
|
"The balanced profile is designed to strike a balance between productivity, surface quality, mechanical properties and dimensional accuracy.")
|
||||||
}
|
}
|
||||||
intent_translations["visual"] = {
|
intent_translations["visual"] = {
|
||||||
"name": catalog.i18nc("@label", "Visual"),
|
"name": catalog.i18nc("@label", "Visual"),
|
||||||
|
@ -44,6 +44,10 @@ class MaterialBrandsModel(BaseMaterialsModel):
|
|||||||
if bool(container_node.getMetaDataEntry("removed", False)):
|
if bool(container_node.getMetaDataEntry("removed", False)):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Ignore materials that are marked as not visible for whatever reason
|
||||||
|
if not bool(container_node.getMetaDataEntry("visible", True)):
|
||||||
|
continue
|
||||||
|
|
||||||
# Add brands we haven't seen yet to the dict, skipping generics
|
# Add brands we haven't seen yet to the dict, skipping generics
|
||||||
brand = container_node.getMetaDataEntry("brand", "")
|
brand = container_node.getMetaDataEntry("brand", "")
|
||||||
if brand.lower() == "generic":
|
if brand.lower() == "generic":
|
||||||
|
@ -344,7 +344,7 @@ class QualityManagementModel(ListModel):
|
|||||||
"quality_type": quality_group.quality_type,
|
"quality_type": quality_group.quality_type,
|
||||||
"quality_changes_group": None,
|
"quality_changes_group": None,
|
||||||
"intent_category": "default",
|
"intent_category": "default",
|
||||||
"section_name": catalog.i18nc("@label", "Default"),
|
"section_name": catalog.i18nc("@label", "Balanced"),
|
||||||
"layer_height": layer_height, # layer_height is only used for sorting
|
"layer_height": layer_height, # layer_height is only used for sorting
|
||||||
}
|
}
|
||||||
item_list.append(item)
|
item_list.append(item)
|
||||||
|
@ -14,17 +14,19 @@ from UM.Operations.TranslateOperation import TranslateOperation
|
|||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.i18n import i18nCatalog
|
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")
|
i18n_catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
class MultiplyObjectsJob(Job):
|
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__()
|
super().__init__()
|
||||||
self._objects = objects
|
self._objects = objects
|
||||||
self._count = count
|
self._count: int = count
|
||||||
self._min_offset = min_offset
|
self._min_offset: int = min_offset
|
||||||
|
self._grid_arrange: bool = grid_arrange
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
status_message = Message(i18n_catalog.i18nc("@info:status", "Multiplying and placing objects"), lifetime = 0,
|
status_message = Message(i18n_catalog.i18nc("@info:status", "Multiplying and placing objects"), lifetime = 0,
|
||||||
@ -39,7 +41,7 @@ class MultiplyObjectsJob(Job):
|
|||||||
|
|
||||||
root = scene.getRoot()
|
root = scene.getRoot()
|
||||||
|
|
||||||
processed_nodes = [] # type: List[SceneNode]
|
processed_nodes: List[SceneNode] = []
|
||||||
nodes = []
|
nodes = []
|
||||||
|
|
||||||
fixed_nodes = []
|
fixed_nodes = []
|
||||||
@ -76,12 +78,12 @@ class MultiplyObjectsJob(Job):
|
|||||||
found_solution_for_all = True
|
found_solution_for_all = True
|
||||||
group_operation = GroupedOperation()
|
group_operation = GroupedOperation()
|
||||||
if nodes:
|
if nodes:
|
||||||
group_operation, not_fit_count = createGroupOperationForArrange(nodes,
|
if self._grid_arrange:
|
||||||
Application.getInstance().getBuildVolume(),
|
arranger = GridArrange(nodes, Application.getInstance().getBuildVolume(), fixed_nodes)
|
||||||
fixed_nodes,
|
else:
|
||||||
factor = 10000,
|
arranger = Nest2DArrange(nodes, Application.getInstance().getBuildVolume(), fixed_nodes, factor=1000)
|
||||||
add_new_nodes_in_scene = True)
|
|
||||||
found_solution_for_all = not_fit_count == 0
|
group_operation, not_fit_count = arranger.createGroupOperationForArrange(add_new_nodes_in_scene=True)
|
||||||
|
|
||||||
if nodes_to_add_without_arrange:
|
if nodes_to_add_without_arrange:
|
||||||
for nested_node in nodes_to_add_without_arrange:
|
for nested_node in nodes_to_add_without_arrange:
|
||||||
|
@ -85,7 +85,7 @@ class PlatformPhysics:
|
|||||||
move_vector = move_vector.set(y = -bbox.bottom + z_offset)
|
move_vector = move_vector.set(y = -bbox.bottom + z_offset)
|
||||||
|
|
||||||
# If there is no convex hull for the node, start calculating it and continue.
|
# If there is no convex hull for the node, start calculating it and continue.
|
||||||
if not node.getDecorator(ConvexHullDecorator) and not node.callDecoration("isNonPrintingMesh"):
|
if not node.getDecorator(ConvexHullDecorator) and not node.callDecoration("isNonPrintingMesh") and node.callDecoration("getLayerData") is None:
|
||||||
node.addDecorator(ConvexHullDecorator())
|
node.addDecorator(ConvexHullDecorator())
|
||||||
|
|
||||||
# only push away objects if this node is a printing mesh
|
# only push away objects if this node is a printing mesh
|
||||||
|
@ -56,6 +56,9 @@ class CuraFormulaFunctions:
|
|||||||
if isinstance(value, SettingFunction):
|
if isinstance(value, SettingFunction):
|
||||||
value = value(extruder_stack, context = context)
|
value = value(extruder_stack, context = context)
|
||||||
|
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = value.lower()
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _getActiveExtruders(self, context: Optional["PropertyEvaluationContext"] = None) -> List[str]:
|
def _getActiveExtruders(self, context: Optional["PropertyEvaluationContext"] = None) -> List[str]:
|
||||||
|
@ -28,6 +28,7 @@ empty_material_container.setMetaDataEntry("type", "material")
|
|||||||
empty_material_container.setMetaDataEntry("base_file", "empty_material")
|
empty_material_container.setMetaDataEntry("base_file", "empty_material")
|
||||||
empty_material_container.setMetaDataEntry("GUID", "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")
|
empty_material_container.setMetaDataEntry("GUID", "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")
|
||||||
empty_material_container.setMetaDataEntry("material", "empty")
|
empty_material_container.setMetaDataEntry("material", "empty")
|
||||||
|
empty_material_container.setMetaDataEntry("brand", "empty_brand")
|
||||||
|
|
||||||
# Empty quality
|
# Empty quality
|
||||||
EMPTY_QUALITY_CONTAINER_ID = "empty_quality"
|
EMPTY_QUALITY_CONTAINER_ID = "empty_quality"
|
||||||
|
@ -69,7 +69,7 @@ class ObjectsModel(ListModel):
|
|||||||
self._group_name_template = catalog.i18nc("@label", "Group #{group_nr}")
|
self._group_name_template = catalog.i18nc("@label", "Group #{group_nr}")
|
||||||
self._group_name_prefix = self._group_name_template.split("#")[0]
|
self._group_name_prefix = self._group_name_template.split("#")[0]
|
||||||
|
|
||||||
self._naming_regex = re.compile("^(.+)\(([0-9]+)\)$")
|
self._naming_regex = re.compile(r"^(.+)\(([0-9]+)\)$")
|
||||||
|
|
||||||
def setActiveBuildPlate(self, nr: int) -> None:
|
def setActiveBuildPlate(self, nr: int) -> None:
|
||||||
if self._build_plate_number != nr:
|
if self._build_plate_number != nr:
|
||||||
|
@ -148,6 +148,9 @@ class CloudMaterialSync(QObject):
|
|||||||
continue
|
continue
|
||||||
if metadata["id"] == "empty_material": # Don't export the empty material.
|
if metadata["id"] == "empty_material": # Don't export the empty material.
|
||||||
continue
|
continue
|
||||||
|
# Ignore materials that are marked as not visible for whatever reason
|
||||||
|
if not bool(metadata.get("visible", True)):
|
||||||
|
continue
|
||||||
material = registry.findContainers(id = metadata["id"])[0]
|
material = registry.findContainers(id = metadata["id"])[0]
|
||||||
suffix = registry.getMimeTypeForContainer(type(material)).preferredSuffix
|
suffix = registry.getMimeTypeForContainer(type(material)).preferredSuffix
|
||||||
filename = metadata["id"] + "." + suffix
|
filename = metadata["id"] + "." + suffix
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
|
|
||||||
How to Profile Cura and See What It is Doing
|
|
||||||
============================================
|
|
||||||
Cura has a simple flame graph profiler available as a plugin which can be used to see what Cura is doing as it runs and how much time it takes. A flame graph profile shows its output as a timeline and stacks of "blocks" which represent parts of the code and are stacked up to show call depth. These often form little peaks which look like flames. It is a simple yet powerful way to visualise the activity of a program.
|
|
||||||
|
|
||||||
|
|
||||||
Setting up and installing the profiler
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
The profiler plugin is kept outside of the Cura source code here: https://github.com/sedwards2009/cura-big-flame-graph
|
|
||||||
|
|
||||||
To install it do:
|
|
||||||
|
|
||||||
* Use `git clone https://github.com/sedwards2009/cura-big-flame-graph.git` to grab a copy of the code.
|
|
||||||
* Copy the `BigFlameGraph` directory into the `plugins` directory in your local Cura.
|
|
||||||
* Set the `URANIUM_FLAME_PROFILER` environment variable to something before starting Cura. This flags to the profiler code in Cura to activate and insert the needed hooks into the code.
|
|
||||||
|
|
||||||
|
|
||||||
Using the profiler
|
|
||||||
------------------
|
|
||||||
To open the profiler go to the Extensions menu and select "Start BFG" from the "Big Flame Graph" menu. A page will open up in your default browser. This is the profiler UI. Click on "Record" to start recording, go to Cura and perform an action and then back in the profiler click on "Stop". The results should now load in.
|
|
||||||
|
|
||||||
The time scale is at the top of the window. The blocks should be read as meaning the blocks at the bottom call the blocks which are stacked on top of them. Hover the mouse to get more detailed information about a block such as the name of the code involved and its duration. Use the zoom buttons or mouse wheel to zoom in. The display can be panned by dragging with the left mouse button.
|
|
||||||
|
|
||||||
Note: The profiler front-end itself is quite "heavy" (ok, not optimised). It runs much better in Google Chrome or Chromium than Firefox. It is also a good idea to keep recording sessions short for the same reason.
|
|
||||||
|
|
||||||
|
|
||||||
What the Profiler Sees
|
|
||||||
----------------------
|
|
||||||
The profiler doesn't capture every function call in Cura. It hooks into a number of important systems which give a good picture of activity without too much run time overhead. The most important system is Uranium's signal mechanism and PyQt5 slots. Functions which are called via the signal mechanism are recorded and their names appear in the results. PyQt5 slots appear in the results with the prefix `[SLOT]`.
|
|
||||||
|
|
||||||
Note that not all slots are captured. Only those slots which belong to classes which use the `pyqtSlot` decorator from the `UM.FlameProfiler` module.
|
|
||||||
|
|
||||||
|
|
||||||
Manually adding profiling code to more detail
|
|
||||||
---------------------------------------------
|
|
||||||
It is also possible to manually add decorators to methods to make them appear in the profiler results. The `UM.FlameProfiler` module contains the `profile` decorator which can be applied to methods. There is also a `profileCall` context manager which can be used with Python's `with` statement to measure a block of code. `profileCall` takes one argument, a label to use in the results.
|
|
@ -1 +0,0 @@
|
|||||||
<mxfile host="www.draw.io" modified="2019-12-20T12:34:56.339Z" agent="Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0" etag="1NLsmsxIqXUmOJee4m9D" version="12.4.3" type="device" pages="1"><diagram id="K0t5C8WxT4tyKudoHXNk" name="Page-1">7VzbcqM4EP0aP+4WSFzsx8SZmd2tpCqTbO1kn1KKkW3VYOQBObHn61cyF0MLM9jhkmyo8gMSLXQ5R+rTDckIT1fbLyFZL2+4R/0RMrztCF+NEDIthEbqZ3i7uMZ13LhiETIvMTpU3LOfNKk0ktoN82hUMBSc+4Kti5UzHgR0Jgp1JAz5S9Fszv1ir2uyoFrF/Yz4eu035ollXDtG7qH+D8oWy7Rn05nEd1YkNU5mEi2Jx19yVfjTCE9DzkV8tdpOqa8WL12XuN3nI3ezgYU0EHUa/OXdPfz4exV+fRLWl92tG9388/m35CnPxN8kE/4zEOqB8ZDFLl2H6IWtfBLI0uWcB+I+uWPI8mzJfO+a7PhGjSMSZPY9LV0uech+Snviy1umrJC3Q5HAjFXrOfP9Kfd5KCsCvu/g0OhePSzpJqSRbHabztcEVTdkWzC8JpFIB8h9n6wj9rQfsmq4IuGCBZdcCL5KjJKFoKGg26MrbGa4ScJTvqIi3EmTtIGTQJ1w3URWXH45MMecJDbLPGvShiRh6yJ7dtbdnWQ3CRZyCll/2SZJ+7MMrT+npDvkFHsjvqBhQAS95JvAi/Iskhe5mR6q9tw6gWdY4xnb8+xxJrtdcDVTOSi8vciQyHFPIiL21An5dwq4UkIf4rNFIIs+natmClImN/RFUi34Wj1sTWYsWFzvba6sQ81dsk6qisu2c3+/aZfM82ig2MUFEeQpY/+ay5nsF9K+lD+53FPjd3tky4FPZdk8lOVPmYdiygM5F8L2rKKSpy9UcbUeBY9vY52XCS+wTotSGkJe5FlYIMSp6Fsa+vJ0pCGTp8KAdbNY207PWE80rD06ZwETjAcD2g2jPUY9o516oBzcz0RubKUghgO9LdhNY9w37rpw/LGROIndo9it6YB404jjmlKyNcRtDfCvMeCdhApyWv+vUMEtSndslmg0qyxUwBWhwqsAdgaR1txutit3MyoRaWVgt7aZ3eH07hJvu0SmdYr3eFBp3aPuloi0TlE39SM9H4sNyLeFvGmUqLVuoddPeA1lGngXKkMuSzOfRBGbKUElVqn+olsmHpJFV9f/qmu5snHpapu7dbVLC4Ec/UO+kGuliodm+1LaLh4c9bRkPBBUcgJ8E85o1dTT1wRSuNEqDN1yDHOY2SWQpXUh9Ylgz8XxVuRvbxVtcwJwXBSAaAIeEc8zaZVP64MHZe8XMl8DHhSvg/YgCT3Z5cySbXV8wBgM2DUrxwXtsWsDVscjaDTNbOqO7qiIHcKUX4cpFgDRrKtkshOw+ZNNTzA+kYg+ylUfhOsZaYdxpSfrPVJBeoJxyCe3h3fvkQrSE4tZqDKA3SzYvQcoqSh9lUo9U3Gm6jZVunXUbYMq1aopUmN315dKxSBNmanWU1WqBT5VQJZbS6U2JQyR/gr62LEy6MLTdSEa1wx40yOn+aNEfz8RkNWgCE93GvFWecOKsDqrNeDdLN79K0I9pv8oImFcUySgI+nIbkSCbYAMlHumSLBtmMoCD2oolQXFiGUYleOC9hhhwOoWUln4pMB3EC2/Fi0OyJRip+bRlh6BjR9tWA92pf3gwk51YdgoB/6tSBasRx+nu7AzXNFbeBvj1PRh+Mjm7caHWeDTffvcQNcGvsKG3+s05cMs4MOSv0g56sOgvVF4fdOSD9ODso/Ce/Q+eI9AIO+cy3voXG2rHd7bE+DErUn1fpz0wXs9RO2W92Z/vDfeBe8d+HYahhp1ee+CDKkFN1BDvHcBj5FZfd5D+2543+BXJ++N93W/Ouk3VncMSItzz3sQq2O7Hd7DASMLV4/LgBu7i1hd/ybhdN6fyeFzvtJqkPeTd3Hcm4AVDlTldWlvwhRVS8e9Cd+XmdUyB9pbdnVKS3MPp9p34U4sPT3SrTvpMWyuK6P6dSdaZnRy5r6C8TduKXzQ30Pb1ePqI/VroQ/L+7pRc7+fRcDX35ror0178BWw1VK2CI/h9qp2J9DenLzquJfFw/85ic0P/y0Gf/oP</diagram></mxfile>
|
|
Before Width: | Height: | Size: 22 KiB |
@ -1,81 +0,0 @@
|
|||||||
|
|
||||||
# Reporting Issues
|
|
||||||
|
|
||||||
Please attach the following information in case <br>
|
|
||||||
you want to report crashing or similar issues.
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
## DxDiag
|
|
||||||
|
|
||||||
### ![Badge Windows]
|
|
||||||
|
|
||||||
The log as produced by **dxdiag**.
|
|
||||||
|
|
||||||
<kbd> start </kbd> » <kbd> run </kbd> » <kbd> dxdiag </kbd> » <kbd> save output </kbd>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
## Cura GUI Log
|
|
||||||
|
|
||||||
If the Cura user interface still starts, you can also <br>
|
|
||||||
reach these directories from the application menu:
|
|
||||||
|
|
||||||
<kbd> Help </kbd> » <kbd> Show settings folder </kbd>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### ![Badge Windows]
|
|
||||||
|
|
||||||
```
|
|
||||||
%APPDATA%\cura\<Cura Version>\cura.log
|
|
||||||
```
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
```
|
|
||||||
C:\Users\<your username>\AppData\Roaming\cura\<Cura Version>\cura.log
|
|
||||||
```
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### ![Badge Linux]
|
|
||||||
|
|
||||||
```
|
|
||||||
~/.local/share/cura/<Cura Version>/cura.log
|
|
||||||
```
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### ![Badge MacOS]
|
|
||||||
|
|
||||||
```
|
|
||||||
~/Library/Application Support/cura/<Cura Version>/cura.log
|
|
||||||
```
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
## Alternative
|
|
||||||
|
|
||||||
An alternative is to install the **[ExtensiveSupportLogging]** <br>
|
|
||||||
plugin this creates a zip folder of the relevant log files.
|
|
||||||
|
|
||||||
If you're experiencing performance issues, we might ask <br>
|
|
||||||
you to connect the CPU profiler in this plugin and attach <br>
|
|
||||||
the collected data to your support ticket.
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
|
|
||||||
<!----------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
[ExtensiveSupportLogging]: https://marketplace.ultimaker.com/app/cura/plugins/UltimakerPackages/ExtensiveSupportLogging
|
|
||||||
|
|
||||||
|
|
||||||
<!---------------------------------[ Badges ]---------------------------------->
|
|
||||||
|
|
||||||
[Badge Windows]: https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logoColor=white&logo=Windows
|
|
||||||
[Badge Linux]: https://img.shields.io/badge/Linux-00A95C?style=for-the-badge&logoColor=white&logo=Linux
|
|
||||||
[Badge MacOS]: https://img.shields.io/badge/MacOS-403C3D?style=for-the-badge&logoColor=white&logo=MacOS
|
|
@ -1,22 +0,0 @@
|
|||||||
Cura Documentation
|
|
||||||
====
|
|
||||||
Welcome to the Cura documentation pages.
|
|
||||||
|
|
||||||
Objective
|
|
||||||
----
|
|
||||||
The goal of this documentation is to give an overview of the architecture of Cura's source code. The purpose of this overview is to make programmers familiar with Cura's source code so that they may contribute more easily, write plug-ins more easily or get started within the Cura team more quickly.
|
|
||||||
|
|
||||||
There are some caveats though. These are *not* within the scope of this documentation:
|
|
||||||
* There is no documentation on individual functions or classes of the code here. For that, refer to the Doxygen documentation and Python Docstrings in the source code itself, or generate the documentation locally using Doxygen.
|
|
||||||
* It's virtually impossible and indeed not worth the effort or money to keep this 100% up to date.
|
|
||||||
* There are no example plug-ins here. There are a number of example plug-ins in the Ultimaker organisation on Github.com to draw from.
|
|
||||||
* The slicing process is not documented here. Refer to CuraEngine for that.
|
|
||||||
|
|
||||||
This documentation will touch on the inner workings of Uranium as well though, due to the nature of the architecture.
|
|
||||||
|
|
||||||
Index
|
|
||||||
----
|
|
||||||
The following chapters are available in this documentation:
|
|
||||||
* [Repositories](repositories.md): An overview of the repositories that together make up the Cura application.
|
|
||||||
* [Profiles](profiles/profiles.md): About the setting and profile system of Cura.
|
|
||||||
* [Scene](scene/scene.md): How Cura's 3D scene looks.
|
|
@ -1,33 +0,0 @@
|
|||||||
Container Stacks
|
|
||||||
====
|
|
||||||
When the user selects the profiles and settings to print with, he can swap out a number of profiles. The profiles that are currently in use are stored in several container stacks. These container stacks always have a definition container at the bottom, which defines all available settings and all available properties for each setting. The profiles on top of that definition can then override the `value` property of some of those settings.
|
|
||||||
|
|
||||||
When deriving a setting value, a container stack starts looking at the top-most profile to see if it contains an override for that setting. If it does, it returns that override. Otherwise, it looks into the second profile. If that also doesn't have an override for this setting, it looks into the third profile, and so on. The last profile is always a definition container which always contains an value for all settings. This way, the profiles at the top will always win over the profiles at the bottom. There is a clear precedence order for which profile wins over which other profile.
|
|
||||||
|
|
||||||
A Machine Instance
|
|
||||||
----
|
|
||||||
A machine instance is a printer that the user has added to his configuration. It consists of multiple container stacks: One for global settings and one for each of the available extruders. This way, different extruders can contain different materials and quality profiles, for instance. The global stack contains a different set of profiles than the extruder stacks.
|
|
||||||
|
|
||||||
While Uranium defines no specific roles for the entries in a container stack, Cura defines rigid roles for each slot in a container stack. These are the layouts for the container stacks of an example printer with 2 extruders.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
To expand on this a bit further, each extruder stack contains the following profiles:
|
|
||||||
* A user profile, where extruder-specific setting changes are stored that are not (yet) saved to a custom profile. If the user changes a setting that can be adjusted per extruder (such as infill density) then it gets stored here. If the user adjusts a setting that is global it will immediately be stored in the user profile of the global stack.
|
|
||||||
* A custom profile. If the user saves his setting changes to a custom profile, it gets moved from the user profile to here. Actually a "custom profile" as the user sees it consists of multiple profiles: one for each extruder and one for the global settings.
|
|
||||||
* An intent profile. The user can select between several intents for his print, such as precision, strength, visual quality, etc. This may be empty as well, which indicates the "default" intent.
|
|
||||||
* A quality profile. The user can select between several quality levels.
|
|
||||||
* A material profile, where the user selects which material is loaded in this extruder.
|
|
||||||
* A nozzle profile, where the user selects which nozzle is installed in this extruder.
|
|
||||||
* Definition changes, which stores the changes that the user made for this extruder in the Printer Settings dialogue.
|
|
||||||
* Extruder. The user is not able to swap this out. This is a definition that lists the extruder number for this extruder and optionally things that are fixed in the printer, such as the nozzle offset.
|
|
||||||
|
|
||||||
The global container stack contains the following profiles:
|
|
||||||
* A user profile, where global setting changes are stored that are not (yet) saved to a custom profile. If the user changes for instance the layer height, the new value for the layer height gets stored here.
|
|
||||||
* A custom profile. If the user saves his setting changes to a custom profile, the global settings that were in the global user profile get moved here.
|
|
||||||
* An intent profile. Currently this must ALWAYS be empty. There are no global intent profiles. This is there for historical reasons.
|
|
||||||
* A quality profile. This contains global settings that match with the quality level that the user selected. This global quality profile cannot be specific to a material or nozzle.
|
|
||||||
* A material profile. Currently this must ALWAYS be empty. There are no global material profiles. This is there for historical reasons.
|
|
||||||
* A variant profile. Currently this must ALWAYS be empty. There are no global variant profiles. This is there for historical reasons.
|
|
||||||
* Definition changes, which stores the changes that the user made to the printer in the Printer Settings dialogue.
|
|
||||||
* Printer. This specifies the currently used printer model, such as Ultimaker 3, Ultimaker S5, etc.
|
|
@ -1,70 +0,0 @@
|
|||||||
Getting a Setting Value
|
|
||||||
====
|
|
||||||
How Cura gets a setting's value is a complex endeavour that requires some explanation. The `value` property gets special treatment for this because there are a few other properties that influence the value. In this page we explain the algorithm to getting a setting value.
|
|
||||||
|
|
||||||
This page explains all possible cases for a setting, but not all of them may apply. For instance, a global setting will not evaluate the per-object settings to get its value. Exceptions to the rules for other types of settings will be written down.
|
|
||||||
|
|
||||||
Per Object Settings
|
|
||||||
----
|
|
||||||
Per-object settings, which are added to an object using the per-object settings tool, will always prevail over other setting values. They are not evaluated with the rest of the settings system because Cura's front-end doesn't need to send all setting values for all objects to CuraEngine separately. It only sends over the per-object settings that get overridden. CuraEngine then evaluates settings that can be changed per-object using the list of settings for that object but if the object doesn't have the setting attached falls back on the settings in the object's extruder. Refer to the [CuraEngine](#CuraEngine) chapter to see how this works.
|
|
||||||
|
|
||||||
Settings where the `settable_per_mesh` property is false will not be shown in Cura's interface in the list of available settings in the per-object settings panel. They cannot be adjusted per object then. CuraEngine will also not evaluate those settings for each object separately. There is (or should always be) a good reason why each of these settings are not evaluated per object: Simply because CuraEngine is not processing one particular mesh at that moment. For instance, when writing the move to change to the next layer, CuraEngine hasn't processed any of the meshes on that layer yet and so the layer change movement speed, or indeed the layer height, can't change for each object.
|
|
||||||
|
|
||||||
The per-object settings are stored in a separate container stack that is particular to the object. The container stack is added to the object via a scene decorator. It has just a single container in it, which contains all of the settings that the user changed.
|
|
||||||
|
|
||||||
Resolve
|
|
||||||
----
|
|
||||||
If the setting is not listed in the per-object settings, it needs to be evaluated from the main settings list. However before evaluating it from a particular extruder, Cura will check if the setting has the `resolve` property. If it does, it returns the output of the `resolve` property and that's everything.
|
|
||||||
|
|
||||||
The `resolve` property is intended for settings which are global in nature, but still need to be influenced by extruder-specific settings. A good example is the Build Plate Temperature, which is very dependent on the material(s) used by the printer, but there can only be a single bed temperature at a time.
|
|
||||||
|
|
||||||
Cura will simply evaluate the `resolve` setting if present, which is an arbitrary Python expression, and return its result as the setting's value. However typically the `resolve` property is a function that takes the values of this setting for all extruders in use and then computes a result based on those. There is a built-in function for that called `extruderValues()`, which returns a list of setting values, one for each extruder. The function can then for instance take the average of those. In the case of the build plate temperature it will take the highest of those. In the case of the adhesion type it will choose "raft" if any extruder uses a raft, or "brim" as second choice, "skirt" as third choice and "none" only if all extruders use "none". Each setting with a `resolve` property has its own way of resolving the setting. The `extruderValues()` function continues with the algorithm as written below, but repeats it for each extruder.
|
|
||||||
|
|
||||||
Limit To Extruder
|
|
||||||
----
|
|
||||||
If a setting is evaluated from a particular extruder stack, it normally gets evaluated from the extruder that the object is assigned to. However there are some exceptions. Some groups of settings belong to a particular "extruder setting", like the Infill Extruder setting, or the Support Extruder setting. Which extruder a setting belongs to is stored in the `limit_to_extruder` property. Settings which have their `limit_to_extruder` property set to `adhesion_extruder_nr`, for instance, belong to the build plate adhesion settings.
|
|
||||||
|
|
||||||
If the `limit_to_extruder` property evaluates to a positive number, instead of getting the setting from the object's extruder it will be obtained from the extruder written in the `limit_to_extruder` property. So even if an object is set to be printed with extruder 0, if the infill extruder is set to extruder 1 any infill setting will be obtained from extruder 1. If `limit_to_extruder` is negative (in particular -1, which is the default), then the setting will be obtained from the object's own extruder.
|
|
||||||
|
|
||||||
This property is communicated to CuraEngine separately. CuraEngine makes sure that the setting is evaluated from the correct extruder. Refer to the [CuraEngine](#CuraEngine) chapter to see how this works.
|
|
||||||
|
|
||||||
Evaluating a Stack
|
|
||||||
----
|
|
||||||
After the resolve and limit to extruder properties have been checked, the setting value needs to be evaluated from an extruder stack.
|
|
||||||
|
|
||||||
This is explained in more detail in the [Container Stacks](container_stacks.md) documentation. In brief, Cura will check the highest container in the extruder stack first to see whether that container overrides the setting. If it does, it returns that as the setting value. Otherwise, it checks the second container on the stack to see if that one overrides it. If it does it returns that value, and otherwise it checks the third container, and so on. If a setting is not overridden by any container in the extruder stack, it continues downward in the global stack. If it is also not overridden there, it eventually arrives at the definition in the bottom of the global stack.
|
|
||||||
|
|
||||||
Evaluating a Definition
|
|
||||||
----
|
|
||||||
If the evaluation for a setting reaches the last entry of the global stack, its definition, a few more things can happen.
|
|
||||||
|
|
||||||
Definition containers have an inheritance structure. For instance, the `ultimaker3` definition container specifies in its metadata that it inherits from `ultimaker`, which in turn inherits from `fdmprinter`. So again here, when evaluating a property from the `ultimaker3` definition it will first look to see if the property is overridden by the `ultimaker3` definition itself, and otherwise refer on to the `ultimaker` definition or otherwise finally to the `fdmprinter` definition. `fdmprinter` is the last line of defence, and it contains *all* properties for *all* settings.
|
|
||||||
|
|
||||||
But even in `fdmprinter`, not all settings have a `value` property. It is not a required property. If the setting doesn't have a `value` property, the `default_value` property is returned, which is a required property. The distinction between `value` and `default_value` is made in order to allow CuraEngine to load a definition file as well when running from the command line (a debugging technique for CuraEngine). It then won't have all of the correct setting values but it at least doesn't need to evaluate all of the Python expressions and you'll be able to make some debugging slices.
|
|
||||||
|
|
||||||
Evaluating a Value Property
|
|
||||||
----
|
|
||||||
The `value` property may contain a formula, which is an arbitrary Python expression that will be executed by Cura to arrive at a setting value. All containers may set the `value` property. Instance containers can only set the `value`, while definitions can set all properties.
|
|
||||||
|
|
||||||
While the value could be any sort of formula, some functions of Python are restricted for security reasons. Since Cura 4.6, profiles are no longer a "trusted" resource and are therefore subject to heavy restrictions. It can use Python's built in mathematical functions and list functions as well as a few basic other ones, but things like writing to a file are prohibited.
|
|
||||||
|
|
||||||
There are also a few extra things that can be used in these expressions:
|
|
||||||
* Any setting key can be used as a variable that contains the setting's value.
|
|
||||||
* As explained before, `extruderValues(key)` is a function that returns a list of setting values for a particular setting for all used extruders.
|
|
||||||
* 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 `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.
|
|
||||||
|
|
||||||
CuraEngine
|
|
||||||
----
|
|
||||||
When starting a slice, Cura will send the scene to CuraEngine and with each model send over the per-object settings that belong to it. It also sends all setting values over, as evaluated from each extruder and from the global stack, and sends the `limit_to_extruder` property along as well. CuraEngine stores this and then starts its slicing process. CuraEngine also has a hierarchical structure for its settings with fallbacks. This is explained in detail in [the documentation of CuraEngine](https://github.com/Ultimaker/CuraEngine/blob/master/docs/settings.md) and shortly again here.
|
|
||||||
|
|
||||||
Each model gets a setting container assigned. The per-object settings are stored in those. The fallback for this container is set to be the extruder with which the object is printed. The extruder uses the current *mesh group* as fallback (which is a concept that Cura's front-end doesn't have). Each mesh group uses the global settings container as fallback.
|
|
||||||
|
|
||||||
During the slicing process CuraEngine will evaluate the settings from its current context as it goes. For instance, when processing the walls for a particular mesh, it will request the Outer Wall Line Width setting from the settings container of that mesh. When it's not processing a particular mesh but for instance the travel moves between two meshes, it uses the currently applicable extruder. So this business logic defines actually how a setting can be configured per mesh, per extruder or only globally. The `settable_per_extruder`, and related properties of settings are only used in the front-end to determine how the settings are shown to the user.
|
|
@ -1,30 +0,0 @@
|
|||||||
Profiles
|
|
||||||
====
|
|
||||||
Cura's profile system is very advanced and has gotten pretty complex. This chapter is an attempt to document how it is structured.
|
|
||||||
|
|
||||||
Index
|
|
||||||
----
|
|
||||||
The following pages describe the profile and setting system of Cura:
|
|
||||||
* [Container Stacks](container_stacks.md): Which profiles can be swapped out and how they are ordered when evaluating a setting.
|
|
||||||
* [Setting Properties](setting_properties.md): What properties can each setting have?
|
|
||||||
* [Getting a Setting Value](getting_a_setting_value.md): How Cura arrives at a value for a certain setting.
|
|
||||||
|
|
||||||
Glossary
|
|
||||||
----
|
|
||||||
The terminology for these profiles is not always obvious. Here is a glossary of the terms that we'll use in this chapter.
|
|
||||||
* **Profile:** Either an *instance container* or a *definition container*.
|
|
||||||
* **Definition container:** Profile that's stored as .def.json file, defining new settings and all of their properties. In Cura these represent printer models and extruder trains.
|
|
||||||
* **Instance container:** Profile that's stored as .inst.cfg file or .xml.fdm_material file, which override some setting values. In Cura these represent the other profiles.
|
|
||||||
* **[Container] stack:** A list of profiles, with one definition container at the bottom and instance containers for the rest. All settings are defined in the definition container. The rest of the profiles each specify a set of value overrides. The profiles at the top always override the profiles at the bottom.
|
|
||||||
* **Machine instance:** An instance of a printer that the user has added. The list of all machine instances is shown in a drop-down in Cura's interface.
|
|
||||||
* **Material:** A type of filament that's being sold by a vendor as a product.
|
|
||||||
* **Filament spool:** A single spool of material.
|
|
||||||
* **Quality profile:** A profile that is one of the options when the user selects which quality level they want to print with.
|
|
||||||
* **Intent profile:** A profile that is one of the options when the user selects what his intent is.
|
|
||||||
* **Custom profile:** A user-made profile that is stored when the user selects to "create a profile from the current settings/overrides".
|
|
||||||
* **Quality-changes profile:** Alternative name for *custom profile*. This name is used in the code more often, but it's a bit misleading so this documentation prefers the term "custom profile".
|
|
||||||
* **User profile:** A profile containing the settings that the user has changed, but not yet saved to a profile.
|
|
||||||
* **Variant profile:** A profile containing some overrides that allow the user to select variants of the definition. As of this writing this is only used for the nozzles.
|
|
||||||
* **Quality level:** A measure of quality where the user can select from, for instance "normal", "fast", "high". When selecting a quality level, Cura will select a matching quality profile for each extruder.
|
|
||||||
* **Quality type:** Alternative name for *quality level*. This name is used in the code more often, but this documentation prefers the term "quality level".
|
|
||||||
* **Inheritance function:** A function through which the `value` of a setting is calculated. This may depend on other settings.
|
|
@ -1,78 +0,0 @@
|
|||||||
Setting Properties
|
|
||||||
====
|
|
||||||
Each setting in Cura has a number of properties. It's not just a key and a value. This page lists the properties that a setting can define.
|
|
||||||
|
|
||||||
* `key` (string): __The identifier by which the setting is referenced.__
|
|
||||||
* This is not a human-readable name, but just a reference string, such as `layer_height_0`.
|
|
||||||
* This is not actually a real property but just an identifier; it can't be changed.
|
|
||||||
* Typically these are named with the most significant category first, in order to sort them better, such as `material_print_temperature`.
|
|
||||||
* `value` (optional): __The current value of the setting.__
|
|
||||||
* This can be a function (an arbitrary Python expression) that depends on the values of other settings.
|
|
||||||
* If it's not present, the `default_value` is used.
|
|
||||||
* `default_value`: __A default value for the setting if `value` is undefined.__
|
|
||||||
* This property is required.
|
|
||||||
* It can't be a Python expression, but it can be any JSON type.
|
|
||||||
* This is made separate so that CuraEngine can read it out for its debugging mode via the command line, without needing a complete Python interpreter.
|
|
||||||
* `label` (string): __The human-readable name for the setting.__
|
|
||||||
* This label is translated.
|
|
||||||
* `description` (string): __A longer description of what the setting does when you change it.__
|
|
||||||
* This description is translated.
|
|
||||||
* `type` (string): __The type of value that this setting contains.__
|
|
||||||
* Allowed types are: `bool`, `str`, `float`, `int`, `enum`, `category`, `[int]`, `vec3`, `polygon` and `polygons`.
|
|
||||||
* `unit` (optional string): __A unit that is displayed at the right-hand side of the text field where the user enters the setting value.__
|
|
||||||
* `resolve` (optional string): __A Python expression that resolves disagreements for global settings if multiple per-extruder profiles define different values for a setting.__
|
|
||||||
* Typically this takes the values for the setting from all stacks and computes one final value for it that will be used for the global setting. For instance, the `resolve` function for the build plate temperature is `max(extruderValues('material_bed_temperature')`, meaning that it will use the hottest bed temperature of all materials of the extruders in use.
|
|
||||||
* `limit_to_extruder` (optional): __A Python expression that indicates which extruder a setting will be obtained from.__
|
|
||||||
* This is used for settings that may be extruder-specific but the extruder is not necessarily the current extruder. For instance, support settings need to be evaluated for the support extruder. Infill settings need to be evaluated for the infill extruder if the infill extruder is changed.
|
|
||||||
* `enabled` (optional string or boolean): __Whether the setting can currently be made visible for the user.__
|
|
||||||
* This can be a simple true/false, or a Python expression that depends on other settings.
|
|
||||||
* Typically used for settings that don't apply when another setting is disabled, such as to hide the support settings if support is disabled.
|
|
||||||
* `minimum_value` (optional): __The lowest acceptable value for this setting.__
|
|
||||||
* If it's any lower, Cura will not allow the user to slice.
|
|
||||||
* This property only applies to numerical settings.
|
|
||||||
* By convention this is used to prevent setting values that are technically or physically impossible, such as a layer height of 0mm.
|
|
||||||
* `maximum_value` (optional): __The highest acceptable value for this setting.__
|
|
||||||
* If it's any higher, Cura will not allow the user to slice.
|
|
||||||
* This property only applies to numerical settings.
|
|
||||||
* By convention this is used to prevent setting values that are technically or physically impossible, such as a support overhang angle of more than 90 degrees.
|
|
||||||
* `minimum_value_warning` (optional): __The threshold under which a warning is displayed to the user.__
|
|
||||||
* This property only applies to numerical settings.
|
|
||||||
* By convention this is used to indicate that it will probably not print very nicely with such a low setting value.
|
|
||||||
* `maximum_value_warning` (optional): __The threshold above which a warning is displayed to the user.__
|
|
||||||
* This property only applies to numerical settings.
|
|
||||||
* By convention this is used to indicate that it will probably not print very nicely with such a high setting value.
|
|
||||||
* `settable_globally` (optional boolean): __Whether the setting can be changed globally.__
|
|
||||||
* For some mesh-type settings such as `support_mesh` this doesn't make sense, so those can't be changed globally. They are not displayed in the main settings list then.
|
|
||||||
* `settable_per_meshgroup` (optional boolean): __Whether a setting can be changed per group of meshes.__
|
|
||||||
* *This is currently unused by Cura.*
|
|
||||||
* `settable_per_extruder` (optional boolean): __Whether a setting can be changed per extruder.__
|
|
||||||
* Some settings, like the build plate temperature, can't be adjusted separately for each extruder. An icon is shown in the interface to indicate this.
|
|
||||||
* If the user changes these settings they are stored in the global stack.
|
|
||||||
* `settable_per_mesh` (optional boolean): __Whether a setting can be changed per mesh.__
|
|
||||||
* The settings that can be changed per mesh are shown in the list of available settings in the per-object settings tool.
|
|
||||||
* `children` (optional list): __A list of child settings.__
|
|
||||||
* These are displayed with an indentation. If all child settings are overridden by the user, the parent setting gets greyed out to indicate that the parent setting has no effect any more. This is not strictly always the case though, because that would depend on the inheritance functions in the `value`.
|
|
||||||
* `icon` (optional string): __A path to an icon to be displayed.__
|
|
||||||
* Only applies to setting categories.
|
|
||||||
* `allow_empty` (optional bool): __Whether the setting is allowed to be empty.__
|
|
||||||
* If it's not, this will be treated as a setting error and Cura will not allow the user to slice.
|
|
||||||
* Only applies to string-type settings.
|
|
||||||
* `warning_description` (optional string): __A warning message to display when the setting has a warning value.__
|
|
||||||
* *This is currently unused by Cura.*
|
|
||||||
* `error_description` (optional string): __An error message to display when the setting has an error value.__
|
|
||||||
* *This is currently unused by Cura.*
|
|
||||||
* `options` (dictionary): __A list of values that the user can choose from.__
|
|
||||||
* The keys of this dictionary are keys that CuraEngine identifies the option with.
|
|
||||||
* The values are human-readable strings and will be translated.
|
|
||||||
* Only applies to (and only required for) enum-type settings.
|
|
||||||
* `comments` (optional string): __Comments to other programmers about the setting.__
|
|
||||||
* *This is currently unused by Cura.*
|
|
||||||
* `is_uuid` (optional boolean): __Whether or not this setting indicates a UUID-4.__
|
|
||||||
* If it is, the setting will indicate an error if it's not in the correct format.
|
|
||||||
* Only applies to string-type settings.
|
|
||||||
* `regex_blacklist_pattern` (optional string): __A regular expression, where if the setting value matches with this regular expression, it gets an error state.__
|
|
||||||
* Only applies to string-type settings.
|
|
||||||
* `error_value` (optional): __If the setting value is equal to this value, it will show a setting error.__
|
|
||||||
* This is used to display errors for non-numerical settings such as checkboxes.
|
|
||||||
* `warning_value` (optional): __If the setting value is equal to this value, it will show a setting warning.__
|
|
||||||
* This is used to display warnings for non-numerical settings such as checkboxes.
|
|
@ -1,33 +0,0 @@
|
|||||||
Repositories
|
|
||||||
====
|
|
||||||
Cura uses a number of repositories where parts of our source code are separated, in order to get a cleaner architecture. Those repositories are:
|
|
||||||
* [Cura](https://github.com/Ultimaker/Cura) is the main repository for the front-end of Cura. This contains:
|
|
||||||
- all of the business logic for the front-end, including the specific types of profiles that are available
|
|
||||||
- the concept of 3D printers and materials
|
|
||||||
- specific tools for handling 3D printed models
|
|
||||||
- pretty much all of the GUI
|
|
||||||
- Ultimaker services such as the Marketplace and accounts.
|
|
||||||
* [Uranium](https://github.com/Ultimaker/Uranium) is the underlying framework the Cura repository is built on. [Uranium](https://github.com/Ultimaker/Uranium) is a framework for desktop applications that handle 3D models. It has a separate back-end. This provides Cura with:
|
|
||||||
- a basic GUI framework ([Qt](https://www.qt.io/))
|
|
||||||
- a 3D scene, a rendering system
|
|
||||||
- a plug-in system
|
|
||||||
- a system for stacked profiles that change settings.
|
|
||||||
* [CuraEngine](https://github.com/Ultimaker/CuraEngine) is the slicer used by Cura in the background. This does the actual process that converts 3D models into a toolpath for the printer.
|
|
||||||
* [libArcus](https://github.com/Ultimaker/libArcus) handles the communication to CuraEngine. [libArcus](https://github.com/Ultimaker/libArcus) is a small library that wraps around [Protobuf](https://developers.google.com/protocol-buffers/) in order to make it run over a local socket.
|
|
||||||
* [cura-build](https://github.com/Ultimaker/cura-build): Cura's build scripts.
|
|
||||||
* [cura-build-environment](https://github.com/Ultimaker/cura-build-environment) build scripts for building dependencies.
|
|
||||||
|
|
||||||
There are also a number of repositories under our control that are not integral parts of Cura's architecture, but more like separated side-gigs:
|
|
||||||
* [libSavitar](https://github.com/Ultimaker/libSavitar) is used for loading and writing 3MF files.
|
|
||||||
* [libCharon](https://github.com/Ultimaker/libCharon) is used for loading and writing UFP files.
|
|
||||||
* [cura-binary-data](https://github.com/Ultimaker/cura-binary-data) pre-compiled parts to make the build system a bit simpler. This holds things which would require considerable tooling to build automatically like:
|
|
||||||
- the machine-readable translation files
|
|
||||||
- the Marlin builds for firmware updates
|
|
||||||
* [Cura-squish-tests](https://github.com/Ultimaker/Cura-squish-tests): automated GUI tests.
|
|
||||||
* [fdm_materials](https://github.com/Ultimaker/fdm_materials) stores Material profiles. This is separated out and combined in our build process, so that the firmware for Ultimaker's printers can use the same set of profiles too.
|
|
||||||
|
|
||||||
Interplay
|
|
||||||
----
|
|
||||||
At a very high level, Cura's repositories interconnect as follows:
|
|
||||||
|
|
||||||

|
|
Before Width: | Height: | Size: 60 KiB |
@ -1,54 +0,0 @@
|
|||||||
digraph {
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "zlib/1.2.12"
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "openssl/1.1.1l"
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "expat/2.4.1"
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "libffi/3.2.1"
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "mpdecimal/2.5.0@ultimaker/testing"
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "libuuid/1.0.3"
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "libxcrypt/4.4.25"
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "bzip2/1.0.8"
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "gdbm/1.19"
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "sqlite3/3.36.0"
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "tk/8.6.10"
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "ncurses/6.2"
|
|
||||||
"cpython/3.10.4@ultimaker/testing" -> "xz_utils/5.2.5"
|
|
||||||
"pynest2d/5.1.0-beta+3@ultimaker/stable" -> "libnest2d/5.1.0-beta+3@ultimaker/stable"
|
|
||||||
"pynest2d/5.1.0-beta+3@ultimaker/stable" -> "cpython/3.10.4@ultimaker/testing"
|
|
||||||
"freetype/2.12.1" -> "libpng/1.6.37"
|
|
||||||
"freetype/2.12.1" -> "zlib/1.2.12"
|
|
||||||
"freetype/2.12.1" -> "bzip2/1.0.8"
|
|
||||||
"freetype/2.12.1" -> "brotli/1.0.9"
|
|
||||||
"savitar/5.1.0-beta+3@ultimaker/stable" -> "pugixml/1.12.1"
|
|
||||||
"savitar/5.1.0-beta+3@ultimaker/stable" -> "cpython/3.10.4@ultimaker/testing"
|
|
||||||
"arcus/5.1.0-beta+3@ultimaker/stable" -> "protobuf/3.17.1"
|
|
||||||
"arcus/5.1.0-beta+3@ultimaker/stable" -> "cpython/3.10.4@ultimaker/testing"
|
|
||||||
"arcus/5.1.0-beta+3@ultimaker/stable" -> "zlib/1.2.12"
|
|
||||||
"libpng/1.6.37" -> "zlib/1.2.12"
|
|
||||||
"curaengine/5.1.0-beta+3@ultimaker/stable" -> "clipper/6.4.2"
|
|
||||||
"curaengine/5.1.0-beta+3@ultimaker/stable" -> "boost/1.78.0"
|
|
||||||
"curaengine/5.1.0-beta+3@ultimaker/stable" -> "rapidjson/1.1.0"
|
|
||||||
"curaengine/5.1.0-beta+3@ultimaker/stable" -> "stb/20200203"
|
|
||||||
"curaengine/5.1.0-beta+3@ultimaker/stable" -> "protobuf/3.17.1"
|
|
||||||
"curaengine/5.1.0-beta+3@ultimaker/stable" -> "arcus/5.1.0-beta+3@ultimaker/stable"
|
|
||||||
"tcl/8.6.10" -> "zlib/1.2.12"
|
|
||||||
"uranium/5.1.0-beta+3@ultimaker/stable" -> "arcus/5.1.0-beta+3@ultimaker/stable"
|
|
||||||
"uranium/5.1.0-beta+3@ultimaker/stable" -> "cpython/3.10.4@ultimaker/testing"
|
|
||||||
"libnest2d/5.1.0-beta+3@ultimaker/stable" -> "boost/1.78.0"
|
|
||||||
"libnest2d/5.1.0-beta+3@ultimaker/stable" -> "clipper/6.4.2"
|
|
||||||
"libnest2d/5.1.0-beta+3@ultimaker/stable" -> "nlopt/2.7.0"
|
|
||||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "arcus/5.1.0-beta+3@ultimaker/stable"
|
|
||||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "curaengine/5.1.0-beta+3@ultimaker/stable"
|
|
||||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "savitar/5.1.0-beta+3@ultimaker/stable"
|
|
||||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "pynest2d/5.1.0-beta+3@ultimaker/stable"
|
|
||||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "uranium/5.1.0-beta+3@ultimaker/stable"
|
|
||||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "fdm_materials/5.1.0-beta+3@ultimaker/stable"
|
|
||||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "cura_binary_data/5.1.0-beta+3@ultimaker/stable"
|
|
||||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "cpython/3.10.4@ultimaker/testing"
|
|
||||||
"fontconfig/2.13.93" -> "freetype/2.12.1"
|
|
||||||
"fontconfig/2.13.93" -> "expat/2.4.1"
|
|
||||||
"fontconfig/2.13.93" -> "libuuid/1.0.3"
|
|
||||||
"tk/8.6.10" -> "tcl/8.6.10"
|
|
||||||
"tk/8.6.10" -> "fontconfig/2.13.93"
|
|
||||||
"tk/8.6.10" -> "xorg/system"
|
|
||||||
"protobuf/3.17.1" -> "zlib/1.2.12"
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="700" height="1010">
|
|
||||||
<defs>
|
|
||||||
<path id="stack-header" d="m0,50 v-30 a20,20 0 0 1 20,-20 h260 a20,20 0 0 1 20,20 v30 z" />
|
|
||||||
<marker id="arrow" refX="2" refY="1.5" markerWidth="3" markerHeight="3" orient="auto-start-reverse">
|
|
||||||
<polygon points="0,0 3,1.5 0,3" />
|
|
||||||
</marker>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<g stroke="black" stroke-width="5" fill="silver"> <!-- Stack headers. -->
|
|
||||||
<use href="#stack-header" x="200" y="555" />
|
|
||||||
<use href="#stack-header" x="5" y="5" />
|
|
||||||
<use href="#stack-header" x="395" y="5" />
|
|
||||||
</g>
|
|
||||||
<g stroke="black" stroke-width="10" fill="none"> <!-- Stack outlines. -->
|
|
||||||
<rect x="200" y="555" width="300" height="450" rx="20" /> <!-- Global stack. -->
|
|
||||||
<rect x="5" y="5" width="300" height="450" rx="20" /> <!-- Left extruder. -->
|
|
||||||
<rect x="395" y="5" width="300" height="450" rx="20" /> <!-- Right extruder. -->
|
|
||||||
</g>
|
|
||||||
<g font-family="sans-serif" font-size="25" dominant-baseline="middle" text-anchor="middle">
|
|
||||||
<text x="350" y="582.5">Global stack</text> <!-- Slightly lowered since the top line is thicker than the bottom. -->
|
|
||||||
<text x="350" y="630">User</text>
|
|
||||||
<text x="350" y="680">Custom</text>
|
|
||||||
<text x="350" y="730">Intent</text>
|
|
||||||
<text x="350" y="780">Quality</text>
|
|
||||||
<text x="350" y="830">Material</text>
|
|
||||||
<text x="350" y="880">Variant</text>
|
|
||||||
<text x="350" y="930">Definition changes</text>
|
|
||||||
<text x="350" y="980">Printer</text>
|
|
||||||
|
|
||||||
<text x="155" y="32.5">Left extruder</text> <!-- Slightly lowered again. -->
|
|
||||||
<text x="155" y="80">User</text>
|
|
||||||
<text x="155" y="130">Custom</text>
|
|
||||||
<text x="155" y="180">Intent</text>
|
|
||||||
<text x="155" y="230">Quality</text>
|
|
||||||
<text x="155" y="280">Material</text>
|
|
||||||
<text x="155" y="330">Nozzle</text>
|
|
||||||
<text x="155" y="380">Definition changes</text>
|
|
||||||
<text x="155" y="430">Extruder</text>
|
|
||||||
|
|
||||||
<text x="545" y="32.5">Right extruder</text> <!-- Slightly lowered again. -->
|
|
||||||
<text x="545" y="80">User</text>
|
|
||||||
<text x="545" y="130">Custom</text>
|
|
||||||
<text x="545" y="180">Intent</text>
|
|
||||||
<text x="545" y="230">Quality</text>
|
|
||||||
<text x="545" y="280">Material</text>
|
|
||||||
<text x="545" y="330">Nozzle</text>
|
|
||||||
<text x="545" y="380">Definition changes</text>
|
|
||||||
<text x="545" y="430">Extruder</text>
|
|
||||||
</g>
|
|
||||||
<g stroke="black" stroke-width="5" marker-end="url(#arrow)"> <!-- Arrows. -->
|
|
||||||
<line x1="155" y1="455" x2="345" y2="545" />
|
|
||||||
<line x1="545" y1="455" x2="355" y2="545" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.6 KiB |
@ -1,70 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
|
|
||||||
<defs>
|
|
||||||
<marker id="arrow" refX="2" refY="1.5" markerWidth="3" markerHeight="3" orient="auto-start-reverse">
|
|
||||||
<polygon points="0,0 3,1.5 0,3" />
|
|
||||||
</marker>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<g marker-end="url(#arrow)" stroke="black" stroke-width="5"> <!-- Arrows. -->
|
|
||||||
<!-- Towards CuraEngine and back. -->
|
|
||||||
<line x1="475" y1="400" x2="475" y2="307.5" />
|
|
||||||
<line x1="475" y1="250" x2="475" y2="210" />
|
|
||||||
<line x1="525" y1="200" x2="525" y2="242.5" />
|
|
||||||
<line x1="525" y1="300" x2="525" y2="390" />
|
|
||||||
|
|
||||||
<!-- From libSavitar. -->
|
|
||||||
<line x1="100" y1="425" x2="142.5" y2="425" />
|
|
||||||
<line x1="300" y1="425" x2="390" y2="425" />
|
|
||||||
|
|
||||||
<!-- From fdm_materials. -->
|
|
||||||
<line x1="350" y1="575" x2="390" y2="575" />
|
|
||||||
|
|
||||||
<!-- To libCharon. -->
|
|
||||||
<line x1="600" y1="500" x2="692.5" y2="500" />
|
|
||||||
<line x1="900" y1="500" x2="945" y2="500" />
|
|
||||||
|
|
||||||
<!-- To Uranium. -->
|
|
||||||
<line x1="500" y1="600" x2="500" y2="690" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g stroke="black" fill="none"> <!-- Boxes representing repositories. -->
|
|
||||||
<g stroke-width="10"> <!-- Major repositories. -->
|
|
||||||
<rect x="400" y="400" width="200" height="200" rx="20" /> <!-- Cura. -->
|
|
||||||
<rect x="350" y="700" width="300" height="200" rx="20" /> <!-- Uranium. -->
|
|
||||||
<rect x="300" y="5" width="400" height="195" rx="20" /> <!-- CuraEngine. -->
|
|
||||||
</g>
|
|
||||||
<g stroke-width="5"> <!-- Minor repositories. -->
|
|
||||||
<rect x="150" y="350" width="150" height="100" rx="20" /> <!-- libSavitar. -->
|
|
||||||
<rect x="100" y="550" width="250" height="100" rx="20" /> <!-- fdm_materials. -->
|
|
||||||
<rect x="430" y="250" width="140" height="50" rx="20" /> <!-- libArcus. -->
|
|
||||||
<rect x="700" y="450" width="200" height="100" rx="20" /> <!-- libCharon. -->
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g font-family="sans-serif" text-anchor="middle" dominant-baseline="middle"> <!-- Labels. -->
|
|
||||||
<g font-size="50"> <!-- Major repositories. -->
|
|
||||||
<text x="500" y="500">Cura</text>
|
|
||||||
<text x="500" y="800">Uranium</text>
|
|
||||||
<text x="500" y="102.5">CuraEngine</text>
|
|
||||||
</g>
|
|
||||||
<g font-size="25"> <!-- Minor repositories and arrows. -->
|
|
||||||
<text x="225" y="400">libSavitar</text>
|
|
||||||
<text x="225" y="600">fdm_materials</text>
|
|
||||||
<text x="500" y="275">libArcus</text>
|
|
||||||
<text x="800" y="500">libCharon</text>
|
|
||||||
|
|
||||||
<g text-anchor="start">
|
|
||||||
<text x="645" y="490" transform="rotate(-90, 645, 490)">G-code</text>
|
|
||||||
<text x="950" y="500">UFP</text>
|
|
||||||
<text x="535" y="345">G-code</text>
|
|
||||||
<text x="345" y="415" transform="rotate(-90, 345, 415)">Model</text>
|
|
||||||
<text x="510" y="645">Built upon</text>
|
|
||||||
</g>
|
|
||||||
<g text-anchor="end">
|
|
||||||
<text x="465" y="345">Scene</text>
|
|
||||||
<text x="90" y="425">3MF</text>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.0 KiB |
@ -1,27 +0,0 @@
|
|||||||
Build Volume
|
|
||||||
====
|
|
||||||
The build volume is a scene node. This node gets placed somewhere in the scene. This is a specialised scene node that draws the build volume and all of its bits and pieces.
|
|
||||||
|
|
||||||
Volume bounds
|
|
||||||
----
|
|
||||||
The build volume draws a cube (for rectangular build plates) that represents the printable build volume. This outline is drawn with a blue line. To render this, the Build Volume scene node generates a cube and instructs OpenGL to draw a wireframe of this cube. This way the wireframe is always a single pixel wide regardless of view distance. This cube is automatically resized when the relevant settings change, like the width, height and depth of the printer, the shape of the build plate, the Print Sequence or the gantry height.
|
|
||||||
|
|
||||||
The build volume also draws a grid underneath the build volume. The grid features 1cm lines which allows the user to roughly estimate how big its print is or the distance between prints. It also features a finer 1mm line pattern within that grid. The grid is drawn as a single quad. This quad is then sent to the graphical card with a specialised shader which draws the grid pattern.
|
|
||||||
|
|
||||||
For elliptical build plates, the volume bounds are drawn as two circles, one at the top and one at the bottom of the available height. The build plate grid is drawn as a tessellated circle, but with the same shader.
|
|
||||||
|
|
||||||
Disallowed areas
|
|
||||||
----
|
|
||||||
The build volume also calculates and draws the disallowed areas. These are drawn as a grey shadow. The point of these disallowed areas is to denote the areas where the user is not allowed to place any objects. The reason to forbid placing an object can be a lot of things.
|
|
||||||
|
|
||||||
One disallowed area that is always present is the border around the build volume. This border is there to prevent the nozzle from going outside of the bounds of the build volume. For instance, if you were to print an object with a brim of 8mm, you won't be able to place that object closer than 8mm to the edge of the build volume. Doing so would draw part of the brim outside of the build volume. The width of these disallowed areas depends on a bunch of things. Most commonly the build plate adhesion setting or the Avoid Distance setting is the culprit. However this border is also affected by the draft shield, ooze shield and Support Horizontal Expansion, among others.
|
|
||||||
|
|
||||||
Another disallowed area stems from the distance between the nozzles for some multi-extrusion printers. The total build volume in Cura is normally the volume that can be reached by either nozzle. However for every extruder that your print uses, the build volume will be shrunk to the intersecting area that all used nozzles can reach. This is done by adding disallowed areas near the border. For instance, if you have two extruders with 18mm X distance between them, and your print uses only the left extruder, there will be an extra border of 18mm on the right hand side of the printer, because the left nozzle can't reach that far to the right. If you then use both extruders, there will be an 18mm border on both sides.
|
|
||||||
|
|
||||||
There are also disallowed areas for features that are printed. There are as of this writing two such disallowed areas: The prime tower and the prime blob. You can't print an object on those locations since they would intersect with the printed feature.
|
|
||||||
|
|
||||||
Then there are disallowed areas imposed by the current printer. Some printers have things in the way of your print, such as clips that hold the build plate down, or cameras, switching bays or wiping brushes. These are encoded in the `machine_disallowed_areas` and `nozzle_disallowed_areas` settings, as polygons. The difference between these two settings is that one is intended to describe where the print head is not allowed to move. The other is intended to describe where the currently active nozzle is not allowed to move. This distinction is meant to allow inactive nozzles to move over things like build plate clips or stickers, which can slide underneath an inactive nozzle.
|
|
||||||
|
|
||||||
Finally, there are disallowed areas imposed by other objects that you want to print. Each object and group has an associated Convex Hull Node, which denotes the volume that the object is going to be taking up while printing. This convex hull is projected down to the build plate and determines there the volume that the object is going to occupy.
|
|
||||||
|
|
||||||
Each type of disallowed area is affected by certain settings. The border around the build volume, for instance, is affected by the brim, but the disallowed areas for printed objects are not. This is because the brim could go outside of the build volume but the brim can't hit any other objects. If the brim comes too close to other objects, it merges with the brim of those objects. As such, generating each type of disallowed area requires specialised business logic to determine how the setting affects the disallowed area. It needs to take the highest of two settings sometimes, or it needs to sum them together, multiplying a certain line width by an accompanying line count setting, and so on. All this logic is implemented in the BuildVolume class.
|
|
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 196 KiB |
Before Width: | Height: | Size: 345 KiB |
Before Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 27 KiB |
@ -1,113 +0,0 @@
|
|||||||
# Operations and the OperationStack
|
|
||||||
|
|
||||||
Cura supports an operation stack. The `OperationStack` class maintains a history of the operations performed in Cura, which allows for undo and redo actions. Every operation registers itself in the stack. The OperationStuck supports the following functions:
|
|
||||||
|
|
||||||
* `push(operation)`: Pushes an operation in the stack and applies the operation. This function is called when an operation pushes itself in the stack.
|
|
||||||
* `undo()`: Reverses the actions performed by the last operation and reduces the current index of the stack.
|
|
||||||
* `redo()`: Applies the actions performed by the next operation in the stack and increments the current index of the stack.
|
|
||||||
* `getOperations()`: Returns a list of all the operations that are currently inside the OperationStack
|
|
||||||
* `canUndo()`: Indicates whether the index of the operation stack has reached the bottom of the stack, which means that there are no more operations to be undone.
|
|
||||||
* `canRedo()`: Indicates whether the index of the operation stack has reached the top of the stack, which means that there are no more operations to be redone.
|
|
||||||
|
|
||||||
**Note 1:** When consecutive operations are performed very quickly after each other, they are merged together at the top of the stack. This action ensures that these minor operation will be undone with one Undo keystroke (e.g. when moving the object around and you press and release the left mouse button really fast, it is considered as one move operation).
|
|
||||||
|
|
||||||
**Note 2:** When an operation is pushed in the middle of the stack, all operations above it are removed from the stack. This ensures that there won't be any "history branches" created.
|
|
||||||
|
|
||||||
### Operations
|
|
||||||
|
|
||||||
Every action that happens in the scene and affects one or multiple models is associated with a subclass of the `Operation` class and is it added to the `OperationStack`. The subclassed operations that can be found in Cura (excluding the ones from downloadable plugins) are the following:
|
|
||||||
|
|
||||||
* [GroupedOperation](#groupedoperation)
|
|
||||||
* [AddSceneNodeOperation](#addscenenodeoperation)
|
|
||||||
* [RemoveSceneNodeOperation](#removescenenodeoperation)
|
|
||||||
* [SetParentOperation](#setparentoperation)
|
|
||||||
* [SetTransformOperation](#settransformoperation)
|
|
||||||
* [SetObjectExtruderOperation](#setobjectextruderoperation)
|
|
||||||
* [GravityOperation](#gravityoperation)
|
|
||||||
* [PlatformPhysicsOperation](#platformphysicsoperation)
|
|
||||||
* [TranslateOperation](#translateoperation)
|
|
||||||
* [ScaleOperation](#scaleoperation)
|
|
||||||
* [RotateOperation](#rotateoperation)
|
|
||||||
* [MirrorOperation](#mirroroperation)
|
|
||||||
* [LayFlatOperation](#layflatoperation)
|
|
||||||
* [SetBuildPlateNumberOperation]()
|
|
||||||
|
|
||||||
### GroupedOperation
|
|
||||||
|
|
||||||
The `GroupedOperation` is an operation that groups several other operations together. The intent of this operation is to hide an underlying chain of operations from the user if they correspond to only one interaction with the user, such as an operation applied to multiple scene nodes or a re-arrangement of multiple items in the scene.
|
|
||||||
|
|
||||||
Once a `GroupedOperation` is pushed into the stack, it applies all of its children operations in one go. Similarly, when it is undone, it reverses all its children operations at once.
|
|
||||||
|
|
||||||
|
|
||||||
### AddSceneNodeOperation
|
|
||||||
|
|
||||||
The `AddSceneNodeOperation` is added to the stack whenever a mesh is loaded inside the `Scene`, either by a `FileReader` or by inserting a [Support Blocker](tools.md#supporteraser-tool) in an object.
|
|
||||||
|
|
||||||
### RemoveSceneNodeOperation
|
|
||||||
|
|
||||||
The `RemoveSceneNodeOperation` is added to the stack whenever a mesh is removed from the Scene by the user or when the user requests to clear the build plate (_Ctrl+D_).
|
|
||||||
|
|
||||||
### SetParentOperation
|
|
||||||
|
|
||||||
The `SetParentOperation` changes the parent of a node. It is primarily used when grouping (the group node is set as the nodes' parent) and ungrouping (the group's children's parent is set to the group's parent before the group node is deleted), or when a SupportEraser node is added to the scene (to set the selected object as the Eraser's parent).
|
|
||||||
|
|
||||||
### SetTransformOperation
|
|
||||||
|
|
||||||
The `SetTransformOperation` translates, rotates, and scales a node all at once. This operation accepts a transformation matrix, an orientation matrix, and a scale matrix, and it is used by the _"Reset All Model Positions"_ and _"Reset All Model Transformations"_ options in the right-click (context) menu.
|
|
||||||
|
|
||||||
### SetObjectExtruderOperation
|
|
||||||
|
|
||||||
This operation is used to set the extruder with which a certain object should be printed with. It adds a [SettingOverrideDecorator](scene.md#settingoverridedecorator) to the object (if it doesn't have any) and then sets the extruder number via the decoration function `node.callDecoration("setActiveExtruder", extruder_id)`.
|
|
||||||
|
|
||||||
### GravityOperation
|
|
||||||
|
|
||||||
The `GravityOperation` moves a scene node down to 0 on the y-axis. It is currently used by the _"Lay flat"_ and _"Select face to align to the build plate"_ actions of the `RotationTool` to ensure that the object will end up touching the build plate after the corresponding rotation operations have be done.
|
|
||||||
|
|
||||||
### PlatformPhysicsOperation
|
|
||||||
|
|
||||||
The `PlatformPhysicsOperation` is generated by the `PlatformPhysics` class and it is associated with the preferences _"Ensure models are kept apart"_ and _"Automatically drop models to the build plate"_. If any of these preferences is set to true, the `PlatformPhysics` class periodically checks to make sure that the two conditions are met and if not, it calculates the move vector for each of the nodes that will satisfy the conditions.
|
|
||||||
|
|
||||||
Once the move vectors have been computed, they are applied to the nodes through consecutive `PlatformPhysicsOperations`, whose job is to use the `translate` function on the nodes.
|
|
||||||
|
|
||||||
**Note:** When there are multiple nodes, multiple `PlatformPhysicsOperations` may be generated (all models may be moved to ensure they are kept apart). These operations eventually get merged together by the `OperationStack` due to the fact that the individual operations are applied very fast one after the other.
|
|
||||||
|
|
||||||
### TranslateOperation
|
|
||||||
|
|
||||||
The `TranslateOperation` applies a linear transformation on a node, moving the node in the scene. This operation is primarily linked to the [TranslateTool](tools.md#translatetool) but it is also used in other places around Cura, such as arranging objects on the build plate (Ctrl+R) and centering an object to the build plate (via the right-click context menu's _"Center Selected Model"_ option).
|
|
||||||
|
|
||||||
When an object is moved using the move tool handles, multiple translate operations are generated to make sure that the object is rendered properly while it is moved. These translate operations are merged together once the user releases the tool handle.
|
|
||||||
|
|
||||||
**Note:** Some functionalities may move (translate) nodes without generating a TranslateOperation (such as when a model with is imported from a 3mf into a certain position). This ensures that the moving of the object cannot be accidentally undone by the user.
|
|
||||||
|
|
||||||
### ScaleOperation
|
|
||||||
|
|
||||||
The `ScaleOperation` scales the selected scene node uniformly or non-uniformly. This operation is primarily generated by the [ScaleTool](tools.md#scaletool).
|
|
||||||
|
|
||||||
When an object is scaled using the scale tool handles, multiple scale operations are generated to make sure that the object is rendered properly while it is being resized. These scale operations are merged together once the user releases the tool handle.
|
|
||||||
|
|
||||||
**Note:** When the _"Scale extremely small models"_ or the _"Scale large models"_ preferences are enabled the model is scaled when it is inserted into the build plate but it **DOES NOT** generate a `ScaleOperation`. This ensures that Cura doesn't register the scaling as an action that can be undone and the user doesn't accidentally end up with a very big or very small model.
|
|
||||||
|
|
||||||
|
|
||||||
### RotateOperation
|
|
||||||
|
|
||||||
The `RotateOperation` rotates the selected scene node(s) according to a given rotation quaternion and, optionally, around a given point. This operation is primarily generated by the [RotationTool](tools.md#rotatetool). It is also used by the arrange algorithm, which may rotate some models to fit them in the build plate.
|
|
||||||
|
|
||||||
When an object is rotated using the rotate tool handles, multiple rotate operations are generated to make sure that the object is rendered properly while it is being rotated. These operations are merged together once the user releases the tool handle.
|
|
||||||
|
|
||||||
### MirrorOperation
|
|
||||||
|
|
||||||
The `MirrorOperation` mirrors the selected object. It is primarily associated with the [MirrorTool](tools.md#mirrortool) and allows for mirroring the object in a certain direction, using the `MirrorToolHandles`.
|
|
||||||
|
|
||||||
The `MirrorOperation` accepts a transformation matrix that should only define values on the diagonal of the matrix, and only the values 1 or -1. It allows for mirroring around the center of the object or around the axis origin. The latter isn't used that often.
|
|
||||||
|
|
||||||
### LayFlatOperation
|
|
||||||
|
|
||||||
The `LayFlatOperation` computes some orientation to hopefully lay the object flat on the build plate. It is generated by the `layFlat()` function of the [RotateTool](tools.md#rotatetool). Contrary to the other operations, the `LayFlatOperation` is computed in a separate thread through the `LayFlatJob` since it can be quite computationally expensive.
|
|
||||||
|
|
||||||
|
|
||||||
### SetBuildPlateNumberOperation
|
|
||||||
|
|
||||||
The `SetBuildPlateNumberOperation` is linked to a legacy feature which allowed the user to have multiple build plates open in Cura at the same time. With this operation it was possible to transfer a node to another build plate through the node's [BuildPlateDecorator](scene.md#buildplatedecorator) by calling the decoration `node.callDecoration("setBuildPlateNumber", new_build_plate_nr)`.
|
|
||||||
|
|
||||||
**Note:** Changing the active build plate is a disabled feature in Cura and it is intended to be completely removed (internal ticket: CURA-4975), along with the `SetBuildPlateNumberOperation`.
|
|
||||||
|
|
@ -1,216 +0,0 @@
|
|||||||
Scene
|
|
||||||
====
|
|
||||||
The 3D scene in Cura is designed as a [Scene Graph](https://en.wikipedia.org/wiki/Scene_graph), which is common in many 3D graphics applications. The scene graph of Cura is usually very flat, but has the possibility to have nested objects which inherit transformations from each other.
|
|
||||||
|
|
||||||
Scene Graph
|
|
||||||
----
|
|
||||||
Cura's scene graph is a mere tree data structure. This tree contains all scene nodes, which represent the objects in the 3D scene.
|
|
||||||
|
|
||||||
The main idea behind the scene tree is that each scene node has a transformation applied to it. The scene nodes can be nested beneath other scene nodes. The transformation of the parents is then also applied to the children. This way you can have scene nodes grouped together and transform the group as a whole. Since the transformations are all linear, this ensures that the elements of this group stay in the same relative position and orientation. It will look as if the whole group is a single object. This idea is very common for games where objects are often composed of multiple 3D models but need to move together as a whole. For Cura it is used to group objects together and to transform the collision area correctly.
|
|
||||||
|
|
||||||
Class Diagram
|
|
||||||
----
|
|
||||||
|
|
||||||
The following class diagram depicts the classes that interact with the Scene
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The scene lives in the Controller of the Application, and it is primarily interacting with SceneNode objects, which are the components of the Scene Graph.
|
|
||||||
|
|
||||||
|
|
||||||
A Typical Scene
|
|
||||||
----
|
|
||||||
Cura's scene has a few nodes that are always present, and a few nodes that are repeated for every object that the user loads onto their build plate. The root of the scene graph is a SceneNode that lives inside the Scene and contains all the other children SceneNodes of the scene. Typically, inside the root you can find the SceneNodes that are always loaded (the Cameras, the [BuildVolume](build_volume.md), and the Platform), the objects that are loaded on the platform, and finally a ConvexHullNode for each object and each group of objects in the Scene.
|
|
||||||
|
|
||||||
Let's take the following example Scene:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The scene graph in this case is the following:
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
**Note 1:** The Platform is actually a child of the BuildVolume.
|
|
||||||
|
|
||||||
**Note 2:** The ConvexHullNodes are not actually named after the object they decorate. Their names are used in the image to convey how the ConvexHullNodes are related to the objects in the scene.
|
|
||||||
|
|
||||||
**Note 3:** The CuraSceneNode that holds the layer data (inside the BuildVolume) is created and destroyed according to the availability of sliced layer data provided by the CuraEngine. See the [LayerDataDecorator](#layerdatadecorator) for more information.
|
|
||||||
|
|
||||||
Accessing SceneNodes in the Scene
|
|
||||||
----
|
|
||||||
|
|
||||||
SceneNodes can be accessed using a `BreadthFirstIterator` or a `DepthFirstIterator`. Each iterator traverses the scene graph and returns a Python iterator, which yields all the SceneNodes and their children.
|
|
||||||
|
|
||||||
``` python
|
|
||||||
for node in BreadthFirstIterator(scene.getRoot()):
|
|
||||||
# do stuff with the node
|
|
||||||
```
|
|
||||||
|
|
||||||
Example result when iterating the above scene graph:
|
|
||||||
|
|
||||||
```python
|
|
||||||
[i for i in BreadthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()]
|
|
||||||
```
|
|
||||||
* 00 = {SceneNode} <SceneNode object: 'Root'>
|
|
||||||
* 01 = {BuildVolume} <BuildVolume object '0x2e35dbce108'>
|
|
||||||
* 02 = {Camera} <Camera object: '3d'>
|
|
||||||
* 03 = {CuraSceneNode} <CuraSceneNode object: 'Torus.stl'>
|
|
||||||
* 04 = {CuraSceneNode} <CuraSceneNode object: 'Group #1'>
|
|
||||||
* 05 = {Camera} <Camera object: 'snapshot'>
|
|
||||||
* 06 = {CuraSceneNode} <CuraSceneNode object: 'Star.stl'>
|
|
||||||
* 07 = {ConvexHullNode} <ConvexHullNode object: '0x2e3000def08'>
|
|
||||||
* 08 = {ConvexHullNode} <ConvexHullNode object: '0x2e36861bd88'>
|
|
||||||
* 09 = {ConvexHullNode} <ConvexHullNode object: '0x2e3000bd4c8'>
|
|
||||||
* 10 = {ConvexHullNode} <ConvexHullNode object: '0x2e35fbb62c8'>
|
|
||||||
* 11 = {ConvexHullNode} <ConvexHullNode object: '0x2e3000a0648'>
|
|
||||||
* 12 = {ConvexHullNode} <ConvexHullNode object: '0x2e30019d0c8'>
|
|
||||||
* 13 = {ConvexHullNode} <ConvexHullNode object: '0x2e3001a2dc8'>
|
|
||||||
* 14 = {Platform} <Platform object '0x2e35a001948'>
|
|
||||||
* 15 = {CuraSceneNode} <CuraSceneNode object: 'Group #2'>
|
|
||||||
* 16 = {CuraSceneNode} <CuraSceneNode object: 'Sphere.stl'>
|
|
||||||
* 17 = {CuraSceneNode} <CuraSceneNode object: 'Cylinder.stl'>
|
|
||||||
* 18 = {CuraSceneNode} <CuraSceneNode object: 'Cube.stl'>
|
|
||||||
|
|
||||||
SceneNodeDecorators
|
|
||||||
----
|
|
||||||
|
|
||||||
SceneNodeDecorators are decorators that can be added to the nodes of the scene to provide them with additional functions.
|
|
||||||
|
|
||||||
Cura provides the following classes derived from the SceneNodeDecorator class:
|
|
||||||
1. [GroupDecorator](#groupdecorator)
|
|
||||||
2. [ConvexHullDecorator](#convexhulldecorator)
|
|
||||||
3. [SettingOverrideDecorator](#settingoverridedecorator)
|
|
||||||
4. [SliceableObjectDecorator](#sliceableobjectdecorator)
|
|
||||||
5. [LayerDataDecorator](#layerdatadecorator)
|
|
||||||
6. [ZOffsetDecorator](#zoffsetdecorator)
|
|
||||||
7. [BlockSlicingDecorator](#blockslicingdecorator)
|
|
||||||
8. [GCodeListDecorator](#gcodelistdecorator)
|
|
||||||
9. [BuildPlateDecorator](#buildplatedecorator)
|
|
||||||
|
|
||||||
GroupDecorator
|
|
||||||
----
|
|
||||||
|
|
||||||
Whenever objects on the build plate are grouped together, a new node is added in the scene as the parent of the grouped objects. Group nodes can be identified when traversing the SceneGraph by running the following:
|
|
||||||
|
|
||||||
```python
|
|
||||||
node.callDecoration("isGroup") == True
|
|
||||||
```
|
|
||||||
|
|
||||||
Group nodes decorated by GroupDecorators are added in the scene either by reading project files which contain grouped objects, or when the user selects multiple objects and groups them together (Ctrl + G).
|
|
||||||
|
|
||||||
Group nodes that are left with only one child are removed from the scene, making their only child a child of the group's parent. In addition, group nodes without any remaining children are removed from the scene.
|
|
||||||
|
|
||||||
ConvexHullDecorator
|
|
||||||
----
|
|
||||||
|
|
||||||
As seen in the scene graph of the scene example, each CuraSceneNode that represents an object on the build plate is linked to a ConvexHullNode which is rendered as the object's shadow on the build plate. The ConvexHullDecorator is the link between these two nodes.
|
|
||||||
|
|
||||||
In essence, the CuraSceneNode has a ConvexHullDecorator which points to the ConvexHullNode of the object. The data of the object's convex hull can be accessed via
|
|
||||||
|
|
||||||
```python
|
|
||||||
convex_hull_polygon = object_node.callDecoration("getConvexHull")
|
|
||||||
```
|
|
||||||
|
|
||||||
The ConvexHullDecorator also provides convex hulls that include the head, the fans, and the adhesion of the object. These are primarily used and rendered when One-at-a-time mode is activated.
|
|
||||||
|
|
||||||
For more information on the functions added to the node by this decorator, visit the [ConvexHullDecorator.py](https://github.com/Ultimaker/Cura/blob/master/cura/Scene/ConvexHullDecorator.py).
|
|
||||||
|
|
||||||
SettingOverrideDecorator
|
|
||||||
----
|
|
||||||
|
|
||||||
SettingOverrideDecorators are primarily used for modifier meshes such as support meshes, cutting meshes, infill meshes, and anti-overhang meshes. When a user converts an object to a modifier mesh, the object's node is decorated by a SettingOverrideDecorator. This decorator adds a PerObjectContainerStack to the CuraSceneNode, which allows the user to modify the settings of the specific model.
|
|
||||||
|
|
||||||
For more information on the functions added to the node by this decorator, visit the [SettingOverrideDecorator.py](https://github.com/Ultimaker/Cura/blob/master/cura/Settings/SettingOverrideDecorator.py).
|
|
||||||
|
|
||||||
|
|
||||||
SliceableObjectDecorator
|
|
||||||
----
|
|
||||||
|
|
||||||
This is a convenience decorator that allows us to easily identify the nodes which can be sliced. All **individual** objects (meshes) added to the build plate receive this decorator, apart from the nodes loaded from GCode files (.gcode, .g, .gz, .ufp).
|
|
||||||
|
|
||||||
The SceneNodes that do not receive this decorator are:
|
|
||||||
|
|
||||||
- Cameras
|
|
||||||
- BuildVolume
|
|
||||||
- Platform
|
|
||||||
- ConvexHullNodes
|
|
||||||
- CuraSceneNodes that serve as group nodes (these have a GroupDecorator instead)
|
|
||||||
- The CuraSceneNode that serves as the layer data node
|
|
||||||
- ToolHandles
|
|
||||||
- NozzleNode
|
|
||||||
- Nodes that contain GCode data. See the [BlockSlicingDecorator](#blockslicingdecorator) for more information on that.
|
|
||||||
|
|
||||||
This decorator provides the following function to the node:
|
|
||||||
|
|
||||||
```python
|
|
||||||
node.callDecoration("isSliceable")
|
|
||||||
```
|
|
||||||
|
|
||||||
LayerDataDecorator
|
|
||||||
----
|
|
||||||
|
|
||||||
Once the Slicing has completed and the CuraEngine has returned the slicing data, Cura creates a CuraSceneNode inside the BuildVolume which is decorated by a LayerDataDecorator. This decorator holds the layer data of the scene.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The layer data can be accessed through the function given to the aforementioned CuraSceneNode by the LayerDataDecorator:
|
|
||||||
|
|
||||||
```python
|
|
||||||
node.callDecoration("getLayerData")
|
|
||||||
```
|
|
||||||
|
|
||||||
This CuraSceneNode is created once Cura has completed processing the Layer data (after the user clicks on the Preview tab after slicing). The CuraSceneNode then is destroyed once any action that changes the Scene occurs (e.g. if the user moves/rotates/scales an object or changes a setting value), indicating that the layer data is no longer available. When that happens, the "Slice" button becomes available again.
|
|
||||||
|
|
||||||
ZOffsetDecorator
|
|
||||||
----
|
|
||||||
|
|
||||||
The ZOffsetDecorator is added to an object in the scene when that object is moved below the build plate. It is primarily used when the "Automatically drop models to the build plate" preference is enabled, in order to make sure that the GravityOperation, which drops the mode on the build plate, is not applied when the object is moved under the build plate.
|
|
||||||
|
|
||||||
The amount the object is moved under the build plate can be retrieved by calling the "getZOffset" decoration on the node:
|
|
||||||
|
|
||||||
```python
|
|
||||||
z_offset = node.callDecoration("getZOffset")
|
|
||||||
```
|
|
||||||
|
|
||||||
The ZOffsetDecorator is removed from the node when the node is move above the build plate.
|
|
||||||
|
|
||||||
BlockSlicingDecorator
|
|
||||||
----
|
|
||||||
|
|
||||||
The BlockSlicingDecorator is the opposite of the SliceableObjectDecorator. It is added on objects loaded on the scene which should not be sliced. This decorator is primarily added on objects loaded from ".gcode", ".ufp", ".g", and ".gz" files. Such an object already contains all the slice information and therefore should not allow Cura to slice it.
|
|
||||||
|
|
||||||
If an object with a BlockSlicingDecorator appears in the scene, the backend (CuraEngine) and the print setup (changing print settings) become disabled, considering that G-code files cannot be modified.
|
|
||||||
|
|
||||||
The BlockSlicingDecorator adds the following decoration function to the node:
|
|
||||||
|
|
||||||
```python
|
|
||||||
node.callDecoration("isBlockSlicing")
|
|
||||||
```
|
|
||||||
|
|
||||||
GCodeListDecorator
|
|
||||||
----
|
|
||||||
|
|
||||||
The GCodeListDecorator is also added only when a file containing GCode is loaded in the scene. It's purpose is to hold a list of all the GCode data of the loaded object.
|
|
||||||
The GCode list data is stored in the scene's gcode_dict attribute which then is used in other places in the Cura code, e.g. to provide the GCode to the GCodeWriter or to the PostProcessingPlugin.
|
|
||||||
|
|
||||||
The GCode data becomes available by calling the "getGCodeList" decoration of the node:
|
|
||||||
|
|
||||||
```python
|
|
||||||
gcode_list = node.callDecoration("getGCodeList")
|
|
||||||
```
|
|
||||||
|
|
||||||
The CuraSceneNode with the GCodeListDecorator is destroyed when another object or project file is loaded in the Scene.
|
|
||||||
|
|
||||||
BuildPlateDecorator
|
|
||||||
----
|
|
||||||
|
|
||||||
The BuildPlateDecorator is added to all the CuraSceneNodes. This decorator is linked to a legacy feature which allowed the user to have multiple build plates open in Cura at the same time. With this decorator it was possible to determine which nodes are present on each build plate, and therefore, which objects should be visible in the currently active build plate. It indicates the number of the build plate this scene node belongs to, which currently is always the build plate -1.
|
|
||||||
|
|
||||||
This decorator provides a function to the node that returns the number of the build plate it belongs to:
|
|
||||||
|
|
||||||
```python
|
|
||||||
node.callDecoration("getBuildPlateNumber")
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** Changing the active build plate is a disabled feature in Cura and it is intended to be completely removed (internal ticket: CURA-4975).
|
|
@ -1,86 +0,0 @@
|
|||||||
# Tools
|
|
||||||
|
|
||||||
Tools are plugin objects which are used to manipulate or interact with the scene and the objects (node) in the scene.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Tools live inside the Controller of the Application and may be associated with ToolHandles. Some of them interact with the scene as a whole (such as the Camera), while others interact with the objects (nodes) in the Scene (selection tool, rotate tool, scale tool etc.). The tools that are available in Cura (excluding the ones provided by downloadable plugins) are the following:
|
|
||||||
|
|
||||||
* [CameraTool](#cameratool)
|
|
||||||
* [SelectionTool](#selectiontool)
|
|
||||||
* [TranslateTool](#translatetool)
|
|
||||||
* [ScaleTool](#scaletool)
|
|
||||||
* [RotateTool](#rotatetool)
|
|
||||||
* [MirrorTool](#mirrortool)
|
|
||||||
* [PerObjectSettingsTool](#perobjectsettingstool)
|
|
||||||
* [SupportEraserTool](#supporteraser)
|
|
||||||
|
|
||||||
*****
|
|
||||||
|
|
||||||
### CameraTool
|
|
||||||
|
|
||||||
The CameraTool is the tool that allows the user to manipulate the Camera. It provides the functions of moving, zooming, and rotating the Camera. This tool does not contain a handle.
|
|
||||||
|
|
||||||
### SelectionTool
|
|
||||||
This tool allows the user to select objects and groups of objects in the scene. The selected objects gain a blue outline and become available in the code through the Selection class.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
This tool does not contain a handle.
|
|
||||||
|
|
||||||
### TranslateTool
|
|
||||||
|
|
||||||
This tool allows the user to move the object around the build plate. The TranslateTool is activated once the user presses the Move icon in the tool sidebar or hits the shortcut (T) while an object is selected.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The TranslateTool contains the TranslateToolHandle, which draws the arrow handles on the selected object(s). The TranslateTool generates TranslateOperations whenever the object is moved around the build plate.
|
|
||||||
|
|
||||||
|
|
||||||
### ScaleTool
|
|
||||||
|
|
||||||
This tool allows the user to scale the selected object(s). The ScaleTool is activated once the user presses the Scale icon in the tool sidebar or hits the shortcut (S) while an object is selected.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The ScaleTool contains the ScaleToolHandle, which draws the box handles on the selected object(s). The ScaleTool generates ScaleOperations whenever the object is scaled.
|
|
||||||
|
|
||||||
### RotateTool
|
|
||||||
|
|
||||||
This tool allows the user to rotate the selected object(s). The RotateTool is activated once the user presses the Rotate icon in the tool sidebar or hits the shortcut (R) while an object is selected.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The RotateTool contains the RotateToolHandle, which draws the donuts (tori) and arrow handles on the selected object(s). The RotateTool generates RotateOperations whenever the object is rotated or if a face is selected to be laid flat on the build plate. It also contains the `layFlat()` action, which generates the [LayFlatOperation](operations.md#layflatoperation).
|
|
||||||
|
|
||||||
|
|
||||||
### MirrorTool
|
|
||||||
|
|
||||||
This tool allows the user to mirror the selected object(s) in the required direction. The MirrorTool is activated once the user presses the Mirror icon in the tool sidebar or hits the shortcut (M) while an object is selected.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The MirrorTool contains the MirrorToolHandle, which draws pyramid handles on the selected object(s). The MirrorTool generates MirrorOperations whenever the object is mirrored against an axis.
|
|
||||||
|
|
||||||
### PerObjectSettingsTool
|
|
||||||
|
|
||||||
This tool allows the user to change the mesh type of the object into one of the following:
|
|
||||||
|
|
||||||
* Normal Model
|
|
||||||
* Print as support
|
|
||||||
* Modify settings for overlaps
|
|
||||||
- Infill mesh only
|
|
||||||
- Cutting mesh
|
|
||||||
* Don't support overlaps
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Contrary to other tools, this tool doesn't have any handles and it does not generate any operations. This means that once an object's type is changed it cannot be undone/redone using the OperationStack. This tool adds a [SettingOverrideDecorator](scene.md#settingoverridedecorator) on the object's node instead, which allows the user to change certain settings only for this mesh.
|
|
||||||
|
|
||||||
### SupportEraser tool
|
|
||||||
|
|
||||||
This tool allows the user to add support blockers on the selected model. The SupportEraserTool is activated once the user pressed the Support Blocker icon in the tool sidebar or hits the shortcut (E) while an object is selected. With this tool active, the user can add support blockers (cubes) on the object by clicking on various places on the selected mesh.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The SupportEraser uses a GroupOperation to add a new CuraSceneNode (the eraser) in the scene and set the selected model as the parent of the eraser. This means that the addition of Erasers in the scene can be undone/redone. The SupportEraser does not have any tool handles.
|
|
94
packaging/AppImage-builder/AppImageBuilder.yml.jinja
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
version: 1
|
||||||
|
|
||||||
|
AppDir:
|
||||||
|
path: {{ app_dir }}
|
||||||
|
app_info:
|
||||||
|
id: com.ultimaker.cura
|
||||||
|
name: UltiMaker Cura
|
||||||
|
icon: {{ icon }}
|
||||||
|
version: {{ version }}
|
||||||
|
exec: UltiMaker-Cura
|
||||||
|
exec_args: $@
|
||||||
|
apt:
|
||||||
|
arch:
|
||||||
|
- amd64
|
||||||
|
allow_unauthenticated: true
|
||||||
|
sources:
|
||||||
|
- sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy main restricted
|
||||||
|
- sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy-updates main restricted
|
||||||
|
- sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy universe
|
||||||
|
- sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy-updates universe
|
||||||
|
- sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy multiverse
|
||||||
|
- sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy-updates multiverse
|
||||||
|
- sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy-backports main restricted
|
||||||
|
universe multiverse
|
||||||
|
- sourceline: deb http://security.ubuntu.com/ubuntu jammy-security main restricted
|
||||||
|
- sourceline: deb http://security.ubuntu.com/ubuntu jammy-security universe
|
||||||
|
- sourceline: deb http://security.ubuntu.com/ubuntu jammy-security multiverse
|
||||||
|
- sourceline: deb https://releases.jfrog.io/artifactory/jfrog-debs xenial contrib
|
||||||
|
- sourceline: deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main
|
||||||
|
- sourceline: deb https://ppa.launchpadcontent.net/ubuntu-toolchain-r/test/ubuntu/
|
||||||
|
jammy main
|
||||||
|
- sourceline: deb https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu/ jammy
|
||||||
|
main
|
||||||
|
- sourceline: deb [arch=amd64] https://packages.microsoft.com/repos/ms-teams stable
|
||||||
|
main
|
||||||
|
- sourceline: deb https://ppa.launchpadcontent.net/ppa-verse/cling/ubuntu/ jammy
|
||||||
|
main
|
||||||
|
- sourceline: deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable
|
||||||
|
main
|
||||||
|
- sourceline: deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_14.x
|
||||||
|
jammy main
|
||||||
|
- sourceline: deb [arch=amd64 signed-by=/usr/share/keyrings/transip-stack.gpg]
|
||||||
|
https://mirror.transip.net/stack/software/deb/Ubuntu_22.04/ ./
|
||||||
|
- sourceline: deb http://repository.spotify.com stable non-free
|
||||||
|
- sourceline: deb [arch=amd64,arm64,armhf] http://packages.microsoft.com/repos/code
|
||||||
|
stable main
|
||||||
|
- sourceline: deb https://packagecloud.io/slacktechnologies/slack/debian/ jessie
|
||||||
|
main
|
||||||
|
include:
|
||||||
|
- xdg-desktop-portal-kde:amd64
|
||||||
|
exclude:
|
||||||
|
- hicolor-icon-theme
|
||||||
|
- adwaita-icon-theme
|
||||||
|
- humanity-icon-theme
|
||||||
|
files:
|
||||||
|
include: []
|
||||||
|
exclude:
|
||||||
|
- usr/share/man
|
||||||
|
- usr/share/doc/*/README.*
|
||||||
|
- usr/share/doc/*/changelog.*
|
||||||
|
- usr/share/doc/*/NEWS.*
|
||||||
|
- usr/share/doc/*/TODO.*
|
||||||
|
runtime:
|
||||||
|
env:
|
||||||
|
APPDIR_LIBRARY_PATH: "$APPDIR/usr/lib/x86_64-linux-gnu:$APPDIR/lib/x86_64-linux-gnu:$APPDIR/usr/lib:$APPDIR/usr/lib/x86_64-linux-gnu/gdk-pixbuf-2.0/2.10.0/loaders"
|
||||||
|
PYTHONPATH: "$APPDIR"
|
||||||
|
QT_PLUGIN_PATH: "$APPDIR/qt/plugins"
|
||||||
|
QML2_IMPORT_PATH: "$APPDIR/qt/qml"
|
||||||
|
QT_QPA_PLATFORMTHEME: xdgdesktopportal
|
||||||
|
test:
|
||||||
|
fedora-30:
|
||||||
|
image: appimagecrafters/tests-env:fedora-30
|
||||||
|
command: ./AppRun
|
||||||
|
use_host_x: True
|
||||||
|
debian-stable:
|
||||||
|
image: appimagecrafters/tests-env:debian-stable
|
||||||
|
command: ./AppRun
|
||||||
|
use_host_x: True
|
||||||
|
archlinux-latest:
|
||||||
|
image: appimagecrafters/tests-env:archlinux-latest
|
||||||
|
command: ./AppRun
|
||||||
|
use_host_x: True
|
||||||
|
centos-7:
|
||||||
|
image: appimagecrafters/tests-env:centos-7
|
||||||
|
command: ./AppRun
|
||||||
|
use_host_x: True
|
||||||
|
ubuntu-xenial:
|
||||||
|
image: appimagecrafters/tests-env:ubuntu-xenial
|
||||||
|
command: ./AppRun
|
||||||
|
use_host_x: True
|
||||||
|
AppImage:
|
||||||
|
arch: {{ arch }}
|
||||||
|
file_name: {{ file_name }}
|
||||||
|
update-information: guess
|
102
packaging/AppImage-builder/create_appimage.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# Copyright (c) 2023 UltiMaker
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from jinja2 import Template
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_workspace(dist_path, appimage_filename):
|
||||||
|
"""
|
||||||
|
Prepare the workspace for building the AppImage.
|
||||||
|
:param dist_path: Path to the distribution of Cura created with pyinstaller.
|
||||||
|
:param appimage_filename: name of the AppImage file.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not os.path.exists(dist_path):
|
||||||
|
raise RuntimeError(f"The dist_path {dist_path} does not exist.")
|
||||||
|
|
||||||
|
if os.path.exists(os.path.join(dist_path, appimage_filename)):
|
||||||
|
os.remove(os.path.join(dist_path, appimage_filename))
|
||||||
|
|
||||||
|
if not os.path.exists("AppDir"):
|
||||||
|
shutil.move(dist_path, "AppDir")
|
||||||
|
else:
|
||||||
|
print(f"AppDir already exists, assuming it is already prepared.")
|
||||||
|
|
||||||
|
copy_files("AppDir")
|
||||||
|
|
||||||
|
|
||||||
|
def build_appimage(dist_path, version, appimage_filename):
|
||||||
|
"""
|
||||||
|
Creates an AppImage file from the build artefacts created so far.
|
||||||
|
"""
|
||||||
|
generate_appimage_builder_config(dist_path, version, appimage_filename)
|
||||||
|
create_appimage()
|
||||||
|
sign_appimage(appimage_filename)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_appimage_builder_config(dist_path, version, appimage_filename):
|
||||||
|
with open(os.path.join(Path(__file__).parent, "AppImageBuilder.yml.jinja"), "r") as appimage_builder_file:
|
||||||
|
appimage_builder = appimage_builder_file.read()
|
||||||
|
|
||||||
|
template = Template(appimage_builder)
|
||||||
|
appimage_builder = template.render(app_dir = "./AppDir",
|
||||||
|
icon = "cura-icon.png",
|
||||||
|
version = version,
|
||||||
|
arch = "x86_64",
|
||||||
|
file_name = appimage_filename)
|
||||||
|
|
||||||
|
with open(os.path.join(Path(__file__).parent, "AppImageBuilder.yml"), "w") as appimage_builder_file:
|
||||||
|
appimage_builder_file.write(appimage_builder)
|
||||||
|
|
||||||
|
|
||||||
|
def copy_files(dist_path):
|
||||||
|
"""
|
||||||
|
Copy metadata files for the metadata of the AppImage.
|
||||||
|
"""
|
||||||
|
copied_files = {
|
||||||
|
os.path.join("..", "icons", "cura-icon.svg"): os.path.join("usr", "share", "icons", "hicolor", "scalable", "apps", "cura-icon.svg"),
|
||||||
|
os.path.join("..", "icons", "cura-icon_64x64.png"): os.path.join("usr", "share", "icons", "hicolor", "64x64", "apps", "cura-icon.png"),
|
||||||
|
os.path.join("..", "icons", "cura-icon_128x128.png"): os.path.join("usr", "share", "icons", "hicolor", "128x128", "apps", "cura-icon.png"),
|
||||||
|
os.path.join("..", "icons", "cura-icon_256x256.png"): os.path.join("usr", "share", "icons", "hicolor", "256x256", "apps", "cura-icon.png"),
|
||||||
|
os.path.join("..", "icons", "cura-icon_256x256.png"): "cura-icon.png",
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: openssl.cnf ???
|
||||||
|
|
||||||
|
packaging_dir = os.path.dirname(__file__)
|
||||||
|
for source, dest in copied_files.items():
|
||||||
|
dest_file_path = os.path.join(dist_path, dest)
|
||||||
|
os.makedirs(os.path.dirname(dest_file_path), exist_ok = True)
|
||||||
|
shutil.copyfile(os.path.join(packaging_dir, source), dest_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
def create_appimage():
|
||||||
|
appimagetool = os.getenv("APPIMAGEBUILDER_LOCATION", "appimage-builder-x86_64.AppImage")
|
||||||
|
command = [appimagetool, "--recipe", os.path.join(Path(__file__).parent, "AppImageBuilder.yml"), "--skip-test"]
|
||||||
|
result = subprocess.call(command)
|
||||||
|
if result != 0:
|
||||||
|
raise RuntimeError(f"The AppImageTool command returned non-zero: {result}")
|
||||||
|
|
||||||
|
|
||||||
|
def sign_appimage(appimage_filename):
|
||||||
|
command = ["gpg", "--yes", "--armor", "--detach-sig", appimage_filename]
|
||||||
|
result = subprocess.call(command)
|
||||||
|
if result != 0:
|
||||||
|
raise RuntimeError(f"The GPG command returned non-zero: {result}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description = "Create AppImages of Cura.")
|
||||||
|
parser.add_argument("dist_path", type = str, help = "Path to where PyInstaller installed the distribution of Cura.")
|
||||||
|
parser.add_argument("version", type = str, help = "Full version number of Cura (e.g. '5.1.0-beta')")
|
||||||
|
parser.add_argument("filename", type = str, help = "Filename of the AppImage (e.g. 'UltiMaker-Cura-5.1.0-beta-Linux-X64.AppImage')")
|
||||||
|
args = parser.parse_args()
|
||||||
|
prepare_workspace(args.dist_path, args.filename)
|
||||||
|
build_appimage(args.dist_path, args.version, args.filename)
|
Before Width: | Height: | Size: 381 KiB After Width: | Height: | Size: 417 KiB |
@ -20,10 +20,15 @@
|
|||||||
...but we can use this longer `substring` expression instead (see https://github.com/wixtoolset/issues/issues/5609 )
|
...but we can use this longer `substring` expression instead (see https://github.com/wixtoolset/issues/issues/5609 )
|
||||||
-->
|
-->
|
||||||
<xsl:key
|
<xsl:key
|
||||||
name="ExeToRemove"
|
name="UltiMaker_Cura_exe_ToRemove"
|
||||||
match="wix:Component[ substring( wix:File/@Source, string-length( wix:File/@Source ) - 3 ) = '.exe' ]"
|
match="wix:Component[ substring( wix:File/@Source, string-length( wix:File/@Source ) - 17 ) = 'UltiMaker-Cura.exe' ]"
|
||||||
use="@Id"
|
use="@Id"
|
||||||
/> <!-- Get the last 4 characters of a string using `substring( s, len(s) - 3 )`, it uses -3 and not -4 because XSLT uses 1-based indexes, not 0-based indexes. -->
|
/>
|
||||||
|
<xsl:key
|
||||||
|
name="CuraEngine_exe_ToRemove"
|
||||||
|
match="wix:Component[ substring( wix:File/@Source, string-length( wix:File/@Source ) - 17 ) = 'CuraEngine.exe' ]"
|
||||||
|
use="@Id"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- By default, copy all elements and nodes into the output... -->
|
<!-- By default, copy all elements and nodes into the output... -->
|
||||||
<xsl:template match="@*|node()">
|
<xsl:template match="@*|node()">
|
||||||
@ -32,6 +37,7 @@
|
|||||||
</xsl:copy>
|
</xsl:copy>
|
||||||
</xsl:template>
|
</xsl:template>
|
||||||
|
|
||||||
<!-- ...but if the element has the "ExeToRemove" key then don't render anything (i.e. removing it from the output) -->
|
<!-- ...but if the element has the "UltiMaker_Cura_exe_ToRemove" or "CuraEngine_exe_ToRemove" key then don't render anything (i.e. removing it from the output) -->
|
||||||
<xsl:template match="*[ self::wix:Component or self::wix:ComponentRef ][ key( 'ExeToRemove', @Id ) ]"/>
|
<xsl:template match="*[ self::wix:Component or self::wix:ComponentRef ][ key( 'UltiMaker_Cura_exe_ToRemove', @Id ) ]"/>
|
||||||
|
<xsl:template match="*[ self::wix:Component or self::wix:ComponentRef ][ key( 'CuraEngine_exe_ToRemove', @Id ) ]"/>
|
||||||
</xsl:stylesheet>
|
</xsl:stylesheet>
|
@ -1095,6 +1095,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
if global_stack.getProperty(key, "settable_per_extruder"):
|
if global_stack.getProperty(key, "settable_per_extruder"):
|
||||||
values_to_set_for_extruders[key] = value
|
values_to_set_for_extruders[key] = value
|
||||||
else:
|
else:
|
||||||
|
if not self._settingIsFromMissingPackage(key, value):
|
||||||
global_stack.definitionChanges.setProperty(key, "value", value)
|
global_stack.definitionChanges.setProperty(key, "value", value)
|
||||||
|
|
||||||
for position, extruder_stack in extruder_stack_dict.items():
|
for position, extruder_stack in extruder_stack_dict.items():
|
||||||
@ -1109,6 +1110,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
extruder_stack.definitionChanges.setProperty(key, "value", value)
|
extruder_stack.definitionChanges.setProperty(key, "value", value)
|
||||||
if parser is not None:
|
if parser is not None:
|
||||||
for key, value in parser["values"].items():
|
for key, value in parser["values"].items():
|
||||||
|
if not self._settingIsFromMissingPackage(key, value):
|
||||||
extruder_stack.definitionChanges.setProperty(key, "value", value)
|
extruder_stack.definitionChanges.setProperty(key, "value", value)
|
||||||
|
|
||||||
def _applyUserChanges(self, global_stack, extruder_stack_dict):
|
def _applyUserChanges(self, global_stack, extruder_stack_dict):
|
||||||
@ -1119,6 +1121,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
if global_stack.getProperty(key, "settable_per_extruder"):
|
if global_stack.getProperty(key, "settable_per_extruder"):
|
||||||
values_to_set_for_extruder_0[key] = value
|
values_to_set_for_extruder_0[key] = value
|
||||||
else:
|
else:
|
||||||
|
if not self._settingIsFromMissingPackage(key, value):
|
||||||
global_stack.userChanges.setProperty(key, "value", value)
|
global_stack.userChanges.setProperty(key, "value", value)
|
||||||
|
|
||||||
for position, extruder_stack in extruder_stack_dict.items():
|
for position, extruder_stack in extruder_stack_dict.items():
|
||||||
@ -1133,6 +1136,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
extruder_stack.userChanges.setProperty(key, "value", value)
|
extruder_stack.userChanges.setProperty(key, "value", value)
|
||||||
if parser is not None:
|
if parser is not None:
|
||||||
for key, value in parser["values"].items():
|
for key, value in parser["values"].items():
|
||||||
|
if not self._settingIsFromMissingPackage(key, value):
|
||||||
extruder_stack.userChanges.setProperty(key, "value", value)
|
extruder_stack.userChanges.setProperty(key, "value", value)
|
||||||
|
|
||||||
def _applyVariants(self, global_stack, extruder_stack_dict):
|
def _applyVariants(self, global_stack, extruder_stack_dict):
|
||||||
@ -1208,6 +1212,15 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
if key not in _ignored_machine_network_metadata:
|
if key not in _ignored_machine_network_metadata:
|
||||||
global_stack.setMetaDataEntry(key, value)
|
global_stack.setMetaDataEntry(key, value)
|
||||||
|
|
||||||
|
def _settingIsFromMissingPackage(self, key, value):
|
||||||
|
# Check if the key and value pair is from the missing package
|
||||||
|
for package in self._dialog.missingPackages:
|
||||||
|
if value.startswith("PLUGIN::"):
|
||||||
|
if (package['id'] + "@" + package['package_version']) in value:
|
||||||
|
Logger.log("w", f"Ignoring {key} value {value} from missing package")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _updateActiveMachine(self, global_stack):
|
def _updateActiveMachine(self, global_stack):
|
||||||
# Actually change the active machine.
|
# Actually change the active machine.
|
||||||
machine_manager = Application.getInstance().getMachineManager()
|
machine_manager = Application.getInstance().getMachineManager()
|
||||||
@ -1327,3 +1340,4 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
missing_packages.append(package)
|
missing_packages.append(package)
|
||||||
|
|
||||||
return missing_packages
|
return missing_packages
|
||||||
|
|
||||||
|
@ -408,24 +408,25 @@ class WorkspaceDialog(QObject):
|
|||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def showMissingMaterialsWarning(self) -> None:
|
def showMissingMaterialsWarning(self) -> None:
|
||||||
result_message = Message(
|
result_message = Message(
|
||||||
i18n_catalog.i18nc("@info:status", "The material used in this project relies on some material definitions not available in Cura, this might produce undesirable print results. We highly recommend installing the full material package from the Marketplace."),
|
i18n_catalog.i18nc("@info:status",
|
||||||
|
"Some of the packages used in the project file are currently not installed in Cura, this might produce undesirable print results. We highly recommend installing the all required packages from the Marketplace."),
|
||||||
lifetime=0,
|
lifetime=0,
|
||||||
title=i18n_catalog.i18nc("@info:title", "Material profiles not installed"),
|
title=i18n_catalog.i18nc("@info:title", "Some required packages are not installed"),
|
||||||
message_type=Message.MessageType.WARNING
|
message_type=Message.MessageType.WARNING
|
||||||
)
|
)
|
||||||
result_message.addAction(
|
result_message.addAction(
|
||||||
"learn_more",
|
"learn_more",
|
||||||
name=i18n_catalog.i18nc("@action:button", "Learn more"),
|
name=i18n_catalog.i18nc("@action:button", "Learn more"),
|
||||||
icon="",
|
icon="",
|
||||||
description="Learn more about project materials.",
|
description=i18n_catalog.i18nc("@label", "Learn more about project packages."),
|
||||||
button_align=Message.ActionButtonAlignment.ALIGN_LEFT,
|
button_align=Message.ActionButtonAlignment.ALIGN_LEFT,
|
||||||
button_style=Message.ActionButtonStyle.LINK
|
button_style=Message.ActionButtonStyle.LINK
|
||||||
)
|
)
|
||||||
result_message.addAction(
|
result_message.addAction(
|
||||||
"install_materials",
|
"install_packages",
|
||||||
name=i18n_catalog.i18nc("@action:button", "Install Materials"),
|
name=i18n_catalog.i18nc("@action:button", "Install Packages"),
|
||||||
icon="",
|
icon="",
|
||||||
description="Install missing materials from project file.",
|
description=i18n_catalog.i18nc("@label", "Install missing packages from project file."),
|
||||||
button_align=Message.ActionButtonAlignment.ALIGN_RIGHT,
|
button_align=Message.ActionButtonAlignment.ALIGN_RIGHT,
|
||||||
button_style=Message.ActionButtonStyle.DEFAULT
|
button_style=Message.ActionButtonStyle.DEFAULT
|
||||||
)
|
)
|
||||||
|
@ -364,7 +364,7 @@ UM.Dialog
|
|||||||
UM.Label
|
UM.Label
|
||||||
{
|
{
|
||||||
id: warningText
|
id: warningText
|
||||||
text: catalog.i18nc("@label", "The material used in this project is currently not installed in Cura.<br/>Install the material profile and reopen the project.")
|
text: catalog.i18nc("@label", "This project contains materials or plugins that are currently not installed in Cura.<br/>Install the missing packages and reopen the project.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,7 +404,7 @@ UM.Dialog
|
|||||||
Cura.PrimaryButton
|
Cura.PrimaryButton
|
||||||
{
|
{
|
||||||
visible: warning
|
visible: warning
|
||||||
text: catalog.i18nc("@action:button", "Install missing material")
|
text: catalog.i18nc("@action:button", "Install missing packages")
|
||||||
onClicked: manager.installMissingPackages()
|
onClicked: manager.installMissingPackages()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -40,7 +40,9 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
|||||||
|
|
||||||
# Indicate that the 3mf mesh writer should not close the archive just yet (we still need to add stuff to it).
|
# Indicate that the 3mf mesh writer should not close the archive just yet (we still need to add stuff to it).
|
||||||
mesh_writer.setStoreArchive(True)
|
mesh_writer.setStoreArchive(True)
|
||||||
mesh_writer.write(stream, nodes, mode)
|
if not mesh_writer.write(stream, nodes, mode):
|
||||||
|
self.setInformation(mesh_writer.getInformation())
|
||||||
|
return False
|
||||||
|
|
||||||
archive = mesh_writer.getArchive()
|
archive = mesh_writer.getArchive()
|
||||||
if archive is None: # This happens if there was no mesh data to write.
|
if archive is None: # This happens if there was no mesh data to write.
|
||||||
@ -98,7 +100,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
|||||||
Logger.error("No permission to write workspace to this stream.")
|
Logger.error("No permission to write workspace to this stream.")
|
||||||
return False
|
return False
|
||||||
except EnvironmentError as e:
|
except EnvironmentError as e:
|
||||||
self.setInformation(catalog.i18nc("@error:zip", "The operating system does not allow saving a project file to this location or with this file name."))
|
self.setInformation(catalog.i18nc("@error:zip", str(e)))
|
||||||
Logger.error("EnvironmentError when writing workspace to this stream: {err}".format(err = str(e)))
|
Logger.error("EnvironmentError when writing workspace to this stream: {err}".format(err = str(e)))
|
||||||
return False
|
return False
|
||||||
mesh_writer.setStoreArchive(False)
|
mesh_writer.setStoreArchive(False)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
# Copyright (c) 2015-2022 Ultimaker B.V.
|
# Copyright (c) 2015-2022 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
from typing import Optional, cast, List, Dict
|
from typing import Optional, cast, List, Dict, Pattern, Set
|
||||||
|
|
||||||
from UM.Mesh.MeshWriter import MeshWriter
|
from UM.Mesh.MeshWriter import MeshWriter
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
@ -17,6 +18,7 @@ from UM.Settings.EmptyInstanceContainer import EmptyInstanceContainer
|
|||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.CuraPackageManager import CuraPackageManager
|
from cura.CuraPackageManager import CuraPackageManager
|
||||||
|
from cura.Settings import CuraContainerStack
|
||||||
from cura.Utils.Threading import call_on_qt_thread
|
from cura.Utils.Threading import call_on_qt_thread
|
||||||
from cura.Snapshot import Snapshot
|
from cura.Snapshot import Snapshot
|
||||||
|
|
||||||
@ -177,13 +179,15 @@ class ThreeMFWriter(MeshWriter):
|
|||||||
archive.writestr(thumbnail_file, thumbnail_buffer.data())
|
archive.writestr(thumbnail_file, thumbnail_buffer.data())
|
||||||
|
|
||||||
# Add PNG to content types file
|
# Add PNG to content types file
|
||||||
thumbnail_type = ET.SubElement(content_types, "Default", Extension = "png", ContentType = "image/png")
|
thumbnail_type = ET.SubElement(content_types, "Default", Extension="png", ContentType="image/png")
|
||||||
# Add thumbnail relation to _rels/.rels file
|
# Add thumbnail relation to _rels/.rels file
|
||||||
thumbnail_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/" + THUMBNAIL_PATH, Id = "rel1", Type = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail")
|
thumbnail_relation_element = ET.SubElement(relations_element, "Relationship",
|
||||||
|
Target="/" + THUMBNAIL_PATH, Id="rel1",
|
||||||
|
Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail")
|
||||||
|
|
||||||
# Write material metadata
|
# Write material metadata
|
||||||
material_metadata = self._getMaterialPackageMetadata()
|
packages_metadata = self._getMaterialPackageMetadata() + self._getPluginPackageMetadata()
|
||||||
self._storeMetadataJson({"packages": material_metadata}, archive, PACKAGE_METADATA_PATH)
|
self._storeMetadataJson({"packages": packages_metadata}, archive, PACKAGE_METADATA_PATH)
|
||||||
|
|
||||||
savitar_scene = Savitar.Scene()
|
savitar_scene = Savitar.Scene()
|
||||||
|
|
||||||
@ -237,9 +241,9 @@ class ThreeMFWriter(MeshWriter):
|
|||||||
archive.writestr(model_file, scene_string)
|
archive.writestr(model_file, scene_string)
|
||||||
archive.writestr(content_types_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(content_types))
|
archive.writestr(content_types_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(content_types))
|
||||||
archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
|
archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
|
||||||
except Exception as e:
|
except Exception as error:
|
||||||
Logger.logException("e", "Error writing zip file")
|
Logger.logException("e", "Error writing zip file")
|
||||||
self.setInformation(catalog.i18nc("@error:zip", "Error writing 3mf file."))
|
self.setInformation(str(error))
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
if not self._store_archive:
|
if not self._store_archive:
|
||||||
@ -255,7 +259,64 @@ class ThreeMFWriter(MeshWriter):
|
|||||||
metadata_file = zipfile.ZipInfo(path)
|
metadata_file = zipfile.ZipInfo(path)
|
||||||
# We have to set the compress type of each file as well (it doesn't keep the type of the entire archive)
|
# We have to set the compress type of each file as well (it doesn't keep the type of the entire archive)
|
||||||
metadata_file.compress_type = zipfile.ZIP_DEFLATED
|
metadata_file.compress_type = zipfile.ZIP_DEFLATED
|
||||||
archive.writestr(metadata_file, json.dumps(metadata, separators=(", ", ": "), indent=4, skipkeys=True, ensure_ascii=False))
|
archive.writestr(metadata_file,
|
||||||
|
json.dumps(metadata, separators=(", ", ": "), indent=4, skipkeys=True, ensure_ascii=False))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _getPluginPackageMetadata() -> List[Dict[str, str]]:
|
||||||
|
"""Get metadata for all backend plugins that are used in the project.
|
||||||
|
|
||||||
|
:return: List of material metadata dictionaries.
|
||||||
|
"""
|
||||||
|
|
||||||
|
backend_plugin_enum_value_regex = re.compile(
|
||||||
|
r"PLUGIN::(?P<plugin_id>\w+)@(?P<version>\d+.\d+.\d+)::(?P<value>\w+)")
|
||||||
|
# This regex parses enum values to find if they contain custom
|
||||||
|
# backend engine values. These custom enum values are in the format
|
||||||
|
# PLUGIN::<plugin_id>@<version>::<value>
|
||||||
|
# where
|
||||||
|
# - plugin_id is the id of the plugin
|
||||||
|
# - version is in the semver format
|
||||||
|
# - value is the value of the enum
|
||||||
|
|
||||||
|
plugin_ids = set()
|
||||||
|
|
||||||
|
def addPluginIdsInStack(stack: CuraContainerStack) -> None:
|
||||||
|
for key in stack.getAllKeys():
|
||||||
|
value = str(stack.getProperty(key, "value"))
|
||||||
|
for plugin_id, _version, _value in backend_plugin_enum_value_regex.findall(value):
|
||||||
|
plugin_ids.add(plugin_id)
|
||||||
|
|
||||||
|
# Go through all stacks and find all the plugin id contained in the project
|
||||||
|
global_stack = CuraApplication.getInstance().getMachineManager().activeMachine
|
||||||
|
addPluginIdsInStack(global_stack)
|
||||||
|
|
||||||
|
for container in global_stack.getContainers():
|
||||||
|
addPluginIdsInStack(container)
|
||||||
|
|
||||||
|
for extruder_stack in global_stack.extruderList:
|
||||||
|
addPluginIdsInStack(extruder_stack)
|
||||||
|
|
||||||
|
for container in extruder_stack.getContainers():
|
||||||
|
addPluginIdsInStack(container)
|
||||||
|
|
||||||
|
metadata = {}
|
||||||
|
|
||||||
|
package_manager = cast(CuraPackageManager, CuraApplication.getInstance().getPackageManager())
|
||||||
|
for plugin_id in plugin_ids:
|
||||||
|
package_data = package_manager.getInstalledPackageInfo(plugin_id)
|
||||||
|
|
||||||
|
metadata[plugin_id] = {
|
||||||
|
"id": plugin_id,
|
||||||
|
"display_name": package_data.get("display_name") if package_data.get("display_name") else "",
|
||||||
|
"package_version": package_data.get("package_version") if package_data.get("package_version") else "",
|
||||||
|
"sdk_version_semver": package_data.get("sdk_version_semver") if package_data.get(
|
||||||
|
"sdk_version_semver") else "",
|
||||||
|
"type": "plugin",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Storing in a dict and fetching values to avoid duplicates
|
||||||
|
return list(metadata.values())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _getMaterialPackageMetadata() -> List[Dict[str, str]]:
|
def _getMaterialPackageMetadata() -> List[Dict[str, str]]:
|
||||||
@ -280,7 +341,8 @@ class ThreeMFWriter(MeshWriter):
|
|||||||
# Don't export bundled materials
|
# Don't export bundled materials
|
||||||
continue
|
continue
|
||||||
|
|
||||||
package_id = package_manager.getMaterialFilePackageId(extruder.material.getFileName(), extruder.material.getMetaDataEntry("GUID"))
|
package_id = package_manager.getMaterialFilePackageId(extruder.material.getFileName(),
|
||||||
|
extruder.material.getMetaDataEntry("GUID"))
|
||||||
package_data = package_manager.getInstalledPackageInfo(package_id)
|
package_data = package_manager.getInstalledPackageInfo(package_id)
|
||||||
|
|
||||||
# We failed to find the package for this material
|
# We failed to find the package for this material
|
||||||
@ -288,10 +350,14 @@ class ThreeMFWriter(MeshWriter):
|
|||||||
Logger.info(f"Could not find package for material in extruder {extruder.id}, skipping.")
|
Logger.info(f"Could not find package for material in extruder {extruder.id}, skipping.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
material_metadata = {"id": package_id,
|
material_metadata = {
|
||||||
|
"id": package_id,
|
||||||
"display_name": package_data.get("display_name") if package_data.get("display_name") else "",
|
"display_name": package_data.get("display_name") if package_data.get("display_name") else "",
|
||||||
"package_version": package_data.get("package_version") if package_data.get("package_version") else "",
|
"package_version": package_data.get("package_version") if package_data.get("package_version") else "",
|
||||||
"sdk_version_semver": package_data.get("sdk_version_semver") if package_data.get("sdk_version_semver") else ""}
|
"sdk_version_semver": package_data.get("sdk_version_semver") if package_data.get(
|
||||||
|
"sdk_version_semver") else "",
|
||||||
|
"type": "material",
|
||||||
|
}
|
||||||
|
|
||||||
metadata[package_id] = material_metadata
|
metadata[package_id] = material_metadata
|
||||||
|
|
||||||
|
60
plugins/3MFWriter/tests/TestMFWriter.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import sys
|
||||||
|
import os.path
|
||||||
|
from typing import Dict, Optional
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from unittest.mock import patch, MagicMock, PropertyMock
|
||||||
|
|
||||||
|
from UM.PackageManager import PackageManager
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||||
|
|
||||||
|
import ThreeMFWriter
|
||||||
|
|
||||||
|
PLUGIN_ID = "my_plugin"
|
||||||
|
DISPLAY_NAME = "MyPlugin"
|
||||||
|
PACKAGE_VERSION = "0.0.1"
|
||||||
|
SDK_VERSION = "8.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def package_manager() -> MagicMock:
|
||||||
|
pm = MagicMock(spec=PackageManager)
|
||||||
|
pm.getInstalledPackageInfo.return_value = {
|
||||||
|
"display_name": DISPLAY_NAME,
|
||||||
|
"package_version": PACKAGE_VERSION,
|
||||||
|
"sdk_version_semver": SDK_VERSION
|
||||||
|
}
|
||||||
|
return pm
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def machine_manager() -> MagicMock:
|
||||||
|
mm = MagicMock(spec=PackageManager)
|
||||||
|
active_machine = MagicMock()
|
||||||
|
active_machine.getAllKeys.return_value = ["infill_pattern", "layer_height", "material_bed_temperature"]
|
||||||
|
active_machine.getProperty.return_value = f"PLUGIN::{PLUGIN_ID}@{PACKAGE_VERSION}::custom_value"
|
||||||
|
active_machine.getContainers.return_value = []
|
||||||
|
active_machine.extruderList = []
|
||||||
|
mm.activeMachine = active_machine
|
||||||
|
return mm
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def application(package_manager, machine_manager):
|
||||||
|
app = MagicMock()
|
||||||
|
app.getPackageManager.return_value = package_manager
|
||||||
|
app.getMachineManager.return_value = machine_manager
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def test_enumParsing(application):
|
||||||
|
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value=application)):
|
||||||
|
packages_metadata = ThreeMFWriter.ThreeMFWriter._getPluginPackageMetadata()[0]
|
||||||
|
|
||||||
|
assert packages_metadata.get("id") == PLUGIN_ID
|
||||||
|
assert packages_metadata.get("display_name") == DISPLAY_NAME
|
||||||
|
assert packages_metadata.get("package_version") == PACKAGE_VERSION
|
||||||
|
assert packages_metadata.get("sdk_version_semver") == SDK_VERSION
|
||||||
|
assert packages_metadata.get("type") == "plugin"
|
@ -8,12 +8,31 @@ message ObjectList
|
|||||||
repeated Setting settings = 2; // meshgroup settings (for one-at-a-time printing)
|
repeated Setting settings = 2; // meshgroup settings (for one-at-a-time printing)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SlotID {
|
||||||
|
SETTINGS_BROADCAST = 0;
|
||||||
|
SIMPLIFY_MODIFY = 100;
|
||||||
|
POSTPROCESS_MODIFY = 101;
|
||||||
|
INFILL_MODIFY = 102;
|
||||||
|
GCODE_PATHS_MODIFY = 103;
|
||||||
|
INFILL_GENERATE = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
message EnginePlugin
|
||||||
|
{
|
||||||
|
SlotID id = 1;
|
||||||
|
string address = 2;
|
||||||
|
uint32 port = 3;
|
||||||
|
string plugin_name = 4;
|
||||||
|
string plugin_version = 5;
|
||||||
|
}
|
||||||
|
|
||||||
message Slice
|
message Slice
|
||||||
{
|
{
|
||||||
repeated ObjectList object_lists = 1; // The meshgroups to be printed one after another
|
repeated ObjectList object_lists = 1; // The meshgroups to be printed one after another
|
||||||
SettingList global_settings = 2; // The global settings used for the whole print job
|
SettingList global_settings = 2; // The global settings used for the whole print job
|
||||||
repeated Extruder extruders = 3; // The settings sent to each extruder object
|
repeated Extruder extruders = 3; // The settings sent to each extruder object
|
||||||
repeated SettingExtruder limit_to_extruder = 4; // From which stack the setting would inherit if not defined per object
|
repeated SettingExtruder limit_to_extruder = 4; // From which stack the setting would inherit if not defined per object
|
||||||
|
repeated EnginePlugin engine_plugins = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Extruder
|
message Extruder
|
||||||
|
@ -46,6 +46,19 @@ catalog = i18nCatalog("cura")
|
|||||||
class CuraEngineBackend(QObject, Backend):
|
class CuraEngineBackend(QObject, Backend):
|
||||||
backendError = Signal()
|
backendError = Signal()
|
||||||
|
|
||||||
|
printDurationMessage = Signal()
|
||||||
|
"""Emitted when we get a message containing print duration and material amount.
|
||||||
|
|
||||||
|
This also implies the slicing has finished.
|
||||||
|
:param time: The amount of time the print will take.
|
||||||
|
:param material_amount: The amount of material the print will use.
|
||||||
|
"""
|
||||||
|
slicingStarted = Signal()
|
||||||
|
"""Emitted when the slicing process starts."""
|
||||||
|
|
||||||
|
slicingCancelled = Signal()
|
||||||
|
"""Emitted when the slicing process is aborted forcefully."""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Starts the back-end plug-in.
|
"""Starts the back-end plug-in.
|
||||||
|
|
||||||
@ -70,7 +83,6 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
os.path.join(CuraApplication.getInstallPrefix(), "bin"),
|
os.path.join(CuraApplication.getInstallPrefix(), "bin"),
|
||||||
os.path.dirname(os.path.abspath(sys.executable)),
|
os.path.dirname(os.path.abspath(sys.executable)),
|
||||||
]
|
]
|
||||||
|
|
||||||
for path in search_path:
|
for path in search_path:
|
||||||
engine_path = os.path.join(path, executable_name)
|
engine_path = os.path.join(path, executable_name)
|
||||||
if os.path.isfile(engine_path):
|
if os.path.isfile(engine_path):
|
||||||
@ -86,9 +98,9 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
self._default_engine_location = execpath
|
self._default_engine_location = execpath
|
||||||
break
|
break
|
||||||
|
|
||||||
application = CuraApplication.getInstance() #type: CuraApplication
|
application: CuraApplication = CuraApplication.getInstance()
|
||||||
self._multi_build_plate_model = None #type: Optional[MultiBuildPlateModel]
|
self._multi_build_plate_model: Optional[MultiBuildPlateModel] = None
|
||||||
self._machine_error_checker = None #type: Optional[MachineErrorChecker]
|
self._machine_error_checker: Optional[MachineErrorChecker] = None
|
||||||
|
|
||||||
if not self._default_engine_location:
|
if not self._default_engine_location:
|
||||||
raise EnvironmentError("Could not find CuraEngine")
|
raise EnvironmentError("Could not find CuraEngine")
|
||||||
@ -99,13 +111,15 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
application.getPreferences().addPreference("backend/location", self._default_engine_location)
|
application.getPreferences().addPreference("backend/location", self._default_engine_location)
|
||||||
|
|
||||||
# Workaround to disable layer view processing if layer view is not active.
|
# Workaround to disable layer view processing if layer view is not active.
|
||||||
self._layer_view_active = False #type: bool
|
self._layer_view_active: bool = False
|
||||||
self._onActiveViewChanged()
|
self._onActiveViewChanged()
|
||||||
|
|
||||||
self._stored_layer_data = [] # type: List[Arcus.PythonMessage]
|
self._stored_layer_data: List[Arcus.PythonMessage] = []
|
||||||
self._stored_optimized_layer_data = {} # type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
|
|
||||||
|
|
||||||
self._scene = application.getController().getScene() #type: Scene
|
# key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
|
||||||
|
self._stored_optimized_layer_data: Dict[int, List[Arcus.PythonMessage]] = {}
|
||||||
|
|
||||||
|
self._scene: Scene = application.getController().getScene()
|
||||||
self._scene.sceneChanged.connect(self._onSceneChanged)
|
self._scene.sceneChanged.connect(self._onSceneChanged)
|
||||||
|
|
||||||
# Triggers for auto-slicing. Auto-slicing is triggered as follows:
|
# Triggers for auto-slicing. Auto-slicing is triggered as follows:
|
||||||
@ -116,7 +130,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
# If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished
|
# If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished
|
||||||
# to start the auto-slicing timer again.
|
# to start the auto-slicing timer again.
|
||||||
#
|
#
|
||||||
self._global_container_stack = None #type: Optional[ContainerStack]
|
self._global_container_stack: Optional[ContainerStack] = None
|
||||||
|
|
||||||
# Listeners for receiving messages from the back-end.
|
# Listeners for receiving messages from the back-end.
|
||||||
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
|
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
|
||||||
@ -128,31 +142,34 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
self._message_handlers["cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates
|
self._message_handlers["cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates
|
||||||
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
|
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
|
||||||
|
|
||||||
self._start_slice_job = None #type: Optional[StartSliceJob]
|
self._start_slice_job: Optional[StartSliceJob] = None
|
||||||
self._start_slice_job_build_plate = None #type: Optional[int]
|
self._start_slice_job_build_plate: Optional[int] = None
|
||||||
self._slicing = False #type: bool # Are we currently slicing?
|
self._slicing: bool = False # Are we currently slicing?
|
||||||
self._restart = False #type: bool # Back-end is currently restarting?
|
self._restart: bool = False # Back-end is currently restarting?
|
||||||
self._tool_active = False #type: bool # If a tool is active, some tasks do not have to do anything
|
self._tool_active: bool = False # If a tool is active, some tasks do not have to do anything
|
||||||
self._always_restart = True #type: bool # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
|
self._always_restart: bool = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
|
||||||
self._process_layers_job = None #type: Optional[ProcessSlicedLayersJob] # The currently active job to process layers, or None if it is not processing layers.
|
self._process_layers_job: Optional[ProcessSlicedLayersJob] = None # The currently active job to process layers, or None if it is not processing layers.
|
||||||
self._build_plates_to_be_sliced = [] #type: List[int] # what needs slicing?
|
self._build_plates_to_be_sliced: List[int] = [] # what needs slicing?
|
||||||
self._engine_is_fresh = True #type: bool # Is the newly started engine used before or not?
|
self._engine_is_fresh: bool = True # Is the newly started engine used before or not?
|
||||||
|
|
||||||
self._backend_log_max_lines = 20000 #type: int # Maximum number of lines to buffer
|
self._backend_log_max_lines: int = 20000 # Maximum number of lines to buffer
|
||||||
self._error_message = None #type: Optional[Message] # Pop-up message that shows errors.
|
self._error_message: Optional[Message] = None # Pop-up message that shows errors.
|
||||||
self._last_num_objects = defaultdict(int) #type: Dict[int, int] # Count number of objects to see if there is something changed
|
|
||||||
self._postponed_scene_change_sources = [] #type: List[SceneNode] # scene change is postponed (by a tool)
|
|
||||||
|
|
||||||
self._time_start_process = None #type: Optional[float]
|
# Count number of objects to see if there is something changed
|
||||||
self._is_disabled = False #type: bool
|
self._last_num_objects: Dict[int, int] = defaultdict(int)
|
||||||
|
self._postponed_scene_change_sources: List[SceneNode] = [] # scene change is postponed (by a tool)
|
||||||
|
|
||||||
|
self._time_start_process: Optional[float] = None
|
||||||
|
self._is_disabled: bool = False
|
||||||
|
|
||||||
application.getPreferences().addPreference("general/auto_slice", False)
|
application.getPreferences().addPreference("general/auto_slice", False)
|
||||||
|
|
||||||
self._use_timer = False #type: bool
|
self._use_timer: bool = False
|
||||||
# When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
|
|
||||||
# This timer will group them up, and only slice for the last setting changed signal.
|
# When you update a setting and other settings get changed through inheritance, many propertyChanged
|
||||||
|
# signals are fired. This timer will group them up, and only slice for the last setting changed signal.
|
||||||
# TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction.
|
# TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction.
|
||||||
self._change_timer = QTimer() #type: QTimer
|
self._change_timer: QTimer = QTimer()
|
||||||
self._change_timer.setSingleShot(True)
|
self._change_timer.setSingleShot(True)
|
||||||
self._change_timer.setInterval(500)
|
self._change_timer.setInterval(500)
|
||||||
self.determineAutoSlicing()
|
self.determineAutoSlicing()
|
||||||
@ -172,10 +189,33 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
self._slicing_error_message.actionTriggered.connect(self._reportBackendError)
|
self._slicing_error_message.actionTriggered.connect(self._reportBackendError)
|
||||||
|
|
||||||
self._resetLastSliceTimeStats()
|
self._resetLastSliceTimeStats()
|
||||||
self._snapshot = None #type: Optional[QImage]
|
self._snapshot: Optional[QImage] = None
|
||||||
|
|
||||||
application.initializationFinished.connect(self.initialize)
|
application.initializationFinished.connect(self.initialize)
|
||||||
|
|
||||||
|
def startPlugins(self) -> None:
|
||||||
|
"""
|
||||||
|
Ensure that all backend plugins are started
|
||||||
|
It assigns unique ports to each plugin to avoid conflicts.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self.stopPlugins()
|
||||||
|
backend_plugins = CuraApplication.getInstance().getBackendPlugins()
|
||||||
|
for backend_plugin in backend_plugins:
|
||||||
|
# Set the port to prevent plugins from using the same one.
|
||||||
|
if backend_plugin.getPort() < 1:
|
||||||
|
backend_plugin.setAvailablePort()
|
||||||
|
backend_plugin.start()
|
||||||
|
|
||||||
|
def stopPlugins(self) -> None:
|
||||||
|
"""
|
||||||
|
Ensure that all backend plugins will be terminated.
|
||||||
|
"""
|
||||||
|
backend_plugins = CuraApplication.getInstance().getBackendPlugins()
|
||||||
|
for backend_plugin in backend_plugins:
|
||||||
|
if backend_plugin.isRunning():
|
||||||
|
backend_plugin.stop()
|
||||||
|
|
||||||
def _resetLastSliceTimeStats(self) -> None:
|
def _resetLastSliceTimeStats(self) -> None:
|
||||||
self._time_start_process = None
|
self._time_start_process = None
|
||||||
self._time_send_message = None
|
self._time_send_message = None
|
||||||
@ -202,7 +242,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
application.getMachineManager().globalContainerChanged.connect(self._onGlobalStackChanged)
|
application.getMachineManager().globalContainerChanged.connect(self._onGlobalStackChanged)
|
||||||
self._onGlobalStackChanged()
|
self._onGlobalStackChanged()
|
||||||
|
|
||||||
# extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash
|
# Extruder enable / disable. Actually wanted to use machine manager here,
|
||||||
|
# but the initialization order causes it to crash
|
||||||
ExtruderManager.getInstance().extrudersChanged.connect(self._extruderChanged)
|
ExtruderManager.getInstance().extrudersChanged.connect(self._extruderChanged)
|
||||||
|
|
||||||
self.backendQuit.connect(self._onBackendQuit)
|
self.backendQuit.connect(self._onBackendQuit)
|
||||||
@ -239,26 +280,14 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
command += ["connect", "127.0.0.1:{0}".format(self._port), ""]
|
command += ["connect", "127.0.0.1:{0}".format(self._port), ""]
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(prog = "cura", add_help = False)
|
parser = argparse.ArgumentParser(prog = "cura", add_help = False)
|
||||||
parser.add_argument("--debug", action = "store_true", default = False, help = "Turn on the debug mode by setting this option.")
|
parser.add_argument("--debug", action = "store_true", default = False,
|
||||||
|
help = "Turn on the debug mode by setting this option.")
|
||||||
known_args = vars(parser.parse_known_args()[0])
|
known_args = vars(parser.parse_known_args()[0])
|
||||||
if known_args["debug"]:
|
if known_args["debug"]:
|
||||||
command.append("-vvv")
|
command.append("-vvv")
|
||||||
|
|
||||||
return command
|
return command
|
||||||
|
|
||||||
printDurationMessage = Signal()
|
|
||||||
"""Emitted when we get a message containing print duration and material amount.
|
|
||||||
|
|
||||||
This also implies the slicing has finished.
|
|
||||||
:param time: The amount of time the print will take.
|
|
||||||
:param material_amount: The amount of material the print will use.
|
|
||||||
"""
|
|
||||||
slicingStarted = Signal()
|
|
||||||
"""Emitted when the slicing process starts."""
|
|
||||||
|
|
||||||
slicingCancelled = Signal()
|
|
||||||
"""Emitted when the slicing process is aborted forcefully."""
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def stopSlicing(self) -> None:
|
def stopSlicing(self) -> None:
|
||||||
self.setState(BackendState.NotStarted)
|
self.setState(BackendState.NotStarted)
|
||||||
@ -266,7 +295,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
self._terminate()
|
self._terminate()
|
||||||
self._createSocket()
|
self._createSocket()
|
||||||
|
|
||||||
if self._process_layers_job is not None: # We were processing layers. Stop that, the layers are going to change soon.
|
if self._process_layers_job is not None:
|
||||||
|
# We were processing layers. Stop that, the layers are going to change soon.
|
||||||
Logger.log("i", "Aborting process layers job...")
|
Logger.log("i", "Aborting process layers job...")
|
||||||
self._process_layers_job.abort()
|
self._process_layers_job.abort()
|
||||||
self._process_layers_job = None
|
self._process_layers_job = None
|
||||||
@ -281,7 +311,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
self.markSliceAll()
|
self.markSliceAll()
|
||||||
self.slice()
|
self.slice()
|
||||||
|
|
||||||
@call_on_qt_thread # must be called from the main thread because of OpenGL
|
@call_on_qt_thread # Must be called from the main thread because of OpenGL
|
||||||
def _createSnapshot(self) -> None:
|
def _createSnapshot(self) -> None:
|
||||||
self._snapshot = None
|
self._snapshot = None
|
||||||
if not CuraApplication.getInstance().isVisible:
|
if not CuraApplication.getInstance().isVisible:
|
||||||
@ -290,7 +320,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
Logger.log("i", "Creating thumbnail image (just before slice)...")
|
Logger.log("i", "Creating thumbnail image (just before slice)...")
|
||||||
try:
|
try:
|
||||||
self._snapshot = Snapshot.snapshot(width = 300, height = 300)
|
self._snapshot = Snapshot.snapshot(width = 300, height = 300)
|
||||||
except:
|
except Exception:
|
||||||
Logger.logException("w", "Failed to create snapshot image")
|
Logger.logException("w", "Failed to create snapshot image")
|
||||||
self._snapshot = None # Failing to create thumbnail should not fail creation of UFP
|
self._snapshot = None # Failing to create thumbnail should not fail creation of UFP
|
||||||
|
|
||||||
@ -302,6 +332,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
|
|
||||||
self._createSnapshot()
|
self._createSnapshot()
|
||||||
|
|
||||||
|
self.startPlugins()
|
||||||
|
|
||||||
Logger.log("i", "Starting to slice...")
|
Logger.log("i", "Starting to slice...")
|
||||||
self._time_start_process = time()
|
self._time_start_process = time()
|
||||||
if not self._build_plates_to_be_sliced:
|
if not self._build_plates_to_be_sliced:
|
||||||
@ -315,7 +347,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not hasattr(self._scene, "gcode_dict"):
|
if not hasattr(self._scene, "gcode_dict"):
|
||||||
self._scene.gcode_dict = {} #type: ignore #Because we are creating the missing attribute here.
|
self._scene.gcode_dict = {} # type: ignore
|
||||||
|
# We need to ignore type because we are creating the missing attribute here.
|
||||||
|
|
||||||
# see if we really have to slice
|
# see if we really have to slice
|
||||||
application = CuraApplication.getInstance()
|
application = CuraApplication.getInstance()
|
||||||
@ -326,9 +359,9 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
|
|
||||||
self._stored_layer_data = []
|
self._stored_layer_data = []
|
||||||
|
|
||||||
|
|
||||||
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
|
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
|
||||||
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #Because we created this attribute above.
|
self._scene.gcode_dict[build_plate_to_be_sliced] = [] # type: ignore
|
||||||
|
# We need to ignore the type because we created this attribute above.
|
||||||
Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced)
|
Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced)
|
||||||
if self._build_plates_to_be_sliced:
|
if self._build_plates_to_be_sliced:
|
||||||
self.slice()
|
self.slice()
|
||||||
@ -345,7 +378,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
self.processingProgress.emit(0.0)
|
self.processingProgress.emit(0.0)
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.backendStateChange.emit(BackendState.NotStarted)
|
||||||
|
|
||||||
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #[] indexed by build plate number
|
self._scene.gcode_dict[build_plate_to_be_sliced] = [] # type: ignore #[] indexed by build plate number
|
||||||
self._slicing = True
|
self._slicing = True
|
||||||
self.slicingStarted.emit()
|
self.slicingStarted.emit()
|
||||||
|
|
||||||
@ -370,6 +403,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
if self._start_slice_job is not None:
|
if self._start_slice_job is not None:
|
||||||
self._start_slice_job.cancel()
|
self._start_slice_job.cancel()
|
||||||
|
|
||||||
|
self.stopPlugins()
|
||||||
|
|
||||||
self.slicingCancelled.emit()
|
self.slicingCancelled.emit()
|
||||||
self.processingProgress.emit(0)
|
self.processingProgress.emit(0)
|
||||||
Logger.log("d", "Attempting to kill the engine process")
|
Logger.log("d", "Attempting to kill the engine process")
|
||||||
@ -384,7 +419,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) # type: ignore
|
Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) # type: ignore
|
||||||
self._process = None # type: ignore
|
self._process = None # type: ignore
|
||||||
|
|
||||||
except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this.
|
except Exception as e:
|
||||||
|
# Terminating a process that is already terminating causes an exception, silently ignore this.
|
||||||
Logger.log("d", "Exception occurred while trying to kill the engine %s", str(e))
|
Logger.log("d", "Exception occurred while trying to kill the engine %s", str(e))
|
||||||
|
|
||||||
def _onStartSliceCompleted(self, job: StartSliceJob) -> None:
|
def _onStartSliceCompleted(self, job: StartSliceJob) -> None:
|
||||||
@ -429,7 +465,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
Logger.log("w", "Global container stack not assigned to CuraEngineBackend!")
|
Logger.log("w", "Global container stack not assigned to CuraEngineBackend!")
|
||||||
return
|
return
|
||||||
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
error_keys = [] #type: List[str]
|
error_keys: List[str] = []
|
||||||
for extruder in extruders:
|
for extruder in extruders:
|
||||||
error_keys.extend(extruder.getErrorKeys())
|
error_keys.extend(extruder.getErrorKeys())
|
||||||
if not extruders:
|
if not extruders:
|
||||||
@ -524,7 +560,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
# Preparation completed, send it to the backend.
|
# Preparation completed, send it to the backend.
|
||||||
self._socket.sendMessage(job.getSliceMessage())
|
self._socket.sendMessage(job.getSliceMessage())
|
||||||
|
|
||||||
# Notify the user that it's now up to the backend to do it's job
|
# Notify the user that it's now up to the backend to do its job
|
||||||
self.setState(BackendState.Processing)
|
self.setState(BackendState.Processing)
|
||||||
|
|
||||||
# Handle time reporting.
|
# Handle time reporting.
|
||||||
@ -551,7 +587,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
self._is_disabled = True
|
self._is_disabled = True
|
||||||
gcode_list = node.callDecoration("getGCodeList")
|
gcode_list = node.callDecoration("getGCodeList")
|
||||||
if gcode_list is not None:
|
if gcode_list is not None:
|
||||||
self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list #type: ignore #Because we generate this attribute dynamically.
|
self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list # type: ignore
|
||||||
|
# We need to ignore type because we generate this attribute dynamically.
|
||||||
|
|
||||||
if self._use_timer == enable_timer:
|
if self._use_timer == enable_timer:
|
||||||
return self._use_timer
|
return self._use_timer
|
||||||
@ -566,7 +603,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
def _numObjectsPerBuildPlate(self) -> Dict[int, int]:
|
def _numObjectsPerBuildPlate(self) -> Dict[int, int]:
|
||||||
"""Return a dict with number of objects per build plate"""
|
"""Return a dict with number of objects per build plate"""
|
||||||
|
|
||||||
num_objects = defaultdict(int) #type: Dict[int, int]
|
num_objects: Dict[int, int] = defaultdict(int)
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
# Only count sliceable objects
|
# Only count sliceable objects
|
||||||
if node.callDecoration("isSliceable"):
|
if node.callDecoration("isSliceable"):
|
||||||
@ -646,11 +683,13 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
self._terminate()
|
self._terminate()
|
||||||
self._createSocket()
|
self._createSocket()
|
||||||
|
|
||||||
if error.getErrorCode() not in [Arcus.ErrorCode.BindFailedError, Arcus.ErrorCode.ConnectionResetError, Arcus.ErrorCode.Debug]:
|
if error.getErrorCode() not in [Arcus.ErrorCode.BindFailedError,
|
||||||
|
Arcus.ErrorCode.ConnectionResetError,
|
||||||
|
Arcus.ErrorCode.Debug]:
|
||||||
Logger.log("w", "A socket error caused the connection to be reset")
|
Logger.log("w", "A socket error caused the connection to be reset")
|
||||||
|
|
||||||
# _terminate()' function sets the job status to 'cancel', after reconnecting to another Port the job status
|
# _terminate()' function sets the job status to 'cancel', after reconnecting to another Port the job status
|
||||||
# needs to be updated. Otherwise backendState is "Unable To Slice"
|
# needs to be updated. Otherwise, backendState is "Unable To Slice"
|
||||||
if error.getErrorCode() == Arcus.ErrorCode.BindFailedError and self._start_slice_job is not None:
|
if error.getErrorCode() == Arcus.ErrorCode.BindFailedError and self._start_slice_job is not None:
|
||||||
self._start_slice_job.setIsCancelled(False)
|
self._start_slice_job.setIsCancelled(False)
|
||||||
|
|
||||||
@ -672,7 +711,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
if node.callDecoration("getLayerData"):
|
if node.callDecoration("getLayerData"):
|
||||||
if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
|
if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
|
||||||
# We can assume that all nodes have a parent as we're looping through the scene (and filter out root)
|
# We can assume that all nodes have a parent as we're looping through the scene and filter out root
|
||||||
cast(SceneNode, node.getParent()).removeChild(node)
|
cast(SceneNode, node.getParent()).removeChild(node)
|
||||||
|
|
||||||
def markSliceAll(self) -> None:
|
def markSliceAll(self) -> None:
|
||||||
@ -701,7 +740,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
:param instance: The setting instance that has changed.
|
:param instance: The setting instance that has changed.
|
||||||
:param property: The property of the setting instance that has changed.
|
:param property: The property of the setting instance that has changed.
|
||||||
"""
|
"""
|
||||||
if property == "value": # Only reslice if the value has changed.
|
if property == "value": # Only re-slice if the value has changed.
|
||||||
self.needsSlicing()
|
self.needsSlicing()
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
|
||||||
@ -765,13 +804,17 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
:param message: The protobuf message signalling that slicing is finished.
|
:param message: The protobuf message signalling that slicing is finished.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.stopPlugins()
|
||||||
|
|
||||||
self.setState(BackendState.Done)
|
self.setState(BackendState.Done)
|
||||||
self.processingProgress.emit(1.0)
|
self.processingProgress.emit(1.0)
|
||||||
self._time_end_slice = time()
|
self._time_end_slice = time()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically.
|
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore
|
||||||
except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
|
# We need to ignore the type because it was generated dynamically.
|
||||||
|
except KeyError:
|
||||||
|
# Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
|
||||||
gcode_list = []
|
gcode_list = []
|
||||||
application = CuraApplication.getInstance()
|
application = CuraApplication.getInstance()
|
||||||
for index, line in enumerate(gcode_list):
|
for index, line in enumerate(gcode_list):
|
||||||
@ -816,7 +859,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
|
self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
|
||||||
except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
|
except KeyError:
|
||||||
|
# Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
|
||||||
pass # Throw the message away.
|
pass # Throw the message away.
|
||||||
|
|
||||||
def _onGCodePrefixMessage(self, message: Arcus.PythonMessage) -> None:
|
def _onGCodePrefixMessage(self, message: Arcus.PythonMessage) -> None:
|
||||||
@ -828,7 +872,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
|
self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
|
||||||
except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
|
except KeyError:
|
||||||
|
# Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
|
||||||
pass # Throw the message away.
|
pass # Throw the message away.
|
||||||
|
|
||||||
def _onSliceUUIDMessage(self, message: Arcus.PythonMessage) -> None:
|
def _onSliceUUIDMessage(self, message: Arcus.PythonMessage) -> None:
|
||||||
@ -955,7 +1000,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
view = CuraApplication.getInstance().getController().getActiveView()
|
view = CuraApplication.getInstance().getController().getActiveView()
|
||||||
if view:
|
if view:
|
||||||
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
|
if view.getPluginId() == "SimulationView":
|
||||||
|
# If switching to layer view, we should process the layers if that hasn't been done yet.
|
||||||
self._layer_view_active = True
|
self._layer_view_active = True
|
||||||
# There is data and we're not slicing at the moment
|
# There is data and we're not slicing at the moment
|
||||||
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
|
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
|
||||||
@ -974,7 +1020,6 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
|
|
||||||
We should reset our state and start listening for new connections.
|
We should reset our state and start listening for new connections.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self._restart:
|
if not self._restart:
|
||||||
if self._process: # type: ignore
|
if self._process: # type: ignore
|
||||||
return_code = self._process.wait()
|
return_code = self._process.wait()
|
||||||
@ -985,6 +1030,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
self.stopSlicing()
|
self.stopSlicing()
|
||||||
else:
|
else:
|
||||||
Logger.log("d", "Backend finished slicing. Resetting process and socket.")
|
Logger.log("d", "Backend finished slicing. Resetting process and socket.")
|
||||||
|
self.stopPlugins()
|
||||||
self._process = None # type: ignore
|
self._process = None # type: ignore
|
||||||
|
|
||||||
def _reportBackendError(self, _message_id: str, _action_id: str) -> None:
|
def _reportBackendError(self, _message_id: str, _action_id: str) -> None:
|
||||||
@ -1007,7 +1053,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
self._global_container_stack = CuraApplication.getInstance().getMachineManager().activeMachine
|
self._global_container_stack = CuraApplication.getInstance().getMachineManager().activeMachine
|
||||||
|
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
|
# Note: Only starts slicing when the value changed.
|
||||||
|
self._global_container_stack.propertyChanged.connect(self._onSettingChanged)
|
||||||
self._global_container_stack.containersChanged.connect(self._onChanged)
|
self._global_container_stack.containersChanged.connect(self._onChanged)
|
||||||
|
|
||||||
for extruder in self._global_container_stack.extruderList:
|
for extruder in self._global_container_stack.extruderList:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# Copyright (c) 2021-2022 Ultimaker B.V.
|
# Copyright (c) 2021-2022 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
import os
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
@ -301,6 +302,23 @@ class StartSliceJob(Job):
|
|||||||
for extruder_stack in global_stack.extruderList:
|
for extruder_stack in global_stack.extruderList:
|
||||||
self._buildExtruderMessage(extruder_stack)
|
self._buildExtruderMessage(extruder_stack)
|
||||||
|
|
||||||
|
for plugin in CuraApplication.getInstance().getBackendPlugins():
|
||||||
|
if not plugin.usePlugin():
|
||||||
|
continue
|
||||||
|
for slot in plugin.getSupportedSlots():
|
||||||
|
# Right now we just send the message for every slot that we support. A single plugin can support
|
||||||
|
# multiple slots
|
||||||
|
# In the future the frontend will need to decide what slots that a plugin actually supports should
|
||||||
|
# also be used. For instance, if you have two plugins and each of them support a_generate and b_generate
|
||||||
|
# only one of each can actually be used (eg; plugin 1 does both, plugin 1 does a_generate and 2 does
|
||||||
|
# b_generate, etc).
|
||||||
|
plugin_message = self._slice_message.addRepeatedMessage("engine_plugins")
|
||||||
|
plugin_message.id = slot
|
||||||
|
plugin_message.address = plugin.getAddress()
|
||||||
|
plugin_message.port = plugin.getPort()
|
||||||
|
plugin_message.plugin_name = plugin.getPluginId()
|
||||||
|
plugin_message.plugin_version = plugin.getVersion()
|
||||||
|
|
||||||
for group in filtered_object_groups:
|
for group in filtered_object_groups:
|
||||||
group_message = self._slice_message.addRepeatedMessage("object_lists")
|
group_message = self._slice_message.addRepeatedMessage("object_lists")
|
||||||
parent = group[0].getParent()
|
parent = group[0].getParent()
|
||||||
|
@ -20,7 +20,6 @@ class MissingPackageList(RemotePackageList):
|
|||||||
def __init__(self, packages_metadata: List[Dict[str, str]], parent: Optional["QObject"] = None) -> None:
|
def __init__(self, packages_metadata: List[Dict[str, str]], parent: Optional["QObject"] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._packages_metadata: List[Dict[str, str]] = packages_metadata
|
self._packages_metadata: List[Dict[str, str]] = packages_metadata
|
||||||
self._package_type_filter = "material"
|
|
||||||
self._search_type = "package_ids"
|
self._search_type = "package_ids"
|
||||||
self._requested_search_string = ",".join(map(lambda package: package["id"], packages_metadata))
|
self._requested_search_string = ",".join(map(lambda package: package["id"], packages_metadata))
|
||||||
|
|
||||||
@ -38,7 +37,14 @@ class MissingPackageList(RemotePackageList):
|
|||||||
|
|
||||||
for package_metadata in self._packages_metadata:
|
for package_metadata in self._packages_metadata:
|
||||||
if package_metadata["id"] not in returned_packages_ids:
|
if package_metadata["id"] not in returned_packages_ids:
|
||||||
package = PackageModel.fromIncompletePackageInformation(package_metadata["display_name"], package_metadata["package_version"], self._package_type_filter)
|
package_type = package_metadata["type"] if "type" in package_metadata else "material"
|
||||||
|
# When this feature was originally introduced only missing materials were detected. With the inclusion
|
||||||
|
# of backend plugins this system was extended to also detect missing plugins. With that change the type
|
||||||
|
# of the package was added to the metadata. Project files before this change do not have this type. So
|
||||||
|
# if the type is not present we assume it is a material.
|
||||||
|
package = PackageModel.fromIncompletePackageInformation(package_metadata["display_name"],
|
||||||
|
package_metadata["package_version"],
|
||||||
|
package_type)
|
||||||
self.appendItem({"package": package})
|
self.appendItem({"package": package})
|
||||||
|
|
||||||
self.itemsChanged.emit()
|
self.itemsChanged.emit()
|
||||||
|
@ -87,12 +87,22 @@ class PackageModel(QObject):
|
|||||||
self._is_missing_package_information = False
|
self._is_missing_package_information = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromIncompletePackageInformation(cls, display_name: str, package_version: str, package_type: str) -> "PackageModel":
|
def fromIncompletePackageInformation(cls, display_name: str, package_version: str,
|
||||||
|
package_type: str) -> "PackageModel":
|
||||||
|
description = ""
|
||||||
|
match package_type:
|
||||||
|
case "material":
|
||||||
|
description = catalog.i18nc("@label:label Ultimaker Marketplace is a brand name, don't translate",
|
||||||
|
"The material package associated with the Cura project could not be found on the Ultimaker Marketplace. Use the partial material profile definition stored in the Cura project file at your own risk.")
|
||||||
|
case "plugin":
|
||||||
|
description = catalog.i18nc("@label:label Ultimaker Marketplace is a brand name, don't translate",
|
||||||
|
"The plugin associated with the Cura project could not be found on the Ultimaker Marketplace. As the plugin may be required to slice the project it might not be possible to correctly slice the file.")
|
||||||
|
|
||||||
package_data = {
|
package_data = {
|
||||||
"display_name": display_name,
|
"display_name": display_name,
|
||||||
"package_version": package_version,
|
"package_version": package_version,
|
||||||
"package_type": package_type,
|
"package_type": package_type,
|
||||||
"description": catalog.i18nc("@label:label Ultimaker Marketplace is a brand name, don't translate", "The material package associated with the Cura project could not be found on the Ultimaker Marketplace. Use the partial material profile definition stored in the Cura project file at your own risk.")
|
"description": description,
|
||||||
}
|
}
|
||||||
package_model = cls(package_data)
|
package_model = cls(package_data)
|
||||||
package_model.setIsMissingPackageInformation(True)
|
package_model.setIsMissingPackageInformation(True)
|
||||||
|
@ -12,7 +12,7 @@ import Cura 1.6 as Cura
|
|||||||
Marketplace
|
Marketplace
|
||||||
{
|
{
|
||||||
modality: Qt.ApplicationModal
|
modality: Qt.ApplicationModal
|
||||||
title: catalog.i18nc("@title", "Install missing Materials")
|
title: catalog.i18nc("@title", "Install missing packages")
|
||||||
pageContentsSource: "MissingPackages.qml"
|
pageContentsSource: "MissingPackages.qml"
|
||||||
showSearchHeader: false
|
showSearchHeader: false
|
||||||
showOnboadBanner: false
|
showOnboadBanner: false
|
||||||
|
@ -5,7 +5,7 @@ import UM 1.4 as UM
|
|||||||
|
|
||||||
Packages
|
Packages
|
||||||
{
|
{
|
||||||
pageTitle: catalog.i18nc("@header", "Install Materials")
|
pageTitle: catalog.i18nc("@header", "Install Packages")
|
||||||
|
|
||||||
bannerVisible: false
|
bannerVisible: false
|
||||||
showUpdateButton: false
|
showUpdateButton: false
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//Copyright (c) 2022 Ultimaker B.V.
|
//Copyright (c) 2022 Ultimaker B.V.
|
||||||
//Cura is released under the terms of the LGPLv3 or higher.
|
//Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.2
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
import UM 1.5 as UM
|
import UM 1.5 as UM
|
||||||
@ -234,10 +234,11 @@ Item
|
|||||||
setDestroyed(true)
|
setDestroyed(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
property int indexWithFocus: -1
|
||||||
delegate: Row
|
delegate: Row
|
||||||
{
|
{
|
||||||
spacing: UM.Theme.getSize("default_margin").width
|
spacing: UM.Theme.getSize("default_margin").width
|
||||||
|
property var settingLoaderItem: settingLoader.item
|
||||||
Loader
|
Loader
|
||||||
{
|
{
|
||||||
id: settingLoader
|
id: settingLoader
|
||||||
@ -340,6 +341,44 @@ Item
|
|||||||
function onPropertiesChanged() { provider.forcePropertiesChanged() }
|
function onPropertiesChanged() { provider.forcePropertiesChanged() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections
|
||||||
|
{
|
||||||
|
target: settingLoader.item
|
||||||
|
function onFocusReceived()
|
||||||
|
{
|
||||||
|
|
||||||
|
contents.indexWithFocus = index
|
||||||
|
contents.positionViewAtIndex(index, ListView.Contain)
|
||||||
|
}
|
||||||
|
function onSetActiveFocusToNextSetting(forward)
|
||||||
|
{
|
||||||
|
if (forward == undefined || forward)
|
||||||
|
{
|
||||||
|
contents.currentIndex = contents.indexWithFocus + 1
|
||||||
|
while(contents.currentItem && contents.currentItem.height <= 0)
|
||||||
|
{
|
||||||
|
contents.currentIndex++
|
||||||
|
}
|
||||||
|
if (contents.currentItem)
|
||||||
|
{
|
||||||
|
contents.currentItem.settingLoaderItem.focusItem.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
contents.currentIndex = contents.indexWithFocus - 1
|
||||||
|
while(contents.currentItem && contents.currentItem.height <= 0)
|
||||||
|
{
|
||||||
|
contents.currentIndex--
|
||||||
|
}
|
||||||
|
if (contents.currentItem)
|
||||||
|
{
|
||||||
|
contents.currentItem.settingLoaderItem.focusItem.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections
|
Connections
|
||||||
{
|
{
|
||||||
target: UM.ActiveTool
|
target: UM.ActiveTool
|
||||||
|
@ -93,6 +93,11 @@ class PostProcessingPlugin(QObject, Extension):
|
|||||||
Logger.logException("e", "Exception in post-processing script.")
|
Logger.logException("e", "Exception in post-processing script.")
|
||||||
if len(self._script_list): # Add comment to g-code if any changes were made.
|
if len(self._script_list): # Add comment to g-code if any changes were made.
|
||||||
gcode_list[0] += ";POSTPROCESSED\n"
|
gcode_list[0] += ";POSTPROCESSED\n"
|
||||||
|
# Add all the active post processor names to data[0]
|
||||||
|
pp_name_list = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("post_processing_scripts")
|
||||||
|
for pp_name in pp_name_list.split("\n"):
|
||||||
|
pp_name = pp_name.split("]")
|
||||||
|
gcode_list[0] += "; " + str(pp_name[0]) + "]\n"
|
||||||
gcode_dict[active_build_plate_id] = gcode_list
|
gcode_dict[active_build_plate_id] = gcode_list
|
||||||
setattr(scene, "gcode_dict", gcode_dict)
|
setattr(scene, "gcode_dict", gcode_dict)
|
||||||
else:
|
else:
|
||||||
|
@ -37,7 +37,7 @@ class CreateThumbnail(Script):
|
|||||||
|
|
||||||
encoded_snapshot_length = len(encoded_snapshot)
|
encoded_snapshot_length = len(encoded_snapshot)
|
||||||
gcode.append(";")
|
gcode.append(";")
|
||||||
gcode.append("; thumbnail begin {} {} {}".format(
|
gcode.append("; thumbnail begin {}x{} {}".format(
|
||||||
width, height, encoded_snapshot_length))
|
width, height, encoded_snapshot_length))
|
||||||
|
|
||||||
chunks = ["; {}".format(encoded_snapshot[i:i+chunk_size])
|
chunks = ["; {}".format(encoded_snapshot[i:i+chunk_size])
|
||||||
|
349
plugins/PostProcessingPlugin/scripts/LimitXYAccelJerk.py
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
# Limit XY Accel: Authored by: Greg Foresi (GregValiant)
|
||||||
|
# July 2023
|
||||||
|
# Sometimes bed-slinger printers need different Accel and Jerk values for the Y but Cura always makes them the same.
|
||||||
|
# This script changes the Accel and/or Jerk from the beginning of the 'Start Layer' to the end of the 'End Layer'.
|
||||||
|
# The existing M201 Max Accel will be changed to limit the Y (and/or X) accel at the printer. If you have Accel enabled in Cura and the XY Accel is set to 3000 then setting the Y limit to 1000 will result in the printer limiting the Y to 1000. This can keep tall skinny prints from breaking loose of the bed and failing. The script was not tested with Junction Deviation.
|
||||||
|
# If enabled - the Jerk setting is changed line-by-line within the gcode as there is no "limit" on Jerk.
|
||||||
|
# if 'Gradual ACCEL change' is enabled then the Accel is changed gradually from the Start to the End layer and that will be the final Accel setting in the file. If 'Gradual' is enabled then the Jerk settings will continue to be changed to the end of the file (rather than ending at the End layer).
|
||||||
|
# This post is intended for printers with moving beds (bed slingers) so UltiMaker printers are excluded.
|
||||||
|
# When setting an accel limit on multi-extruder printers ALL extruders are effected.
|
||||||
|
# This post does not distinguish between Print Accel and Travel Accel. The limit is the limit for all regardless. Example: Skin Accel = 1000 and Outer Wall accel = 500. If the limit is set to 300 then both Skin and Outer Wall will be Accel = 300.
|
||||||
|
# 9/15/2023 added support for RepRap M566 command for Jerk in mm/min
|
||||||
|
|
||||||
|
from ..Script import Script
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
import re
|
||||||
|
from UM.Message import Message
|
||||||
|
|
||||||
|
class LimitXYAccelJerk(Script):
|
||||||
|
|
||||||
|
def initialize(self) -> None:
|
||||||
|
super().initialize()
|
||||||
|
# Get the Accel and Jerk and set the values in the setting boxes--
|
||||||
|
mycura = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
|
extruder = mycura.extruderList
|
||||||
|
accel_print = extruder[0].getProperty("acceleration_print", "value")
|
||||||
|
accel_travel = extruder[0].getProperty("acceleration_travel", "value")
|
||||||
|
jerk_print_old = extruder[0].getProperty("jerk_print", "value")
|
||||||
|
jerk_travel_old = extruder[0].getProperty("jerk_travel", "value")
|
||||||
|
self._instance.setProperty("x_accel_limit", "value", round(accel_print))
|
||||||
|
self._instance.setProperty("y_accel_limit", "value", round(accel_print))
|
||||||
|
self._instance.setProperty("x_jerk", "value", jerk_print_old)
|
||||||
|
self._instance.setProperty("y_jerk", "value", jerk_print_old)
|
||||||
|
ext_count = int(mycura.getProperty("machine_extruder_count", "value"))
|
||||||
|
machine_name = str(mycura.getProperty("machine_name", "value"))
|
||||||
|
if str(mycura.getProperty("machine_gcode_flavor", "value")) == "RepRap (RepRap)":
|
||||||
|
self._instance.setProperty("jerk_cmd", "value", "reprap_flavor")
|
||||||
|
else:
|
||||||
|
self._instance.setProperty("jerk_cmd", "value", "marlin_flavor")
|
||||||
|
firmware_flavor = str(mycura.getProperty("machine_gcode_flavor", "value"))
|
||||||
|
|
||||||
|
# Warn the user if the printer is an Ultimaker-------------------------
|
||||||
|
if "Ultimaker" in machine_name or "UltiGCode" in firmware_flavor or "Griffin" in firmware_flavor:
|
||||||
|
Message(text = "<NOTICE> [Limit the X-Y Accel/Jerk] DID NOT RUN because Ultimaker printers don't have sliding beds.").show()
|
||||||
|
|
||||||
|
# Warn the user if the printer is multi-extruder------------------
|
||||||
|
if ext_count > 1:
|
||||||
|
Message(text = "<NOTICE> 'Limit the X-Y Accel/Jerk': The post processor treats all extruders the same. If you have multiple extruders they will all be subject to the same Accel and Jerk limits imposed. If you have different Travel and Print Accel they will also be subject to the same limits. If that is not acceptable then you should not use this Post Processor.").show()
|
||||||
|
|
||||||
|
def getSettingDataString(self):
|
||||||
|
return """{
|
||||||
|
"name": "Limit the X-Y Accel/Jerk (all extruders equal)",
|
||||||
|
"key": "LimitXYAccelJerk",
|
||||||
|
"metadata": {},
|
||||||
|
"version": 2,
|
||||||
|
"settings":
|
||||||
|
{
|
||||||
|
"type_of_change":
|
||||||
|
{
|
||||||
|
"label": "Immediate or Gradual change",
|
||||||
|
"description": "An 'Immediate' change will insert the new numbers immediately at the Start Layer. A 'Gradual' change will transition from the starting Accel to the new Accel limit across a range of layers.",
|
||||||
|
"type": "enum",
|
||||||
|
"options": {
|
||||||
|
"immediate_change": "Immediate",
|
||||||
|
"gradual_change": "Gradual"},
|
||||||
|
"default_value": "immediate_change"
|
||||||
|
},
|
||||||
|
"x_accel_limit":
|
||||||
|
{
|
||||||
|
"label": "X MAX Acceleration",
|
||||||
|
"description": "If this number is lower than the 'X Print Accel' in Cura then this will limit the Accel on the X axis. Enter the Maximum Acceleration value for the X axis. This will affect both Print and Travel Accel. If you enable an End Layer then at the end of that layer the Accel Limit will be reset (unless you choose 'Gradual' in which case the new limit goes to the top layer).",
|
||||||
|
"type": "int",
|
||||||
|
"enabled": true,
|
||||||
|
"minimum_value": 50,
|
||||||
|
"unit": "mm/sec² ",
|
||||||
|
"default_value": 500
|
||||||
|
},
|
||||||
|
"y_accel_limit":
|
||||||
|
{
|
||||||
|
"label": "Y MAX Acceleration",
|
||||||
|
"description": "If this number is lower than the Y accel in Cura then this will limit the Accel on the Y axis. Enter the Maximum Acceleration value for the Y axis. This will affect both Print and Travel Accel. If you enable an End Layer then at the end of that layer the Accel Limit will be reset (unless you choose 'Gradual' in which case the new limit goes to the top layer).",
|
||||||
|
"type": "int",
|
||||||
|
"enabled": true,
|
||||||
|
"minimum_value": 50,
|
||||||
|
"unit": "mm/sec² ",
|
||||||
|
"default_value": 500
|
||||||
|
},
|
||||||
|
"jerk_enable":
|
||||||
|
{
|
||||||
|
"label": "Change the Jerk",
|
||||||
|
"description": "Whether to change the Jerk values.",
|
||||||
|
"type": "bool",
|
||||||
|
"enabled": true,
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
|
"jerk_cmd":
|
||||||
|
{
|
||||||
|
"label": "G-Code Jerk Command",
|
||||||
|
"description": "Marlin uses M205. RepRap might use M566.",
|
||||||
|
"type": "enum",
|
||||||
|
"options": {
|
||||||
|
"marlin_flavor": "M205",
|
||||||
|
"reprap_flavor": "M566"},
|
||||||
|
"default_value": "marlin_flavor",
|
||||||
|
"enabled": "jerk_enable"
|
||||||
|
},
|
||||||
|
"x_jerk":
|
||||||
|
{
|
||||||
|
"label": " X jerk",
|
||||||
|
"description": "Enter the Jerk value for the X axis. Enter '0' to use the existing X Jerk. This setting will affect both the Print and Travel jerk.",
|
||||||
|
"type": "int",
|
||||||
|
"enabled": "jerk_enable",
|
||||||
|
"unit": "mm/sec ",
|
||||||
|
"default_value": 8
|
||||||
|
},
|
||||||
|
"y_jerk":
|
||||||
|
{
|
||||||
|
"label": " Y jerk",
|
||||||
|
"description": "Enter the Jerk value for the Y axis. Enter '0' to use the existing Y Jerk. This setting will affect both the Print and Travel jerk.",
|
||||||
|
"type": "int",
|
||||||
|
"enabled": "jerk_enable",
|
||||||
|
"unit": "mm/sec ",
|
||||||
|
"default_value": 8
|
||||||
|
},
|
||||||
|
"start_layer":
|
||||||
|
{
|
||||||
|
"label": "From Start of Layer:",
|
||||||
|
"description": "Use the Cura Preview numbers. Enter the Layer to start the changes at. The minimum is Layer 1.",
|
||||||
|
"type": "int",
|
||||||
|
"default_value": 1,
|
||||||
|
"minimum_value": 1,
|
||||||
|
"unit": "Lay# ",
|
||||||
|
"enabled": "type_of_change == 'immediate_change'"
|
||||||
|
},
|
||||||
|
"end_layer":
|
||||||
|
{
|
||||||
|
"label": "To End of Layer",
|
||||||
|
"description": "Use the Cura Preview numbers. Enter '-1' for the entire file or enter a layer number. The changes will end at your 'End Layer' and revert back to the original numbers.",
|
||||||
|
"type": "int",
|
||||||
|
"default_value": -1,
|
||||||
|
"minimum_value": -1,
|
||||||
|
"unit": "Lay# ",
|
||||||
|
"enabled": "type_of_change == 'immediate_change'"
|
||||||
|
},
|
||||||
|
"gradient_start_layer":
|
||||||
|
{
|
||||||
|
"label": " Gradual From Layer:",
|
||||||
|
"description": "Use the Cura Preview numbers. Enter the Layer to start the changes at. The minimum is Layer 1.",
|
||||||
|
"type": "int",
|
||||||
|
"default_value": 1,
|
||||||
|
"minimum_value": 1,
|
||||||
|
"unit": "Lay# ",
|
||||||
|
"enabled": "type_of_change == 'gradual_change'"
|
||||||
|
},
|
||||||
|
"gradient_end_layer":
|
||||||
|
{
|
||||||
|
"label": " Gradual To Layer",
|
||||||
|
"description": "Use the Cura Preview numbers. Enter '-1' for the top layer or enter a layer number. The last 'Gradual' change will continue to the end of the file.",
|
||||||
|
"type": "int",
|
||||||
|
"default_value": -1,
|
||||||
|
"minimum_value": -1,
|
||||||
|
"unit": "Lay# ",
|
||||||
|
"enabled": "type_of_change == 'gradual_change'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
|
||||||
|
def execute(self, data):
|
||||||
|
mycura = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
|
extruder = mycura.extruderList
|
||||||
|
machine_name = str(mycura.getProperty("machine_name", "value"))
|
||||||
|
print_sequence = str(mycura.getProperty("print_sequence", "value"))
|
||||||
|
|
||||||
|
# Exit if 'one_at_a_time' is enabled-------------------------
|
||||||
|
if print_sequence == "one_at_a_time":
|
||||||
|
Message(text = "<NOTICE> [Limit the X-Y Accel/Jerk] DID NOT RUN. This post processor is not compatible with 'One-at-a-Time' mode.").show()
|
||||||
|
data[0] += "; [LimitXYAccelJerk] DID NOT RUN because Cura is set to 'One-at-a-Time' mode.\n"
|
||||||
|
return data
|
||||||
|
|
||||||
|
# Exit if the printer is an Ultimaker-------------------------
|
||||||
|
if "Ultimaker" in machine_name:
|
||||||
|
Message(text = "<NOTICE> [Limit the X-Y Accel/Jerk] DID NOT RUN. This post processor is for bed slinger printers only.").show()
|
||||||
|
data[0] += "; [LimitXYAccelJerk] DID NOT RUN because the printer doesn't have a sliding bed.\n"
|
||||||
|
return data
|
||||||
|
|
||||||
|
type_of_change = str(self.getSettingValueByKey("type_of_change"))
|
||||||
|
accel_print_enabled = bool(extruder[0].getProperty("acceleration_enabled", "value"))
|
||||||
|
accel_travel_enabled = bool(extruder[0].getProperty("acceleration_travel_enabled", "value"))
|
||||||
|
accel_print = extruder[0].getProperty("acceleration_print", "value")
|
||||||
|
accel_travel = extruder[0].getProperty("acceleration_travel", "value")
|
||||||
|
jerk_print_enabled = str(extruder[0].getProperty("jerk_enabled", "value"))
|
||||||
|
jerk_travel_enabled = str(extruder[0].getProperty("jerk_travel_enabled", "value"))
|
||||||
|
jerk_print_old = extruder[0].getProperty("jerk_print", "value")
|
||||||
|
jerk_travel_old = extruder[0].getProperty("jerk_travel", "value")
|
||||||
|
if int(accel_print) >= int(accel_travel):
|
||||||
|
accel_old = accel_print
|
||||||
|
else:
|
||||||
|
accel_old = accel_travel
|
||||||
|
jerk_travel = str(extruder[0].getProperty("jerk_travel", "value"))
|
||||||
|
if int(jerk_print_old) >= int(jerk_travel_old):
|
||||||
|
jerk_old = jerk_print_old
|
||||||
|
else:
|
||||||
|
jerk_old = jerk_travel_old
|
||||||
|
|
||||||
|
#Set the new Accel values----------------------------------------------------------
|
||||||
|
x_accel = str(self.getSettingValueByKey("x_accel_limit"))
|
||||||
|
y_accel = str(self.getSettingValueByKey("y_accel_limit"))
|
||||||
|
x_jerk = int(self.getSettingValueByKey("x_jerk"))
|
||||||
|
y_jerk = int(self.getSettingValueByKey("y_jerk"))
|
||||||
|
if str(self.getSettingValueByKey("jerk_cmd")) == "reprap_flavor":
|
||||||
|
jerk_cmd = "M566"
|
||||||
|
x_jerk *= 60
|
||||||
|
y_jerk *= 60
|
||||||
|
jerk_old *= 60
|
||||||
|
else:
|
||||||
|
jerk_cmd = "M205"
|
||||||
|
|
||||||
|
# Put the strings together-------------------------------------------
|
||||||
|
m201_limit_new = f"M201 X{x_accel} Y{y_accel}"
|
||||||
|
m201_limit_old = f"M201 X{round(accel_old)} Y{round(accel_old)}"
|
||||||
|
if x_jerk == 0:
|
||||||
|
m205_jerk_pattern = "Y(\d*)"
|
||||||
|
m205_jerk_new = f"Y{y_jerk}"
|
||||||
|
if y_jerk == 0:
|
||||||
|
m205_jerk_pattern = "X(\d*)"
|
||||||
|
m205_jerk_new = f"X{x_jerk}"
|
||||||
|
if x_jerk != 0 and y_jerk != 0:
|
||||||
|
m205_jerk_pattern = jerk_cmd + " X(\d*) Y(\d*)"
|
||||||
|
m205_jerk_new = jerk_cmd + f" X{x_jerk} Y{y_jerk}"
|
||||||
|
m205_jerk_old = jerk_cmd + f" X{jerk_old} Y{jerk_old}"
|
||||||
|
type_of_change = self.getSettingValueByKey("type_of_change")
|
||||||
|
|
||||||
|
#Get the indexes of the start and end layers----------------------------------------
|
||||||
|
if type_of_change == 'immediate_change':
|
||||||
|
start_layer = int(self.getSettingValueByKey("start_layer"))-1
|
||||||
|
end_layer = int(self.getSettingValueByKey("end_layer"))
|
||||||
|
else:
|
||||||
|
start_layer = int(self.getSettingValueByKey("gradient_start_layer"))-1
|
||||||
|
end_layer = int(self.getSettingValueByKey("gradient_end_layer"))
|
||||||
|
start_index = 2
|
||||||
|
end_index = len(data)-2
|
||||||
|
for num in range(2,len(data)-1):
|
||||||
|
if ";LAYER:" + str(start_layer) + "\n" in data[num]:
|
||||||
|
start_index = num
|
||||||
|
break
|
||||||
|
if int(end_layer) > 0:
|
||||||
|
for num in range(3,len(data)-1):
|
||||||
|
try:
|
||||||
|
if ";LAYER:" + str(end_layer) + "\n" in data[num]:
|
||||||
|
end_index = num
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
end_index = len(data)-2
|
||||||
|
|
||||||
|
#Add Accel limit and new Jerk at start layer-----------------------------------------------------
|
||||||
|
if type_of_change == "immediate_change":
|
||||||
|
layer = data[start_index]
|
||||||
|
lines = layer.split("\n")
|
||||||
|
for index, line in enumerate(lines):
|
||||||
|
if lines[index].startswith(";LAYER:"):
|
||||||
|
lines.insert(index+1,m201_limit_new)
|
||||||
|
if self.getSettingValueByKey("jerk_enable"):
|
||||||
|
lines.insert(index+2,m205_jerk_new)
|
||||||
|
data[start_index] = "\n".join(lines)
|
||||||
|
break
|
||||||
|
|
||||||
|
#Alter any existing jerk lines. Accel lines can be ignored-----------------------------------
|
||||||
|
for num in range(start_index,end_index,1):
|
||||||
|
layer = data[num]
|
||||||
|
lines = layer.split("\n")
|
||||||
|
for index, line in enumerate(lines):
|
||||||
|
if line.startswith("M205") or line.startswith("M566"):
|
||||||
|
lines[index] = re.sub(m205_jerk_pattern, m205_jerk_new, line)
|
||||||
|
data[num] = "\n".join(lines)
|
||||||
|
if end_layer != -1:
|
||||||
|
try:
|
||||||
|
layer = data[end_index-1]
|
||||||
|
lines = layer.split("\n")
|
||||||
|
lines.insert(len(lines)-2,m201_limit_old)
|
||||||
|
lines.insert(len(lines)-2,m205_jerk_old)
|
||||||
|
data[end_index-1] = "\n".join(lines)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
data[len(data)-1] = m201_limit_old + "\n" + m205_jerk_old + "\n" + data[len(data)-1]
|
||||||
|
return data
|
||||||
|
|
||||||
|
elif type_of_change == "gradual_change":
|
||||||
|
layer_spread = end_index - start_index
|
||||||
|
if accel_old >= int(x_accel):
|
||||||
|
x_accel_hyst = round((accel_old - int(x_accel)) / layer_spread)
|
||||||
|
else:
|
||||||
|
x_accel_hyst = round((int(x_accel) - accel_old) / layer_spread)
|
||||||
|
if accel_old >= int(y_accel):
|
||||||
|
y_accel_hyst = round((accel_old - int(y_accel)) / layer_spread)
|
||||||
|
else:
|
||||||
|
y_accel_hyst = round((int(y_accel) - accel_old) / layer_spread)
|
||||||
|
|
||||||
|
if accel_old >= int(x_accel):
|
||||||
|
x_accel_start = round(round((accel_old - x_accel_hyst)/25)*25)
|
||||||
|
else:
|
||||||
|
x_accel_start = round(round((x_accel_hyst + accel_old)/25)*25)
|
||||||
|
if accel_old >= int(y_accel):
|
||||||
|
y_accel_start = round(round((accel_old - y_accel_hyst)/25)*25)
|
||||||
|
else:
|
||||||
|
y_accel_start = round(round((y_accel_hyst + accel_old)/25)*25)
|
||||||
|
m201_limit_new = "M201 X" + str(x_accel_start) + " Y" + str(y_accel_start)
|
||||||
|
#Add Accel limit and new Jerk at start layer-------------------------------------------------------------
|
||||||
|
layer = data[start_index]
|
||||||
|
lines = layer.split("\n")
|
||||||
|
for index, line in enumerate(lines):
|
||||||
|
if lines[index].startswith(";LAYER:"):
|
||||||
|
lines.insert(index+1,m201_limit_new)
|
||||||
|
if self.getSettingValueByKey("jerk_enable"):
|
||||||
|
lines.insert(index+2,m205_jerk_new)
|
||||||
|
data[start_index] = "\n".join(lines)
|
||||||
|
break
|
||||||
|
for num in range(start_index + 1, end_index,1):
|
||||||
|
layer = data[num]
|
||||||
|
lines = layer.split("\n")
|
||||||
|
if accel_old >= int(x_accel):
|
||||||
|
x_accel_start -= x_accel_hyst
|
||||||
|
if x_accel_start < int(x_accel): x_accel_start = int(x_accel)
|
||||||
|
else:
|
||||||
|
x_accel_start += x_accel_hyst
|
||||||
|
if x_accel_start > int(x_accel): x_accel_start = int(x_accel)
|
||||||
|
if accel_old >= int(y_accel):
|
||||||
|
y_accel_start -= y_accel_hyst
|
||||||
|
if y_accel_start < int(y_accel): y_accel_start = int(y_accel)
|
||||||
|
else:
|
||||||
|
y_accel_start += y_accel_hyst
|
||||||
|
if y_accel_start > int(y_accel): y_accel_start = int(y_accel)
|
||||||
|
m201_limit_new = "M201 X" + str(round(round(x_accel_start/25)*25)) + " Y" + str(round(round(y_accel_start/25)*25))
|
||||||
|
for index, line in enumerate(lines):
|
||||||
|
if line.startswith(";LAYER:"):
|
||||||
|
lines.insert(index+1, m201_limit_new)
|
||||||
|
continue
|
||||||
|
data[num] = "\n".join(lines)
|
||||||
|
|
||||||
|
#Alter any existing jerk lines. Accel lines can be ignored---------------
|
||||||
|
if self.getSettingValueByKey("jerk_enable"):
|
||||||
|
for num in range(start_index,len(data)-1,1):
|
||||||
|
layer = data[num]
|
||||||
|
lines = layer.split("\n")
|
||||||
|
for index, line in enumerate(lines):
|
||||||
|
if line.startswith("M205") or line.startswith("M566"):
|
||||||
|
lines[index] = re.sub(m205_jerk_pattern, m205_jerk_new, line)
|
||||||
|
data[num] = "\n".join(lines)
|
||||||
|
data[len(data)-1] = m201_limit_old + "\n" + m205_jerk_old + "\n" + data[len(data)-1]
|
||||||
|
return data
|
@ -173,7 +173,7 @@ class SendMaterialJob(Job):
|
|||||||
|
|
||||||
result = {} # type: Dict[str, LocalMaterial]
|
result = {} # type: Dict[str, LocalMaterial]
|
||||||
all_materials = CuraApplication.getInstance().getContainerRegistry().findInstanceContainersMetadata(type = "material")
|
all_materials = CuraApplication.getInstance().getContainerRegistry().findInstanceContainersMetadata(type = "material")
|
||||||
all_base_files = [material for material in all_materials if material["id"] == material.get("base_file")] # Don't send materials without base_file: The empty material doesn't need to be sent.
|
all_base_files = [material for material in all_materials if material["id"] == material.get("base_file") and material.get("visible", True)] # Don't send materials without base_file: The empty material doesn't need to be sent.
|
||||||
|
|
||||||
# Find the latest version of all material containers in the registry.
|
# Find the latest version of all material containers in the registry.
|
||||||
for material_metadata in all_base_files:
|
for material_metadata in all_base_files:
|
||||||
|
@ -27,14 +27,7 @@ class AutoDetectBaudJob(Job):
|
|||||||
write_timeout = 3
|
write_timeout = 3
|
||||||
read_timeout = 3
|
read_timeout = 3
|
||||||
tries = 2
|
tries = 2
|
||||||
|
|
||||||
programmer = Stk500v2()
|
|
||||||
serial = None
|
serial = None
|
||||||
try:
|
|
||||||
programmer.connect(self._serial_port)
|
|
||||||
serial = programmer.leaveISP()
|
|
||||||
except ispBase.IspError:
|
|
||||||
programmer.close()
|
|
||||||
|
|
||||||
for retry in range(tries):
|
for retry in range(tries):
|
||||||
for baud_rate in self._all_baud_rates:
|
for baud_rate in self._all_baud_rates:
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
# Copyright (c) 2023 UltiMaker
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
from typing import Tuple, List
|
||||||
|
import io
|
||||||
|
from UM.VersionUpgrade import VersionUpgrade
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class VersionUpgrade54to55(VersionUpgrade):
|
||||||
|
profile_regex = re.compile(
|
||||||
|
r"um\_(?P<machine>s(3|5|7))_(?P<core_type>aa|cc|bb)(?P<nozzle_size>0\.(6|4|8))_(?P<material>pla|petg|abs|cpe|cpe_plus|nylon|pc|petcf|tough_pla|tpu)_(?P<layer_height>0\.\d{1,2}mm)")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _isUpgradedUltimakerDefinitionId(definition_id: str) -> bool:
|
||||||
|
if definition_id.startswith("ultimaker_s5"):
|
||||||
|
return True
|
||||||
|
if definition_id.startswith("ultimaker_s3"):
|
||||||
|
return True
|
||||||
|
if definition_id.startswith("ultimaker_s7"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _isBrandedMaterialID(material_id: str) -> bool:
|
||||||
|
return material_id.startswith("ultimaker_")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def upgradeStack(serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||||
|
"""
|
||||||
|
Upgrades stacks to have the new version number.
|
||||||
|
|
||||||
|
:param serialized: The original contents of the stack.
|
||||||
|
:param filename: The original file name of the stack.
|
||||||
|
:return: A list of new file names, and a list of the new contents for
|
||||||
|
those files.
|
||||||
|
"""
|
||||||
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
|
parser.read_string(serialized)
|
||||||
|
|
||||||
|
# Update version number.
|
||||||
|
if "general" not in parser:
|
||||||
|
parser["general"] = {}
|
||||||
|
|
||||||
|
extruder_definition_id = parser["containers"]["7"]
|
||||||
|
if parser["metadata"]["type"] == "extruder_train" and VersionUpgrade54to55._isUpgradedUltimakerDefinitionId(extruder_definition_id):
|
||||||
|
# We only need to update certain Ultimaker extruder ID's
|
||||||
|
material_id = parser["containers"]["4"]
|
||||||
|
quality_id = parser["containers"]["3"]
|
||||||
|
intent_id = parser["containers"]["2"]
|
||||||
|
if VersionUpgrade54to55._isBrandedMaterialID(material_id):
|
||||||
|
# We have an Ultimaker branded material ID, so we should change the intent & quality!
|
||||||
|
|
||||||
|
quality_id = VersionUpgrade54to55.profile_regex.sub(
|
||||||
|
r"um_\g<machine>_\g<core_type>\g<nozzle_size>_um-\g<material>_\g<layer_height>", quality_id)
|
||||||
|
|
||||||
|
|
||||||
|
intent_id = VersionUpgrade54to55.profile_regex.sub(
|
||||||
|
r"um_\g<machine>_\g<core_type>\g<nozzle_size>_um-\g<material>_\g<layer_height>", intent_id)
|
||||||
|
|
||||||
|
parser["containers"]["3"] = quality_id
|
||||||
|
parser["containers"]["2"] = intent_id
|
||||||
|
|
||||||
|
# We're not changing any settings, but we are changing how certain stacks are handled.
|
||||||
|
parser["general"]["version"] = "6"
|
||||||
|
|
||||||
|
result = io.StringIO()
|
||||||
|
parser.write(result)
|
||||||
|
return [filename], [result.getvalue()]
|
35
plugins/VersionUpgrade/VersionUpgrade54to55/__init__.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Copyright (c) 2023 UltiMaker
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Any, Dict, TYPE_CHECKING
|
||||||
|
|
||||||
|
from . import VersionUpgrade54to55
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from UM.Application import Application
|
||||||
|
|
||||||
|
upgrade = VersionUpgrade54to55.VersionUpgrade54to55()
|
||||||
|
|
||||||
|
|
||||||
|
def getMetaData() -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"version_upgrade": {
|
||||||
|
# From To Upgrade function
|
||||||
|
("machine_stack", 5000022): ("machine_stack", 6000022, upgrade.upgradeStack),
|
||||||
|
("extruder_train", 5000022): ("extruder_train", 6000022, upgrade.upgradeStack),
|
||||||
|
},
|
||||||
|
"sources": {
|
||||||
|
"machine_stack": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./machine_instances"}
|
||||||
|
},
|
||||||
|
"extruder_train": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./extruders"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def register(app: "Application") -> Dict[str, Any]:
|
||||||
|
return {"version_upgrade": upgrade}
|
8
plugins/VersionUpgrade/VersionUpgrade54to55/plugin.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "Version Upgrade 5.4 to 5.5",
|
||||||
|
"author": "UltiMaker",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Upgrades configurations from Cura 5.4 to Cura 5.5.",
|
||||||
|
"api": 8,
|
||||||
|
"i18n-catalog": "cura"
|
||||||
|
}
|
@ -910,6 +910,9 @@ class XmlMaterialProfile(InstanceContainer):
|
|||||||
base_metadata["properties"] = property_values
|
base_metadata["properties"] = property_values
|
||||||
base_metadata["definition"] = "fdmprinter"
|
base_metadata["definition"] = "fdmprinter"
|
||||||
|
|
||||||
|
# Certain materials are loaded but should not be visible / selectable to the user.
|
||||||
|
base_metadata["visible"] = not base_metadata.get("abstract_color", False)
|
||||||
|
|
||||||
compatible_entries = data.iterfind("./um:settings/um:setting[@key='hardware compatible']", cls.__namespaces)
|
compatible_entries = data.iterfind("./um:settings/um:setting[@key='hardware compatible']", cls.__namespaces)
|
||||||
try:
|
try:
|
||||||
common_compatibility = cls._parseCompatibleValue(next(compatible_entries).text) # type: ignore
|
common_compatibility = cls._parseCompatibleValue(next(compatible_entries).text) # type: ignore
|
||||||
|
@ -9,5 +9,9 @@
|
|||||||
"Ultimaker Original": "ultimaker_original",
|
"Ultimaker Original": "ultimaker_original",
|
||||||
"Ultimaker Original+": "ultimaker_original_plus",
|
"Ultimaker Original+": "ultimaker_original_plus",
|
||||||
"Ultimaker Original Dual Extrusion": "ultimaker_original_dual",
|
"Ultimaker Original Dual Extrusion": "ultimaker_original_dual",
|
||||||
"IMADE3D JellyBOX": "imade3d_jellybox"
|
"IMADE3D JellyBOX": "imade3d_jellybox",
|
||||||
|
"DUAL600": "strateo3d",
|
||||||
|
"IDEX420": "strateo3d_IDEX420",
|
||||||
|
"IDEX420 Duplicate": "strateo3d_IDEX420_duplicate",
|
||||||
|
"IDEX420 Mirror": "strateo3d_IDEX420_mirror"
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "printerlinter"
|
name = "printerlinter"
|
||||||
description = "Cura UltiMaker printer linting tool"
|
description = "Cura UltiMaker printer linting tool"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "UltiMaker", email = "cura@ultimaker.com" }
|
{ name = "UltiMaker", email = "cura@ultimaker.com" }
|
||||||
]
|
]
|
||||||
|
@ -1,26 +1,27 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional, List
|
||||||
|
|
||||||
from .linters.profile import Profile
|
from .linters.profile import Profile
|
||||||
from .linters.defintion import Definition
|
from .linters.defintion import Definition
|
||||||
from .linters.linter import Linter
|
from .linters.linter import Linter
|
||||||
from .linters.meshes import Meshes
|
from .linters.meshes import Meshes
|
||||||
|
from .linters.directory import Directory
|
||||||
|
|
||||||
|
|
||||||
def getLinter(file: Path, settings: dict) -> Optional[Linter]:
|
def getLinter(file: Path, settings: dict) -> Optional[List[Linter]]:
|
||||||
""" Returns a Linter depending on the file format """
|
""" Returns a Linter depending on the file format """
|
||||||
if not file.exists():
|
if not file.exists():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if ".inst" in file.suffixes and ".cfg" in file.suffixes:
|
if ".inst" in file.suffixes and ".cfg" in file.suffixes:
|
||||||
return Profile(file, settings)
|
return [Directory(file, settings), Profile(file, settings)]
|
||||||
|
|
||||||
if ".def" in file.suffixes and ".json" in file.suffixes:
|
if ".def" in file.suffixes and ".json" in file.suffixes:
|
||||||
if file.stem in ("fdmprinter.def", "fdmextruder.def"):
|
if file.stem in ("fdmprinter.def", "fdmextruder.def"):
|
||||||
return None
|
return None
|
||||||
return Definition(file, settings)
|
return [Directory(file, settings), Definition(file, settings)]
|
||||||
|
|
||||||
if file.parent.stem == "meshes":
|
if file.parent.stem == "meshes":
|
||||||
return Meshes(file, settings)
|
return [Meshes(file, settings)]
|
||||||
|
|
||||||
return None
|
return [Directory(file, settings)]
|
||||||
|
31
printer-linter/src/printerlinter/linters/directory.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import Iterator
|
||||||
|
|
||||||
|
from ..diagnostic import Diagnostic
|
||||||
|
from .linter import Linter
|
||||||
|
|
||||||
|
|
||||||
|
class Directory(Linter):
|
||||||
|
def __init__(self, file: Path, settings: dict) -> None:
|
||||||
|
""" Finds issues in the parent directory"""
|
||||||
|
super().__init__(file, settings)
|
||||||
|
|
||||||
|
def check(self) -> Iterator[Diagnostic]:
|
||||||
|
if self._settings["checks"].get("diagnostic-resources-macos-app-directory-name", False):
|
||||||
|
for check in self.checkForDotInDirName():
|
||||||
|
yield check
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
def checkForDotInDirName(self) -> Iterator[Diagnostic]:
|
||||||
|
""" Check if there is a dot in the directory name, MacOS has trouble signing and notarizing otherwise """
|
||||||
|
if any("." in p for p in self._file.parent.parts):
|
||||||
|
yield Diagnostic(
|
||||||
|
file = self._file,
|
||||||
|
diagnostic_name = "diagnostic-resources-macos-app-directory-name",
|
||||||
|
message = f"Directory name containing a `.` not allowed {self._file.suffix}, rename directory containing this file e.q: `_`",
|
||||||
|
level = "Error",
|
||||||
|
offset = 1
|
||||||
|
)
|
||||||
|
yield
|
||||||
|
|
@ -71,12 +71,16 @@ def main() -> None:
|
|||||||
|
|
||||||
def diagnoseIssuesWithFile(file: Path, settings: dict) -> List[Diagnostic]:
|
def diagnoseIssuesWithFile(file: Path, settings: dict) -> List[Diagnostic]:
|
||||||
""" For file, runs all diagnostic checks in settings and returns a list of diagnostics """
|
""" For file, runs all diagnostic checks in settings and returns a list of diagnostics """
|
||||||
linter = factory.getLinter(file, settings)
|
linters = factory.getLinter(file, settings)
|
||||||
|
|
||||||
if not linter:
|
if not linters:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return list(filter(lambda d: d is not None, linter.check()))
|
linter_results = []
|
||||||
|
for linter in linters:
|
||||||
|
linter_results.extend(list(filter(lambda d: d is not None, linter.check())))
|
||||||
|
|
||||||
|
return linter_results
|
||||||
|
|
||||||
|
|
||||||
def applyFixesToFile(file, settings, full_body_check) -> None:
|
def applyFixesToFile(file, settings, full_body_check) -> None:
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"display_name": "3MF Reader",
|
"display_name": "3MF Reader",
|
||||||
"description": "Provides support for reading 3MF files.",
|
"description": "Provides support for reading 3MF files.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"display_name": "3MF Writer",
|
"display_name": "3MF Writer",
|
||||||
"description": "Provides support for writing 3MF files.",
|
"description": "Provides support for writing 3MF files.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"display_name": "AMF Reader",
|
"display_name": "AMF Reader",
|
||||||
"description": "Provides support for reading AMF files.",
|
"description": "Provides support for reading AMF files.",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "fieldOfView",
|
"author_id": "fieldOfView",
|
||||||
@ -57,7 +57,7 @@
|
|||||||
"display_name": "Cura Backups",
|
"display_name": "Cura Backups",
|
||||||
"description": "Backup and restore your configuration.",
|
"description": "Backup and restore your configuration.",
|
||||||
"package_version": "1.2.0",
|
"package_version": "1.2.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -74,7 +74,7 @@
|
|||||||
"display_name": "CuraEngine Backend",
|
"display_name": "CuraEngine Backend",
|
||||||
"description": "Provides the link to the CuraEngine slicing backend.",
|
"description": "Provides the link to the CuraEngine slicing backend.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -91,7 +91,7 @@
|
|||||||
"display_name": "Cura Profile Reader",
|
"display_name": "Cura Profile Reader",
|
||||||
"description": "Provides support for importing Cura profiles.",
|
"description": "Provides support for importing Cura profiles.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -108,7 +108,7 @@
|
|||||||
"display_name": "Cura Profile Writer",
|
"display_name": "Cura Profile Writer",
|
||||||
"description": "Provides support for exporting Cura profiles.",
|
"description": "Provides support for exporting Cura profiles.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -125,7 +125,7 @@
|
|||||||
"display_name": "Ultimaker Digital Library",
|
"display_name": "Ultimaker Digital Library",
|
||||||
"description": "Connects to the Digital Library, allowing Cura to open files from and save files to the Digital Library.",
|
"description": "Connects to the Digital Library, allowing Cura to open files from and save files to the Digital Library.",
|
||||||
"package_version": "1.1.0",
|
"package_version": "1.1.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -142,7 +142,7 @@
|
|||||||
"display_name": "Firmware Update Checker",
|
"display_name": "Firmware Update Checker",
|
||||||
"description": "Checks for firmware updates.",
|
"description": "Checks for firmware updates.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -159,7 +159,7 @@
|
|||||||
"display_name": "Firmware Updater",
|
"display_name": "Firmware Updater",
|
||||||
"description": "Provides a machine actions for updating firmware.",
|
"description": "Provides a machine actions for updating firmware.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -176,7 +176,7 @@
|
|||||||
"display_name": "Compressed G-code Reader",
|
"display_name": "Compressed G-code Reader",
|
||||||
"description": "Reads g-code from a compressed archive.",
|
"description": "Reads g-code from a compressed archive.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -193,7 +193,7 @@
|
|||||||
"display_name": "Compressed G-code Writer",
|
"display_name": "Compressed G-code Writer",
|
||||||
"description": "Writes g-code to a compressed archive.",
|
"description": "Writes g-code to a compressed archive.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -210,7 +210,7 @@
|
|||||||
"display_name": "G-Code Profile Reader",
|
"display_name": "G-Code Profile Reader",
|
||||||
"description": "Provides support for importing profiles from g-code files.",
|
"description": "Provides support for importing profiles from g-code files.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -227,7 +227,7 @@
|
|||||||
"display_name": "G-Code Reader",
|
"display_name": "G-Code Reader",
|
||||||
"description": "Allows loading and displaying G-code files.",
|
"description": "Allows loading and displaying G-code files.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "VictorLarchenko",
|
"author_id": "VictorLarchenko",
|
||||||
@ -244,7 +244,7 @@
|
|||||||
"display_name": "G-Code Writer",
|
"display_name": "G-Code Writer",
|
||||||
"description": "Writes g-code to a file.",
|
"description": "Writes g-code to a file.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -261,7 +261,7 @@
|
|||||||
"display_name": "Image Reader",
|
"display_name": "Image Reader",
|
||||||
"description": "Enables ability to generate printable geometry from 2D image files.",
|
"description": "Enables ability to generate printable geometry from 2D image files.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -278,7 +278,7 @@
|
|||||||
"display_name": "Legacy Cura Profile Reader",
|
"display_name": "Legacy Cura Profile Reader",
|
||||||
"description": "Provides support for importing profiles from legacy Cura versions.",
|
"description": "Provides support for importing profiles from legacy Cura versions.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -295,7 +295,7 @@
|
|||||||
"display_name": "Machine Settings Action",
|
"display_name": "Machine Settings Action",
|
||||||
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
|
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "fieldOfView",
|
"author_id": "fieldOfView",
|
||||||
@ -312,7 +312,7 @@
|
|||||||
"display_name": "Model Checker",
|
"display_name": "Model Checker",
|
||||||
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
|
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -329,7 +329,7 @@
|
|||||||
"display_name": "Monitor Stage",
|
"display_name": "Monitor Stage",
|
||||||
"description": "Provides a monitor stage in Cura.",
|
"description": "Provides a monitor stage in Cura.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -346,7 +346,7 @@
|
|||||||
"display_name": "Per-Object Settings Tool",
|
"display_name": "Per-Object Settings Tool",
|
||||||
"description": "Provides the per-model settings.",
|
"description": "Provides the per-model settings.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -363,7 +363,7 @@
|
|||||||
"display_name": "Post Processing",
|
"display_name": "Post Processing",
|
||||||
"description": "Extension that allows for user created scripts for post processing.",
|
"description": "Extension that allows for user created scripts for post processing.",
|
||||||
"package_version": "2.2.1",
|
"package_version": "2.2.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -380,7 +380,7 @@
|
|||||||
"display_name": "Prepare Stage",
|
"display_name": "Prepare Stage",
|
||||||
"description": "Provides a prepare stage in Cura.",
|
"description": "Provides a prepare stage in Cura.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -397,7 +397,7 @@
|
|||||||
"display_name": "Preview Stage",
|
"display_name": "Preview Stage",
|
||||||
"description": "Provides a preview stage in Cura.",
|
"description": "Provides a preview stage in Cura.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -414,7 +414,7 @@
|
|||||||
"display_name": "Removable Drive Output Device",
|
"display_name": "Removable Drive Output Device",
|
||||||
"description": "Provides removable drive hotplugging and writing support.",
|
"description": "Provides removable drive hotplugging and writing support.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -431,7 +431,7 @@
|
|||||||
"display_name": "Sentry Logger",
|
"display_name": "Sentry Logger",
|
||||||
"description": "Logs certain events so that they can be used by the crash reporter",
|
"description": "Logs certain events so that they can be used by the crash reporter",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -448,7 +448,7 @@
|
|||||||
"display_name": "Simulation View",
|
"display_name": "Simulation View",
|
||||||
"description": "Provides the Simulation view.",
|
"description": "Provides the Simulation view.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -465,7 +465,7 @@
|
|||||||
"display_name": "Slice Info",
|
"display_name": "Slice Info",
|
||||||
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -482,7 +482,7 @@
|
|||||||
"display_name": "Solid View",
|
"display_name": "Solid View",
|
||||||
"description": "Provides a normal solid mesh view.",
|
"description": "Provides a normal solid mesh view.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -499,7 +499,7 @@
|
|||||||
"display_name": "Support Eraser Tool",
|
"display_name": "Support Eraser Tool",
|
||||||
"description": "Creates an eraser mesh to block the printing of support in certain places.",
|
"description": "Creates an eraser mesh to block the printing of support in certain places.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -516,7 +516,7 @@
|
|||||||
"display_name": "Trimesh Reader",
|
"display_name": "Trimesh Reader",
|
||||||
"description": "Provides support for reading model files.",
|
"description": "Provides support for reading model files.",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -533,7 +533,7 @@
|
|||||||
"display_name": "Marketplace",
|
"display_name": "Marketplace",
|
||||||
"description": "Find, manage and install new Cura packages.",
|
"description": "Find, manage and install new Cura packages.",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -550,7 +550,7 @@
|
|||||||
"display_name": "UFP Reader",
|
"display_name": "UFP Reader",
|
||||||
"description": "Provides support for reading Ultimaker Format Packages.",
|
"description": "Provides support for reading Ultimaker Format Packages.",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -567,7 +567,7 @@
|
|||||||
"display_name": "UFP Writer",
|
"display_name": "UFP Writer",
|
||||||
"description": "Provides support for writing Ultimaker Format Packages.",
|
"description": "Provides support for writing Ultimaker Format Packages.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -584,7 +584,7 @@
|
|||||||
"display_name": "Ultimaker Machine Actions",
|
"display_name": "Ultimaker Machine Actions",
|
||||||
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
|
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -601,7 +601,7 @@
|
|||||||
"display_name": "UM3 Network Printing",
|
"display_name": "UM3 Network Printing",
|
||||||
"description": "Manages network connections to Ultimaker 3 printers.",
|
"description": "Manages network connections to Ultimaker 3 printers.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -618,7 +618,7 @@
|
|||||||
"display_name": "USB Printing",
|
"display_name": "USB Printing",
|
||||||
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
|
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
|
||||||
"package_version": "1.0.2",
|
"package_version": "1.0.2",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -635,7 +635,7 @@
|
|||||||
"display_name": "Version Upgrade 2.1 to 2.2",
|
"display_name": "Version Upgrade 2.1 to 2.2",
|
||||||
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
|
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -652,7 +652,7 @@
|
|||||||
"display_name": "Version Upgrade 2.2 to 2.4",
|
"display_name": "Version Upgrade 2.2 to 2.4",
|
||||||
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
|
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -669,7 +669,7 @@
|
|||||||
"display_name": "Version Upgrade 2.5 to 2.6",
|
"display_name": "Version Upgrade 2.5 to 2.6",
|
||||||
"description": "Upgrades configurations from Cura 2.5 to Cura 2.6.",
|
"description": "Upgrades configurations from Cura 2.5 to Cura 2.6.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -686,7 +686,7 @@
|
|||||||
"display_name": "Version Upgrade 2.6 to 2.7",
|
"display_name": "Version Upgrade 2.6 to 2.7",
|
||||||
"description": "Upgrades configurations from Cura 2.6 to Cura 2.7.",
|
"description": "Upgrades configurations from Cura 2.6 to Cura 2.7.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -703,7 +703,7 @@
|
|||||||
"display_name": "Version Upgrade 2.7 to 3.0",
|
"display_name": "Version Upgrade 2.7 to 3.0",
|
||||||
"description": "Upgrades configurations from Cura 2.7 to Cura 3.0.",
|
"description": "Upgrades configurations from Cura 2.7 to Cura 3.0.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -720,7 +720,7 @@
|
|||||||
"display_name": "Version Upgrade 3.0 to 3.1",
|
"display_name": "Version Upgrade 3.0 to 3.1",
|
||||||
"description": "Upgrades configurations from Cura 3.0 to Cura 3.1.",
|
"description": "Upgrades configurations from Cura 3.0 to Cura 3.1.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -737,7 +737,7 @@
|
|||||||
"display_name": "Version Upgrade 3.2 to 3.3",
|
"display_name": "Version Upgrade 3.2 to 3.3",
|
||||||
"description": "Upgrades configurations from Cura 3.2 to Cura 3.3.",
|
"description": "Upgrades configurations from Cura 3.2 to Cura 3.3.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -754,7 +754,7 @@
|
|||||||
"display_name": "Version Upgrade 3.3 to 3.4",
|
"display_name": "Version Upgrade 3.3 to 3.4",
|
||||||
"description": "Upgrades configurations from Cura 3.3 to Cura 3.4.",
|
"description": "Upgrades configurations from Cura 3.3 to Cura 3.4.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -771,7 +771,7 @@
|
|||||||
"display_name": "Version Upgrade 3.4 to 3.5",
|
"display_name": "Version Upgrade 3.4 to 3.5",
|
||||||
"description": "Upgrades configurations from Cura 3.4 to Cura 3.5.",
|
"description": "Upgrades configurations from Cura 3.4 to Cura 3.5.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -788,7 +788,7 @@
|
|||||||
"display_name": "Version Upgrade 3.5 to 4.0",
|
"display_name": "Version Upgrade 3.5 to 4.0",
|
||||||
"description": "Upgrades configurations from Cura 3.5 to Cura 4.0.",
|
"description": "Upgrades configurations from Cura 3.5 to Cura 4.0.",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -805,7 +805,7 @@
|
|||||||
"display_name": "Version Upgrade 4.0 to 4.1",
|
"display_name": "Version Upgrade 4.0 to 4.1",
|
||||||
"description": "Upgrades configurations from Cura 4.0 to Cura 4.1.",
|
"description": "Upgrades configurations from Cura 4.0 to Cura 4.1.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -822,7 +822,7 @@
|
|||||||
"display_name": "Version Upgrade 4.1 to 4.2",
|
"display_name": "Version Upgrade 4.1 to 4.2",
|
||||||
"description": "Upgrades configurations from Cura 4.1 to Cura 4.2.",
|
"description": "Upgrades configurations from Cura 4.1 to Cura 4.2.",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -839,7 +839,7 @@
|
|||||||
"display_name": "Version Upgrade 4.2 to 4.3",
|
"display_name": "Version Upgrade 4.2 to 4.3",
|
||||||
"description": "Upgrades configurations from Cura 4.2 to Cura 4.3.",
|
"description": "Upgrades configurations from Cura 4.2 to Cura 4.3.",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -856,7 +856,7 @@
|
|||||||
"display_name": "Version Upgrade 4.3 to 4.4",
|
"display_name": "Version Upgrade 4.3 to 4.4",
|
||||||
"description": "Upgrades configurations from Cura 4.3 to Cura 4.4.",
|
"description": "Upgrades configurations from Cura 4.3 to Cura 4.4.",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -873,7 +873,7 @@
|
|||||||
"display_name": "Version Upgrade 4.4 to 4.5",
|
"display_name": "Version Upgrade 4.4 to 4.5",
|
||||||
"description": "Upgrades configurations from Cura 4.4 to Cura 4.5.",
|
"description": "Upgrades configurations from Cura 4.4 to Cura 4.5.",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -890,7 +890,7 @@
|
|||||||
"display_name": "Version Upgrade 4.5 to 4.6",
|
"display_name": "Version Upgrade 4.5 to 4.6",
|
||||||
"description": "Upgrades configurations from Cura 4.5 to Cura 4.6.",
|
"description": "Upgrades configurations from Cura 4.5 to Cura 4.6.",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -907,7 +907,7 @@
|
|||||||
"display_name": "Version Upgrade 4.6.0 to 4.6.2",
|
"display_name": "Version Upgrade 4.6.0 to 4.6.2",
|
||||||
"description": "Upgrades configurations from Cura 4.6.0 to Cura 4.6.2.",
|
"description": "Upgrades configurations from Cura 4.6.0 to Cura 4.6.2.",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -924,7 +924,7 @@
|
|||||||
"display_name": "Version Upgrade 4.6.2 to 4.7",
|
"display_name": "Version Upgrade 4.6.2 to 4.7",
|
||||||
"description": "Upgrades configurations from Cura 4.6.2 to Cura 4.7.",
|
"description": "Upgrades configurations from Cura 4.6.2 to Cura 4.7.",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -941,7 +941,7 @@
|
|||||||
"display_name": "Version Upgrade 4.7.0 to 4.8.0",
|
"display_name": "Version Upgrade 4.7.0 to 4.8.0",
|
||||||
"description": "Upgrades configurations from Cura 4.7.0 to Cura 4.8.0",
|
"description": "Upgrades configurations from Cura 4.7.0 to Cura 4.8.0",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -958,7 +958,7 @@
|
|||||||
"display_name": "Version Upgrade 4.8.0 to 4.9.0",
|
"display_name": "Version Upgrade 4.8.0 to 4.9.0",
|
||||||
"description": "Upgrades configurations from Cura 4.8.0 to Cura 4.9.0",
|
"description": "Upgrades configurations from Cura 4.8.0 to Cura 4.9.0",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -975,7 +975,7 @@
|
|||||||
"display_name": "Version Upgrade 4.9 to 4.10",
|
"display_name": "Version Upgrade 4.9 to 4.10",
|
||||||
"description": "Upgrades configurations from Cura 4.9 to Cura 4.10",
|
"description": "Upgrades configurations from Cura 4.9 to Cura 4.10",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -992,7 +992,7 @@
|
|||||||
"display_name": "Version Upgrade 4.11 to 4.12",
|
"display_name": "Version Upgrade 4.11 to 4.12",
|
||||||
"description": "Upgrades configurations from Cura 4.11 to Cura 4.12",
|
"description": "Upgrades configurations from Cura 4.11 to Cura 4.12",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"sdk_version_semver": "7.7.0",
|
"sdk_version_semver": "7.7.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
@ -1010,7 +1010,7 @@
|
|||||||
"display_name": "Version Upgrade 4.13 to 5.0",
|
"display_name": "Version Upgrade 4.13 to 5.0",
|
||||||
"description": "Upgrades configurations from Cura 4.13 to Cura 5.0",
|
"description": "Upgrades configurations from Cura 4.13 to Cura 5.0",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1027,7 +1027,7 @@
|
|||||||
"display_name": "Version Upgrade 5.2 to 5.3",
|
"display_name": "Version Upgrade 5.2 to 5.3",
|
||||||
"description": "Upgrades configurations from Cura 5.2 to Cura 5.3",
|
"description": "Upgrades configurations from Cura 5.2 to Cura 5.3",
|
||||||
"package_version": "1.0.0",
|
"package_version": "1.0.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1044,7 +1044,7 @@
|
|||||||
"display_name": "X3D Reader",
|
"display_name": "X3D Reader",
|
||||||
"description": "Provides support for reading X3D files.",
|
"description": "Provides support for reading X3D files.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "SevaAlekseyev",
|
"author_id": "SevaAlekseyev",
|
||||||
@ -1061,7 +1061,7 @@
|
|||||||
"display_name": "XML Material Profiles",
|
"display_name": "XML Material Profiles",
|
||||||
"description": "Provides capabilities to read and write XML-based material profiles.",
|
"description": "Provides capabilities to read and write XML-based material profiles.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1078,7 +1078,7 @@
|
|||||||
"display_name": "X-Ray View",
|
"display_name": "X-Ray View",
|
||||||
"description": "Provides the X-Ray view.",
|
"description": "Provides the X-Ray view.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com",
|
"website": "https://ultimaker.com",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1095,7 +1095,7 @@
|
|||||||
"display_name": "Generic ABS",
|
"display_name": "Generic ABS",
|
||||||
"description": "The generic ABS profile which other profiles can be based upon.",
|
"description": "The generic ABS profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1113,7 +1113,7 @@
|
|||||||
"display_name": "Generic BAM",
|
"display_name": "Generic BAM",
|
||||||
"description": "The generic BAM profile which other profiles can be based upon.",
|
"description": "The generic BAM profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1131,7 +1131,7 @@
|
|||||||
"display_name": "Generic CFF CPE",
|
"display_name": "Generic CFF CPE",
|
||||||
"description": "The generic CFF CPE profile which other profiles can be based upon.",
|
"description": "The generic CFF CPE profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1149,7 +1149,7 @@
|
|||||||
"display_name": "Generic CFF PA",
|
"display_name": "Generic CFF PA",
|
||||||
"description": "The generic CFF PA profile which other profiles can be based upon.",
|
"description": "The generic CFF PA profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1167,7 +1167,7 @@
|
|||||||
"display_name": "Generic CPE",
|
"display_name": "Generic CPE",
|
||||||
"description": "The generic CPE profile which other profiles can be based upon.",
|
"description": "The generic CPE profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1185,7 +1185,7 @@
|
|||||||
"display_name": "Generic CPE+",
|
"display_name": "Generic CPE+",
|
||||||
"description": "The generic CPE+ profile which other profiles can be based upon.",
|
"description": "The generic CPE+ profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1203,7 +1203,7 @@
|
|||||||
"display_name": "Generic GFF CPE",
|
"display_name": "Generic GFF CPE",
|
||||||
"description": "The generic GFF CPE profile which other profiles can be based upon.",
|
"description": "The generic GFF CPE profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1221,7 +1221,7 @@
|
|||||||
"display_name": "Generic GFF PA",
|
"display_name": "Generic GFF PA",
|
||||||
"description": "The generic GFF PA profile which other profiles can be based upon.",
|
"description": "The generic GFF PA profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1239,7 +1239,7 @@
|
|||||||
"display_name": "Generic HIPS",
|
"display_name": "Generic HIPS",
|
||||||
"description": "The generic HIPS profile which other profiles can be based upon.",
|
"description": "The generic HIPS profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1257,7 +1257,7 @@
|
|||||||
"display_name": "Generic Nylon",
|
"display_name": "Generic Nylon",
|
||||||
"description": "The generic Nylon profile which other profiles can be based upon.",
|
"description": "The generic Nylon profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1275,7 +1275,7 @@
|
|||||||
"display_name": "Generic PC",
|
"display_name": "Generic PC",
|
||||||
"description": "The generic PC profile which other profiles can be based upon.",
|
"description": "The generic PC profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1293,7 +1293,7 @@
|
|||||||
"display_name": "Generic PETG",
|
"display_name": "Generic PETG",
|
||||||
"description": "The generic PETG profile which other profiles can be based upon.",
|
"description": "The generic PETG profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1311,7 +1311,7 @@
|
|||||||
"display_name": "Generic PLA",
|
"display_name": "Generic PLA",
|
||||||
"description": "The generic PLA profile which other profiles can be based upon.",
|
"description": "The generic PLA profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1329,7 +1329,7 @@
|
|||||||
"display_name": "Generic PP",
|
"display_name": "Generic PP",
|
||||||
"description": "The generic PP profile which other profiles can be based upon.",
|
"description": "The generic PP profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1347,7 +1347,7 @@
|
|||||||
"display_name": "Generic PVA",
|
"display_name": "Generic PVA",
|
||||||
"description": "The generic PVA profile which other profiles can be based upon.",
|
"description": "The generic PVA profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1365,7 +1365,7 @@
|
|||||||
"display_name": "Generic Tough PLA",
|
"display_name": "Generic Tough PLA",
|
||||||
"description": "The generic Tough PLA profile which other profiles can be based upon.",
|
"description": "The generic Tough PLA profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1383,7 +1383,7 @@
|
|||||||
"display_name": "Generic TPU",
|
"display_name": "Generic TPU",
|
||||||
"description": "The generic TPU profile which other profiles can be based upon.",
|
"description": "The generic TPU profile which other profiles can be based upon.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Generic",
|
"author_id": "Generic",
|
||||||
@ -1401,7 +1401,7 @@
|
|||||||
"display_name": "Dagoma Chromatik PLA",
|
"display_name": "Dagoma Chromatik PLA",
|
||||||
"description": "Filament testé et approuvé pour les imprimantes 3D Dagoma. Chromatik est l'idéal pour débuter et suivre les tutoriels premiers pas. Il vous offre qualité et résistance pour chacune de vos impressions.",
|
"description": "Filament testé et approuvé pour les imprimantes 3D Dagoma. Chromatik est l'idéal pour débuter et suivre les tutoriels premiers pas. Il vous offre qualité et résistance pour chacune de vos impressions.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://dagoma.fr/boutique/filaments.html",
|
"website": "https://dagoma.fr/boutique/filaments.html",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Dagoma",
|
"author_id": "Dagoma",
|
||||||
@ -1418,7 +1418,7 @@
|
|||||||
"display_name": "FABtotum ABS",
|
"display_name": "FABtotum ABS",
|
||||||
"description": "This material is easy to be extruded but it is not the simplest to use. It is one of the most used in 3D printing to get very well finished objects. It is not sustainable and its smoke can be dangerous if inhaled. The reason to prefer this filament to PLA is mainly because of its precision and mechanical specs. ABS (for plastic) stands for Acrylonitrile Butadiene Styrene and it is a thermoplastic which is widely used in everyday objects. It can be printed with any FFF 3D printer which can get to high temperatures as it must be extruded in a range between 220° and 245°, so it’s compatible with all versions of the FABtotum Personal fabricator.",
|
"description": "This material is easy to be extruded but it is not the simplest to use. It is one of the most used in 3D printing to get very well finished objects. It is not sustainable and its smoke can be dangerous if inhaled. The reason to prefer this filament to PLA is mainly because of its precision and mechanical specs. ABS (for plastic) stands for Acrylonitrile Butadiene Styrene and it is a thermoplastic which is widely used in everyday objects. It can be printed with any FFF 3D printer which can get to high temperatures as it must be extruded in a range between 220° and 245°, so it’s compatible with all versions of the FABtotum Personal fabricator.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=40",
|
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=40",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "FABtotum",
|
"author_id": "FABtotum",
|
||||||
@ -1435,7 +1435,7 @@
|
|||||||
"display_name": "FABtotum Nylon",
|
"display_name": "FABtotum Nylon",
|
||||||
"description": "When 3D printing started this material was not listed among the extrudable filaments. It is flexible as well as resistant to tractions. It is well known for its uses in textile but also in industries which require a strong and flexible material. There are different kinds of Nylon: 3D printing mostly uses Nylon 6 and Nylon 6.6, which are the most common. It requires higher temperatures to be printed, so a 3D printer must be able to reach them (around 240°C): the FABtotum, of course, can.",
|
"description": "When 3D printing started this material was not listed among the extrudable filaments. It is flexible as well as resistant to tractions. It is well known for its uses in textile but also in industries which require a strong and flexible material. There are different kinds of Nylon: 3D printing mostly uses Nylon 6 and Nylon 6.6, which are the most common. It requires higher temperatures to be printed, so a 3D printer must be able to reach them (around 240°C): the FABtotum, of course, can.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=53",
|
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=53",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "FABtotum",
|
"author_id": "FABtotum",
|
||||||
@ -1452,7 +1452,7 @@
|
|||||||
"display_name": "FABtotum PLA",
|
"display_name": "FABtotum PLA",
|
||||||
"description": "It is the most common filament used for 3D printing. It is studied to be bio-degradable as it comes from corn starch’s sugar mainly. It is completely made of renewable sources and has no footprint on polluting. PLA stands for PolyLactic Acid and it is a thermoplastic that today is still considered the easiest material to be 3D printed. It can be extruded at lower temperatures: the standard range of FABtotum’s one is between 185° and 195°.",
|
"description": "It is the most common filament used for 3D printing. It is studied to be bio-degradable as it comes from corn starch’s sugar mainly. It is completely made of renewable sources and has no footprint on polluting. PLA stands for PolyLactic Acid and it is a thermoplastic that today is still considered the easiest material to be 3D printed. It can be extruded at lower temperatures: the standard range of FABtotum’s one is between 185° and 195°.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=39",
|
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=39",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "FABtotum",
|
"author_id": "FABtotum",
|
||||||
@ -1469,7 +1469,7 @@
|
|||||||
"display_name": "FABtotum TPU Shore 98A",
|
"display_name": "FABtotum TPU Shore 98A",
|
||||||
"description": "",
|
"description": "",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=66",
|
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=66",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "FABtotum",
|
"author_id": "FABtotum",
|
||||||
@ -1486,7 +1486,7 @@
|
|||||||
"display_name": "Fiberlogy HD PLA",
|
"display_name": "Fiberlogy HD PLA",
|
||||||
"description": "With our HD PLA you have many more options. You can use this material in two ways. Choose the one you like best. You can use it as a normal PLA and get prints characterized by a very good adhesion between the layers and high precision. You can also make your prints acquire similar properties to that of ABS – better impact resistance and high temperature resistance. All you need is an oven. Yes, an oven! By annealing our HD PLA in an oven, in accordance with the manual, you will avoid all the inconveniences of printing with ABS, such as unpleasant odour or hazardous fumes.",
|
"description": "With our HD PLA you have many more options. You can use this material in two ways. Choose the one you like best. You can use it as a normal PLA and get prints characterized by a very good adhesion between the layers and high precision. You can also make your prints acquire similar properties to that of ABS – better impact resistance and high temperature resistance. All you need is an oven. Yes, an oven! By annealing our HD PLA in an oven, in accordance with the manual, you will avoid all the inconveniences of printing with ABS, such as unpleasant odour or hazardous fumes.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "http://fiberlogy.com/en/fiberlogy-filaments/filament-hd-pla/",
|
"website": "http://fiberlogy.com/en/fiberlogy-filaments/filament-hd-pla/",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Fiberlogy",
|
"author_id": "Fiberlogy",
|
||||||
@ -1503,7 +1503,7 @@
|
|||||||
"display_name": "Filo3D PLA",
|
"display_name": "Filo3D PLA",
|
||||||
"description": "Fast, safe and reliable printing. PLA is ideal for the fast and reliable printing of parts and prototypes with a great surface quality.",
|
"description": "Fast, safe and reliable printing. PLA is ideal for the fast and reliable printing of parts and prototypes with a great surface quality.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://dagoma.fr",
|
"website": "https://dagoma.fr",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Dagoma",
|
"author_id": "Dagoma",
|
||||||
@ -1520,7 +1520,7 @@
|
|||||||
"display_name": "IMADE3D JellyBOX PETG",
|
"display_name": "IMADE3D JellyBOX PETG",
|
||||||
"description": "",
|
"description": "",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "http://shop.imade3d.com/filament.html",
|
"website": "http://shop.imade3d.com/filament.html",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "IMADE3D",
|
"author_id": "IMADE3D",
|
||||||
@ -1537,7 +1537,7 @@
|
|||||||
"display_name": "IMADE3D JellyBOX PLA",
|
"display_name": "IMADE3D JellyBOX PLA",
|
||||||
"description": "",
|
"description": "",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "http://shop.imade3d.com/filament.html",
|
"website": "http://shop.imade3d.com/filament.html",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "IMADE3D",
|
"author_id": "IMADE3D",
|
||||||
@ -1554,7 +1554,7 @@
|
|||||||
"display_name": "Octofiber PLA",
|
"display_name": "Octofiber PLA",
|
||||||
"description": "PLA material from Octofiber.",
|
"description": "PLA material from Octofiber.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://nl.octofiber.com/3d-printing-filament/pla.html",
|
"website": "https://nl.octofiber.com/3d-printing-filament/pla.html",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Octofiber",
|
"author_id": "Octofiber",
|
||||||
@ -1571,7 +1571,7 @@
|
|||||||
"display_name": "PolyFlex™ PLA",
|
"display_name": "PolyFlex™ PLA",
|
||||||
"description": "PolyFlex™ is a highly flexible yet easy to print 3D printing material. Featuring good elasticity and a large strain-to- failure, PolyFlex™ opens up a completely new realm of applications.",
|
"description": "PolyFlex™ is a highly flexible yet easy to print 3D printing material. Featuring good elasticity and a large strain-to- failure, PolyFlex™ opens up a completely new realm of applications.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "http://www.polymaker.com/shop/polyflex/",
|
"website": "http://www.polymaker.com/shop/polyflex/",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Polymaker",
|
"author_id": "Polymaker",
|
||||||
@ -1588,7 +1588,7 @@
|
|||||||
"display_name": "PolyMax™ PLA",
|
"display_name": "PolyMax™ PLA",
|
||||||
"description": "PolyMax™ PLA is a 3D printing material with excellent mechanical properties and printing quality. PolyMax™ PLA has an impact resistance of up to nine times that of regular PLA, and better overall mechanical properties than ABS.",
|
"description": "PolyMax™ PLA is a 3D printing material with excellent mechanical properties and printing quality. PolyMax™ PLA has an impact resistance of up to nine times that of regular PLA, and better overall mechanical properties than ABS.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "http://www.polymaker.com/shop/polymax/",
|
"website": "http://www.polymaker.com/shop/polymax/",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Polymaker",
|
"author_id": "Polymaker",
|
||||||
@ -1605,7 +1605,7 @@
|
|||||||
"display_name": "PolyPlus™ PLA True Colour",
|
"display_name": "PolyPlus™ PLA True Colour",
|
||||||
"description": "PolyPlus™ PLA is a premium PLA designed for all desktop FDM/FFF 3D printers. It is produced with our patented Jam-Free™ technology that ensures consistent extrusion and prevents jams.",
|
"description": "PolyPlus™ PLA is a premium PLA designed for all desktop FDM/FFF 3D printers. It is produced with our patented Jam-Free™ technology that ensures consistent extrusion and prevents jams.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "http://www.polymaker.com/shop/polyplus-true-colour/",
|
"website": "http://www.polymaker.com/shop/polyplus-true-colour/",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Polymaker",
|
"author_id": "Polymaker",
|
||||||
@ -1622,7 +1622,7 @@
|
|||||||
"display_name": "PolyWood™ PLA",
|
"display_name": "PolyWood™ PLA",
|
||||||
"description": "PolyWood™ is a wood mimic printing material that contains no actual wood ensuring a clean Jam-Free™ printing experience.",
|
"description": "PolyWood™ is a wood mimic printing material that contains no actual wood ensuring a clean Jam-Free™ printing experience.",
|
||||||
"package_version": "1.0.1",
|
"package_version": "1.0.1",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "http://www.polymaker.com/shop/polywood/",
|
"website": "http://www.polymaker.com/shop/polywood/",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Polymaker",
|
"author_id": "Polymaker",
|
||||||
@ -1639,7 +1639,7 @@
|
|||||||
"display_name": "Ultimaker ABS",
|
"display_name": "Ultimaker ABS",
|
||||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com/products/materials/abs",
|
"website": "https://ultimaker.com/products/materials/abs",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1658,7 +1658,7 @@
|
|||||||
"display_name": "Ultimaker Breakaway",
|
"display_name": "Ultimaker Breakaway",
|
||||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com/products/materials/breakaway",
|
"website": "https://ultimaker.com/products/materials/breakaway",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1677,7 +1677,7 @@
|
|||||||
"display_name": "Ultimaker CPE",
|
"display_name": "Ultimaker CPE",
|
||||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com/products/materials/abs",
|
"website": "https://ultimaker.com/products/materials/abs",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1696,7 +1696,7 @@
|
|||||||
"display_name": "Ultimaker CPE+",
|
"display_name": "Ultimaker CPE+",
|
||||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com/products/materials/cpe",
|
"website": "https://ultimaker.com/products/materials/cpe",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1715,7 +1715,7 @@
|
|||||||
"display_name": "Ultimaker Nylon",
|
"display_name": "Ultimaker Nylon",
|
||||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com/products/materials/abs",
|
"website": "https://ultimaker.com/products/materials/abs",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1734,7 +1734,7 @@
|
|||||||
"display_name": "Ultimaker PC",
|
"display_name": "Ultimaker PC",
|
||||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com/products/materials/pc",
|
"website": "https://ultimaker.com/products/materials/pc",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1753,7 +1753,7 @@
|
|||||||
"display_name": "Ultimaker PLA",
|
"display_name": "Ultimaker PLA",
|
||||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com/products/materials/abs",
|
"website": "https://ultimaker.com/products/materials/abs",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1772,7 +1772,7 @@
|
|||||||
"display_name": "Ultimaker PP",
|
"display_name": "Ultimaker PP",
|
||||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com/products/materials/pp",
|
"website": "https://ultimaker.com/products/materials/pp",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1791,7 +1791,7 @@
|
|||||||
"display_name": "Ultimaker PVA",
|
"display_name": "Ultimaker PVA",
|
||||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com/products/materials/abs",
|
"website": "https://ultimaker.com/products/materials/abs",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1810,7 +1810,7 @@
|
|||||||
"display_name": "Ultimaker TPU 95A",
|
"display_name": "Ultimaker TPU 95A",
|
||||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com/products/materials/tpu-95a",
|
"website": "https://ultimaker.com/products/materials/tpu-95a",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1829,7 +1829,7 @@
|
|||||||
"display_name": "Ultimaker Tough PLA",
|
"display_name": "Ultimaker Tough PLA",
|
||||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://ultimaker.com/products/materials/tough-pla",
|
"website": "https://ultimaker.com/products/materials/tough-pla",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "UltimakerPackages",
|
"author_id": "UltimakerPackages",
|
||||||
@ -1848,7 +1848,7 @@
|
|||||||
"display_name": "Vertex Delta ABS",
|
"display_name": "Vertex Delta ABS",
|
||||||
"description": "ABS material and quality files for the Delta Vertex K8800.",
|
"description": "ABS material and quality files for the Delta Vertex K8800.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://vertex3dprinter.eu",
|
"website": "https://vertex3dprinter.eu",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Velleman",
|
"author_id": "Velleman",
|
||||||
@ -1865,7 +1865,7 @@
|
|||||||
"display_name": "Vertex Delta PET",
|
"display_name": "Vertex Delta PET",
|
||||||
"description": "ABS material and quality files for the Delta Vertex K8800.",
|
"description": "ABS material and quality files for the Delta Vertex K8800.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://vertex3dprinter.eu",
|
"website": "https://vertex3dprinter.eu",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Velleman",
|
"author_id": "Velleman",
|
||||||
@ -1882,7 +1882,7 @@
|
|||||||
"display_name": "Vertex Delta PLA",
|
"display_name": "Vertex Delta PLA",
|
||||||
"description": "ABS material and quality files for the Delta Vertex K8800.",
|
"description": "ABS material and quality files for the Delta Vertex K8800.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://vertex3dprinter.eu",
|
"website": "https://vertex3dprinter.eu",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Velleman",
|
"author_id": "Velleman",
|
||||||
@ -1899,7 +1899,7 @@
|
|||||||
"display_name": "Vertex Delta TPU",
|
"display_name": "Vertex Delta TPU",
|
||||||
"description": "ABS material and quality files for the Delta Vertex K8800.",
|
"description": "ABS material and quality files for the Delta Vertex K8800.",
|
||||||
"package_version": "1.4.0",
|
"package_version": "1.4.0",
|
||||||
"sdk_version": "8.4.0",
|
"sdk_version": "8.5.0",
|
||||||
"website": "https://vertex3dprinter.eu",
|
"website": "https://vertex3dprinter.eu",
|
||||||
"author": {
|
"author": {
|
||||||
"author_id": "Velleman",
|
"author_id": "Velleman",
|
||||||
|
52
resources/definitions/creality_ender3v3se.def.json
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"name": "Creality Ender-3 V3 SE",
|
||||||
|
"inherits": "creality_base",
|
||||||
|
"metadata":
|
||||||
|
{
|
||||||
|
"visible": true,
|
||||||
|
"manufacturer": "Creality3D",
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"first_start_actions": [ "MachineSettingsAction" ],
|
||||||
|
"has_machine_quality": true,
|
||||||
|
"has_materials": true,
|
||||||
|
"has_variants": true,
|
||||||
|
"machine_extruder_trains": { "0": "creality_base_extruder_0" },
|
||||||
|
"preferred_material": "generic_pla",
|
||||||
|
"preferred_quality_type": "standard",
|
||||||
|
"preferred_variant_name": "0.4mm Nozzle",
|
||||||
|
"quality_definition": "creality_base",
|
||||||
|
"variants_name": "Nozzle Size"
|
||||||
|
},
|
||||||
|
"overrides":
|
||||||
|
{
|
||||||
|
"gantry_height": { "value": 25 },
|
||||||
|
"machine_depth": { "default_value": 220 },
|
||||||
|
"machine_head_with_fans_polygon":
|
||||||
|
{
|
||||||
|
"default_value": [
|
||||||
|
[-20, 10],
|
||||||
|
[10, 10],
|
||||||
|
[10, -10],
|
||||||
|
[-20, -10]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"machine_heated_bed": { "default_value": true },
|
||||||
|
"machine_height": { "default_value": 250 },
|
||||||
|
"machine_max_acceleration_e": { "value": 5000 },
|
||||||
|
"machine_max_acceleration_x": { "value": 5000.0 },
|
||||||
|
"machine_max_acceleration_y": { "value": 5000.0 },
|
||||||
|
"machine_max_acceleration_z": { "value": 500.0 },
|
||||||
|
"machine_max_feedrate_e": { "value": 100 },
|
||||||
|
"machine_max_feedrate_x": { "value": 500 },
|
||||||
|
"machine_max_feedrate_y": { "value": 500 },
|
||||||
|
"machine_max_feedrate_z": { "value": 30 },
|
||||||
|
"machine_name": { "default_value": "Creality Ender-3 V3 SE" },
|
||||||
|
"machine_start_gcode": { "default_value": "M220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\n\nG28 ;Home\n\nM420 S1; Enable mesh leveling\n\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nM109 S[material_print_temperature_layer_0]\nG1 X10.1 Y145.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y145.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder\nG1 E-1.0000 F1800 ;Retract a bit\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 E0.0000 F1800 \n" },
|
||||||
|
"machine_width": { "default_value": 220 },
|
||||||
|
"retraction_amount": { "value": 0.8 },
|
||||||
|
"retraction_speed": { "default_value": 40 },
|
||||||
|
"speed_layer_0": { "value": 30 },
|
||||||
|
"speed_print": { "value": 180 }
|
||||||
|
}
|
||||||
|
}
|