Merge branch 'main' of https://github.com/mehmetsutas/Cura
|
@ -1,4 +0,0 @@
|
||||||
.git
|
|
||||||
.github
|
|
||||||
resources/materials
|
|
||||||
CuraEngine
|
|
21
.github/workflows/ci.yml
vendored
|
@ -1,21 +0,0 @@
|
||||||
---
|
|
||||||
name: CI
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- 'WIP**'
|
|
||||||
- '4.*'
|
|
||||||
- 'CURA-*'
|
|
||||||
pull_request:
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: ultimaker/cura-build-environment
|
|
||||||
steps:
|
|
||||||
- name: Checkout Cura
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Build
|
|
||||||
run: docker/build.sh
|
|
||||||
- name: Test
|
|
||||||
run: docker/test.sh
|
|
153
.github/workflows/conan-package-create.yml
vendored
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
name: Create and Upload Conan package
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
recipe_id_full:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
recipe_id_latest:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
runs_on:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
python_version:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
conan_config_branch:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
conan_logging_level:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
conan_clean_local_cache:
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
conan_upload_community:
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
create_from_source:
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
env:
|
||||||
|
CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }}
|
||||||
|
CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }}
|
||||||
|
CONAN_LOGIN_USERNAME_CURA_CE: ${{ secrets.CONAN_USER }}
|
||||||
|
CONAN_PASSWORD_CURA_CE: ${{ secrets.CONAN_PASS }}
|
||||||
|
CONAN_LOG_RUN_TO_OUTPUT: 1
|
||||||
|
CONAN_LOGGING_LEVEL: ${{ inputs.conan_logging_level }}
|
||||||
|
CONAN_NON_INTERACTIVE: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
conan-package-create:
|
||||||
|
runs-on: ${{ inputs.runs_on }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Python and pip
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ inputs.python_version }}
|
||||||
|
cache: 'pip'
|
||||||
|
cache-dependency-path: .github/workflows/requirements-conan-package.txt
|
||||||
|
|
||||||
|
- name: Install Python requirements for runner
|
||||||
|
run: pip install -r .github/workflows/requirements-conan-package.txt
|
||||||
|
|
||||||
|
- name: Use Conan download cache (Bash)
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache"
|
||||||
|
|
||||||
|
- name: Use Conan download cache (Powershell)
|
||||||
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
run: conan config set storage.download_cache="C:\Users\runneradmin\.conan\conan_download_cache"
|
||||||
|
|
||||||
|
- name: Cache Conan local repository packages (Bash)
|
||||||
|
uses: actions/cache@v3
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
$HOME/.conan/data
|
||||||
|
$HOME/.conan/conan_download_cache
|
||||||
|
key: conan-${{ runner.os }}-${{ runner.arch }}-create-cache
|
||||||
|
|
||||||
|
- name: Cache Conan local repository packages (Powershell)
|
||||||
|
uses: actions/cache@v3
|
||||||
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
C:\Users\runneradmin\.conan\data
|
||||||
|
C:\.conan
|
||||||
|
C:\Users\runneradmin\.conan\conan_download_cache
|
||||||
|
key: conan-${{ runner.os }}-${{ runner.arch }}-create-cache
|
||||||
|
|
||||||
|
- name: Install MacOS system requirements
|
||||||
|
if: ${{ runner.os == 'Macos' }}
|
||||||
|
run: brew install autoconf automake ninja
|
||||||
|
|
||||||
|
- name: Install Linux system requirements
|
||||||
|
if: ${{ runner.os == 'Linux' }}
|
||||||
|
run: |
|
||||||
|
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||||
|
sudo apt update
|
||||||
|
sudo apt upgrade
|
||||||
|
sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config -y
|
||||||
|
|
||||||
|
- name: Install GCC-12 on ubuntu-22.04
|
||||||
|
if: ${{ startsWith(inputs.runs_on, 'ubuntu-22.04') }}
|
||||||
|
run: |
|
||||||
|
sudo apt install g++-12 gcc-12 -y
|
||||||
|
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12
|
||||||
|
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
|
||||||
|
|
||||||
|
- name: Create the default Conan profile
|
||||||
|
run: conan profile new default --detect
|
||||||
|
|
||||||
|
- name: Get Conan configuration from branch
|
||||||
|
if: ${{ 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
|
||||||
|
if: ${{ inputs.conan_config_branch == '' }}
|
||||||
|
run: conan config install https://github.com/Ultimaker/conan-config.git
|
||||||
|
|
||||||
|
- name: Create the Packages
|
||||||
|
if: ${{ !inputs.create_from_source }}
|
||||||
|
run: conan install ${{ inputs.recipe_id_full }} --build=missing --update
|
||||||
|
|
||||||
|
- name: Create the Packages (from source)
|
||||||
|
if: ${{ inputs.create_from_source }}
|
||||||
|
run: conan create . ${{ inputs.recipe_id_full }} --build=missing --update
|
||||||
|
|
||||||
|
- name: Remove the latest alias
|
||||||
|
if: ${{ inputs.create_from_source && inputs.recipe_id_latest != '' && runner.os == 'Linux' }}
|
||||||
|
run: |
|
||||||
|
conan remove ${{ inputs.recipe_id_latest }} -r cura -f || true
|
||||||
|
conan remove ${{ inputs.recipe_id_latest }} -r cura-ce -f || true
|
||||||
|
|
||||||
|
- name: Create the latest alias
|
||||||
|
if: ${{ inputs.create_from_source && inputs.recipe_id_latest != '' && always() }}
|
||||||
|
run: conan alias ${{ inputs.recipe_id_latest }} ${{ inputs.recipe_id_full }}
|
||||||
|
|
||||||
|
- name: Upload the Package(s)
|
||||||
|
if: always()
|
||||||
|
run: conan upload "*" -r cura --all -c
|
||||||
|
|
||||||
|
- name: Upload the Package(s) community
|
||||||
|
if: ${{ always() && inputs.conan_upload_community == true }}
|
||||||
|
run: conan upload "*" -r cura-ce -c
|
103
.github/workflows/conan-package.yml
vendored
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
---
|
||||||
|
name: conan-package
|
||||||
|
|
||||||
|
# Exports the recipe, sources and binaries for Mac, Windows and Linux and upload these to the server such that these can
|
||||||
|
# be used downstream.
|
||||||
|
#
|
||||||
|
# It should run on pushes against main or CURA-* branches, but it will only create the binaries for main and release branches
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
create_binaries_windows:
|
||||||
|
required: true
|
||||||
|
default: false
|
||||||
|
description: 'create binaries Windows'
|
||||||
|
create_binaries_linux:
|
||||||
|
required: true
|
||||||
|
default: false
|
||||||
|
description: 'create binaries Linux'
|
||||||
|
create_binaries_macos:
|
||||||
|
required: true
|
||||||
|
default: false
|
||||||
|
description: 'create binaries Macos'
|
||||||
|
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'plugins/**'
|
||||||
|
- 'resources/**'
|
||||||
|
- 'cura/**'
|
||||||
|
- 'icons/**'
|
||||||
|
- 'tests/**'
|
||||||
|
- 'packaging/**'
|
||||||
|
- '.github/workflows/conan-*.yml'
|
||||||
|
- '.github/workflows/notify.yml'
|
||||||
|
- '.github/workflows/requirements-conan-package.txt'
|
||||||
|
- 'requirements*.txt'
|
||||||
|
- 'conanfile.py'
|
||||||
|
- 'conandata.yml'
|
||||||
|
- 'GitVersion.yml'
|
||||||
|
- '*.jinja'
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'CURA-*'
|
||||||
|
- '[1-9].[0-9]'
|
||||||
|
- '[1-9].[0-9][0-9]'
|
||||||
|
tags:
|
||||||
|
- '[1-9].[0-9].[0-9]+'
|
||||||
|
- '[1-9].[0-9][0-9].[0-9]+'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
conan-recipe-version:
|
||||||
|
uses: ultimaker/cura/.github/workflows/conan-recipe-version.yml@main
|
||||||
|
with:
|
||||||
|
project_name: cura
|
||||||
|
|
||||||
|
conan-package-export:
|
||||||
|
needs: [ conan-recipe-version ]
|
||||||
|
uses: ultimaker/cura/.github/workflows/conan-recipe-export.yml@main
|
||||||
|
with:
|
||||||
|
recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }}
|
||||||
|
recipe_id_latest: ${{ needs.conan-recipe-version.outputs.recipe_id_latest }}
|
||||||
|
runs_on: 'ubuntu-20.04'
|
||||||
|
python_version: '3.10.x'
|
||||||
|
conan_logging_level: 'info'
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
conan-package-create-linux:
|
||||||
|
if: ${{ (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || (github.event_name == 'workflow_dispatch' && inputs.create_binaries_linux) }}
|
||||||
|
needs: [ conan-recipe-version, conan-package-export ]
|
||||||
|
|
||||||
|
uses: ultimaker/cura/.github/workflows/conan-package-create.yml@main
|
||||||
|
with:
|
||||||
|
recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }}
|
||||||
|
runs_on: 'ubuntu-20.04'
|
||||||
|
python_version: '3.10.x'
|
||||||
|
conan_logging_level: 'info'
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
notify-export:
|
||||||
|
if: ${{ always() }}
|
||||||
|
needs: [ conan-recipe-version, conan-package-export ]
|
||||||
|
|
||||||
|
uses: ultimaker/cura/.github/workflows/notify.yml@main
|
||||||
|
with:
|
||||||
|
success: ${{ contains(join(needs.*.result, ','), 'success') }}
|
||||||
|
success_title: "New Conan recipe exported in ${{ github.repository }}"
|
||||||
|
success_body: "Exported ${{ needs.conan-recipe-version.outputs.recipe_id_full }}"
|
||||||
|
failure_title: "Failed to export Conan Export in ${{ github.repository }}"
|
||||||
|
failure_body: "Failed to exported ${{ needs.conan-recipe-version.outputs.recipe_id_full }}"
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
notify-create:
|
||||||
|
if: ${{ always() && ((github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || (github.event_name == 'workflow_dispatch' && inputs.create_binaries_linux)) }}
|
||||||
|
needs: [ conan-recipe-version, conan-package-create-linux ]
|
||||||
|
|
||||||
|
uses: ultimaker/cura/.github/workflows/notify.yml@main
|
||||||
|
with:
|
||||||
|
success: ${{ contains(join(needs.*.result, ','), 'success') }}
|
||||||
|
success_title: "New binaries created in ${{ github.repository }}"
|
||||||
|
success_body: "Created binaries for ${{ needs.conan-recipe-version.outputs.recipe_id_full }}"
|
||||||
|
failure_title: "Failed to create binaries in ${{ github.repository }}"
|
||||||
|
failure_body: "Failed to created binaries for ${{ needs.conan-recipe-version.outputs.recipe_id_full }}"
|
||||||
|
secrets: inherit
|
106
.github/workflows/conan-recipe-export.yml
vendored
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
name: Export Conan Recipe to server
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
recipe_id_full:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
recipe_id_latest:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
runs_on:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
python_version:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
conan_config_branch:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
conan_logging_level:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
conan_export_binaries:
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
conan_upload_community:
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
env:
|
||||||
|
CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }}
|
||||||
|
CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }}
|
||||||
|
CONAN_LOGIN_USERNAME_CURA_CE: ${{ secrets.CONAN_USER }}
|
||||||
|
CONAN_PASSWORD_CURA_CE: ${{ secrets.CONAN_PASS }}
|
||||||
|
CONAN_LOG_RUN_TO_OUTPUT: 1
|
||||||
|
CONAN_LOGGING_LEVEL: ${{ inputs.conan_logging_level }}
|
||||||
|
CONAN_NON_INTERACTIVE: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
package-export:
|
||||||
|
runs-on: ${{ inputs.runs_on }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout project
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Python and pip
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ inputs.python_version }}
|
||||||
|
cache: 'pip'
|
||||||
|
cache-dependency-path: .github/workflows/requirements-conan-package.txt
|
||||||
|
|
||||||
|
- name: Install Python requirements and Create default Conan profile
|
||||||
|
run: |
|
||||||
|
pip install -r .github/workflows/requirements-conan-package.txt
|
||||||
|
conan profile new default --detect
|
||||||
|
|
||||||
|
- name: Cache Conan local repository packages
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: $HOME/.conan/data
|
||||||
|
key: ${{ runner.os }}-conan-export-cache
|
||||||
|
|
||||||
|
- name: Get Conan configuration from branch
|
||||||
|
if: ${{ 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
|
||||||
|
if: ${{ inputs.conan_config_branch == '' }}
|
||||||
|
run: conan config install https://github.com/Ultimaker/conan-config.git
|
||||||
|
|
||||||
|
- name: Export the Package (binaries)
|
||||||
|
if: ${{ inputs.conan_export_binaries }}
|
||||||
|
run: conan create . ${{ inputs.recipe_id_full }} --build=missing --update
|
||||||
|
|
||||||
|
- name: Export the Package
|
||||||
|
if: ${{ !inputs.conan_export_binaries }}
|
||||||
|
run: conan export . ${{ inputs.recipe_id_full }}
|
||||||
|
|
||||||
|
- name: Remove the latest alias
|
||||||
|
if: ${{ inputs.recipe_id_latest != '' && runner.os == 'Linux' }}
|
||||||
|
run: |
|
||||||
|
conan remove ${{ inputs.recipe_id_latest }} -r cura -f || true
|
||||||
|
conan remove ${{ inputs.recipe_id_latest }} -r cura-ce -f || true
|
||||||
|
|
||||||
|
- name: Create the latest alias
|
||||||
|
if: ${{ inputs.recipe_id_latest != '' && always() }}
|
||||||
|
run: conan alias ${{ inputs.recipe_id_latest }} ${{ inputs.recipe_id_full }}
|
||||||
|
|
||||||
|
- name: Upload the Package(s)
|
||||||
|
if: always()
|
||||||
|
run: conan upload "*" -r cura --all -c
|
||||||
|
|
||||||
|
- name: Upload the Package(s) community
|
||||||
|
if: ${{ always() && inputs.conan_upload_community == true }}
|
||||||
|
run: conan upload "*" -r cura-ce -c
|
181
.github/workflows/conan-recipe-version.yml
vendored
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
name: Get Conan Recipe Version
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
project_name:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
additional_buildmetadata:
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
type: string
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
recipe_id_full:
|
||||||
|
description: "The full Conan recipe id: <name>/<version>@<user>/<channel>"
|
||||||
|
value: ${{ jobs.get-semver.outputs.recipe_id_full }}
|
||||||
|
|
||||||
|
recipe_id_latest:
|
||||||
|
description: "The full Conan recipe aliased (latest) id: <name>/(latest)@<user>/<channel>"
|
||||||
|
value: ${{ jobs.get-semver.outputs.recipe_id_latest }}
|
||||||
|
|
||||||
|
recipe_semver_full:
|
||||||
|
description: "The full semver <Major>.<Minor>.<Patch>-<PreReleaseTag>+<BuildMetaData>"
|
||||||
|
value: ${{ jobs.get-semver.outputs.semver_full }}
|
||||||
|
|
||||||
|
is_release_branch:
|
||||||
|
description: "is current branch a release branch?"
|
||||||
|
value: ${{ jobs.get-semver.outputs.release_branch }}
|
||||||
|
|
||||||
|
recipe_user:
|
||||||
|
description: "The conan user"
|
||||||
|
value: ${{ jobs.get-semver.outputs.user }}
|
||||||
|
|
||||||
|
recipe_channel:
|
||||||
|
description: "The conan channel"
|
||||||
|
value: ${{ jobs.get-semver.outputs.channel }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
get-semver:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
recipe_id_full: ${{ steps.get-conan-broadcast-data.outputs.recipe_id_full }}
|
||||||
|
recipe_id_latest: ${{ steps.get-conan-broadcast-data.outputs.recipe_id_latest }}
|
||||||
|
semver_full: ${{ steps.get-conan-broadcast-data.outputs.semver_full }}
|
||||||
|
is_release_branch: ${{ steps.get-conan-broadcast-data.outputs.is_release_branch }}
|
||||||
|
user: ${{ steps.get-conan-broadcast-data.outputs.user }}
|
||||||
|
channel: ${{ steps.get-conan-broadcast-data.outputs.channel }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.head_ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Python and pip
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.10.x"
|
||||||
|
cache: 'pip'
|
||||||
|
cache-dependency-path: .github/workflows/requirements-conan-package.txt
|
||||||
|
|
||||||
|
- name: Install Python requirements and Create default Conan profile
|
||||||
|
run: |
|
||||||
|
pip install -r .github/workflows/requirements-conan-package.txt
|
||||||
|
pip install gitpython
|
||||||
|
|
||||||
|
- id: get-conan-broadcast-data
|
||||||
|
name: Get Conan broadcast data
|
||||||
|
run: |
|
||||||
|
import subprocess
|
||||||
|
from conans import tools
|
||||||
|
from conans.errors import ConanException
|
||||||
|
from git import Repo
|
||||||
|
|
||||||
|
repo = Repo('.')
|
||||||
|
user = "${{ github.repository_owner }}".lower()
|
||||||
|
project_name = "${{ inputs.project_name }}"
|
||||||
|
event_name = "${{ github.event_name }}"
|
||||||
|
issue_number = "${{ github.ref }}".split('/')[2]
|
||||||
|
is_tag = "${{ github.ref_type }}" == "tag"
|
||||||
|
is_release_branch = False
|
||||||
|
buildmetadata = "" if "${{ inputs.additional_buildmetadata }}" == "" else "${{ inputs.additional_buildmetadata }}_"
|
||||||
|
|
||||||
|
# FIXME: for when we push a tag (such as an release)
|
||||||
|
channel = "testing"
|
||||||
|
if is_tag:
|
||||||
|
branch_version = tools.Version("${{ github.ref_name }}")
|
||||||
|
is_release_branch = True
|
||||||
|
channel = "_"
|
||||||
|
user = "_"
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
branch_version = tools.Version(repo.active_branch.name)
|
||||||
|
except ConanException:
|
||||||
|
branch_version = tools.Version('0.0.0')
|
||||||
|
if "${{ github.ref_name }}" == f"{branch_version.major}.{branch_version.minor}":
|
||||||
|
channel = 'stable'
|
||||||
|
is_release_branch = True
|
||||||
|
elif "${{ github.ref_name }}" in ("main", "master"):
|
||||||
|
channel = 'testing'
|
||||||
|
else:
|
||||||
|
channel = repo.active_branch.name.split("_")[0].replace("-", "_").lower()
|
||||||
|
|
||||||
|
if event_name == "pull_request":
|
||||||
|
channel = f"pr_{issue_number}"
|
||||||
|
|
||||||
|
# %% Get the actual version
|
||||||
|
latest_branch_version = tools.Version("0.0.0")
|
||||||
|
latest_branch_tag = None
|
||||||
|
for tag in repo.git.tag(merged = True).splitlines():
|
||||||
|
try:
|
||||||
|
version = tools.Version(tag)
|
||||||
|
except ConanException:
|
||||||
|
continue
|
||||||
|
if version > latest_branch_version:
|
||||||
|
latest_branch_version = version
|
||||||
|
latest_branch_tag = repo.tag(tag)
|
||||||
|
|
||||||
|
# %% Get the actual version
|
||||||
|
no_commits = 0
|
||||||
|
for commit in repo.iter_commits("HEAD"):
|
||||||
|
if commit == latest_branch_tag.commit:
|
||||||
|
break
|
||||||
|
no_commits += 1
|
||||||
|
|
||||||
|
if no_commits == 0:
|
||||||
|
# This is a release
|
||||||
|
actual_version = f"{latest_branch_version.major}.{latest_branch_version.minor}.{latest_branch_version.patch}"
|
||||||
|
if channel == "stable":
|
||||||
|
user = "_"
|
||||||
|
channel = "_"
|
||||||
|
else:
|
||||||
|
if latest_branch_version.prerelease and not "." in latest_branch_version.prerelease:
|
||||||
|
# The prerealese did not contain a version number, default it to 1
|
||||||
|
latest_branch_version.prerelease += ".1"
|
||||||
|
if event_name == "pull_request":
|
||||||
|
actual_version = f"{latest_branch_version.major}.{latest_branch_version.minor}.{latest_branch_version.patch}-{latest_branch_version.prerelease.lower()}+{buildmetadata}pr_{issue_number}_{no_commits}"
|
||||||
|
else:
|
||||||
|
if channel in ("stable", "_", ""):
|
||||||
|
channel_metadata = f"{no_commits}"
|
||||||
|
else:
|
||||||
|
channel_metadata = f"{channel}_{no_commits}"
|
||||||
|
# FIXME: for when we create a new release branch
|
||||||
|
if latest_branch_version.prerelease == "":
|
||||||
|
bump_up_minor = int(latest_branch_version.minor) + 1
|
||||||
|
actual_version = f"{latest_branch_version.major}.{bump_up_minor}.{latest_branch_version.patch}-alpha+{buildmetadata}{channel_metadata}"
|
||||||
|
else:
|
||||||
|
actual_version = f"{latest_branch_version.major}.{latest_branch_version.minor}.{latest_branch_version.patch}-{latest_branch_version.prerelease.lower()}+{buildmetadata}{channel_metadata}"
|
||||||
|
|
||||||
|
# %% print to output
|
||||||
|
cmd_name = ["echo", f"::set-output name=name::{project_name}"]
|
||||||
|
subprocess.call(cmd_name)
|
||||||
|
cmd_version = ["echo", f"::set-output name=version::{actual_version}"]
|
||||||
|
subprocess.call(cmd_version)
|
||||||
|
cmd_channel = ["echo", f"::set-output name=channel::{channel}"]
|
||||||
|
subprocess.call(cmd_channel)
|
||||||
|
cmd_id_full= ["echo", f"::set-output name=recipe_id_full::{project_name}/{actual_version}@{user}/{channel}"]
|
||||||
|
subprocess.call(cmd_id_full)
|
||||||
|
cmd_id_latest = ["echo", f"::set-output name=recipe_id_latest::{project_name}/latest@{user}/{channel}"]
|
||||||
|
subprocess.call(cmd_id_latest)
|
||||||
|
cmd_semver_full = ["echo", f"::set-output name=semver_full::{actual_version}"]
|
||||||
|
subprocess.call(cmd_semver_full)
|
||||||
|
cmd_is_release_branch = ["echo", f"::set-output name=is_release_branch::{str(is_release_branch).lower()}"]
|
||||||
|
subprocess.call(cmd_is_release_branch)
|
||||||
|
|
||||||
|
print("::group::Conan Recipe Information")
|
||||||
|
print(f"name = {project_name}")
|
||||||
|
print(f"version = {actual_version}")
|
||||||
|
print(f"user = {user}")
|
||||||
|
print(f"channel = {channel}")
|
||||||
|
print(f"recipe_id_full = {project_name}/{actual_version}@{user}/{channel}")
|
||||||
|
print(f"recipe_id_latest = {project_name}/latest@{user}/{channel}")
|
||||||
|
print(f"semver_full = {actual_version}")
|
||||||
|
print(f"is_release_branch = {str(is_release_branch).lower()}")
|
||||||
|
print("::endgroup::")
|
||||||
|
shell: python
|
254
.github/workflows/cura-installer.yml
vendored
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
name: Cura Installer
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
cura_conan_version:
|
||||||
|
description: 'Cura Conan Version'
|
||||||
|
default: 'cura/latest@ultimaker/testing'
|
||||||
|
required: true
|
||||||
|
conan_args:
|
||||||
|
description: 'Conan args: eq.: --require-override'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
conan_config:
|
||||||
|
description: 'Conan config branch to use'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
enterprise:
|
||||||
|
description: 'Build Cura as an Enterprise edition'
|
||||||
|
required: true
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
staging:
|
||||||
|
description: 'Use staging API'
|
||||||
|
required: true
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
installer:
|
||||||
|
description: 'Create the installer'
|
||||||
|
required: true
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
# Run the nightly at 3:25 UTC on working days
|
||||||
|
#FIXME: Provide the same default values as the workflow dispatch
|
||||||
|
schedule:
|
||||||
|
- cron: '25 3 * * 1-5'
|
||||||
|
|
||||||
|
env:
|
||||||
|
CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }}
|
||||||
|
CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }}
|
||||||
|
CONAN_LOGIN_USERNAME_CURA_CE: ${{ secrets.CONAN_USER }}
|
||||||
|
CONAN_PASSWORD_CURA_CE: ${{ secrets.CONAN_PASS }}
|
||||||
|
CONAN_LOG_RUN_TO_OUTPUT: 1
|
||||||
|
CONAN_LOGGING_LEVEL: ${{ inputs.conan_logging_level }}
|
||||||
|
CONAN_NON_INTERACTIVE: 1
|
||||||
|
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
|
||||||
|
MAC_NOTARIZE_USER: ${{ secrets.MAC_NOTARIZE_USER }}
|
||||||
|
MAC_NOTARIZE_PASS: ${{ secrets.MAC_NOTARIZE_PASS }}
|
||||||
|
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
|
||||||
|
MACOS_CERT_PASS: ${{ secrets.MACOS_CERT_PASS }}
|
||||||
|
MACOS_CERT_USER: ${{ secrets.MACOS_CERT_USER }}
|
||||||
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
|
MACOS_CERT_PASSPHRASE: ${{ secrets.MACOS_CERT_PASSPHRASE }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cura-installer-create:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { os: macos-11, os_id: 'mac' }
|
||||||
|
- { os: windows-2022, os_id: 'win64' }
|
||||||
|
- { os: ubuntu-20.04, os_id: 'linux' }
|
||||||
|
- { os: ubuntu-22.04, os_id: 'linux-modern' }
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Python and pip
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10.x'
|
||||||
|
cache: 'pip'
|
||||||
|
cache-dependency-path: .github/workflows/requirements-conan-package.txt
|
||||||
|
|
||||||
|
- name: Install Python requirements for runner
|
||||||
|
run: pip install -r .github/workflows/requirements-conan-package.txt
|
||||||
|
|
||||||
|
- name: Use Conan download cache (Bash)
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache"
|
||||||
|
|
||||||
|
- name: Use Conan download cache (Powershell)
|
||||||
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
run: conan config set storage.download_cache="C:\Users\runneradmin\.conan\conan_download_cache"
|
||||||
|
|
||||||
|
- name: Cache Conan local repository packages (Bash)
|
||||||
|
uses: actions/cache@v3
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
$HOME/.conan/data
|
||||||
|
$HOME/.conan/conan_download_cache
|
||||||
|
key: conan-${{ runner.os }}-${{ runner.arch }}-installer-cache
|
||||||
|
|
||||||
|
- name: Cache Conan local repository packages (Powershell)
|
||||||
|
uses: actions/cache@v3
|
||||||
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
C:\Users\runneradmin\.conan\data
|
||||||
|
C:\.conan
|
||||||
|
C:\Users\runneradmin\.conan\conan_download_cache
|
||||||
|
key: conan-${{ runner.os }}-${{ runner.arch }}-installer-cache
|
||||||
|
|
||||||
|
- name: Install MacOS system requirements
|
||||||
|
if: ${{ runner.os == 'Macos' }}
|
||||||
|
run: brew install autoconf automake ninja create-dmg
|
||||||
|
|
||||||
|
- name: Install Linux system requirements
|
||||||
|
if: ${{ runner.os == 'Linux' }}
|
||||||
|
run: |
|
||||||
|
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||||
|
sudo apt update
|
||||||
|
sudo apt upgrade
|
||||||
|
sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config -y
|
||||||
|
wget --no-check-certificate --quiet https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O $GITHUB_WORKSPACE/appimagetool
|
||||||
|
chmod +x $GITHUB_WORKSPACE/appimagetool
|
||||||
|
echo "APPIMAGETOOL_LOCATION=$GITHUB_WORKSPACE/appimagetool" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install GCC-12 on ubuntu-22.04
|
||||||
|
if: ${{ matrix.os == 'ubuntu-22.04' }}
|
||||||
|
run: |
|
||||||
|
sudo apt install g++-12 gcc-12 -y
|
||||||
|
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12
|
||||||
|
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12
|
||||||
|
|
||||||
|
- name: Create the default Conan profile
|
||||||
|
run: conan profile new default --detect
|
||||||
|
|
||||||
|
- name: Configure GPG Key Linux (Bash)
|
||||||
|
if: ${{ runner.os == 'Linux' }}
|
||||||
|
run: echo -n "$GPG_PRIVATE_KEY" | base64 --decode | gpg --import
|
||||||
|
|
||||||
|
- name: Configure Macos keychain (Bash)
|
||||||
|
id: macos-keychain
|
||||||
|
if: ${{ runner.os == 'Macos' }}
|
||||||
|
uses: apple-actions/import-codesign-certs@v1
|
||||||
|
with:
|
||||||
|
p12-file-base64: ${{ secrets.MACOS_CERT_P12 }}
|
||||||
|
p12-password: ${{ secrets.MACOS_CERT_PASSPHRASE }}
|
||||||
|
|
||||||
|
- name: Clean Conan local cache
|
||||||
|
if: ${{ inputs.conan_clean_local_cache }}
|
||||||
|
run: conan remove "*" -f
|
||||||
|
|
||||||
|
- name: Get Conan configuration from branch
|
||||||
|
if: ${{ 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
|
||||||
|
if: ${{ inputs.conan_config_branch == '' }}
|
||||||
|
run: conan config install https://github.com/Ultimaker/conan-config.git
|
||||||
|
|
||||||
|
- name: Create the Packages
|
||||||
|
run: conan install ${{ inputs.cura_conan_version }} ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=${{ inputs.enterprise }} -o cura:staging=${{ inputs.staging }} --json "cura_inst/conan_install_info.json"
|
||||||
|
|
||||||
|
- name: Set Environment variables for Cura (bash)
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
run: |
|
||||||
|
. ./cura_inst/bin/activate_github_actions_env.sh
|
||||||
|
. ./cura_inst/bin/activate_github_actions_version_env.sh
|
||||||
|
|
||||||
|
- name: Set Environment variables for Cura (Powershell)
|
||||||
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
run: |
|
||||||
|
.\cura_inst\Scripts\activate_github_actions_env.ps1
|
||||||
|
.\cura_inst\Scripts\activate_github_actions_version_env.ps1
|
||||||
|
|
||||||
|
- name: Unlock Macos keychain (Bash)
|
||||||
|
if: ${{ runner.os == 'Macos' }}
|
||||||
|
run: security unlock -p $TEMP_KEYCHAIN_PASSWORD signing_temp.keychain
|
||||||
|
env:
|
||||||
|
TEMP_KEYCHAIN_PASSWORD: ${{ steps.macos-keychain.outputs.keychain-password }}
|
||||||
|
|
||||||
|
# FIXME: This is a workaround to ensure that we use and pack a shared library for OpenSSL 1.1.1l. We currently compile
|
||||||
|
# OpenSSL statically for CPython, but our Python Dependenies (such as PyQt6) require a shared library.
|
||||||
|
# Because Conan won't allow for building the same library with two different options (easily) we need to install it explicitly
|
||||||
|
# and do a manual copy to the VirtualEnv, such that Pyinstaller can find it.
|
||||||
|
|
||||||
|
- name: Install OpenSSL shared
|
||||||
|
run: conan install openssl/1.1.1l@_/_ --build=missing --update -o openssl:shared=True -g deploy
|
||||||
|
|
||||||
|
- name: Copy OpenSSL shared (Bash)
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
run: |
|
||||||
|
cp ./openssl/lib/*.so* ./cura_inst/bin/ || true
|
||||||
|
cp ./openssl/lib/*.dylib* ./cura_inst/bin/ || true
|
||||||
|
|
||||||
|
- name: Copy OpenSSL shared (Powershell)
|
||||||
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
run: |
|
||||||
|
cp openssl/bin/*.dll ./cura_inst/Scripts/
|
||||||
|
cp openssl/lib/*.lib ./cura_inst/Lib/
|
||||||
|
|
||||||
|
- name: Create the Cura dist
|
||||||
|
run: pyinstaller ./cura_inst/Ultimaker-Cura.spec
|
||||||
|
|
||||||
|
- name: Archive the artifacts (bash)
|
||||||
|
if: ${{ github.event.inputs.installer == 'false' && runner.os != 'Windows' }}
|
||||||
|
run: tar -zcf "./Ultimaker-Cura-$CURA_VERSION_FULL-${{ matrix.os_id }}.tar.gz" "./Ultimaker-Cura/"
|
||||||
|
working-directory: dist
|
||||||
|
|
||||||
|
- name: Archive the artifacts (Powershell)
|
||||||
|
if: ${{ github.event.inputs.installer == 'false' && runner.os == 'Windows' }}
|
||||||
|
run: Compress-Archive -Path ".\Ultimaker-Cura" -DestinationPath ".\Ultimaker-Cura-$Env:CURA_VERSION_FULL-${{ matrix.os_id }}.zip"
|
||||||
|
working-directory: dist
|
||||||
|
|
||||||
|
- name: Create the Windows exe installer (Powershell)
|
||||||
|
if: ${{ github.event.inputs.installer == 'true' && runner.os == 'Windows' }}
|
||||||
|
run: |
|
||||||
|
python ..\cura_inst\packaging\NSIS\create_windows_installer.py ../cura_inst . "Ultimaker-Cura-$Env:CURA_VERSION_FULL-${{ matrix.os_id }}.exe"
|
||||||
|
working-directory: dist
|
||||||
|
|
||||||
|
- name: Create the Linux AppImage (Bash)
|
||||||
|
if: ${{ github.event.inputs.installer == 'true' && runner.os == 'Linux' }}
|
||||||
|
run: python ../cura_inst/packaging/AppImage/create_appimage.py ./Ultimaker-Cura $CURA_VERSION_FULL "Ultimaker-Cura-$CURA_VERSION_FULL-${{ matrix.os_id }}.AppImage"
|
||||||
|
working-directory: dist
|
||||||
|
|
||||||
|
- name: Create the MacOS dmg (Bash)
|
||||||
|
if: ${{ github.event.inputs.installer == 'true' && runner.os == 'Macos' }}
|
||||||
|
run: python ../cura_inst/packaging/dmg/dmg_sign_noterize.py ../cura_inst . "Ultimaker-Cura-$CURA_VERSION_FULL-${{ matrix.os_id }}.dmg"
|
||||||
|
working-directory: dist
|
||||||
|
|
||||||
|
- name: Upload the artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: Ultimaker-Cura-${{ env.CURA_VERSION_FULL }}-${{ matrix.os_id }}
|
||||||
|
path: |
|
||||||
|
dist/*.tar.gz
|
||||||
|
dist/*.zip
|
||||||
|
dist/*.exe
|
||||||
|
dist/*.msi
|
||||||
|
dist/*.dmg
|
||||||
|
dist/*.AppImage
|
||||||
|
dist/*.asc
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
|
notify-export:
|
||||||
|
if: ${{ always() }}
|
||||||
|
needs: [ cura-installer-create ]
|
||||||
|
|
||||||
|
uses: ultimaker/cura/.github/workflows/notify.yml@main
|
||||||
|
with:
|
||||||
|
success: ${{ contains(join(needs.*.result, ','), 'success') }}
|
||||||
|
success_title: "Create the Cura distributions"
|
||||||
|
success_body: "Installers for ${{ inputs.cura_conan_version }}"
|
||||||
|
failure_title: "Failed to create the Cura distributions"
|
||||||
|
failure_body: "Failed to create at least 1 installer for ${{ inputs.cura_conan_version }}"
|
||||||
|
secrets: inherit
|
2
.github/workflows/no-response.yml
vendored
|
@ -7,7 +7,7 @@ on:
|
||||||
types: [created]
|
types: [created]
|
||||||
schedule:
|
schedule:
|
||||||
# Schedule for ten minutes after the hour, every hour
|
# Schedule for ten minutes after the hour, every hour
|
||||||
- cron: '10 * * * *'
|
- cron: '* */12 * * *'
|
||||||
|
|
||||||
# By specifying the access of one of the scopes, all of those that are not
|
# By specifying the access of one of the scopes, all of those that are not
|
||||||
# specified are set to 'none'.
|
# specified are set to 'none'.
|
||||||
|
|
54
.github/workflows/notify.yml
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
name: Get Conan Recipe Version
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
success:
|
||||||
|
required: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
success_title:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
success_body:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
failure_title:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
failure_body:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
slackNotification:
|
||||||
|
name: Slack Notification
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Slack notify on-success
|
||||||
|
if: ${{ inputs.success }}
|
||||||
|
uses: rtCamp/action-slack-notify@v2
|
||||||
|
env:
|
||||||
|
SLACK_USERNAME: ${{ github.repository }}
|
||||||
|
SLACK_COLOR: green
|
||||||
|
SLACK_ICON: https://github.com/Ultimaker/Cura/blob/main/icons/cura-128.png?raw=true
|
||||||
|
SLACK_TITLE: ${{ inputs.success_title }}
|
||||||
|
SLACK_MESSAGE: ${{ inputs.success_body }}
|
||||||
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||||
|
|
||||||
|
- name: Slack notify on-failure
|
||||||
|
if: ${{ !inputs.success }}
|
||||||
|
uses: rtCamp/action-slack-notify@v2
|
||||||
|
env:
|
||||||
|
SLACK_USERNAME: ${{ github.repository }}
|
||||||
|
SLACK_COLOR: red
|
||||||
|
SLACK_ICON: https://github.com/Ultimaker/Cura/blob/main/icons/cura-128.png?raw=true
|
||||||
|
SLACK_TITLE: ${{ inputs.failure_title }}
|
||||||
|
SLACK_MESSAGE: ${{ inputs.failure_body }}
|
||||||
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
2
.github/workflows/requirements-conan-package.txt
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
conan!=1.51.0,!=1.51.1,!=1.51.2,!=1.51.3
|
||||||
|
sip
|
164
.github/workflows/unit-test.yml
vendored
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
---
|
||||||
|
name: unit-test
|
||||||
|
# FIXME: This should be a reusable workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'plugins/**'
|
||||||
|
- 'resources/**'
|
||||||
|
- 'cura/**'
|
||||||
|
- 'icons/**'
|
||||||
|
- 'tests/**'
|
||||||
|
- 'packaging/**'
|
||||||
|
- '.github/workflows/conan-*.yml'
|
||||||
|
- '.github/workflows/unit-test.yml'
|
||||||
|
- '.github/workflows/notify.yml'
|
||||||
|
- '.github/workflows/requirements-conan-package.txt'
|
||||||
|
- 'requirements*.txt'
|
||||||
|
- 'conanfile.py'
|
||||||
|
- 'conandata.yml'
|
||||||
|
- 'GitVersion.yml'
|
||||||
|
- '*.jinja'
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'CURA-*'
|
||||||
|
- '[1-9]+.[0-9]+'
|
||||||
|
tags:
|
||||||
|
- '[0-9]+.[0-9]+.[0-9]+'
|
||||||
|
- '[0-9]+.[0-9]+-beta'
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'plugins/**'
|
||||||
|
- 'resources/**'
|
||||||
|
- 'cura/**'
|
||||||
|
- 'icons/**'
|
||||||
|
- 'tests/**'
|
||||||
|
- 'packaging/**'
|
||||||
|
- '.github/workflows/conan-*.yml'
|
||||||
|
- '.github/workflows/unit-test.yml'
|
||||||
|
- '.github/workflows/notify.yml'
|
||||||
|
- '.github/workflows/requirements-conan-package.txt'
|
||||||
|
- 'requirements*.txt'
|
||||||
|
- 'conanfile.py'
|
||||||
|
- 'conandata.yml'
|
||||||
|
- 'GitVersion.yml'
|
||||||
|
- '*.jinja'
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- '[1-9]+.[0-9]+'
|
||||||
|
tags:
|
||||||
|
- '[0-9]+.[0-9]+.[0-9]+'
|
||||||
|
- '[0-9]+.[0-9]+-beta'
|
||||||
|
|
||||||
|
env:
|
||||||
|
CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }}
|
||||||
|
CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }}
|
||||||
|
CONAN_LOGIN_USERNAME_CURA_CE: ${{ secrets.CONAN_USER }}
|
||||||
|
CONAN_PASSWORD_CURA_CE: ${{ secrets.CONAN_PASS }}
|
||||||
|
CONAN_LOG_RUN_TO_OUTPUT: 1
|
||||||
|
CONAN_LOGGING_LEVEL: info
|
||||||
|
CONAN_NON_INTERACTIVE: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
conan-recipe-version:
|
||||||
|
uses: ultimaker/cura/.github/workflows/conan-recipe-version.yml@main
|
||||||
|
with:
|
||||||
|
project_name: cura
|
||||||
|
|
||||||
|
testing:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs: [ conan-recipe-version ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Python and pip
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10.x'
|
||||||
|
architecture: 'x64'
|
||||||
|
cache: 'pip'
|
||||||
|
cache-dependency-path: .github/workflows/requirements-conan-package.txt
|
||||||
|
|
||||||
|
- name: Install Python requirements and Create default Conan profile
|
||||||
|
run: |
|
||||||
|
pip install -r requirements-conan-package.txt
|
||||||
|
conan profile new default --detect
|
||||||
|
working-directory: .github/workflows/
|
||||||
|
|
||||||
|
- name: Use Conan download cache (Bash)
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache"
|
||||||
|
|
||||||
|
- name: Cache Conan local repository packages (Bash)
|
||||||
|
uses: actions/cache@v3
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
$HOME/.conan/data
|
||||||
|
$HOME/.conan/conan_download_cache
|
||||||
|
key: conan-${{ runner.os }}-${{ runner.arch }}-unit-cache
|
||||||
|
|
||||||
|
- name: Install Linux system requirements
|
||||||
|
if: ${{ runner.os == 'Linux' }}
|
||||||
|
run: 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 -y
|
||||||
|
|
||||||
|
- name: Get Conan configuration
|
||||||
|
run: conan config install https://github.com/Ultimaker/conan-config.git
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: conan install . ${{ needs.conan-recipe-version.outputs.recipe_id_full }} --build=missing --update -o cura:devtools=True -g VirtualPythonEnv -if venv
|
||||||
|
|
||||||
|
- name: Upload the Dependency package(s)
|
||||||
|
run: conan upload "*" -r cura --all -c
|
||||||
|
|
||||||
|
- name: Set Environment variables for Cura (bash)
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
run: |
|
||||||
|
. ./venv/bin/activate_github_actions_env.sh
|
||||||
|
|
||||||
|
- name: Run Unit Test
|
||||||
|
id: run-test
|
||||||
|
run: |
|
||||||
|
pytest --junitxml=junit_cura.xml
|
||||||
|
working-directory: tests
|
||||||
|
|
||||||
|
- name: Upload Test Results
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: Test Results
|
||||||
|
path: "tests/**/*.xml"
|
||||||
|
|
||||||
|
publish-test-results:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs: [ testing ]
|
||||||
|
if: success() || failure()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Python and pip
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10.x'
|
||||||
|
architecture: 'x64'
|
||||||
|
cache: 'pip'
|
||||||
|
cache-dependency-path: .github/workflows/requirements-conan-package.txt
|
||||||
|
|
||||||
|
- name: Download Artifacts
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
|
||||||
|
- name: Publish Unit Test Results
|
||||||
|
id: test-results
|
||||||
|
uses: EnricoMi/publish-unit-test-result-action@v1
|
||||||
|
with:
|
||||||
|
files: "artifacts/**/*.xml"
|
||||||
|
|
||||||
|
- name: Conclusion
|
||||||
|
run: echo "Conclusion is ${{ fromJSON( steps.test-results.outputs.json ).conclusion }}"
|
11
.gitignore
vendored
|
@ -89,4 +89,13 @@ CuraEngine
|
||||||
#Prevents import failures when plugin running tests
|
#Prevents import failures when plugin running tests
|
||||||
plugins/__init__.py
|
plugins/__init__.py
|
||||||
|
|
||||||
/venv
|
venv/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
conaninfo.txt
|
||||||
|
conan.lock
|
||||||
|
conan_imports_manifest.txt
|
||||||
|
conanbuildinfo.txt
|
||||||
|
graph_info.json
|
||||||
|
Ultimaker-Cura.spec
|
||||||
|
.run/
|
||||||
|
|
25
.run_templates/pycharm_cura_run.run.xml.jinja
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="{{ name }}" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
|
||||||
|
<module name="{{ module_name }}" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />{% for key, value in env_vars.items() %}
|
||||||
|
<env name="{{ key }}" value="{{ value }}" />{% endfor %}
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="{{ sdk_path }}" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/{{ script_name }}" />
|
||||||
|
<option name="PARAMETERS" value="{{ parameters }}" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
23
.run_templates/pycharm_cura_test.run.xml.jinja
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="{{ name }}" type="tests" factoryName="py.test" nameIsGenerated="true">
|
||||||
|
<module name="{{ module_name }}" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />{% for key, value in env_vars.items() %}
|
||||||
|
<env name="{{ key }}" value="{{ value }}" />{% endfor %}
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="{{ sdk_path }}" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/tests" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="_new_keywords" value="""" />
|
||||||
|
<option name="_new_parameters" value="""" />
|
||||||
|
<option name="_new_additionalArguments" value="""" />
|
||||||
|
<option name="_new_target" value=""$PROJECT_DIR$/{{ script_name }}"" />
|
||||||
|
<option name="_new_targetType" value=""PATH"" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -1,6 +1,8 @@
|
||||||
# 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.
|
||||||
|
|
||||||
|
# For MSVC flags, will be ignored on non-Windows OS's and this project in general. Only needed for cura-build-environment.
|
||||||
|
cmake_policy(SET CMP0091 NEW)
|
||||||
project(cura)
|
project(cura)
|
||||||
cmake_minimum_required(VERSION 3.18)
|
cmake_minimum_required(VERSION 3.18)
|
||||||
|
|
||||||
|
|
14
CuraVersion.py.jinja
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# Copyright (c) 2022 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
CuraAppName = "{{ cura_app_name }}"
|
||||||
|
CuraAppDisplayName = "{{ cura_app_display_name }}"
|
||||||
|
CuraVersion = "{{ cura_version }}"
|
||||||
|
CuraBuildType = "{{ cura_build_type }}"
|
||||||
|
CuraDebugMode = {{ cura_debug_mode }}
|
||||||
|
CuraCloudAPIRoot = "{{ cura_cloud_api_root }}"
|
||||||
|
CuraCloudAPIVersion = "{{ cura_cloud_api_version }}"
|
||||||
|
CuraCloudAccountAPIRoot = "{{ cura_cloud_account_api_root }}"
|
||||||
|
CuraMarketplaceRoot = "{{ cura_marketplace_root }}"
|
||||||
|
CuraDigitalFactoryURL = "{{ cura_digital_factory_url }}"
|
||||||
|
CuraLatestURL = "{{ cura_latest_url }}"
|
45
Dockerfile
|
@ -1,45 +0,0 @@
|
||||||
FROM ultimaker/cura-build-environment:1
|
|
||||||
|
|
||||||
# Environment vars for easy configuration
|
|
||||||
ENV CURA_APP_DIR=/srv/cura
|
|
||||||
|
|
||||||
# Ensure our sources dir exists
|
|
||||||
RUN mkdir $CURA_APP_DIR
|
|
||||||
|
|
||||||
# Setup CuraEngine
|
|
||||||
ENV CURA_ENGINE_BRANCH=master
|
|
||||||
WORKDIR $CURA_APP_DIR
|
|
||||||
RUN git clone -b $CURA_ENGINE_BRANCH --depth 1 https://github.com/Ultimaker/CuraEngine
|
|
||||||
WORKDIR $CURA_APP_DIR/CuraEngine
|
|
||||||
RUN mkdir build
|
|
||||||
WORKDIR $CURA_APP_DIR/CuraEngine/build
|
|
||||||
RUN cmake3 ..
|
|
||||||
RUN make
|
|
||||||
RUN make install
|
|
||||||
|
|
||||||
# TODO: setup libCharon
|
|
||||||
|
|
||||||
# Setup Uranium
|
|
||||||
ENV URANIUM_BRANCH=master
|
|
||||||
WORKDIR $CURA_APP_DIR
|
|
||||||
RUN git clone -b $URANIUM_BRANCH --depth 1 https://github.com/Ultimaker/Uranium
|
|
||||||
|
|
||||||
# Setup materials
|
|
||||||
ENV MATERIALS_BRANCH=master
|
|
||||||
WORKDIR $CURA_APP_DIR
|
|
||||||
RUN git clone -b $MATERIALS_BRANCH --depth 1 https://github.com/Ultimaker/fdm_materials materials
|
|
||||||
|
|
||||||
# Setup Cura
|
|
||||||
WORKDIR $CURA_APP_DIR/Cura
|
|
||||||
ADD . .
|
|
||||||
RUN mv $CURA_APP_DIR/materials resources/materials
|
|
||||||
|
|
||||||
# Make sure Cura can find CuraEngine
|
|
||||||
RUN ln -s /usr/local/bin/CuraEngine $CURA_APP_DIR/Cura
|
|
||||||
|
|
||||||
# Run Cura
|
|
||||||
WORKDIR $CURA_APP_DIR/Cura
|
|
||||||
ENV PYTHONPATH=${PYTHONPATH}:$CURA_APP_DIR/Uranium
|
|
||||||
RUN chmod +x ./CuraEngine
|
|
||||||
RUN chmod +x ./run_in_docker.sh
|
|
||||||
CMD "./run_in_docker.sh"
|
|
55
GitVersion.yml
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
mode: ContinuousDelivery
|
||||||
|
next-version: 5.1
|
||||||
|
branches:
|
||||||
|
main:
|
||||||
|
regex: ^main$
|
||||||
|
mode: ContinuousDelivery
|
||||||
|
tag: alpha
|
||||||
|
increment: None
|
||||||
|
prevent-increment-of-merged-branch-version: true
|
||||||
|
track-merge-target: false
|
||||||
|
source-branches: [ ]
|
||||||
|
tracks-release-branches: false
|
||||||
|
is-release-branch: false
|
||||||
|
is-mainline: true
|
||||||
|
pre-release-weight: 55000
|
||||||
|
develop:
|
||||||
|
regex: ^CURA-.*$
|
||||||
|
mode: ContinuousDelivery
|
||||||
|
tag: alpha
|
||||||
|
increment: None
|
||||||
|
prevent-increment-of-merged-branch-version: false
|
||||||
|
track-merge-target: true
|
||||||
|
source-branches: [ 'main' ]
|
||||||
|
tracks-release-branches: true
|
||||||
|
is-release-branch: false
|
||||||
|
is-mainline: false
|
||||||
|
pre-release-weight: 0
|
||||||
|
release:
|
||||||
|
regex: ^[\d]+\.[\d]+$
|
||||||
|
mode: ContinuousDelivery
|
||||||
|
tag: beta
|
||||||
|
increment: None
|
||||||
|
prevent-increment-of-merged-branch-version: true
|
||||||
|
track-merge-target: false
|
||||||
|
source-branches: [ 'main' ]
|
||||||
|
tracks-release-branches: false
|
||||||
|
is-release-branch: true
|
||||||
|
is-mainline: false
|
||||||
|
pre-release-weight: 30000
|
||||||
|
pull-request-main:
|
||||||
|
regex: ^(pull|pull\-requests|pr)[/-]
|
||||||
|
mode: ContinuousDelivery
|
||||||
|
tag: alpha+
|
||||||
|
increment: Inherit
|
||||||
|
prevent-increment-of-merged-branch-version: true
|
||||||
|
tag-number-pattern: '[/-](?<number>\d+)[-/]'
|
||||||
|
track-merge-target: true
|
||||||
|
source-branches: [ 'main' ]
|
||||||
|
tracks-release-branches: false
|
||||||
|
is-release-branch: false
|
||||||
|
is-mainline: false
|
||||||
|
pre-release-weight: 30000
|
||||||
|
ignore:
|
||||||
|
sha: [ ]
|
||||||
|
merge-message-formats: { }
|
125
README.md
|
@ -1,61 +1,96 @@
|
||||||
Cura
|
|
||||||
====
|
|
||||||
Ultimaker Cura is a state-of-the-art slicer application to prepare your 3D models for printing with a 3D printer. With hundreds of settings and hundreds of community-managed print profiles, Ultimaker Cura is sure to lead your next project to a success.
|
|
||||||
|
|
||||||

|
<br>
|
||||||
|
|
||||||
Logging Issues
|
<div align = center>
|
||||||
------------
|
|
||||||
For crashes and similar issues, please attach the following information:
|
|
||||||
|
|
||||||
* (On Windows) The log as produced by dxdiag (start -> run -> dxdiag -> save output)
|
[![Badge Issues]][Issues]
|
||||||
* The Cura GUI log file, located at
|
[![Badge PullRequests]][PullRequests]
|
||||||
* `%APPDATA%\cura\<Cura version>\cura.log` (Windows), or usually `C:\Users\<your username>\AppData\Roaming\cura\<Cura version>\cura.log`
|
[![Badge Closed]][Closed]
|
||||||
* `$HOME/Library/Application Support/cura/<Cura version>/cura.log` (OSX)
|
|
||||||
* `$HOME/.local/share/cura/<Cura version>/cura.log` (Ubuntu/Linux)
|
|
||||||
|
|
||||||
If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder
|
[![Badge Size]][#]
|
||||||
|
[![Badge License]][License]
|
||||||
|
[![Badge Contributors]][Contributors]
|
||||||
|
|
||||||
For additional support, you could also ask in the [#cura channel](https://web.libera.chat/#cura) on [libera.chat](https://libera.chat/). For help with development, there is also the [#cura-dev channel](https://web.libera.chat/#cura-dev).
|
[![Badge Test]][Test]
|
||||||
|
[![Badge Conan]][Conan]
|
||||||
|
|
||||||
Dependencies
|
<br>
|
||||||
------------
|
<br>
|
||||||
* [Uranium](https://github.com/Ultimaker/Uranium) Cura is built on top of the Uranium framework.
|
|
||||||
* [CuraEngine](https://github.com/Ultimaker/CuraEngine) This will be needed at runtime to perform the actual slicing.
|
|
||||||
* [fdm_materials](https://github.com/Ultimaker/fdm_materials) Required to load a printer that has swappable material profiles.
|
|
||||||
* [PySerial](https://github.com/pyserial/pyserial) Only required for USB printing support.
|
|
||||||
* [python-zeroconf](https://github.com/jstasiak/python-zeroconf) Only required to detect mDNS-enabled printers.
|
|
||||||
|
|
||||||
For a list of required Python packages, with their recommended version, see `requirements.txt`.
|
![Logo]
|
||||||
|
|
||||||
This list is not exhaustive at the moment, please check the links in the next section for more details.
|
# Ultimaker Cura
|
||||||
|
|
||||||
Build scripts
|
*State-of-the-art slicer app to prepare* <br>
|
||||||
-------------
|
*your 3D models for your 3D printer.*
|
||||||
Please check out [cura-build](https://github.com/Ultimaker/cura-build) for detailed building instructions.
|
|
||||||
|
|
||||||
If you want to build the entire environment from scratch before building Cura as well, [cura-build-environment](https://github.com/Ultimaker/cura-build-environment) might be a starting point before cura-build. (Again, see cura-build for more details.)
|
*With hundreds of settings & community-managed print profiles,* <br>
|
||||||
|
*Ultimaker Cura is sure to lead your next project to a success.*
|
||||||
|
|
||||||
Running from Source
|
<br>
|
||||||
-------------
|
<br>
|
||||||
Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Running-Cura-from-Source) for details about running Cura from source.
|
|
||||||
|
|
||||||
Plugins
|
[![Button Building]][Building]
|
||||||
-------------
|
[![Button Plugins]][Plugins]
|
||||||
Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Plugin-Directory) for details about creating and using plugins.
|
[![Button Machines]][Machines]
|
||||||
|
|
||||||
Supported printers
|
[![Button Report]][Report]
|
||||||
-------------
|
[![Button Settings]][Settings]
|
||||||
Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Adding-new-machine-profiles-to-Cura) for guidelines about adding support for new machines.
|
[![Button Localize]][Localize]
|
||||||
|
|
||||||
Configuring Cura
|
<br>
|
||||||
----------------
|
<br>
|
||||||
Please check out [Wiki page](https://github.com/Ultimaker/Cura/wiki/Cura-Settings) about configuration options for developers.
|
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="./cura-logo.PNG">
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="./cura-logo-dark.PNG">
|
||||||
|
<img alt="Shows cura open on the preview screen with a large benchy model in the center." src="./cura-logo.PNG">
|
||||||
|
</picture>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
|
||||||
|
<!----------------------------------------------------------------------------->
|
||||||
|
|
||||||
|
[Contributors]: https://github.com/Ultimaker/Cura/graphs/contributors
|
||||||
|
[PullRequests]: https://github.com/Ultimaker/Cura/pulls
|
||||||
|
[Machines]: https://github.com/Ultimaker/Cura/wiki/Adding-new-machine-profiles-to-Cura
|
||||||
|
[Building]: https://github.com/Ultimaker/Cura/wiki/Running-Cura-from-Source
|
||||||
|
[Localize]: https://github.com/Ultimaker/Cura/wiki/Translating-Cura
|
||||||
|
[Settings]: https://github.com/Ultimaker/Cura/wiki/Cura-Settings
|
||||||
|
[Plugins]: https://github.com/Ultimaker/Cura/wiki/Plugin-Directory
|
||||||
|
[Closed]: https://github.com/Ultimaker/Cura/issues?q=is%3Aissue+is%3Aclosed
|
||||||
|
[Issues]: https://github.com/Ultimaker/Cura/issues
|
||||||
|
[Conan]: https://github.com/Ultimaker/Cura/actions/workflows/conan-package.yml
|
||||||
|
[Test]: https://github.com/Ultimaker/Cura/actions/workflows/unit-test.yml
|
||||||
|
|
||||||
|
[License]: LICENSE
|
||||||
|
[Report]: docs/Report.md
|
||||||
|
[Logo]: resources/images/cura-icon.png
|
||||||
|
[#]: #
|
||||||
|
|
||||||
|
|
||||||
|
<!---------------------------------[ Badges ]---------------------------------->
|
||||||
|
|
||||||
|
[Badge Contributors]: https://img.shields.io/github/contributors/ultimaker/cura?style=for-the-badge&logoColor=white&labelColor=db5e8a&color=ab4a6c&logo=GitHub
|
||||||
|
[Badge PullRequests]: https://img.shields.io/github/issues-pr/ultimaker/cura?style=for-the-badge&logoColor=white&labelColor=bb9f3e&color=937d31&logo=GitExtensions
|
||||||
|
[Badge License]: https://img.shields.io/badge/License-LGPL3-336887.svg?style=for-the-badge&labelColor=458cb5&logoColor=white&logo=GNU
|
||||||
|
[Badge Closed]: https://img.shields.io/github/issues-closed/ultimaker/cura?style=for-the-badge&logoColor=white&labelColor=629944&color=446a30&logo=AddThis
|
||||||
|
[Badge Issues]: https://img.shields.io/github/issues/ultimaker/cura?style=for-the-badge&logoColor=white&labelColor=c34360&color=933349&logo=AdBlock
|
||||||
|
[Badge Conan]: https://img.shields.io/github/workflow/status/Ultimaker/Cura/conan-package?style=for-the-badge&logoColor=white&labelColor=6185aa&color=4c6987&logo=Conan&label=Conan%20Package
|
||||||
|
[Badge Test]: https://img.shields.io/github/workflow/status/Ultimaker/Cura/unit-test?style=for-the-badge&logoColor=white&labelColor=4a999d&color=346c6e&logo=Codacy&label=Unit%20Test
|
||||||
|
[Badge Size]: https://img.shields.io/github/repo-size/ultimaker/cura?style=for-the-badge&logoColor=white&labelColor=715a97&color=584674&logo=GoogleAnalytics
|
||||||
|
|
||||||
|
|
||||||
|
<!---------------------------------[ Buttons ]--------------------------------->
|
||||||
|
|
||||||
|
[Button Localize]: https://img.shields.io/badge/Help_Localize-e2467d?style=for-the-badge&logoColor=white&logo=GoogleTranslate
|
||||||
|
[Button Machines]: https://img.shields.io/badge/Adding_Machines-yellow?style=for-the-badge&logoColor=white&logo=CloudFoundry
|
||||||
|
[Button Settings]: https://img.shields.io/badge/Configuration-00979D?style=for-the-badge&logoColor=white&logo=CodeReview
|
||||||
|
[Button Building]: https://img.shields.io/badge/Building_Cura-blue?style=for-the-badge&logoColor=white&logo=GitBook
|
||||||
|
[Button Plugins]: https://img.shields.io/badge/Plugin_Usage-569A31?style=for-the-badge&logoColor=white&logo=ROS
|
||||||
|
[Button Report]: https://img.shields.io/badge/Report_Issues-C9284D?style=for-the-badge&logoColor=white&logo=Cliqz
|
||||||
|
|
||||||
Translating Cura
|
|
||||||
----------------
|
|
||||||
Please check out [Wiki page](https://github.com/Ultimaker/Cura/wiki/Translating-Cura) about how to translate Cura into other languages.
|
|
||||||
|
|
||||||
License
|
|
||||||
----------------
|
|
||||||
Cura is released under the terms of the LGPLv3 or higher. A copy of this license should be included with the software.
|
|
||||||
|
|
273
Ultimaker-Cura.spec.jinja
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from PyInstaller.utils.hooks import collect_all
|
||||||
|
|
||||||
|
|
||||||
|
datas = {{ datas }}
|
||||||
|
binaries = {{ binaries }}
|
||||||
|
|
||||||
|
hiddenimports = {{ hiddenimports }}
|
||||||
|
|
||||||
|
{% for value in collect_all %}tmp_ret = collect_all('{{ value }}')
|
||||||
|
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
# Add dynamic libs in the venv bin/Script Path. This is needed because we might copy some additional libs
|
||||||
|
# e.q.: OpenSSL 1.1.1l in that directory with a separate:
|
||||||
|
# `conan install openssl@1.1.1l -g deploy && cp openssl/bin/*.so cura_inst/bin`
|
||||||
|
binaries.extend([(str(bin), ".") for bin in Path("{{ venv_script_path }}").glob("*.so*")])
|
||||||
|
binaries.extend([(str(bin), ".") for bin in Path("{{ venv_script_path }}").glob("*.dll")])
|
||||||
|
binaries.extend([(str(bin), ".") for bin in Path("{{ venv_script_path }}").glob("*.dylib")])
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
[{{ entrypoint }}],
|
||||||
|
pathex=[],
|
||||||
|
binaries=binaries,
|
||||||
|
datas=datas,
|
||||||
|
hiddenimports=hiddenimports,
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher,
|
||||||
|
noarchive=False
|
||||||
|
)
|
||||||
|
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
[],
|
||||||
|
exclude_binaries=True,
|
||||||
|
name=r'{{ name }}',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip={{ strip }},
|
||||||
|
upx={{ upx }},
|
||||||
|
console=False,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch={{ target_arch }},
|
||||||
|
codesign_identity=os.getenv('CODESIGN_IDENTITY', None),
|
||||||
|
entitlements_file={{ entitlements_file }},
|
||||||
|
icon={{ icon }}
|
||||||
|
)
|
||||||
|
|
||||||
|
coll = COLLECT(
|
||||||
|
exe,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
name=r'{{ name }}'
|
||||||
|
)
|
||||||
|
|
||||||
|
{% if macos == true %}
|
||||||
|
# PyInstaller seems to copy everything in the resource folder for the MacOS, this causes issues with codesigning and notarizing
|
||||||
|
# The folder structure should adhere to the one specified in Table 2-5
|
||||||
|
# https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1
|
||||||
|
# The class below is basically ducktyping the BUNDLE class of PyInstaller and using our own `assemble` method for more fine-grain and specific
|
||||||
|
# control. Some code of the method below is copied from:
|
||||||
|
# https://github.com/pyinstaller/pyinstaller/blob/22d1d2a5378228744cc95f14904dae1664df32c4/PyInstaller/building/osx.py#L115
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2005-2022, PyInstaller Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the GNU General Public License (version 2
|
||||||
|
# or later) with exception for distributing the bootloader.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import plistlib
|
||||||
|
import shutil
|
||||||
|
import PyInstaller.utils.osx as osxutils
|
||||||
|
from pathlib import Path
|
||||||
|
from PyInstaller.building.osx import BUNDLE
|
||||||
|
from PyInstaller.building.utils import (_check_path_overlap, _rmtree, add_suffix_to_extension, checkCache)
|
||||||
|
from PyInstaller.building.datastruct import logger
|
||||||
|
from PyInstaller.building.icon import normalize_icon_type
|
||||||
|
|
||||||
|
|
||||||
|
class UMBUNDLE(BUNDLE):
|
||||||
|
def assemble(self):
|
||||||
|
from PyInstaller.config import CONF
|
||||||
|
|
||||||
|
if _check_path_overlap(self.name) and os.path.isdir(self.name):
|
||||||
|
_rmtree(self.name)
|
||||||
|
logger.info("Building BUNDLE %s", self.tocbasename)
|
||||||
|
|
||||||
|
# Create a minimal Mac bundle structure.
|
||||||
|
macos_path = Path(self.name, "Contents", "MacOS")
|
||||||
|
resources_path = Path(self.name, "Contents", "Resources")
|
||||||
|
frameworks_path = Path(self.name, "Contents", "Frameworks")
|
||||||
|
os.makedirs(macos_path)
|
||||||
|
os.makedirs(resources_path)
|
||||||
|
os.makedirs(frameworks_path)
|
||||||
|
|
||||||
|
# Makes sure the icon exists and attempts to convert to the proper format if applicable
|
||||||
|
self.icon = normalize_icon_type(self.icon, ("icns",), "icns", CONF["workpath"])
|
||||||
|
|
||||||
|
# Ensure icon path is absolute
|
||||||
|
self.icon = os.path.abspath(self.icon)
|
||||||
|
|
||||||
|
# Copy icns icon to Resources directory.
|
||||||
|
shutil.copy(self.icon, os.path.join(self.name, 'Contents', 'Resources'))
|
||||||
|
|
||||||
|
# Key/values for a minimal Info.plist file
|
||||||
|
info_plist_dict = {
|
||||||
|
"CFBundleDisplayName": self.appname,
|
||||||
|
"CFBundleName": self.appname,
|
||||||
|
|
||||||
|
# Required by 'codesign' utility.
|
||||||
|
# The value for CFBundleIdentifier is used as the default unique name of your program for Code Signing
|
||||||
|
# purposes. It even identifies the APP for access to restricted OS X areas like Keychain.
|
||||||
|
#
|
||||||
|
# The identifier used for signing must be globally unique. The usual form for this identifier is a
|
||||||
|
# hierarchical name in reverse DNS notation, starting with the toplevel domain, followed by the company
|
||||||
|
# name, followed by the department within the company, and ending with the product name. Usually in the
|
||||||
|
# form: com.mycompany.department.appname
|
||||||
|
# CLI option --osx-bundle-identifier sets this value.
|
||||||
|
"CFBundleIdentifier": self.bundle_identifier,
|
||||||
|
"CFBundleExecutable": os.path.basename(self.exename),
|
||||||
|
"CFBundleIconFile": os.path.basename(self.icon),
|
||||||
|
"CFBundleInfoDictionaryVersion": "6.0",
|
||||||
|
"CFBundlePackageType": "APPL",
|
||||||
|
"CFBundleShortVersionString": self.version,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set some default values. But they still can be overwritten by the user.
|
||||||
|
if self.console:
|
||||||
|
# Setting EXE console=True implies LSBackgroundOnly=True.
|
||||||
|
info_plist_dict['LSBackgroundOnly'] = True
|
||||||
|
else:
|
||||||
|
# Let's use high resolution by default.
|
||||||
|
info_plist_dict['NSHighResolutionCapable'] = True
|
||||||
|
|
||||||
|
# Merge info_plist settings from spec file
|
||||||
|
if isinstance(self.info_plist, dict) and self.info_plist:
|
||||||
|
info_plist_dict.update(self.info_plist)
|
||||||
|
|
||||||
|
plist_filename = os.path.join(self.name, "Contents", "Info.plist")
|
||||||
|
with open(plist_filename, "wb") as plist_fh:
|
||||||
|
plistlib.dump(info_plist_dict, plist_fh)
|
||||||
|
|
||||||
|
links = []
|
||||||
|
_QT_BASE_PATH = {'PySide2', 'PySide6', 'PyQt5', 'PyQt6', 'PySide6'}
|
||||||
|
for inm, fnm, typ in self.toc:
|
||||||
|
# Adjust name for extensions, if applicable
|
||||||
|
inm, fnm, typ = add_suffix_to_extension(inm, fnm, typ)
|
||||||
|
inm = Path(inm)
|
||||||
|
fnm = Path(fnm)
|
||||||
|
# Copy files from cache. This ensures that are used files with relative paths to dynamic library
|
||||||
|
# dependencies (@executable_path)
|
||||||
|
if typ in ('EXTENSION', 'BINARY') or (typ == 'DATA' and inm.suffix == '.so'):
|
||||||
|
if any(['.' in p for p in inm.parent.parts]):
|
||||||
|
inm = Path(inm.name)
|
||||||
|
fnm = Path(checkCache(
|
||||||
|
str(fnm),
|
||||||
|
strip = self.strip,
|
||||||
|
upx = self.upx,
|
||||||
|
upx_exclude = self.upx_exclude,
|
||||||
|
dist_nm = str(inm),
|
||||||
|
target_arch = self.target_arch,
|
||||||
|
codesign_identity = self.codesign_identity,
|
||||||
|
entitlements_file = self.entitlements_file,
|
||||||
|
strict_arch_validation = (typ == 'EXTENSION'),
|
||||||
|
))
|
||||||
|
frame_dst = frameworks_path.joinpath(inm)
|
||||||
|
if not frame_dst.exists():
|
||||||
|
if frame_dst.is_dir():
|
||||||
|
os.makedirs(frame_dst, exist_ok = True)
|
||||||
|
else:
|
||||||
|
os.makedirs(frame_dst.parent, exist_ok = True)
|
||||||
|
shutil.copy(fnm, frame_dst, follow_symlinks = True)
|
||||||
|
macos_dst = macos_path.joinpath(inm)
|
||||||
|
if not macos_dst.exists():
|
||||||
|
if macos_dst.is_dir():
|
||||||
|
os.makedirs(macos_dst, exist_ok = True)
|
||||||
|
else:
|
||||||
|
os.makedirs(macos_dst.parent, exist_ok = True)
|
||||||
|
|
||||||
|
# Create relative symlink to the framework
|
||||||
|
symlink_to = Path(*[".." for p in macos_dst.relative_to(macos_path).parts], "Frameworks").joinpath(
|
||||||
|
frame_dst.relative_to(frameworks_path))
|
||||||
|
try:
|
||||||
|
macos_dst.symlink_to(symlink_to)
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if typ == 'DATA':
|
||||||
|
if any(['.' in p for p in inm.parent.parts]) or inm.suffix == '.so':
|
||||||
|
# Skip info dist egg and some not needed folders in tcl and tk, since they all contain dots in their files
|
||||||
|
logger.warning(f"Skipping DATA file {inm}")
|
||||||
|
continue
|
||||||
|
res_dst = resources_path.joinpath(inm)
|
||||||
|
if not res_dst.exists():
|
||||||
|
if res_dst.is_dir():
|
||||||
|
os.makedirs(res_dst, exist_ok = True)
|
||||||
|
else:
|
||||||
|
os.makedirs(res_dst.parent, exist_ok = True)
|
||||||
|
shutil.copy(fnm, res_dst, follow_symlinks = True)
|
||||||
|
macos_dst = macos_path.joinpath(inm)
|
||||||
|
if not macos_dst.exists():
|
||||||
|
if macos_dst.is_dir():
|
||||||
|
os.makedirs(macos_dst, exist_ok = True)
|
||||||
|
else:
|
||||||
|
os.makedirs(macos_dst.parent, exist_ok = True)
|
||||||
|
|
||||||
|
# Create relative symlink to the resource
|
||||||
|
symlink_to = Path(*[".." for p in macos_dst.relative_to(macos_path).parts], "Resources").joinpath(
|
||||||
|
res_dst.relative_to(resources_path))
|
||||||
|
try:
|
||||||
|
macos_dst.symlink_to(symlink_to)
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
macos_dst = macos_path.joinpath(inm)
|
||||||
|
if not macos_dst.exists():
|
||||||
|
if macos_dst.is_dir():
|
||||||
|
os.makedirs(macos_dst, exist_ok = True)
|
||||||
|
else:
|
||||||
|
os.makedirs(macos_dst.parent, exist_ok = True)
|
||||||
|
shutil.copy(fnm, macos_dst, follow_symlinks = True)
|
||||||
|
|
||||||
|
# Sign the bundle
|
||||||
|
logger.info('Signing the BUNDLE...')
|
||||||
|
try:
|
||||||
|
osxutils.sign_binary(self.name, self.codesign_identity, self.entitlements_file, deep = True)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error while signing the bundle: {e}")
|
||||||
|
logger.warning("You will need to sign the bundle manually!")
|
||||||
|
|
||||||
|
logger.info(f"Building BUNDLE {self.tocbasename} completed successfully.")
|
||||||
|
|
||||||
|
app = UMBUNDLE(
|
||||||
|
coll,
|
||||||
|
name='{{ name }}.app',
|
||||||
|
icon={{ icon }},
|
||||||
|
bundle_identifier={{ osx_bundle_identifier }},
|
||||||
|
version={{ version }},
|
||||||
|
info_plist={
|
||||||
|
'CFBundleDisplayName': '{{ display_name }}',
|
||||||
|
'NSPrincipalClass': 'NSApplication',
|
||||||
|
'CFBundleDevelopmentRegion': 'English',
|
||||||
|
'CFBundleExecutable': '{{ name }}',
|
||||||
|
'CFBundleInfoDictionaryVersion': '6.0',
|
||||||
|
'CFBundlePackageType': 'APPL',
|
||||||
|
'CFBundleShortVersionString': {{ short_version }},
|
||||||
|
'CFBundleDocumentTypes': [{
|
||||||
|
'CFBundleTypeRole': 'Viewer',
|
||||||
|
'CFBundleTypeExtensions': ['*'],
|
||||||
|
'CFBundleTypeName': 'Model Files',
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
){% endif %}
|
34
build.sh
|
@ -1,34 +0,0 @@
|
||||||
set -e
|
|
||||||
set -u
|
|
||||||
|
|
||||||
export OLD_PWD=`pwd`
|
|
||||||
export CMAKE=/c/software/PCL/cmake-3.0.1-win32-x86/bin/cmake.exe
|
|
||||||
export MAKE=mingw32-make.exe
|
|
||||||
export PATH=/c/mingw-w64/i686-4.9.2-posix-dwarf-rt_v3-rev1/mingw32/bin:$PATH
|
|
||||||
|
|
||||||
mkdir -p /c/software/protobuf/_build
|
|
||||||
cd /c/software/protobuf/_build
|
|
||||||
$CMAKE ../
|
|
||||||
$MAKE install
|
|
||||||
|
|
||||||
mkdir -p /c/software/libArcus/_build
|
|
||||||
cd /c/software/libArcus/_build
|
|
||||||
$CMAKE ../
|
|
||||||
$MAKE install
|
|
||||||
|
|
||||||
mkdir -p /c/software/PinkUnicornEngine/_build
|
|
||||||
cd /c/software/PinkUnicornEngine/_build
|
|
||||||
$CMAKE ../
|
|
||||||
$MAKE
|
|
||||||
|
|
||||||
cd $OLD_PWD
|
|
||||||
export PYTHONPATH=`pwd`/../libArcus/python:/c/Software/Uranium/
|
|
||||||
/c/python34/python setup.py py2exe
|
|
||||||
|
|
||||||
cp /c/software/PinkUnicornEngine/_build/CuraEngine.exe dist/
|
|
||||||
cp /c/software/libArcus/_install/bin/libArcus.dll dist/
|
|
||||||
cp /c/mingw-w64/i686-4.9.2-posix-dwarf-rt_v3-rev1/mingw32/bin/libgcc_s_dw2-1.dll dist/
|
|
||||||
cp /c/mingw-w64/i686-4.9.2-posix-dwarf-rt_v3-rev1/mingw32/bin/libwinpthread-1.dll dist/
|
|
||||||
cp /c/mingw-w64/i686-4.9.2-posix-dwarf-rt_v3-rev1/mingw32/bin/libstdc++-6.dll dist/
|
|
||||||
|
|
||||||
/c/program\ files\ \(x86\)/NSIS/makensis.exe installer.nsi
|
|
424
conandata.yml
Normal file
|
@ -0,0 +1,424 @@
|
||||||
|
---
|
||||||
|
# Usage: defaults to None
|
||||||
|
# If you're on a release branch create an entry for that **version** e.q.: `5.1.0` update the requirements (use pinned versions, not latest)
|
||||||
|
# also create a beta entry for that **version** e.q.: `5.1.0-beta`, update the requirements (use the <dep_name>/(latest)@ultimaker/stable)
|
||||||
|
#
|
||||||
|
# If you're working on a feature/bugfix branch from a release branch, create an entry for that **channel**, update the requirements (use
|
||||||
|
# the <dep_name>/(latest)@ultimaker/stable)
|
||||||
|
#
|
||||||
|
# If you're working on a feature/bugfix branch from a main branch, it is optional to create an entry for that **channel**, update the
|
||||||
|
# requirements (use the <dep_name>/(latest)@ultimaker/testing)
|
||||||
|
#
|
||||||
|
# Subject to change in the future!
|
||||||
|
"None":
|
||||||
|
requirements:
|
||||||
|
- "pyarcus/(latest)@ultimaker/testing"
|
||||||
|
- "curaengine/(latest)@ultimaker/testing"
|
||||||
|
- "pysavitar/(latest)@ultimaker/testing"
|
||||||
|
- "pynest2d/(latest)@ultimaker/testing"
|
||||||
|
- "uranium/(latest)@ultimaker/testing"
|
||||||
|
- "fdm_materials/(latest)@ultimaker/testing"
|
||||||
|
- "cura_binary_data/(latest)@ultimaker/testing"
|
||||||
|
- "cpython/3.10.4"
|
||||||
|
internal_requirements:
|
||||||
|
- "fdm_materials_private/(latest)@ultimaker/testing"
|
||||||
|
- "cura_private_data/(latest)@ultimaker/testing"
|
||||||
|
runinfo:
|
||||||
|
entrypoint: "cura_app.py"
|
||||||
|
pyinstaller:
|
||||||
|
datas:
|
||||||
|
cura_plugins:
|
||||||
|
package: "cura"
|
||||||
|
src: "plugins"
|
||||||
|
dst: "share/cura/plugins"
|
||||||
|
cura_resources:
|
||||||
|
package: "cura"
|
||||||
|
src: "resources"
|
||||||
|
dst: "share/cura/resources"
|
||||||
|
cura_private_data:
|
||||||
|
package: "cura_private_data"
|
||||||
|
src: "resources"
|
||||||
|
dst: "share/cura/resources"
|
||||||
|
internal: true
|
||||||
|
uranium_plugins:
|
||||||
|
package: "uranium"
|
||||||
|
src: "plugins"
|
||||||
|
dst: "share/uranium/plugins"
|
||||||
|
uranium_resources:
|
||||||
|
package: "uranium"
|
||||||
|
src: "resources"
|
||||||
|
dst: "share/uranium/resources"
|
||||||
|
uranium_um_qt_qml_um:
|
||||||
|
package: "uranium"
|
||||||
|
src: "site-packages/UM/Qt/qml/UM"
|
||||||
|
dst: "PyQt6/Qt6/qml/UM"
|
||||||
|
cura_binary_data:
|
||||||
|
package: "cura_binary_data"
|
||||||
|
src: "resources/cura/resources"
|
||||||
|
dst: "share/cura/resources"
|
||||||
|
uranium_binary_data:
|
||||||
|
package: "cura_binary_data"
|
||||||
|
src: "resources/uranium/resources"
|
||||||
|
dst: "share/uranium/resources"
|
||||||
|
windows_binary_data:
|
||||||
|
package: "cura_binary_data"
|
||||||
|
src: "windows"
|
||||||
|
dst: "share/windows"
|
||||||
|
fdm_materials:
|
||||||
|
package: "fdm_materials"
|
||||||
|
src: "materials"
|
||||||
|
dst: "share/cura/resources/materials"
|
||||||
|
fdm_materials_private:
|
||||||
|
package: "fdm_materials_private"
|
||||||
|
src: "resources/materials"
|
||||||
|
dst: "share/cura/resources/materials"
|
||||||
|
internal: true
|
||||||
|
tcl:
|
||||||
|
package: "tcl"
|
||||||
|
src: "lib/tcl8.6"
|
||||||
|
dst: "tcl"
|
||||||
|
tk:
|
||||||
|
package: "tk"
|
||||||
|
src: "lib/tk8.6"
|
||||||
|
dst: "tk"
|
||||||
|
binaries:
|
||||||
|
curaengine:
|
||||||
|
package: "curaengine"
|
||||||
|
src: "bin"
|
||||||
|
dst: "."
|
||||||
|
binary: "CuraEngine"
|
||||||
|
hiddenimports:
|
||||||
|
- "pySavitar"
|
||||||
|
- "pyArcus"
|
||||||
|
- "pynest2d"
|
||||||
|
- "PyQt6"
|
||||||
|
- "PyQt6.QtNetwork"
|
||||||
|
- "PyQt6.sip"
|
||||||
|
- "logging.handlers"
|
||||||
|
- "zeroconf"
|
||||||
|
- "fcntl"
|
||||||
|
- "stl"
|
||||||
|
- "serial"
|
||||||
|
collect_all:
|
||||||
|
- "cura"
|
||||||
|
- "UM"
|
||||||
|
- "serial"
|
||||||
|
- "Charon"
|
||||||
|
- "sqlite3"
|
||||||
|
- "trimesh"
|
||||||
|
- "win32ctypes"
|
||||||
|
- "PyQt6"
|
||||||
|
- "PyQt6.QtNetwork"
|
||||||
|
- "PyQt6.sip"
|
||||||
|
- "stl"
|
||||||
|
icon:
|
||||||
|
Windows: "./icons/Cura.ico"
|
||||||
|
Macos: "./icons/cura.icns"
|
||||||
|
Linux: "./icons/cura-128.png"
|
||||||
|
"5.2.0-alpha":
|
||||||
|
requirements:
|
||||||
|
- "pyarcus/(latest)@ultimaker/testing"
|
||||||
|
- "curaengine/(latest)@ultimaker/testing"
|
||||||
|
- "pysavitar/(latest)@ultimaker/testing"
|
||||||
|
- "pynest2d/(latest)@ultimaker/testing"
|
||||||
|
- "uranium/(latest)@ultimaker/testing"
|
||||||
|
- "fdm_materials/(latest)@ultimaker/testing"
|
||||||
|
- "cura_binary_data/(latest)@ultimaker/testing"
|
||||||
|
- "cpython/3.10.4"
|
||||||
|
internal_requirements:
|
||||||
|
- "fdm_materials_private/(latest)@ultimaker/testing"
|
||||||
|
- "cura_private_data/(latest)@ultimaker/testing"
|
||||||
|
runinfo:
|
||||||
|
entrypoint: "cura_app.py"
|
||||||
|
pyinstaller:
|
||||||
|
datas:
|
||||||
|
cura_plugins:
|
||||||
|
package: "cura"
|
||||||
|
src: "plugins"
|
||||||
|
dst: "share/cura/plugins"
|
||||||
|
cura_resources:
|
||||||
|
package: "cura"
|
||||||
|
src: "resources"
|
||||||
|
dst: "share/cura/resources"
|
||||||
|
cura_private_data:
|
||||||
|
package: "cura_private_data"
|
||||||
|
src: "resources"
|
||||||
|
dst: "share/cura/resources"
|
||||||
|
internal: true
|
||||||
|
uranium_plugins:
|
||||||
|
package: "uranium"
|
||||||
|
src: "plugins"
|
||||||
|
dst: "share/uranium/plugins"
|
||||||
|
uranium_resources:
|
||||||
|
package: "uranium"
|
||||||
|
src: "resources"
|
||||||
|
dst: "share/uranium/resources"
|
||||||
|
uranium_um_qt_qml_um:
|
||||||
|
package: "uranium"
|
||||||
|
src: "site-packages/UM/Qt/qml/UM"
|
||||||
|
dst: "PyQt6/Qt6/qml/UM"
|
||||||
|
cura_binary_data:
|
||||||
|
package: "cura_binary_data"
|
||||||
|
src: "resources/cura/resources"
|
||||||
|
dst: "share/cura/resources"
|
||||||
|
uranium_binary_data:
|
||||||
|
package: "cura_binary_data"
|
||||||
|
src: "resources/uranium/resources"
|
||||||
|
dst: "share/uranium/resources"
|
||||||
|
windows_binary_data:
|
||||||
|
package: "cura_binary_data"
|
||||||
|
src: "windows"
|
||||||
|
dst: "share/windows"
|
||||||
|
fdm_materials:
|
||||||
|
package: "fdm_materials"
|
||||||
|
src: "materials"
|
||||||
|
dst: "share/cura/resources/materials"
|
||||||
|
fdm_materials_private:
|
||||||
|
package: "fdm_materials_private"
|
||||||
|
src: "resources/materials"
|
||||||
|
dst: "share/cura/resources/materials"
|
||||||
|
internal: true
|
||||||
|
tcl:
|
||||||
|
package: "tcl"
|
||||||
|
src: "lib/tcl8.6"
|
||||||
|
dst: "tcl"
|
||||||
|
tk:
|
||||||
|
package: "tk"
|
||||||
|
src: "lib/tk8.6"
|
||||||
|
dst: "tk"
|
||||||
|
binaries:
|
||||||
|
curaengine:
|
||||||
|
package: "curaengine"
|
||||||
|
src: "bin"
|
||||||
|
dst: "."
|
||||||
|
binary: "CuraEngine"
|
||||||
|
hiddenimports:
|
||||||
|
- "pySavitar"
|
||||||
|
- "pyArcus"
|
||||||
|
- "pynest2d"
|
||||||
|
- "PyQt6"
|
||||||
|
- "PyQt6.QtNetwork"
|
||||||
|
- "PyQt6.sip"
|
||||||
|
- "logging.handlers"
|
||||||
|
- "zeroconf"
|
||||||
|
- "fcntl"
|
||||||
|
- "stl"
|
||||||
|
- "serial"
|
||||||
|
collect_all:
|
||||||
|
- "cura"
|
||||||
|
- "UM"
|
||||||
|
- "serial"
|
||||||
|
- "Charon"
|
||||||
|
- "sqlite3"
|
||||||
|
- "trimesh"
|
||||||
|
- "win32ctypes"
|
||||||
|
- "PyQt6"
|
||||||
|
- "PyQt6.QtNetwork"
|
||||||
|
- "PyQt6.sip"
|
||||||
|
- "stl"
|
||||||
|
icon:
|
||||||
|
Windows: "./icons/Cura.ico"
|
||||||
|
Macos: "./icons/cura.icns"
|
||||||
|
Linux: "./icons/cura-128.png"
|
||||||
|
"5.1.0":
|
||||||
|
requirements:
|
||||||
|
- "arcus/5.1.0"
|
||||||
|
- "curaengine/5.1.0"
|
||||||
|
- "savitar/5.1.0"
|
||||||
|
- "pynest2d/5.1.0"
|
||||||
|
- "uranium/5.1.0"
|
||||||
|
- "fdm_materials/5.1.0"
|
||||||
|
- "cura_binary_data/5.1.0"
|
||||||
|
- "cpython/3.10.4"
|
||||||
|
runinfo:
|
||||||
|
entrypoint: "cura_app.py"
|
||||||
|
pyinstaller:
|
||||||
|
datas:
|
||||||
|
cura_plugins:
|
||||||
|
package: "cura"
|
||||||
|
src: "plugins"
|
||||||
|
dst: "share/cura/plugins"
|
||||||
|
cura_resources:
|
||||||
|
package: "cura"
|
||||||
|
src: "resources"
|
||||||
|
dst: "share/cura/resources"
|
||||||
|
uranium_plugins:
|
||||||
|
package: "uranium"
|
||||||
|
src: "plugins"
|
||||||
|
dst: "share/uranium/plugins"
|
||||||
|
uranium_resources:
|
||||||
|
package: "uranium"
|
||||||
|
src: "resources"
|
||||||
|
dst: "share/uranium/resources"
|
||||||
|
uranium_um_qt_qml_um:
|
||||||
|
package: "uranium"
|
||||||
|
src: "site-packages/UM/Qt/qml/UM"
|
||||||
|
dst: "PyQt6/Qt6/qml/UM"
|
||||||
|
cura_binary_data:
|
||||||
|
package: "cura_binary_data"
|
||||||
|
src: "resources/cura/resources"
|
||||||
|
dst: "share/cura/resources"
|
||||||
|
uranium_binary_data:
|
||||||
|
package: "cura_binary_data"
|
||||||
|
src: "resources/uranium/resources"
|
||||||
|
dst: "share/uranium/resources"
|
||||||
|
windows_binary_data:
|
||||||
|
package: "cura_binary_data"
|
||||||
|
src: "windows"
|
||||||
|
dst: "share/windows"
|
||||||
|
fdm_materials:
|
||||||
|
package: "fdm_materials"
|
||||||
|
src: "materials"
|
||||||
|
dst: "share/cura/resources/materials"
|
||||||
|
tcl:
|
||||||
|
package: "tcl"
|
||||||
|
src: "lib/tcl8.6"
|
||||||
|
dst: "tcl"
|
||||||
|
tk:
|
||||||
|
package: "tk"
|
||||||
|
src: "lib/tk8.6"
|
||||||
|
dst: "tk"
|
||||||
|
binaries:
|
||||||
|
curaengine:
|
||||||
|
package: "curaengine"
|
||||||
|
src: "bin"
|
||||||
|
dst: "."
|
||||||
|
binary: "CuraEngine"
|
||||||
|
hiddenimports:
|
||||||
|
- "pySavitar"
|
||||||
|
- "pyArcus"
|
||||||
|
- "pynest2d"
|
||||||
|
- "PyQt6"
|
||||||
|
- "PyQt6.QtNetwork"
|
||||||
|
- "PyQt6.sip"
|
||||||
|
- "logging.handlers"
|
||||||
|
- "zeroconf"
|
||||||
|
- "fcntl"
|
||||||
|
- "stl"
|
||||||
|
- "serial"
|
||||||
|
collect_all:
|
||||||
|
- "cura"
|
||||||
|
- "UM"
|
||||||
|
- "serial"
|
||||||
|
- "Charon"
|
||||||
|
- "sqlite3"
|
||||||
|
- "trimesh"
|
||||||
|
- "win32ctypes"
|
||||||
|
- "PyQt6"
|
||||||
|
- "PyQt6.QtNetwork"
|
||||||
|
- "PyQt6.sip"
|
||||||
|
- "stl"
|
||||||
|
icon:
|
||||||
|
Windows: "./icons/Cura.ico"
|
||||||
|
Macos: "./icons/cura.icns"
|
||||||
|
Linux: "./icons/cura-128.png"
|
||||||
|
pycharm_targets:
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_run.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: cura
|
||||||
|
script_name: cura_app.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_run.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: cura_external_engine
|
||||||
|
parameters: --external-backend
|
||||||
|
script_name: cura_app.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in tests
|
||||||
|
script_name: tests/
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestBuildVolume.py
|
||||||
|
script_name: tests/TestBuildVolume.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestConvexHullDecorator.py
|
||||||
|
script_name: tests/TestConvexHullDecorator.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestCuraSceneNode.py
|
||||||
|
script_name: tests/TestCuraSceneNode.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestCuraSceneNode.py
|
||||||
|
script_name: tests/TestExtruderManager.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestGCodeListDecorator.py
|
||||||
|
script_name: tests/TestGCodeListDecorator.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestIntentManager.py
|
||||||
|
script_name: tests/TestIntentManager.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestLayer.py
|
||||||
|
script_name: tests/TestLayer.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestMachineAction.py
|
||||||
|
script_name: tests/TestMachineAction.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestMachineManager.py
|
||||||
|
script_name: tests/TestMachineManager.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestOAuth2.py
|
||||||
|
script_name: tests/TestOAuth2.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestObjectsModel.py
|
||||||
|
script_name: tests/TestObjectsModel.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestPrintInformation.py
|
||||||
|
script_name: tests/TestPrintInformation.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestProfileRequirements.py
|
||||||
|
script_name: tests/TestProfileRequirements.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestThemes.py
|
||||||
|
script_name: tests/TestThemes.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestContainerManager.py
|
||||||
|
script_name: tests/Settings/TestContainerManager.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestCuraContainerRegistry.py
|
||||||
|
script_name: tests/Settings/TestCuraContainerRegistry.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestCuraStackBuilder.py
|
||||||
|
script_name: tests/Settings/TestCuraStackBuilder.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestDefinitionContainer.py
|
||||||
|
script_name: tests/Settings/TestDefinitionContainer.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestExtruderStack.py
|
||||||
|
script_name: tests/Settings/TestExtruderStack.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestGlobalStack.py
|
||||||
|
script_name: tests/Settings/TestGlobalStack.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestProfiles.py
|
||||||
|
script_name: tests/Settings/TestProfiles.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestSettingInheritanceManager.py
|
||||||
|
script_name: tests/Settings/TestSettingInheritanceManager.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestSettingOverrideDecorator.py
|
||||||
|
script_name: tests/Settings/TestSettingOverrideDecorator.py
|
||||||
|
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
|
||||||
|
module_name: Cura
|
||||||
|
name: pytest in TestSettingVisibilityPresets.py
|
||||||
|
script_name: tests/Settings/TestSettingVisibilityPresets.py
|
442
conanfile.py
Normal file
|
@ -0,0 +1,442 @@
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from jinja2 import Template
|
||||||
|
|
||||||
|
from conans import tools
|
||||||
|
from conan import ConanFile
|
||||||
|
from conan.tools import files
|
||||||
|
from conan.tools.env import VirtualRunEnv, Environment
|
||||||
|
from conan.errors import ConanInvalidConfiguration
|
||||||
|
|
||||||
|
required_conan_version = ">=1.48.0"
|
||||||
|
|
||||||
|
|
||||||
|
class CuraConan(ConanFile):
|
||||||
|
name = "cura"
|
||||||
|
license = "LGPL-3.0"
|
||||||
|
author = "Ultimaker B.V."
|
||||||
|
url = "https://github.com/Ultimaker/cura"
|
||||||
|
description = "3D printer / slicing GUI built on top of the Uranium framework"
|
||||||
|
topics = ("conan", "python", "pyqt5", "qt", "qml", "3d-printing", "slicer")
|
||||||
|
build_policy = "missing"
|
||||||
|
exports = "LICENSE*", "Ultimaker-Cura.spec.jinja", "CuraVersion.py.jinja"
|
||||||
|
settings = "os", "compiler", "build_type", "arch"
|
||||||
|
no_copy_source = True # We won't build so no need to copy sources to the build folder
|
||||||
|
|
||||||
|
# FIXME: Remove specific branch once merged to main
|
||||||
|
# Extending the conanfile with the UMBaseConanfile https://github.com/Ultimaker/conan-ultimaker-index/tree/CURA-9177_Fix_CI_CD/recipes/umbase
|
||||||
|
python_requires = "umbase/0.1.5@ultimaker/testing"
|
||||||
|
python_requires_extend = "umbase.UMBaseConanfile"
|
||||||
|
|
||||||
|
options = {
|
||||||
|
"enterprise": ["True", "False", "true", "false"], # Workaround for GH Action passing boolean as lowercase string
|
||||||
|
"staging": ["True", "False", "true", "false"], # Workaround for GH Action passing boolean as lowercase string
|
||||||
|
"devtools": [True, False], # FIXME: Split this up in testing and (development / build (pyinstaller) / system installer) tools
|
||||||
|
"cloud_api_version": "ANY",
|
||||||
|
"display_name": "ANY", # TODO: should this be an option??
|
||||||
|
"cura_debug_mode": [True, False], # FIXME: Use profiles
|
||||||
|
"internal": [True, False]
|
||||||
|
}
|
||||||
|
default_options = {
|
||||||
|
"enterprise": "False",
|
||||||
|
"staging": "False",
|
||||||
|
"devtools": False,
|
||||||
|
"cloud_api_version": "1",
|
||||||
|
"display_name": "Ultimaker Cura",
|
||||||
|
"cura_debug_mode": False, # Not yet implemented
|
||||||
|
"internal": False,
|
||||||
|
}
|
||||||
|
scm = {
|
||||||
|
"type": "git",
|
||||||
|
"subfolder": ".",
|
||||||
|
"url": "auto",
|
||||||
|
"revision": "auto"
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _pycharm_targets(self):
|
||||||
|
return self.conan_data["pycharm_targets"]
|
||||||
|
|
||||||
|
# FIXME: These env vars should be defined in the runenv.
|
||||||
|
_cura_env = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _cura_run_env(self):
|
||||||
|
if self._cura_env:
|
||||||
|
return self._cura_env
|
||||||
|
|
||||||
|
self._cura_env = Environment()
|
||||||
|
self._cura_env.define("QML2_IMPORT_PATH", str(self._site_packages.joinpath("PyQt6", "Qt6", "qml")))
|
||||||
|
self._cura_env.define("QT_PLUGIN_PATH", str(self._site_packages.joinpath("PyQt6", "Qt6", "plugins")))
|
||||||
|
|
||||||
|
if self.settings.os == "Linux":
|
||||||
|
self._cura_env.define("QT_QPA_FONTDIR", "/usr/share/fonts")
|
||||||
|
self._cura_env.define("QT_QPA_PLATFORMTHEME", "xdgdesktopportal")
|
||||||
|
self._cura_env.define("QT_XKB_CONFIG_ROOT", "/usr/share/X11/xkb")
|
||||||
|
return self._cura_env
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _staging(self):
|
||||||
|
return self.options.staging in ["True", 'true']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _enterprise(self):
|
||||||
|
return self.options.enterprise in ["True", 'true']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _cloud_api_root(self):
|
||||||
|
return "https://api-staging.ultimaker.com" if self._staging else "https://api.ultimaker.com"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _cloud_account_api_root(self):
|
||||||
|
return "https://account-staging.ultimaker.com" if self._staging else "https://account.ultimaker.com"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _marketplace_root(self):
|
||||||
|
return "https://marketplace-staging.ultimaker.com" if self._staging else "https://marketplace.ultimaker.com"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _digital_factory_url(self):
|
||||||
|
return "https://digitalfactory-staging.ultimaker.com" if self._staging else "https://digitalfactory.ultimaker.com"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _cura_latest_url(self):
|
||||||
|
return "https://software.ultimaker.com/latest.json"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def requirements_txts(self):
|
||||||
|
if self.options.devtools:
|
||||||
|
return ["requirements.txt", "requirements-ultimaker.txt", "requirements-dev.txt"]
|
||||||
|
return ["requirements.txt", "requirements-ultimaker.txt"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _base_dir(self):
|
||||||
|
if self.install_folder is None:
|
||||||
|
if self.build_folder is not None:
|
||||||
|
return Path(self.build_folder)
|
||||||
|
return Path(os.getcwd(), "venv")
|
||||||
|
if self.in_local_cache:
|
||||||
|
return Path(self.install_folder)
|
||||||
|
else:
|
||||||
|
return Path(self.source_folder, "venv")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _share_dir(self):
|
||||||
|
return self._base_dir.joinpath("share")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _script_dir(self):
|
||||||
|
if self.settings.os == "Windows":
|
||||||
|
return self._base_dir.joinpath("Scripts")
|
||||||
|
return self._base_dir.joinpath("bin")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _site_packages(self):
|
||||||
|
if self.settings.os == "Windows":
|
||||||
|
return self._base_dir.joinpath("Lib", "site-packages")
|
||||||
|
py_version = tools.Version(self.deps_cpp_info["cpython"].version)
|
||||||
|
return self._base_dir.joinpath("lib", f"python{py_version.major}.{py_version.minor}", "site-packages")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _py_interp(self):
|
||||||
|
py_interp = self._script_dir.joinpath(Path(self.deps_user_info["cpython"].python).name)
|
||||||
|
if self.settings.os == "Windows":
|
||||||
|
py_interp = Path(*[f'"{p}"' if " " in p else p for p in py_interp.parts])
|
||||||
|
return py_interp
|
||||||
|
|
||||||
|
def _generate_cura_version(self, location):
|
||||||
|
with open(Path(__file__).parent.joinpath("CuraVersion.py.jinja"), "r") as f:
|
||||||
|
cura_version_py = Template(f.read())
|
||||||
|
|
||||||
|
cura_version = self.version
|
||||||
|
if self.options.internal:
|
||||||
|
version = tools.Version(self.version)
|
||||||
|
cura_version = f"{version.major}.{version.minor}.{version.patch}-{version.prerelease.replace('+', '+internal_')}"
|
||||||
|
|
||||||
|
with open(Path(location, "CuraVersion.py"), "w") as f:
|
||||||
|
f.write(cura_version_py.render(
|
||||||
|
cura_app_name = self.name,
|
||||||
|
cura_app_display_name = self.options.display_name,
|
||||||
|
cura_version = cura_version,
|
||||||
|
cura_build_type = "Enterprise" if self._enterprise else "",
|
||||||
|
cura_debug_mode = self.options.cura_debug_mode,
|
||||||
|
cura_cloud_api_root = self._cloud_api_root,
|
||||||
|
cura_cloud_api_version = self.options.cloud_api_version,
|
||||||
|
cura_cloud_account_api_root = self._cloud_account_api_root,
|
||||||
|
cura_marketplace_root = self._marketplace_root,
|
||||||
|
cura_digital_factory_url = self._digital_factory_url,
|
||||||
|
cura_latest_url = self._cura_latest_url))
|
||||||
|
|
||||||
|
def _generate_pyinstaller_spec(self, location, entrypoint_location, icon_path, entitlements_file):
|
||||||
|
pyinstaller_metadata = self._um_data()["pyinstaller"]
|
||||||
|
datas = [(str(self._base_dir.joinpath("conan_install_info.json")), ".")]
|
||||||
|
for data in pyinstaller_metadata["datas"].values():
|
||||||
|
if not self.options.internal and data.get("internal", False):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "package" in data: # get the paths from conan package
|
||||||
|
if data["package"] == self.name:
|
||||||
|
if self.in_local_cache:
|
||||||
|
src_path = Path(self.package_folder, data["src"])
|
||||||
|
else:
|
||||||
|
src_path = Path(self.source_folder, data["src"])
|
||||||
|
else:
|
||||||
|
src_path = Path(self.deps_cpp_info[data["package"]].rootpath, data["src"])
|
||||||
|
elif "root" in data: # get the paths relative from the sourcefolder
|
||||||
|
src_path = Path(self.source_folder, data["root"], data["src"])
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
if src_path.exists():
|
||||||
|
datas.append((str(src_path), data["dst"]))
|
||||||
|
|
||||||
|
binaries = []
|
||||||
|
for binary in pyinstaller_metadata["binaries"].values():
|
||||||
|
if "package" in binary: # get the paths from conan package
|
||||||
|
src_path = Path(self.deps_cpp_info[binary["package"]].rootpath, binary["src"])
|
||||||
|
elif "root" in binary: # get the paths relative from the sourcefolder
|
||||||
|
src_path = Path(self.source_folder, binary["root"], binary["src"])
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
if not src_path.exists():
|
||||||
|
continue
|
||||||
|
for bin in src_path.glob(binary["binary"] + ".*[exe|dll|so|dylib]"):
|
||||||
|
binaries.append((str(bin), binary["dst"]))
|
||||||
|
for bin in src_path.glob(binary["binary"]):
|
||||||
|
binaries.append((str(bin), binary["dst"]))
|
||||||
|
|
||||||
|
for _, dependency in self.dependencies.items():
|
||||||
|
for bin_paths in dependency.cpp_info.bindirs:
|
||||||
|
binaries.extend([(f"{p}", ".") for p in Path(bin_paths).glob("**/*.dll")])
|
||||||
|
binaries.extend([(f"{p}", ".") for p in Path(bin_paths).glob("**/*.dylib")])
|
||||||
|
binaries.extend([(f"{p}", ".") for p in Path(bin_paths).glob("**/*.so")])
|
||||||
|
|
||||||
|
# Copy dynamic libs from lib path
|
||||||
|
binaries.extend([(f"{p}", ".") for p in Path(self._base_dir.joinpath("lib")).glob("**/*.dylib")])
|
||||||
|
|
||||||
|
# Collect all dll's from PyQt6 and place them in the root
|
||||||
|
binaries.extend([(f"{p}", ".") for p in Path(self._site_packages, "PyQt6", "Qt6").glob("**/*.dll")])
|
||||||
|
|
||||||
|
with open(Path(__file__).parent.joinpath("Ultimaker-Cura.spec.jinja"), "r") as f:
|
||||||
|
pyinstaller = Template(f.read())
|
||||||
|
|
||||||
|
cura_version = tools.Version(self.version) if self.version else tools.Version("0.0.0")
|
||||||
|
|
||||||
|
with open(Path(location, "Ultimaker-Cura.spec"), "w") as f:
|
||||||
|
f.write(pyinstaller.render(
|
||||||
|
name = str(self.options.display_name).replace(" ", "-"),
|
||||||
|
display_name = self.options.display_name,
|
||||||
|
entrypoint = entrypoint_location,
|
||||||
|
datas = datas,
|
||||||
|
binaries = binaries,
|
||||||
|
venv_script_path = str(self._script_dir),
|
||||||
|
hiddenimports = pyinstaller_metadata["hiddenimports"],
|
||||||
|
collect_all = pyinstaller_metadata["collect_all"],
|
||||||
|
icon = icon_path,
|
||||||
|
entitlements_file = entitlements_file,
|
||||||
|
osx_bundle_identifier = "'nl.ultimaker.cura'" if self.settings.os == "Macos" else "None",
|
||||||
|
upx = str(self.settings.os == "Windows"),
|
||||||
|
strip = False, # This should be possible on Linux and MacOS but, it can also cause issues on some distributions. Safest is to disable it for now
|
||||||
|
target_arch = "'x86_64'" if self.settings.os == "Macos" else "None", # FIXME: Make this dependent on the settings.arch_target
|
||||||
|
macos = self.settings.os == "Macos",
|
||||||
|
version = f"'{self.version}'",
|
||||||
|
short_version = f"'{cura_version.major}.{cura_version.minor}.{cura_version.patch}'",
|
||||||
|
))
|
||||||
|
|
||||||
|
def configure(self):
|
||||||
|
self.options["pyarcus"].shared = True
|
||||||
|
self.options["pysavitar"].shared = True
|
||||||
|
self.options["pynest2d"].shared = True
|
||||||
|
self.options["cpython"].shared = True
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
if self.version and tools.Version(self.version) <= tools.Version("4"):
|
||||||
|
raise ConanInvalidConfiguration("Only versions 5+ are support")
|
||||||
|
|
||||||
|
def requirements(self):
|
||||||
|
for req in self._um_data()["requirements"]:
|
||||||
|
self.requires(req)
|
||||||
|
if self.options.internal:
|
||||||
|
for req in self._um_data()["internal_requirements"]:
|
||||||
|
self.requires(req)
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
self.folders.source = "."
|
||||||
|
self.folders.build = "venv"
|
||||||
|
self.folders.generators = Path(self.folders.build, "conan")
|
||||||
|
|
||||||
|
self.cpp.package.libdirs = [os.path.join("site-packages", "cura")]
|
||||||
|
self.cpp.package.bindirs = ["bin"]
|
||||||
|
self.cpp.package.resdirs = ["resources", "plugins", "packaging", "pip_requirements"] # pip_requirements should be the last item in the list
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
cura_run_envvars = self._cura_run_env.vars(self, scope = "run")
|
||||||
|
ext = ".ps1" if self.settings.os == "Windows" else ".sh"
|
||||||
|
cura_run_envvars.save_script(self.folders.generators.joinpath(f"cura_run_environment{ext}"))
|
||||||
|
|
||||||
|
vr = VirtualRunEnv(self)
|
||||||
|
vr.generate()
|
||||||
|
|
||||||
|
self._generate_cura_version(Path(self.source_folder, "cura"))
|
||||||
|
|
||||||
|
if self.options.devtools:
|
||||||
|
entitlements_file = "'{}'".format(Path(self.source_folder, "packaging", "dmg", "cura.entitlements"))
|
||||||
|
self._generate_pyinstaller_spec(location = self.generators_folder,
|
||||||
|
entrypoint_location = "'{}'".format(Path(self.source_folder, self._um_data()["runinfo"]["entrypoint"])).replace("\\", "\\\\"),
|
||||||
|
icon_path = "'{}'".format(Path(self.source_folder, "packaging", self._um_data()["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"),
|
||||||
|
entitlements_file = entitlements_file if self.settings.os == "Macos" else "None")
|
||||||
|
|
||||||
|
def imports(self):
|
||||||
|
self.copy("CuraEngine.exe", root_package = "curaengine", src = "@bindirs", dst = "", keep_path = False)
|
||||||
|
self.copy("CuraEngine", root_package = "curaengine", src = "@bindirs", dst = "", keep_path = False)
|
||||||
|
|
||||||
|
files.rmdir(self, "resources/materials")
|
||||||
|
self.copy("*.fdm_material", root_package = "fdm_materials", src = "@resdirs", dst = "resources/materials", keep_path = False)
|
||||||
|
self.copy("*.sig", root_package = "fdm_materials", src = "@resdirs", dst = "resources/materials", keep_path = False)
|
||||||
|
|
||||||
|
if self.options.internal:
|
||||||
|
self.copy("*.fdm_material", root_package = "fdm_materials_private", src = "@resdirs", dst = "resources/materials", keep_path = False)
|
||||||
|
self.copy("*.sig", root_package = "fdm_materials_private", src = "@resdirs", dst = "resources/materials", keep_path = False)
|
||||||
|
self.copy("*", root_package = "cura_private_data", src = self.deps_cpp_info["cura_private_data"].resdirs[0],
|
||||||
|
dst = self._share_dir.joinpath("cura", "resources"), keep_path = True)
|
||||||
|
|
||||||
|
# Copy resources of cura_binary_data
|
||||||
|
self.copy("*", root_package = "cura_binary_data", src = self.deps_cpp_info["cura_binary_data"].resdirs[0],
|
||||||
|
dst = self._share_dir.joinpath("cura", "resources"), keep_path = True)
|
||||||
|
self.copy("*", root_package = "cura_binary_data", src = self.deps_cpp_info["cura_binary_data"].resdirs[1],
|
||||||
|
dst =self._share_dir.joinpath("uranium", "resources"), keep_path = True)
|
||||||
|
|
||||||
|
self.copy("*.dll", src = "@bindirs", dst = self._site_packages)
|
||||||
|
self.copy("*.pyd", src = "@libdirs", dst = self._site_packages)
|
||||||
|
self.copy("*.pyi", src = "@libdirs", dst = self._site_packages)
|
||||||
|
self.copy("*.dylib", src = "@libdirs", dst = self._script_dir)
|
||||||
|
|
||||||
|
def deploy(self):
|
||||||
|
# Copy CuraEngine.exe to bindirs of Virtual Python Environment
|
||||||
|
# TODO: Fix source such that it will get the curaengine relative from the executable (Python bindir in this case)
|
||||||
|
self.copy_deps("CuraEngine.exe", root_package = "curaengine", src = self.deps_cpp_info["curaengine"].bindirs[0],
|
||||||
|
dst = self._base_dir,
|
||||||
|
keep_path = False)
|
||||||
|
self.copy_deps("CuraEngine", root_package = "curaengine", src = self.deps_cpp_info["curaengine"].bindirs[0], dst = self._base_dir,
|
||||||
|
keep_path = False)
|
||||||
|
|
||||||
|
# Copy resources of Cura (keep folder structure)
|
||||||
|
self.copy("*", src = self.cpp_info.bindirs[0], dst = self._base_dir, keep_path = False)
|
||||||
|
self.copy("*", src = self.cpp_info.libdirs[0], dst = self._site_packages.joinpath("cura"), keep_path = True)
|
||||||
|
self.copy("*", src = self.cpp_info.resdirs[0], dst = self._share_dir.joinpath("cura", "resources"), keep_path = True)
|
||||||
|
self.copy("*", src = self.cpp_info.resdirs[1], dst = self._share_dir.joinpath("cura", "plugins"), keep_path = True)
|
||||||
|
|
||||||
|
# Copy materials (flat)
|
||||||
|
self.copy_deps("*.fdm_material", root_package = "fdm_materials", src = self.deps_cpp_info["fdm_materials"].resdirs[0],
|
||||||
|
dst = self._share_dir.joinpath("cura", "resources", "materials"), keep_path = False)
|
||||||
|
self.copy_deps("*.sig", root_package = "fdm_materials", src = self.deps_cpp_info["fdm_materials"].resdirs[0],
|
||||||
|
dst = self._share_dir.joinpath("cura", "resources", "materials"), keep_path = False)
|
||||||
|
|
||||||
|
# Copy internal resources
|
||||||
|
if self.options.internal:
|
||||||
|
self.copy_deps("*.fdm_material", root_package = "fdm_materials_private", src = self.deps_cpp_info["fdm_materials_private"].resdirs[0],
|
||||||
|
dst = self._share_dir.joinpath("cura", "resources", "materials"), keep_path = False)
|
||||||
|
self.copy_deps("*.sig", root_package = "fdm_materials_private", src = self.deps_cpp_info["fdm_materials_private"].resdirs[0],
|
||||||
|
dst = self._share_dir.joinpath("cura", "resources", "materials"), keep_path = False)
|
||||||
|
self.copy_deps("*", root_package = "cura_private_data", src = self.deps_cpp_info["cura_private_data"].resdirs[0],
|
||||||
|
dst = self._share_dir.joinpath("cura", "resources"), keep_path = True)
|
||||||
|
|
||||||
|
# Copy resources of Uranium (keep folder structure)
|
||||||
|
self.copy_deps("*", root_package = "uranium", src = self.deps_cpp_info["uranium"].resdirs[0],
|
||||||
|
dst = self._share_dir.joinpath("uranium", "resources"), keep_path = True)
|
||||||
|
self.copy_deps("*", root_package = "uranium", src = self.deps_cpp_info["uranium"].resdirs[1],
|
||||||
|
dst = self._share_dir.joinpath("uranium", "plugins"), keep_path = True)
|
||||||
|
self.copy_deps("*", root_package = "uranium", src = self.deps_cpp_info["uranium"].libdirs[0],
|
||||||
|
dst = self._site_packages.joinpath("UM"),
|
||||||
|
keep_path = True)
|
||||||
|
self.copy_deps("*", root_package = "uranium", src = str(Path(self.deps_cpp_info["uranium"].libdirs[0], "Qt", "qml", "UM")),
|
||||||
|
dst = self._site_packages.joinpath("PyQt6", "Qt6", "qml", "UM"),
|
||||||
|
keep_path = True)
|
||||||
|
|
||||||
|
# Copy resources of cura_binary_data
|
||||||
|
self.copy_deps("*", root_package = "cura_binary_data", src = self.deps_cpp_info["cura_binary_data"].resdirs[0],
|
||||||
|
dst = self._share_dir.joinpath("cura"), keep_path = True)
|
||||||
|
self.copy_deps("*", root_package = "cura_binary_data", src = self.deps_cpp_info["cura_binary_data"].resdirs[1],
|
||||||
|
dst = self._share_dir.joinpath("uranium"), keep_path = True)
|
||||||
|
if self.settings.os == "Windows":
|
||||||
|
self.copy_deps("*", root_package = "cura_binary_data", src = self.deps_cpp_info["cura_binary_data"].resdirs[2],
|
||||||
|
dst = self._share_dir.joinpath("windows"), keep_path = True)
|
||||||
|
|
||||||
|
self.copy_deps("*.dll", src = "@bindirs", dst = self._site_packages)
|
||||||
|
self.copy_deps("*.pyd", src = "@libdirs", dst = self._site_packages)
|
||||||
|
self.copy_deps("*.pyi", src = "@libdirs", dst = self._site_packages)
|
||||||
|
self.copy_deps("*.dylib", src = "@libdirs", dst = self._base_dir.joinpath("lib"))
|
||||||
|
|
||||||
|
# Copy packaging scripts
|
||||||
|
self.copy("*", src = self.cpp_info.resdirs[2], dst = self._base_dir.joinpath("packaging"))
|
||||||
|
|
||||||
|
# Copy requirements.txt's
|
||||||
|
self.copy("*.txt", src = self.cpp_info.resdirs[-1], dst = self._base_dir.joinpath("pip_requirements"))
|
||||||
|
|
||||||
|
# Generate the GitHub Action version info Environment
|
||||||
|
cura_version = tools.Version(self.version)
|
||||||
|
env_prefix = "Env:" if self.settings.os == "Windows" else ""
|
||||||
|
activate_github_actions_version_env = Template(r"""echo "CURA_VERSION_MAJOR={{ cura_version_major }}" >> ${{ env_prefix }}GITHUB_ENV
|
||||||
|
echo "CURA_VERSION_MINOR={{ cura_version_minor }}" >> ${{ env_prefix }}GITHUB_ENV
|
||||||
|
echo "CURA_VERSION_PATCH={{ cura_version_patch }}" >> ${{ env_prefix }}GITHUB_ENV
|
||||||
|
echo "CURA_VERSION_BUILD={{ cura_version_build }}" >> ${{ env_prefix }}GITHUB_ENV
|
||||||
|
echo "CURA_VERSION_FULL={{ cura_version_full }}" >> ${{ env_prefix }}GITHUB_ENV
|
||||||
|
""").render(cura_version_major = cura_version.major,
|
||||||
|
cura_version_minor = cura_version.minor,
|
||||||
|
cura_version_patch = cura_version.patch,
|
||||||
|
cura_version_build = cura_version.build if cura_version.build != "" else "0",
|
||||||
|
cura_version_full = self.version,
|
||||||
|
env_prefix = env_prefix)
|
||||||
|
|
||||||
|
ext = ".sh" if self.settings.os != "Windows" else ".ps1"
|
||||||
|
files.save(self, self._script_dir.joinpath(f"activate_github_actions_version_env{ext}"), activate_github_actions_version_env)
|
||||||
|
|
||||||
|
self._generate_cura_version(Path(self._site_packages, "cura"))
|
||||||
|
|
||||||
|
entitlements_file = "'{}'".format(Path(self.cpp_info.res_paths[2], "dmg", "cura.entitlements"))
|
||||||
|
self._generate_pyinstaller_spec(location = self._base_dir,
|
||||||
|
entrypoint_location = "'{}'".format(Path(self.cpp_info.bin_paths[0], self._um_data()["runinfo"]["entrypoint"])).replace("\\", "\\\\"),
|
||||||
|
icon_path = "'{}'".format(Path(self.cpp_info.res_paths[2], self._um_data()["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"),
|
||||||
|
entitlements_file = entitlements_file if self.settings.os == "Macos" else "None")
|
||||||
|
|
||||||
|
def package(self):
|
||||||
|
self.copy("cura_app.py", src = ".", dst = self.cpp.package.bindirs[0])
|
||||||
|
self.copy("*", src = "cura", dst = self.cpp.package.libdirs[0])
|
||||||
|
self.copy("*", src = "resources", dst = self.cpp.package.resdirs[0])
|
||||||
|
self.copy("*", src = "plugins", dst = self.cpp.package.resdirs[1])
|
||||||
|
self.copy("requirement*.txt", src = ".", dst = self.cpp.package.resdirs[-1])
|
||||||
|
self.copy("*", src = "packaging", dst = self.cpp.package.resdirs[2])
|
||||||
|
|
||||||
|
def package_info(self):
|
||||||
|
self.user_info.pip_requirements = "requirements.txt"
|
||||||
|
self.user_info.pip_requirements_git = "requirements-ultimaker.txt"
|
||||||
|
self.user_info.pip_requirements_build = "requirements-dev.txt"
|
||||||
|
|
||||||
|
if self.in_local_cache:
|
||||||
|
self.runenv_info.append_path("PYTHONPATH", str(Path(self.cpp_info.lib_paths[0]).parent))
|
||||||
|
self.runenv_info.append_path("PYTHONPATH", self.cpp_info.res_paths[1]) # Add plugins to PYTHONPATH
|
||||||
|
else:
|
||||||
|
self.runenv_info.append_path("PYTHONPATH", self.source_folder)
|
||||||
|
self.runenv_info.append_path("PYTHONPATH", os.path.join(self.source_folder, "plugins"))
|
||||||
|
|
||||||
|
def package_id(self):
|
||||||
|
del self.info.settings.os
|
||||||
|
del self.info.settings.compiler
|
||||||
|
del self.info.settings.build_type
|
||||||
|
del self.info.settings.arch
|
||||||
|
|
||||||
|
# The following options shouldn't be used to determine the hash, since these are only used to set the CuraVersion.py
|
||||||
|
# which will als be generated by the deploy method during the `conan install cura/5.1.0@_/_`
|
||||||
|
del self.info.options.enterprise
|
||||||
|
del self.info.options.staging
|
||||||
|
del self.info.options.devtools
|
||||||
|
del self.info.options.cloud_api_version
|
||||||
|
del self.info.options.display_name
|
||||||
|
del self.info.options.cura_debug_mode
|
||||||
|
|
||||||
|
# TODO: Use the hash of requirements.txt and requirements-ultimaker.txt, Because changing these will actually result in a different
|
||||||
|
# Cura. This is needed because the requirements.txt aren't managed by Conan and therefor not resolved in the package_id. This isn't
|
||||||
|
# ideal but an acceptable solution for now.
|
BIN
cura-logo-dark.PNG
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
cura-logo.PNG
Before Width: | Height: | Size: 520 KiB After Width: | Height: | Size: 1 MiB |
|
@ -1,19 +1,26 @@
|
||||||
# Copyright (c) 2021 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 enum
|
import enum
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import json
|
||||||
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer, pyqtEnum
|
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer, pyqtEnum
|
||||||
from typing import Any, Optional, Dict, TYPE_CHECKING, Callable
|
from PyQt6.QtNetwork import QNetworkRequest
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
from UM.Decorators import deprecated
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
from UM.TaskManagement.HttpRequestManager import HttpRequestManager
|
||||||
|
from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
|
||||||
from cura.OAuth2.AuthorizationService import AuthorizationService
|
from cura.OAuth2.AuthorizationService import AuthorizationService
|
||||||
from cura.OAuth2.Models import OAuth2Settings, UserProfile
|
from cura.OAuth2.Models import OAuth2Settings, UserProfile
|
||||||
from cura.UltimakerCloud import UltimakerCloudConstants
|
from cura.UltimakerCloud import UltimakerCloudConstants
|
||||||
|
from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
from PyQt6.QtNetwork import QNetworkReply
|
||||||
|
|
||||||
i18n_catalog = i18nCatalog("cura")
|
i18n_catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
@ -78,6 +85,7 @@ class Account(QObject):
|
||||||
self._logged_in = False
|
self._logged_in = False
|
||||||
self._user_profile: Optional[UserProfile] = None
|
self._user_profile: Optional[UserProfile] = None
|
||||||
self._additional_rights: Dict[str, Any] = {}
|
self._additional_rights: Dict[str, Any] = {}
|
||||||
|
self._permissions: List[str] = [] # List of account permission keys, e.g. ["digital-factory.print-job.write"]
|
||||||
self._sync_state = SyncState.IDLE
|
self._sync_state = SyncState.IDLE
|
||||||
self._manual_sync_enabled = False
|
self._manual_sync_enabled = False
|
||||||
self._update_packages_enabled = False
|
self._update_packages_enabled = False
|
||||||
|
@ -109,6 +117,7 @@ class Account(QObject):
|
||||||
|
|
||||||
self._sync_services: Dict[str, int] = {}
|
self._sync_services: Dict[str, int] = {}
|
||||||
"""contains entries "service_name" : SyncState"""
|
"""contains entries "service_name" : SyncState"""
|
||||||
|
self.syncRequested.connect(self._updatePermissions)
|
||||||
|
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
self._authorization_service.initialize(self._application.getPreferences())
|
self._authorization_service.initialize(self._application.getPreferences())
|
||||||
|
@ -311,13 +320,63 @@ class Account(QObject):
|
||||||
|
|
||||||
self._authorization_service.deleteAuthData()
|
self._authorization_service.deleteAuthData()
|
||||||
|
|
||||||
|
@deprecated("Get permissions from the 'permissions' property", since = "5.2.0")
|
||||||
def updateAdditionalRight(self, **kwargs) -> None:
|
def updateAdditionalRight(self, **kwargs) -> None:
|
||||||
"""Update the additional rights of the account.
|
"""Update the additional rights of the account.
|
||||||
The argument(s) are the rights that need to be set"""
|
The argument(s) are the rights that need to be set"""
|
||||||
self._additional_rights.update(kwargs)
|
self._additional_rights.update(kwargs)
|
||||||
self.additionalRightsChanged.emit(self._additional_rights)
|
self.additionalRightsChanged.emit(self._additional_rights)
|
||||||
|
|
||||||
|
@deprecated("Get permissions from the 'permissions' property", since = "5.2.0")
|
||||||
@pyqtProperty("QVariantMap", notify = additionalRightsChanged)
|
@pyqtProperty("QVariantMap", notify = additionalRightsChanged)
|
||||||
def additionalRights(self) -> Dict[str, Any]:
|
def additionalRights(self) -> Dict[str, Any]:
|
||||||
"""A dictionary which can be queried for additional account rights."""
|
"""A dictionary which can be queried for additional account rights."""
|
||||||
return self._additional_rights
|
return self._additional_rights
|
||||||
|
|
||||||
|
permissionsChanged = pyqtSignal()
|
||||||
|
@pyqtProperty("QVariantList", notify = permissionsChanged)
|
||||||
|
def permissions(self) -> List[str]:
|
||||||
|
"""
|
||||||
|
The permission keys that the user has in his account.
|
||||||
|
"""
|
||||||
|
return self._permissions
|
||||||
|
|
||||||
|
def _updatePermissions(self) -> None:
|
||||||
|
"""
|
||||||
|
Update the list of permissions that the user has.
|
||||||
|
"""
|
||||||
|
def callback(reply: "QNetworkReply"):
|
||||||
|
status_code = reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute)
|
||||||
|
if status_code is None:
|
||||||
|
Logger.error("Server did not respond to request to get list of permissions.")
|
||||||
|
return
|
||||||
|
if status_code >= 300:
|
||||||
|
Logger.error(f"Request to get list of permission resulted in HTTP error {status_code}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
reply_data = json.loads(bytes(reply.readAll()).decode("UTF-8"))
|
||||||
|
except (UnicodeDecodeError, json.JSONDecodeError, ValueError) as e:
|
||||||
|
Logger.logException("e", f"Could not parse response to permission list request: {e}")
|
||||||
|
return
|
||||||
|
if "errors" in reply_data:
|
||||||
|
Logger.error(f"Request to get list of permission resulted in error response: {reply_data['errors']}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if "data" in reply_data and "permissions" in reply_data["data"]:
|
||||||
|
permissions = sorted(reply_data["data"]["permissions"])
|
||||||
|
if permissions != self._permissions:
|
||||||
|
self._permissions = permissions
|
||||||
|
self.permissionsChanged.emit()
|
||||||
|
|
||||||
|
def error_callback(reply: "QNetworkReply", error: "QNetworkReply.NetworkError"):
|
||||||
|
Logger.error(f"Request for user permissions list failed. Network error: {error}")
|
||||||
|
|
||||||
|
HttpRequestManager.getInstance().get(
|
||||||
|
url = f"{self._oauth_root}/users/permissions",
|
||||||
|
scope = JsonDecoratorScope(UltimakerCloudScope(self._application)),
|
||||||
|
callback = callback,
|
||||||
|
error_callback = error_callback,
|
||||||
|
timeout = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,22 @@
|
||||||
# ---------
|
# ---------
|
||||||
DEFAULT_CURA_APP_NAME = "cura"
|
DEFAULT_CURA_APP_NAME = "cura"
|
||||||
DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura"
|
DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura"
|
||||||
DEFAULT_CURA_VERSION = "master"
|
DEFAULT_CURA_VERSION = "dev"
|
||||||
DEFAULT_CURA_BUILD_TYPE = ""
|
DEFAULT_CURA_BUILD_TYPE = ""
|
||||||
DEFAULT_CURA_DEBUG_MODE = False
|
DEFAULT_CURA_DEBUG_MODE = False
|
||||||
|
DEFAULT_CURA_LATEST_URL = "https://software.ultimaker.com/latest.json"
|
||||||
|
|
||||||
# Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for
|
# 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.0.0"
|
CuraSDKVersion = "8.1.0"
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cura.CuraVersion import CuraLatestURL
|
||||||
|
if CuraLatestURL == "":
|
||||||
|
CuraLatestURL = DEFAULT_CURA_LATEST_URL
|
||||||
|
except ImportError:
|
||||||
|
CuraLatestURL = DEFAULT_CURA_LATEST_URL
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cura.CuraVersion import CuraAppName # type: ignore
|
from cura.CuraVersion import CuraAppName # type: ignore
|
||||||
|
@ -60,3 +68,14 @@ try:
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME
|
CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME
|
||||||
|
|
||||||
|
DEPENDENCY_INFO = {}
|
||||||
|
try:
|
||||||
|
from pathlib import Path
|
||||||
|
conan_install_info = Path(__file__).parent.parent.joinpath("conan_install_info.json")
|
||||||
|
if conan_install_info.exists():
|
||||||
|
import json
|
||||||
|
with open(conan_install_info, "r") as f:
|
||||||
|
DEPENDENCY_INFO = json.loads(f.read())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
|
@ -74,14 +74,14 @@ class ShapeArray:
|
||||||
# If the child-nodes are included, adjust convex hulls as well:
|
# If the child-nodes are included, adjust convex hulls as well:
|
||||||
if include_children:
|
if include_children:
|
||||||
children = node.getAllChildren()
|
children = node.getAllChildren()
|
||||||
if not children is None:
|
if children is not None:
|
||||||
for child in children:
|
for child in children:
|
||||||
# 'Inefficient' combination of convex hulls through known code rather than mess it up:
|
# 'Inefficient' combination of convex hulls through known code rather than mess it up:
|
||||||
child_hull = child.callDecoration("getConvexHull")
|
child_hull = child.callDecoration("getConvexHull")
|
||||||
if not child_hull is None:
|
if child_hull is not None:
|
||||||
hull_verts = hull_verts.unionConvexHulls(child_hull)
|
hull_verts = hull_verts.unionConvexHulls(child_hull)
|
||||||
child_hull_head = child.callDecoration("getConvexHullHead") or child_hull
|
child_hull_head = child.callDecoration("getConvexHullHead") or child_hull
|
||||||
if not child_hull_head is None:
|
if child_hull_head is not None:
|
||||||
hull_head_verts = hull_head_verts.unionConvexHulls(child_hull_head)
|
hull_head_verts = hull_head_verts.unionConvexHulls(child_hull_head)
|
||||||
|
|
||||||
offset_verts = hull_head_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
|
offset_verts = hull_head_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
|
||||||
|
|
|
@ -136,7 +136,7 @@ class Backup:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
current_version = Version(self._application.getVersion())
|
current_version = Version(self._application.getVersion())
|
||||||
version_to_restore = Version(self.meta_data.get("cura_release", "master"))
|
version_to_restore = Version(self.meta_data.get("cura_release", "dev"))
|
||||||
|
|
||||||
if current_version < version_to_restore:
|
if current_version < version_to_restore:
|
||||||
# Cannot restore version newer than current because settings might have changed.
|
# Cannot restore version newer than current because settings might have changed.
|
||||||
|
|
|
@ -565,8 +565,8 @@ class BuildVolume(SceneNode):
|
||||||
self._updateScaleFactor()
|
self._updateScaleFactor()
|
||||||
|
|
||||||
self._volume_aabb = AxisAlignedBox(
|
self._volume_aabb = AxisAlignedBox(
|
||||||
minimum = Vector(min_w, min_h - 1.0, min_d).scale(self._scale_vector),
|
minimum = Vector(min_w, min_h - 1.0, min_d),
|
||||||
maximum = Vector(max_w, max_h - self._raft_thickness - self._extra_z_clearance, max_d).scale(self._scale_vector)
|
maximum = Vector(max_w, max_h - self._raft_thickness - self._extra_z_clearance, max_d)
|
||||||
)
|
)
|
||||||
|
|
||||||
bed_adhesion_size = self.getEdgeDisallowedSize()
|
bed_adhesion_size = self.getEdgeDisallowedSize()
|
||||||
|
@ -575,8 +575,8 @@ class BuildVolume(SceneNode):
|
||||||
# This is probably wrong in all other cases. TODO!
|
# This is probably wrong in all other cases. TODO!
|
||||||
# The +1 and -1 is added as there is always a bit of extra room required to work properly.
|
# The +1 and -1 is added as there is always a bit of extra room required to work properly.
|
||||||
scale_to_max_bounds = AxisAlignedBox(
|
scale_to_max_bounds = AxisAlignedBox(
|
||||||
minimum = Vector(min_w + bed_adhesion_size + 1, min_h, min_d + self._disallowed_area_size - bed_adhesion_size + 1).scale(self._scale_vector),
|
minimum = Vector(min_w + bed_adhesion_size + 1, min_h, min_d + self._disallowed_area_size - bed_adhesion_size + 1),
|
||||||
maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - self._disallowed_area_size + bed_adhesion_size - 1).scale(self._scale_vector)
|
maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - self._disallowed_area_size + bed_adhesion_size - 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
self._application.getController().getScene()._maximum_bounds = scale_to_max_bounds # type: ignore
|
self._application.getController().getScene()._maximum_bounds = scale_to_max_bounds # type: ignore
|
||||||
|
@ -645,7 +645,7 @@ class BuildVolume(SceneNode):
|
||||||
for extruder in extruders:
|
for extruder in extruders:
|
||||||
extruder.propertyChanged.connect(self._onSettingPropertyChanged)
|
extruder.propertyChanged.connect(self._onSettingPropertyChanged)
|
||||||
|
|
||||||
self._width = self._global_container_stack.getProperty("machine_width", "value") * self._scale_vector.x
|
self._width = self._global_container_stack.getProperty("machine_width", "value")
|
||||||
machine_height = self._global_container_stack.getProperty("machine_height", "value")
|
machine_height = self._global_container_stack.getProperty("machine_height", "value")
|
||||||
if self._global_container_stack.getProperty("print_sequence", "value") == "one_at_a_time" and len(self._scene_objects) > 1:
|
if self._global_container_stack.getProperty("print_sequence", "value") == "one_at_a_time" and len(self._scene_objects) > 1:
|
||||||
self._height = min(self._global_container_stack.getProperty("gantry_height", "value") * self._scale_vector.z, machine_height)
|
self._height = min(self._global_container_stack.getProperty("gantry_height", "value") * self._scale_vector.z, machine_height)
|
||||||
|
@ -656,7 +656,7 @@ class BuildVolume(SceneNode):
|
||||||
else:
|
else:
|
||||||
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
||||||
self._build_volume_message.hide()
|
self._build_volume_message.hide()
|
||||||
self._depth = self._global_container_stack.getProperty("machine_depth", "value") * self._scale_vector.y
|
self._depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||||
self._shape = self._global_container_stack.getProperty("machine_shape", "value")
|
self._shape = self._global_container_stack.getProperty("machine_shape", "value")
|
||||||
|
|
||||||
self._updateDisallowedAreas()
|
self._updateDisallowedAreas()
|
||||||
|
@ -752,8 +752,8 @@ class BuildVolume(SceneNode):
|
||||||
return
|
return
|
||||||
self._updateScaleFactor()
|
self._updateScaleFactor()
|
||||||
self._height = self._global_container_stack.getProperty("machine_height", "value") * self._scale_vector.z
|
self._height = self._global_container_stack.getProperty("machine_height", "value") * self._scale_vector.z
|
||||||
self._width = self._global_container_stack.getProperty("machine_width", "value") * self._scale_vector.x
|
self._width = self._global_container_stack.getProperty("machine_width", "value")
|
||||||
self._depth = self._global_container_stack.getProperty("machine_depth", "value") * self._scale_vector.y
|
self._depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||||
self._shape = self._global_container_stack.getProperty("machine_shape", "value")
|
self._shape = self._global_container_stack.getProperty("machine_shape", "value")
|
||||||
|
|
||||||
def _updateDisallowedAreasAndRebuild(self):
|
def _updateDisallowedAreasAndRebuild(self):
|
||||||
|
@ -770,14 +770,6 @@ class BuildVolume(SceneNode):
|
||||||
self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
|
self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
|
|
||||||
def _scaleAreas(self, result_areas: List[Polygon]) -> None:
|
|
||||||
if self._global_container_stack is None:
|
|
||||||
return
|
|
||||||
for i, polygon in enumerate(result_areas):
|
|
||||||
result_areas[i] = polygon.scale(
|
|
||||||
100.0 / max(100.0, self._global_container_stack.getProperty("material_shrinkage_percentage_xy", "value"))
|
|
||||||
)
|
|
||||||
|
|
||||||
def _updateDisallowedAreas(self) -> None:
|
def _updateDisallowedAreas(self) -> None:
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack:
|
||||||
return
|
return
|
||||||
|
@ -833,11 +825,9 @@ class BuildVolume(SceneNode):
|
||||||
|
|
||||||
self._disallowed_areas = []
|
self._disallowed_areas = []
|
||||||
for extruder_id in result_areas:
|
for extruder_id in result_areas:
|
||||||
self._scaleAreas(result_areas[extruder_id])
|
|
||||||
self._disallowed_areas.extend(result_areas[extruder_id])
|
self._disallowed_areas.extend(result_areas[extruder_id])
|
||||||
self._disallowed_areas_no_brim = []
|
self._disallowed_areas_no_brim = []
|
||||||
for extruder_id in result_areas_no_brim:
|
for extruder_id in result_areas_no_brim:
|
||||||
self._scaleAreas(result_areas_no_brim[extruder_id])
|
|
||||||
self._disallowed_areas_no_brim.extend(result_areas_no_brim[extruder_id])
|
self._disallowed_areas_no_brim.extend(result_areas_no_brim[extruder_id])
|
||||||
|
|
||||||
def _computeDisallowedAreasPrinted(self, used_extruders):
|
def _computeDisallowedAreasPrinted(self, used_extruders):
|
||||||
|
@ -993,6 +983,9 @@ class BuildVolume(SceneNode):
|
||||||
half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2
|
half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2
|
||||||
half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2
|
half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2
|
||||||
|
|
||||||
|
# We need at a minimum a very small border around the edge so that models can't go off the build plate
|
||||||
|
border_size = max(border_size, 0.1)
|
||||||
|
|
||||||
if self._shape != "elliptic":
|
if self._shape != "elliptic":
|
||||||
if border_size - left_unreachable_border > 0:
|
if border_size - left_unreachable_border > 0:
|
||||||
result[extruder_id].append(Polygon(numpy.array([
|
result[extruder_id].append(Polygon(numpy.array([
|
||||||
|
|
|
@ -115,6 +115,7 @@ from . import CuraActions
|
||||||
from . import PlatformPhysics
|
from . import PlatformPhysics
|
||||||
from . import PrintJobPreviewImageProvider
|
from . import PrintJobPreviewImageProvider
|
||||||
from .AutoSave import AutoSave
|
from .AutoSave import AutoSave
|
||||||
|
from .Machines.Models.MachineListModel import MachineListModel
|
||||||
from .Machines.Models.ActiveIntentQualitiesModel import ActiveIntentQualitiesModel
|
from .Machines.Models.ActiveIntentQualitiesModel import ActiveIntentQualitiesModel
|
||||||
from .Machines.Models.IntentSelectionModel import IntentSelectionModel
|
from .Machines.Models.IntentSelectionModel import IntentSelectionModel
|
||||||
from .SingleInstance import SingleInstance
|
from .SingleInstance import SingleInstance
|
||||||
|
@ -152,6 +153,7 @@ class CuraApplication(QtApplication):
|
||||||
super().__init__(name = ApplicationMetadata.CuraAppName,
|
super().__init__(name = ApplicationMetadata.CuraAppName,
|
||||||
app_display_name = ApplicationMetadata.CuraAppDisplayName,
|
app_display_name = ApplicationMetadata.CuraAppDisplayName,
|
||||||
version = ApplicationMetadata.CuraVersion if not ApplicationMetadata.IsAlternateVersion else ApplicationMetadata.CuraBuildType,
|
version = ApplicationMetadata.CuraVersion if not ApplicationMetadata.IsAlternateVersion else ApplicationMetadata.CuraBuildType,
|
||||||
|
latest_url = ApplicationMetadata.CuraLatestURL,
|
||||||
api_version = ApplicationMetadata.CuraSDKVersion,
|
api_version = ApplicationMetadata.CuraSDKVersion,
|
||||||
build_type = ApplicationMetadata.CuraBuildType,
|
build_type = ApplicationMetadata.CuraBuildType,
|
||||||
is_debug_mode = ApplicationMetadata.CuraDebugMode,
|
is_debug_mode = ApplicationMetadata.CuraDebugMode,
|
||||||
|
@ -355,8 +357,14 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
Resources.addSecureSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources"))
|
Resources.addSecureSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources"))
|
||||||
if not hasattr(sys, "frozen"):
|
if not hasattr(sys, "frozen"):
|
||||||
resource_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")
|
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources"))
|
||||||
Resources.addSecureSearchPath(resource_path)
|
|
||||||
|
# local Conan cache
|
||||||
|
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "..", "resources"))
|
||||||
|
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "..", "plugins"))
|
||||||
|
|
||||||
|
# venv site-packages
|
||||||
|
Resources.addSearchPath(os.path.join(app_root, "..", "share", "cura", "resources"))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _initializeSettingDefinitions(cls):
|
def _initializeSettingDefinitions(cls):
|
||||||
|
@ -814,6 +822,12 @@ class CuraApplication(QtApplication):
|
||||||
def run(self):
|
def run(self):
|
||||||
super().run()
|
super().run()
|
||||||
|
|
||||||
|
if len(ApplicationMetadata.DEPENDENCY_INFO) > 0:
|
||||||
|
Logger.debug("Using Conan managed dependencies: " + ", ".join(
|
||||||
|
[dep["recipe"]["id"] for dep in ApplicationMetadata.DEPENDENCY_INFO["installed"] if dep["recipe"]["version"] != "latest"]))
|
||||||
|
else:
|
||||||
|
Logger.warning("Could not find conan_install_info.json")
|
||||||
|
|
||||||
Logger.log("i", "Initializing machine error checker")
|
Logger.log("i", "Initializing machine error checker")
|
||||||
self._machine_error_checker = MachineErrorChecker(self)
|
self._machine_error_checker = MachineErrorChecker(self)
|
||||||
self._machine_error_checker.initialize()
|
self._machine_error_checker.initialize()
|
||||||
|
@ -1176,6 +1190,7 @@ class CuraApplication(QtApplication):
|
||||||
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
||||||
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
||||||
qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel")
|
qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel")
|
||||||
|
qmlRegisterType(MachineListModel, "Cura", 1, 0, "MachineListModel")
|
||||||
|
|
||||||
self.processEvents()
|
self.processEvents()
|
||||||
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
|
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
|
||||||
|
|
|
@ -14,6 +14,7 @@ from cura.Settings.GlobalStack import GlobalStack
|
||||||
from UM.PackageManager import PackageManager # The class we're extending.
|
from UM.PackageManager import PackageManager # The class we're extending.
|
||||||
from UM.Resources import Resources # To find storage paths for some resource types.
|
from UM.Resources import Resources # To find storage paths for some resource types.
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
from urllib.parse import unquote_plus
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
@ -54,6 +55,14 @@ class CuraPackageManager(PackageManager):
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer)
|
self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer)
|
||||||
self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer)
|
self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer)
|
||||||
|
self._installation_dirs_dict["variants"] = Resources.getStoragePath(CuraApplication.ResourceTypes.VariantInstanceContainer)
|
||||||
|
|
||||||
|
# Due to a bug in Cura 5.1.0 we needed to change the directory structure of the curapackage on the server side (See SD-3871).
|
||||||
|
# Although the material intent profiles will be installed in the `intent` folder, the curapackage from the server side will
|
||||||
|
# have an `intents` folder. For completeness, we will look in both locations of in the curapackage and map them both to the
|
||||||
|
# `intent` folder.
|
||||||
|
self._installation_dirs_dict["intents"] = Resources.getStoragePath(CuraApplication.ResourceTypes.IntentInstanceContainer)
|
||||||
|
self._installation_dirs_dict["intent"] = Resources.getStoragePath(CuraApplication.ResourceTypes.IntentInstanceContainer)
|
||||||
|
|
||||||
super().initialize()
|
super().initialize()
|
||||||
|
|
||||||
|
@ -80,6 +89,7 @@ class CuraPackageManager(PackageManager):
|
||||||
|
|
||||||
def getMaterialFilePackageId(self, file_name: str, guid: str) -> str:
|
def getMaterialFilePackageId(self, file_name: str, guid: str) -> str:
|
||||||
"""Get the id of the installed material package that contains file_name"""
|
"""Get the id of the installed material package that contains file_name"""
|
||||||
|
file_name = unquote_plus(file_name)
|
||||||
for material_package in [f for f in os.scandir(self._installation_dirs_dict["materials"]) if f.is_dir()]:
|
for material_package in [f for f in os.scandir(self._installation_dirs_dict["materials"]) if f.is_dir()]:
|
||||||
package_id = material_package.name
|
package_id = material_package.name
|
||||||
|
|
||||||
|
@ -98,6 +108,7 @@ class CuraPackageManager(PackageManager):
|
||||||
return package_id
|
return package_id
|
||||||
|
|
||||||
Logger.error("Could not find package_id for file: {} with GUID: {} ".format(file_name, guid))
|
Logger.error("Could not find package_id for file: {} with GUID: {} ".format(file_name, guid))
|
||||||
|
Logger.error(f"Bundled paths searched: {list(Resources.getSecureSearchPaths())}")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def getMachinesUsingPackage(self, package_id: str) -> Tuple[List[Tuple[GlobalStack, str, str]], List[Tuple[GlobalStack, str, str]]]:
|
def getMachinesUsingPackage(self, package_id: str) -> Tuple[List[Tuple[GlobalStack, str, str]], List[Tuple[GlobalStack, str, str]]]:
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
# Copyright (c) 2020 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
CuraAppName = "@CURA_APP_NAME@"
|
|
||||||
CuraAppDisplayName = "@CURA_APP_DISPLAY_NAME@"
|
|
||||||
CuraVersion = "@CURA_VERSION@"
|
|
||||||
CuraBuildType = "@CURA_BUILDTYPE@"
|
|
||||||
CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False
|
|
||||||
CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@"
|
|
||||||
CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@"
|
|
||||||
CuraCloudAccountAPIRoot = "@CURA_CLOUD_ACCOUNT_API_ROOT@"
|
|
||||||
CuraMarketplaceRoot = "@CURA_MARKETPLACE_ROOT@"
|
|
||||||
CuraDigitalFactoryURL = "@CURA_DIGITAL_FACTORY_URL@"
|
|
|
@ -45,11 +45,7 @@ class MachineErrorChecker(QObject):
|
||||||
|
|
||||||
self._start_time = 0. # measure checking time
|
self._start_time = 0. # measure checking time
|
||||||
|
|
||||||
# This timer delays the starting of error check so we can react less frequently if the user is frequently
|
self._setCheckTimer()
|
||||||
# changing settings.
|
|
||||||
self._error_check_timer = QTimer(self)
|
|
||||||
self._error_check_timer.setInterval(100)
|
|
||||||
self._error_check_timer.setSingleShot(True)
|
|
||||||
|
|
||||||
self._keys_to_check = set() # type: Set[str]
|
self._keys_to_check = set() # type: Set[str]
|
||||||
|
|
||||||
|
@ -66,6 +62,18 @@ class MachineErrorChecker(QObject):
|
||||||
|
|
||||||
self._onMachineChanged()
|
self._onMachineChanged()
|
||||||
|
|
||||||
|
def _setCheckTimer(self) -> None:
|
||||||
|
"""A QTimer to regulate error check frequency
|
||||||
|
|
||||||
|
This timer delays the starting of error check
|
||||||
|
so we can react less frequently if the user is frequently
|
||||||
|
changing settings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._error_check_timer = QTimer(self)
|
||||||
|
self._error_check_timer.setInterval(100)
|
||||||
|
self._error_check_timer.setSingleShot(True)
|
||||||
|
|
||||||
def _onMachineChanged(self) -> None:
|
def _onMachineChanged(self) -> None:
|
||||||
if self._global_stack:
|
if self._global_stack:
|
||||||
self._global_stack.propertyChanged.disconnect(self.startErrorCheckPropertyChanged)
|
self._global_stack.propertyChanged.disconnect(self.startErrorCheckPropertyChanged)
|
||||||
|
|
99
cura/Machines/Models/MachineListModel.py
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
# Copyright (c) 2022 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
# The MachineListModel is used to display the connected printers in the interface. Both the abstract machines and all
|
||||||
|
# online cloud connected printers are represented within this ListModel. Additional information such as the number of
|
||||||
|
# connected printers for each printer type is gathered.
|
||||||
|
|
||||||
|
from PyQt6.QtCore import Qt, QTimer
|
||||||
|
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
from UM.Util import parseBool
|
||||||
|
|
||||||
|
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
|
||||||
|
|
||||||
|
class MachineListModel(ListModel):
|
||||||
|
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||||
|
IdRole = Qt.ItemDataRole.UserRole + 2
|
||||||
|
HasRemoteConnectionRole = Qt.ItemDataRole.UserRole + 3
|
||||||
|
MetaDataRole = Qt.ItemDataRole.UserRole + 4
|
||||||
|
IsOnlineRole = Qt.ItemDataRole.UserRole + 5
|
||||||
|
MachineCountRole = Qt.ItemDataRole.UserRole + 6
|
||||||
|
IsAbstractMachine = Qt.ItemDataRole.UserRole + 7
|
||||||
|
|
||||||
|
def __init__(self, parent=None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self._catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
self.addRoleName(self.NameRole, "name")
|
||||||
|
self.addRoleName(self.IdRole, "id")
|
||||||
|
self.addRoleName(self.HasRemoteConnectionRole, "hasRemoteConnection")
|
||||||
|
self.addRoleName(self.MetaDataRole, "metadata")
|
||||||
|
self.addRoleName(self.IsOnlineRole, "isOnline")
|
||||||
|
self.addRoleName(self.MachineCountRole, "machineCount")
|
||||||
|
self.addRoleName(self.IsAbstractMachine, "isAbstractMachine")
|
||||||
|
|
||||||
|
self._change_timer = QTimer()
|
||||||
|
self._change_timer.setInterval(200)
|
||||||
|
self._change_timer.setSingleShot(True)
|
||||||
|
self._change_timer.timeout.connect(self._update)
|
||||||
|
|
||||||
|
# Listen to changes
|
||||||
|
CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
|
||||||
|
CuraContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
|
||||||
|
CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
|
||||||
|
self._updateDelayed()
|
||||||
|
|
||||||
|
def _onContainerChanged(self, container) -> None:
|
||||||
|
"""Handler for container added/removed events from registry"""
|
||||||
|
|
||||||
|
# We only need to update when the added / removed container GlobalStack
|
||||||
|
if isinstance(container, GlobalStack):
|
||||||
|
self._updateDelayed()
|
||||||
|
|
||||||
|
def _updateDelayed(self) -> None:
|
||||||
|
self._change_timer.start()
|
||||||
|
|
||||||
|
def _update(self) -> None:
|
||||||
|
self.setItems([]) # Clear items
|
||||||
|
|
||||||
|
other_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type="machine")
|
||||||
|
|
||||||
|
abstract_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(is_abstract_machine = "True")
|
||||||
|
abstract_machine_stacks.sort(key = lambda machine: machine.getName(), reverse = True)
|
||||||
|
|
||||||
|
for abstract_machine in abstract_machine_stacks:
|
||||||
|
definition_id = abstract_machine.definition.getId()
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
machines_manager = CuraApplication.getInstance().getMachineManager()
|
||||||
|
online_machine_stacks = machines_manager.getMachinesWithDefinition(definition_id, online_only = True)
|
||||||
|
|
||||||
|
# Create a list item for abstract machine
|
||||||
|
self.addItem(abstract_machine, len(online_machine_stacks))
|
||||||
|
other_machine_stacks.remove(abstract_machine)
|
||||||
|
|
||||||
|
# Create list of machines that are children of the abstract machine
|
||||||
|
for stack in online_machine_stacks:
|
||||||
|
self.addItem(stack)
|
||||||
|
# Remove this machine from the other stack list
|
||||||
|
other_machine_stacks.remove(stack)
|
||||||
|
|
||||||
|
for stack in other_machine_stacks:
|
||||||
|
self.addItem(stack)
|
||||||
|
|
||||||
|
def addItem(self, container_stack: ContainerStack, machine_count: int = 0) -> None:
|
||||||
|
if parseBool(container_stack.getMetaDataEntry("hidden", False)):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.appendItem({"name": container_stack.getName(),
|
||||||
|
"id": container_stack.getId(),
|
||||||
|
"metadata": container_stack.getMetaData().copy(),
|
||||||
|
"isOnline": parseBool(container_stack.getMetaDataEntry("is_online", False)),
|
||||||
|
"isAbstractMachine": parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False)),
|
||||||
|
"machineCount": machine_count,
|
||||||
|
})
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) 2018 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.
|
||||||
|
|
||||||
from typing import Optional, TYPE_CHECKING, List
|
from typing import Optional, TYPE_CHECKING, List
|
||||||
|
@ -6,6 +6,8 @@ from typing import Optional, TYPE_CHECKING, List
|
||||||
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot, QUrl
|
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot, QUrl
|
||||||
from PyQt6.QtGui import QImage
|
from PyQt6.QtGui import QImage
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||||
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
||||||
|
@ -86,6 +88,18 @@ class PrintJobOutputModel(QObject):
|
||||||
self._owner = owner
|
self._owner = owner
|
||||||
self.ownerChanged.emit()
|
self.ownerChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = ownerChanged)
|
||||||
|
def isMine(self) -> bool:
|
||||||
|
"""
|
||||||
|
Returns whether this print job was sent by the currently logged in user.
|
||||||
|
|
||||||
|
This checks the owner of the print job with the owner of the currently
|
||||||
|
logged in account. Both of these are human-readable account names which
|
||||||
|
may be duplicate. In practice the harm here is limited, but it's the
|
||||||
|
best we can do with the information available to the API.
|
||||||
|
"""
|
||||||
|
return self._owner == CuraApplication.getInstance().getCuraAPI().account.userName
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify=assignedPrinterChanged)
|
@pyqtProperty(QObject, notify=assignedPrinterChanged)
|
||||||
def assignedPrinter(self):
|
def assignedPrinter(self):
|
||||||
return self._assigned_printer
|
return self._assigned_printer
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
# Copyright (c) 2019 Ultimaker B.V.
|
# Copyright (c) 2019 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.
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Peripheral:
|
class Peripheral:
|
||||||
"""Data class that represents a peripheral for a printer.
|
"""Data class that represents a peripheral for a printer.
|
||||||
|
|
||||||
Output device plug-ins may specify that the printer has a certain set of
|
Output device plug-ins may specify that the printer has a certain set of
|
||||||
peripherals. This set is then possibly shown in the interface of the monitor
|
peripherals. This set is then possibly shown in the interface of the monitor
|
||||||
stage.
|
stage.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
type (string): A unique ID for the type of peripheral.
|
||||||
|
name (string): A human-readable name for the peripheral.
|
||||||
"""
|
"""
|
||||||
|
type: str
|
||||||
def __init__(self, peripheral_type: str, name: str) -> None:
|
name: str
|
||||||
"""Constructs the peripheral.
|
|
||||||
|
|
||||||
:param peripheral_type: A unique ID for the type of peripheral.
|
|
||||||
:param name: A human-readable name for the peripheral.
|
|
||||||
"""
|
|
||||||
self.type = peripheral_type
|
|
||||||
self.name = name
|
|
||||||
|
|
|
@ -139,7 +139,7 @@ class CuraSceneController(QObject):
|
||||||
def setActiveBuildPlate(self, nr):
|
def setActiveBuildPlate(self, nr):
|
||||||
if nr == self._active_build_plate:
|
if nr == self._active_build_plate:
|
||||||
return
|
return
|
||||||
Logger.log("d", "Select build plate: %s" % nr)
|
Logger.debug(f"Selected build plate: {nr}")
|
||||||
self._active_build_plate = nr
|
self._active_build_plate = nr
|
||||||
Selection.clear()
|
Selection.clear()
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
:param container_type: :type{string} Type of the container (machine, quality, ...)
|
:param container_type: :type{string} Type of the container (machine, quality, ...)
|
||||||
:param container_name: :type{string} Name to check
|
:param container_name: :type{string} Name to check
|
||||||
"""
|
"""
|
||||||
container_class = ContainerStack if container_type == "machine" else InstanceContainer
|
container_class = ContainerStack if "machine" in container_type else InstanceContainer
|
||||||
|
|
||||||
return self.findContainersMetadata(container_type = container_class, id = container_name, type = container_type, ignore_case = True) or \
|
return self.findContainersMetadata(container_type = container_class, id = container_name, type = container_type, ignore_case = True) or \
|
||||||
self.findContainersMetadata(container_type = container_class, name = container_name, type = container_type)
|
self.findContainersMetadata(container_type = container_class, name = container_name, type = container_type)
|
||||||
|
|
|
@ -427,4 +427,4 @@ class _ContainerIndexes:
|
||||||
}
|
}
|
||||||
|
|
||||||
# Reverse lookup: type -> index
|
# Reverse lookup: type -> index
|
||||||
TypeIndexMap = dict([(v, k) for k, v in IndexTypeMap.items()])
|
TypeIndexMap = {v: k for k, v in IndexTypeMap.items()}
|
||||||
|
|
|
@ -27,7 +27,7 @@ class CuraStackBuilder:
|
||||||
:return: The new global stack or None if an error occurred.
|
:return: The new global stack or None if an error occurred.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication # inline import needed due to circular import
|
||||||
application = CuraApplication.getInstance()
|
application = CuraApplication.getInstance()
|
||||||
registry = application.getContainerRegistry()
|
registry = application.getContainerRegistry()
|
||||||
container_tree = ContainerTree.getInstance()
|
container_tree = ContainerTree.getInstance()
|
||||||
|
@ -91,7 +91,7 @@ class CuraStackBuilder:
|
||||||
:param extruder_position: The position of the current extruder.
|
:param extruder_position: The position of the current extruder.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication # inline import needed due to circular import
|
||||||
application = CuraApplication.getInstance()
|
application = CuraApplication.getInstance()
|
||||||
registry = application.getContainerRegistry()
|
registry = application.getContainerRegistry()
|
||||||
|
|
||||||
|
@ -199,13 +199,21 @@ class CuraStackBuilder:
|
||||||
|
|
||||||
:return: A new Global stack instance with the specified parameters.
|
:return: A new Global stack instance with the specified parameters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
|
||||||
application = CuraApplication.getInstance()
|
|
||||||
registry = application.getContainerRegistry()
|
|
||||||
|
|
||||||
stack = GlobalStack(new_stack_id)
|
stack = GlobalStack(new_stack_id)
|
||||||
stack.setDefinition(definition)
|
stack.setDefinition(definition)
|
||||||
|
cls.createUserContainer(new_stack_id, definition, stack, variant_container, material_container, quality_container)
|
||||||
|
return stack
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def createUserContainer(cls, new_stack_id: str, definition: DefinitionContainerInterface,
|
||||||
|
stack: GlobalStack,
|
||||||
|
variant_container: "InstanceContainer",
|
||||||
|
material_container: "InstanceContainer",
|
||||||
|
quality_container: "InstanceContainer") -> None:
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
application = CuraApplication.getInstance()
|
||||||
|
|
||||||
|
registry = application.getContainerRegistry()
|
||||||
|
|
||||||
# Create user container
|
# Create user container
|
||||||
user_container = cls.createUserChangesContainer(new_stack_id + "_user", definition.getId(), new_stack_id,
|
user_container = cls.createUserChangesContainer(new_stack_id + "_user", definition.getId(), new_stack_id,
|
||||||
|
@ -221,8 +229,6 @@ class CuraStackBuilder:
|
||||||
|
|
||||||
registry.addContainer(user_container)
|
registry.addContainer(user_container)
|
||||||
|
|
||||||
return stack
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str,
|
def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str,
|
||||||
is_global_stack: bool) -> "InstanceContainer":
|
is_global_stack: bool) -> "InstanceContainer":
|
||||||
|
@ -259,3 +265,51 @@ class CuraStackBuilder:
|
||||||
container_stack.definitionChanges = definition_changes_container
|
container_stack.definitionChanges = definition_changes_container
|
||||||
|
|
||||||
return definition_changes_container
|
return definition_changes_container
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def createAbstractMachine(cls, definition_id: str) -> Optional[GlobalStack]:
|
||||||
|
"""Create a new instance of an abstract machine.
|
||||||
|
|
||||||
|
:param definition_id: The ID of the machine definition to use.
|
||||||
|
|
||||||
|
:return: The new Abstract Machine or None if an error occurred.
|
||||||
|
"""
|
||||||
|
abstract_machine_id = f"{definition_id}_abstract_machine"
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
application = CuraApplication.getInstance()
|
||||||
|
registry = application.getContainerRegistry()
|
||||||
|
container_tree = ContainerTree.getInstance()
|
||||||
|
|
||||||
|
if registry.findContainerStacks(is_abstract_machine = "True", id = abstract_machine_id):
|
||||||
|
# This abstract machine already exists
|
||||||
|
return None
|
||||||
|
|
||||||
|
match registry.findDefinitionContainers(type = "machine", id = definition_id):
|
||||||
|
case []:
|
||||||
|
# It should not be possible for the definition to be missing since an abstract machine will only
|
||||||
|
# be created as a result of a machine with definition_id being created.
|
||||||
|
Logger.error(f"Definition {definition_id} was not found!")
|
||||||
|
return None
|
||||||
|
case [machine_definition, *_definitions]:
|
||||||
|
machine_node = container_tree.machines[machine_definition.getId()]
|
||||||
|
name = machine_definition.getName()
|
||||||
|
|
||||||
|
stack = GlobalStack(abstract_machine_id)
|
||||||
|
stack.setMetaDataEntry("is_abstract_machine", True)
|
||||||
|
stack.setMetaDataEntry("is_online", True)
|
||||||
|
stack.setDefinition(machine_definition)
|
||||||
|
cls.createUserContainer(
|
||||||
|
name,
|
||||||
|
machine_definition,
|
||||||
|
stack,
|
||||||
|
application.empty_variant_container,
|
||||||
|
application.empty_material_container,
|
||||||
|
machine_node.preferredGlobalQuality().container,
|
||||||
|
)
|
||||||
|
|
||||||
|
stack.setName(name)
|
||||||
|
|
||||||
|
registry.addContainer(stack)
|
||||||
|
|
||||||
|
return stack
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) 2019 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.
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
@ -8,10 +8,9 @@ import uuid
|
||||||
|
|
||||||
from PyQt6.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
|
from PyQt6.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
|
||||||
|
|
||||||
from UM.Decorators import deprecated, override
|
from UM.Decorators import override
|
||||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.SettingInstance import InstanceState
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.Interfaces import PropertyEvaluationContext
|
from UM.Settings.Interfaces import PropertyEvaluationContext
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -344,14 +343,12 @@ class GlobalStack(CuraContainerStack):
|
||||||
def getName(self) -> str:
|
def getName(self) -> str:
|
||||||
return self._metadata.get("group_name", self._metadata.get("name", ""))
|
return self._metadata.get("group_name", self._metadata.get("name", ""))
|
||||||
|
|
||||||
def setName(self, name: "str") -> None:
|
def setName(self, name: str) -> None:
|
||||||
super().setName(name)
|
super().setName(name)
|
||||||
|
|
||||||
nameChanged = pyqtSignal()
|
nameChanged = pyqtSignal()
|
||||||
name = pyqtProperty(str, fget=getName, fset=setName, notify=nameChanged)
|
name = pyqtProperty(str, fget=getName, fset=setName, notify=nameChanged)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## private:
|
## private:
|
||||||
global_stack_mime = MimeType(
|
global_stack_mime = MimeType(
|
||||||
name = "application/x-cura-globalstack",
|
name = "application/x-cura-globalstack",
|
||||||
|
|
|
@ -19,6 +19,7 @@ from UM.Logger import Logger
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
|
|
||||||
from UM.Settings.SettingFunction import SettingFunction
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Signal import postponeSignals, CompressTechnique
|
from UM.Signal import postponeSignals, CompressTechnique
|
||||||
|
|
||||||
import cura.CuraApplication # Imported like this to prevent circular references.
|
import cura.CuraApplication # Imported like this to prevent circular references.
|
||||||
|
@ -186,6 +187,32 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
self.outputDevicesChanged.emit()
|
self.outputDevicesChanged.emit()
|
||||||
|
|
||||||
|
def getMachinesWithDefinition(self, definition_id: str, online_only=False) -> List[ContainerStack]:
|
||||||
|
""" Fetches all container stacks that match definition_id.
|
||||||
|
|
||||||
|
:param definition_id: The id of the machine definition.
|
||||||
|
:return: A list of Containers that match definition_id
|
||||||
|
"""
|
||||||
|
from cura.CuraApplication import CuraApplication # In function to avoid circular import
|
||||||
|
application = CuraApplication.getInstance()
|
||||||
|
registry = application.getContainerRegistry()
|
||||||
|
|
||||||
|
machines = registry.findContainerStacks(type="machine")
|
||||||
|
# Filter machines that match definition
|
||||||
|
machines = filter(lambda machine: machine.definition.id == definition_id, machines)
|
||||||
|
# Filter only LAN and Cloud printers
|
||||||
|
machines = filter(lambda machine: ConnectionType.CloudConnection in machine.configuredConnectionTypes or
|
||||||
|
ConnectionType.NetworkConnection in machine.configuredConnectionTypes,
|
||||||
|
machines)
|
||||||
|
if online_only:
|
||||||
|
# LAN printers can have is_online = False but should still be included,
|
||||||
|
# their online status is only checked when they are the active printer.
|
||||||
|
machines = filter(lambda machine: parseBool(machine.getMetaDataEntry("is_online", False) or
|
||||||
|
ConnectionType.NetworkConnection in machine.configuredConnectionTypes),
|
||||||
|
machines)
|
||||||
|
|
||||||
|
return list(machines)
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = currentConfigurationChanged)
|
@pyqtProperty(QObject, notify = currentConfigurationChanged)
|
||||||
def currentConfiguration(self) -> PrinterConfigurationModel:
|
def currentConfiguration(self) -> PrinterConfigurationModel:
|
||||||
return self._current_printer_configuration
|
return self._current_printer_configuration
|
||||||
|
|
|
@ -10,10 +10,13 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# This class manages a all registered upon-exit checks that need to be perform when the application tries to exit.
|
# This class manages all registered upon-exit checks
|
||||||
# For example, to show a confirmation dialog when there is USB printing in progress, etc. All callbacks will be called
|
# that need to be performed when the application tries to exit.
|
||||||
# in the order of when they got registered. If all callbacks "passes", that is, for example, if the user clicks "yes"
|
# For example, show a confirmation dialog when there is USB printing in progress.
|
||||||
# on the exit confirmation dialog or nothing that's blocking the exit, then the application will quit after that.
|
# All callbacks will be called in the order of when they were registered.
|
||||||
|
# If all callbacks "pass", for example:
|
||||||
|
# if the user clicks "yes" on the exit confirmation dialog
|
||||||
|
# and nothing else is blocking the exit, then the application will quit.
|
||||||
#
|
#
|
||||||
class OnExitCallbackManager:
|
class OnExitCallbackManager:
|
||||||
|
|
||||||
|
@ -35,10 +38,12 @@ class OnExitCallbackManager:
|
||||||
def getIsAllChecksPassed(self) -> bool:
|
def getIsAllChecksPassed(self) -> bool:
|
||||||
return self._is_all_checks_passed
|
return self._is_all_checks_passed
|
||||||
|
|
||||||
# Trigger the next callback if available. If not, it means that all callbacks have "passed", which means we should
|
# Trigger the next callback if there is one.
|
||||||
# not block the application to quit, and it will call the application to actually quit.
|
# If not, all callbacks have "passed",
|
||||||
|
# which means we should not prevent the application from quitting,
|
||||||
|
# and we call the application to actually quit.
|
||||||
def triggerNextCallback(self) -> None:
|
def triggerNextCallback(self) -> None:
|
||||||
# Get the next callback and schedule that if
|
# Get the next callback and schedule it
|
||||||
this_callback = None
|
this_callback = None
|
||||||
if self._current_callback_idx < len(self._on_exit_callback_list):
|
if self._current_callback_idx < len(self._on_exit_callback_list):
|
||||||
this_callback = self._on_exit_callback_list[self._current_callback_idx]
|
this_callback = self._on_exit_callback_list[self._current_callback_idx]
|
||||||
|
@ -55,10 +60,11 @@ class OnExitCallbackManager:
|
||||||
# Tell the application to exit
|
# Tell the application to exit
|
||||||
self._application.callLater(self._application.closeApplication)
|
self._application.callLater(self._application.closeApplication)
|
||||||
|
|
||||||
# This is the callback function which an on-exit callback should call when it finishes, it should provide the
|
# Callback function which an on-exit callback calls when it finishes.
|
||||||
# "should_proceed" flag indicating whether this check has "passed", or in other words, whether quitting the
|
# It provides a "should_proceed" flag indicating whether the check has "passed",
|
||||||
# application should be blocked. If the last on-exit callback doesn't block the quitting, it will call the next
|
# or whether quitting the application should be blocked.
|
||||||
# registered on-exit callback if available.
|
# If the last on-exit callback doesn't block quitting, it will call the next
|
||||||
|
# registered on-exit callback if one is available.
|
||||||
def onCurrentCallbackFinished(self, should_proceed: bool = True) -> None:
|
def onCurrentCallbackFinished(self, should_proceed: bool = True) -> None:
|
||||||
if not should_proceed:
|
if not should_proceed:
|
||||||
Logger.log("d", "on-app-exit callback finished and we should not proceed.")
|
Logger.log("d", "on-app-exit callback finished and we should not proceed.")
|
||||||
|
|
|
@ -38,6 +38,8 @@ class PrintInformation(QObject):
|
||||||
|
|
||||||
self.initializeCuraMessagePrintTimeProperties()
|
self.initializeCuraMessagePrintTimeProperties()
|
||||||
|
|
||||||
|
self.slice_uuid: Optional[str] = None
|
||||||
|
|
||||||
# Indexed by build plate number
|
# Indexed by build plate number
|
||||||
self._material_lengths = {} # type: Dict[int, List[float]]
|
self._material_lengths = {} # type: Dict[int, List[float]]
|
||||||
self._material_weights = {} # type: Dict[int, List[float]]
|
self._material_weights = {} # type: Dict[int, List[float]]
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Abort at the first error.
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
|
||||||
PROJECT_DIR="$( cd "${SCRIPT_DIR}/.." && pwd )"
|
|
||||||
|
|
||||||
# Make sure that environment variables are set properly
|
|
||||||
export PATH="${CURA_BUILD_ENV_PATH}/bin:${PATH}"
|
|
||||||
export PKG_CONFIG_PATH="${CURA_BUILD_ENV_PATH}/lib/pkgconfig:${PKG_CONFIG_PATH}"
|
|
||||||
export LD_LIBRARY_PATH="${CURA_BUILD_ENV_PATH}/lib:${LD_LIBRARY_PATH}"
|
|
||||||
|
|
||||||
cd "${PROJECT_DIR}"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Clone Uranium and set PYTHONPATH first
|
|
||||||
#
|
|
||||||
|
|
||||||
# Check the branch to use for Uranium.
|
|
||||||
# It tries the following branch names and uses the first one that's available.
|
|
||||||
# - GITHUB_HEAD_REF: the branch name of a PR. If it's not a PR, it will be empty.
|
|
||||||
# - GITHUB_BASE_REF: the branch a PR is based on. If it's not a PR, it will be empty.
|
|
||||||
# - GITHUB_REF: the branch name if it's a branch on the repository;
|
|
||||||
# refs/pull/123/merge if it's a pull_request.
|
|
||||||
# - master: the master branch. It should always exist.
|
|
||||||
|
|
||||||
# For debugging.
|
|
||||||
echo "GITHUB_REF: ${GITHUB_REF}"
|
|
||||||
echo "GITHUB_HEAD_REF: ${GITHUB_HEAD_REF}"
|
|
||||||
echo "GITHUB_BASE_REF: ${GITHUB_BASE_REF}"
|
|
||||||
|
|
||||||
GIT_REF_NAME_LIST=( "${GITHUB_HEAD_REF}" "${GITHUB_BASE_REF}" "${GITHUB_REF}" "master" )
|
|
||||||
for git_ref_name in "${GIT_REF_NAME_LIST[@]}"
|
|
||||||
do
|
|
||||||
if [ -z "${git_ref_name}" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
git_ref_name="$(basename "${git_ref_name}")"
|
|
||||||
# Skip refs/pull/1234/merge as pull requests use it as GITHUB_REF
|
|
||||||
if [[ "${git_ref_name}" == "merge" ]]; then
|
|
||||||
echo "Skip [${git_ref_name}]"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
URANIUM_BRANCH="${git_ref_name}"
|
|
||||||
output="$(git ls-remote --heads https://github.com/Ultimaker/Uranium.git "${URANIUM_BRANCH}")"
|
|
||||||
if [ -n "${output}" ]; then
|
|
||||||
echo "Found Uranium branch [${URANIUM_BRANCH}]."
|
|
||||||
break
|
|
||||||
else
|
|
||||||
echo "Could not find Uranium branch [${URANIUM_BRANCH}], try next."
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Using Uranium branch ${URANIUM_BRANCH} ..."
|
|
||||||
git clone --depth=1 -b "${URANIUM_BRANCH}" https://github.com/Ultimaker/Uranium.git "${PROJECT_DIR}"/Uranium
|
|
||||||
export PYTHONPATH="${PROJECT_DIR}/Uranium:.:${PYTHONPATH}"
|
|
||||||
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake \
|
|
||||||
-DCMAKE_BUILD_TYPE=Debug \
|
|
||||||
-DCMAKE_PREFIX_PATH="${CURA_BUILD_ENV_PATH}" \
|
|
||||||
-DURANIUM_DIR="${PROJECT_DIR}/Uranium" \
|
|
||||||
-DBUILD_TESTS=ON \
|
|
||||||
-DPRINT_PLUGIN_LIST=OFF \
|
|
||||||
-DGENERATE_TRANSLATIONS=OFF \
|
|
||||||
..
|
|
||||||
make
|
|
|
@ -1,3 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
cd build
|
|
||||||
ctest -j4 --output-on-failure -T Test
|
|
81
docs/Report.md
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
|
||||||
|
# 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
|
|
@ -2,32 +2,77 @@ Setting Properties
|
||||||
====
|
====
|
||||||
Each setting in Cura has a number of properties. It's not just a key and a value. This page lists the properties that a setting can define.
|
Each setting in Cura has a number of properties. It's not just a key and a value. This page lists the properties that a setting can define.
|
||||||
|
|
||||||
* `key` (string): The identifier by which the setting is referenced. This is not a human-readable name, but just a reference string, such as `layer_height_0`. Typically these are named with the most significant category first, in order to sort them better, such as `material_print_temperature`. This is not actually a real property but just an identifier; it can't be changed.
|
* `key` (string): __The identifier by which the setting is referenced.__
|
||||||
* `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.
|
* This is not a human-readable name, but just a reference string, such as `layer_height_0`.
|
||||||
* `default_value`: A default value for the setting if `value` is undefined. This property is required however. It can't be a Python expression, but it can be any JSON type. This is made separate so that CuraEngine can read it out as well for its debugging mode via the command line, without needing a complete Python interpreter.
|
* This is not actually a real property but just an identifier; it can't be changed.
|
||||||
* `label` (string): The human-readable name for the setting. This label is translated.
|
* Typically these are named with the most significant category first, in order to sort them better, such as `material_print_temperature`.
|
||||||
* `description` (string): A longer description of what the setting does when you change it. This description is translated as well.
|
* `value` (optional): __The current value of the setting.__
|
||||||
* `type` (string): The type of value that this setting contains. Allowed types are: `bool`, `str`, `float`, `int`, `enum`, `category`, `[int]`, `vec3`, `polygon` and `polygons`.
|
* This can be a function (an arbitrary Python expression) that depends on the values of other settings.
|
||||||
* `unit` (optional string): A unit that is displayed at the right-hand side of the text field where the user enters the setting value.
|
* If it's not present, the `default_value` is used.
|
||||||
* `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.
|
* `default_value`: __A default value for the setting if `value` is undefined.__
|
||||||
* `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.
|
* This property is required.
|
||||||
* `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.
|
* It can't be a Python expression, but it can be any JSON type.
|
||||||
* `minimum_value` (optional): The lowest acceptable value for this setting. If it's any lower, Cura will not allow the user to slice. By convention this is used to prevent setting values that are technically or physically impossible, such as a layer height of 0mm. This property only applies to numerical settings.
|
* 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.
|
||||||
* `maximum_value` (optional): The highest acceptable value for this setting. If it's any higher, Cura will not allow the user to slice. By convention this is used to prevent setting values that are technically or physically impossible, such as a support overhang angle of more than 90 degrees. This property only applies to numerical settings.
|
* `label` (string): __The human-readable name for the setting.__
|
||||||
* `minimum_value_warning` (optional): The threshold under which a warning is displayed to the user. By convention this is used to indicate that it will probably not print very nicely with such a low setting value. This property only applies to numerical settings.
|
* This label is translated.
|
||||||
* `maximum_value_warning` (optional): The threshold above which a warning is displayed to the user. By convention this is used to indicate that it will probably not print very nicely with such a high setting value. This property only applies to numerical settings.
|
* `description` (string): __A longer description of what the setting does when you change it.__
|
||||||
* `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.
|
* This description is translated.
|
||||||
* `settable_per_meshgroup` (optional boolean): Whether a setting can be changed per group of meshes. Currently unused in Cura.
|
* `type` (string): __The type of value that this setting contains.__
|
||||||
* `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.
|
* Allowed types are: `bool`, `str`, `float`, `int`, `enum`, `category`, `[int]`, `vec3`, `polygon` and `polygons`.
|
||||||
* `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.
|
* `unit` (optional string): __A unit that is displayed at the right-hand side of the text field where the user enters the setting value.__
|
||||||
* `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`.
|
* `resolve` (optional string): __A Python expression that resolves disagreements for global settings if multiple per-extruder profiles define different values for a setting.__
|
||||||
* `icon` (optional string): A path to an icon to be displayed. Only applies to setting categories.
|
* 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.
|
||||||
* `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.
|
* `limit_to_extruder` (optional): __A Python expression that indicates which extruder a setting will be obtained from.__
|
||||||
* `warning_description` (optional string): A warning message to display when the setting has a warning value. This is currently unused by Cura.
|
* 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.
|
||||||
* `error_description` (optional string): An error message to display when the setting has an error value. This is currently unused by Cura.
|
* `enabled` (optional string or boolean): __Whether the setting can currently be made visible for the user.__
|
||||||
* `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.
|
* This can be a simple true/false, or a Python expression that depends on other settings.
|
||||||
* `comments` (optional string): Comments to other programmers about the setting. This is not used by Cura.
|
* Typically used for settings that don't apply when another setting is disabled, such as to hide the support settings if support is disabled.
|
||||||
* `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.
|
* `minimum_value` (optional): __The lowest acceptable value for this setting.__
|
||||||
* `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.
|
* If it's any lower, Cura will not allow the user to slice.
|
||||||
* `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.
|
* This property only applies to numerical settings.
|
||||||
* `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.
|
* 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,18 +1,30 @@
|
||||||
Repositories
|
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 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), 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, as well as Ultimaker services such as the Marketplace and accounts.
|
* [Cura](https://github.com/Ultimaker/Cura) is the main repository for the front-end of Cura. This contains:
|
||||||
* The Cura repository is built on [Uranium](https://github.com/Ultimaker/Uranium), a framework for desktop applications that handle 3D models and have 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 and a system for stacked profiles that change settings.
|
- all of the business logic for the front-end, including the specific types of profiles that are available
|
||||||
* In order to slice, Cura starts [CuraEngine](https://github.com/Ultimaker/CuraEngine) in the background. This does the actual process that converts 3D models into a toolpath for the printer.
|
- the concept of 3D printers and materials
|
||||||
* Communication to CuraEngine goes via [libArcus](https://github.com/Ultimaker/libArcus), a small library that wraps around [Protobuf](https://developers.google.com/protocol-buffers/) in order to make it run over a local socket.
|
- specific tools for handling 3D printed models
|
||||||
* Cura's build scripts are in [cura-build](https://github.com/Ultimaker/cura-build) and build scripts for building dependencies are in [cura-build-environment](https://github.com/Ultimaker/cura-build-environment).
|
- 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:
|
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:
|
||||||
* Loading and writing 3MF files is done through [libSavitar](https://github.com/Ultimaker/libSavitar).
|
* [libSavitar](https://github.com/Ultimaker/libSavitar) is used for loading and writing 3MF files.
|
||||||
* Loading and writing UFP files is done through [libCharon](https://github.com/Ultimaker/libCharon).
|
* [libCharon](https://github.com/Ultimaker/libCharon) is used for loading and writing UFP files.
|
||||||
* To make the build system a bit simpler, some parts are pre-compiled in [cura-binary-data](https://github.com/Ultimaker/cura-binary-data). This holds things like the machine-readable translation files and the Marlin builds for firmware updates, which would require considerable tooling to build automatically.
|
* [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:
|
||||||
* There are automated GUI tests in [Cura-squish-tests](https://github.com/Ultimaker/Cura-squish-tests).
|
- the machine-readable translation files
|
||||||
* Material profiles are stored in [fdm_materials](https://github.com/Ultimaker/fdm_materials). 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.
|
- 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
|
Interplay
|
||||||
----
|
----
|
||||||
|
|
54
docs/resources/deps.dot
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
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"
|
||||||
|
}
|
BIN
icons/cura.icns
20
packaging/AppImage/AppRun
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
scriptdir=$(dirname $0)
|
||||||
|
|
||||||
|
export PYTHONPATH="$scriptdir/lib/python3.10"
|
||||||
|
export LD_LIBRARY_PATH=$scriptdir
|
||||||
|
export QT_PLUGIN_PATH="$scriptdir/qt/plugins"
|
||||||
|
export QML2_IMPORT_PATH="$scriptdir/qt/qml"
|
||||||
|
export QT_QPA_FONTDIR=/usr/share/fonts
|
||||||
|
export QT_QPA_PLATFORMTHEME=xdgdesktopportal
|
||||||
|
export QT_XKB_CONFIG_ROOT=/usr/share/X11/xkb
|
||||||
|
|
||||||
|
# Use the openssl.cnf packaged in the AppImage
|
||||||
|
export OPENSSL_CONF="$scriptdir/openssl.cnf"
|
||||||
|
|
||||||
|
$scriptdir/Ultimaker-Cura "$@"
|
||||||
|
|
||||||
|
# If this variable is set on Zorin OS 16 Cura would crash
|
||||||
|
# unset `QT_STYLE_OVERRIDE` as a precaution
|
||||||
|
unset QT_STYLE_OVERRIDE
|
76
packaging/AppImage/create_appimage.py
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
# Copyright (c) 2022 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
import argparse # Command line arguments parsing and help.
|
||||||
|
from jinja2 import Template
|
||||||
|
import os # Finding installation directory.
|
||||||
|
import os.path # Finding files.
|
||||||
|
import shutil # Copying files.
|
||||||
|
import stat # For setting file permissions.
|
||||||
|
import subprocess # For calling system commands.
|
||||||
|
|
||||||
|
def build_appimage(dist_path, version, appimage_filename):
|
||||||
|
"""
|
||||||
|
Creates an AppImage file from the build artefacts created so far.
|
||||||
|
"""
|
||||||
|
copy_metadata_files(dist_path, version)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.remove(os.path.join(dist_path, appimage_filename)) # Ensure any old file is removed, if it exists.
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass # If it didn't exist, that's even better.
|
||||||
|
|
||||||
|
generate_appimage(dist_path, appimage_filename)
|
||||||
|
|
||||||
|
sign_appimage(dist_path, appimage_filename)
|
||||||
|
|
||||||
|
def copy_metadata_files(dist_path, version):
|
||||||
|
"""
|
||||||
|
Copy metadata files for the metadata of the AppImage.
|
||||||
|
"""
|
||||||
|
copied_files = {
|
||||||
|
os.path.join("..", "icons", "cura-icon_256x256.png"): "cura-icon.png",
|
||||||
|
"cura.appdata.xml": "cura.appdata.xml",
|
||||||
|
"AppRun": "AppRun"
|
||||||
|
}
|
||||||
|
|
||||||
|
packaging_dir = os.path.dirname(__file__)
|
||||||
|
for source, dest in copied_files.items():
|
||||||
|
print("Copying", os.path.join(packaging_dir, source), "to", os.path.join(dist_path, dest))
|
||||||
|
shutil.copyfile(os.path.join(packaging_dir, source), os.path.join(dist_path, dest))
|
||||||
|
|
||||||
|
# Ensure that AppRun has the proper permissions: 755 (user reads, writes and executes, group reads and executes, world reads and executes).
|
||||||
|
print("Changing permissions for AppRun")
|
||||||
|
os.chmod(os.path.join(dist_path, "AppRun"), stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
|
||||||
|
|
||||||
|
# Provision the Desktop file with the correct version number.
|
||||||
|
template_path = os.path.join(packaging_dir, "cura.desktop.jinja")
|
||||||
|
desktop_path = os.path.join(dist_path, "cura.desktop")
|
||||||
|
print("Provisioning desktop file from", template_path, "to", desktop_path)
|
||||||
|
with open(template_path, "r") as f:
|
||||||
|
desktop_file = Template(f.read())
|
||||||
|
with open(desktop_path, "w") as f:
|
||||||
|
f.write(desktop_file.render(cura_version = version))
|
||||||
|
|
||||||
|
def generate_appimage(dist_path, appimage_filename):
|
||||||
|
appimage_path = os.path.join(dist_path, "..", appimage_filename)
|
||||||
|
appimagetool = os.getenv("APPIMAGETOOL_LOCATION", "appimagetool")
|
||||||
|
command = [appimagetool, "--appimage-extract-and-run", f"{dist_path}/", appimage_path]
|
||||||
|
result = subprocess.call(command)
|
||||||
|
if result != 0:
|
||||||
|
raise RuntimeError(f"The AppImageTool command returned non-zero: {result}")
|
||||||
|
|
||||||
|
def sign_appimage(dist_path, appimage_filename):
|
||||||
|
appimage_path = os.path.join(dist_path, "..", appimage_filename)
|
||||||
|
command = ["gpg", "--yes", "--armor", "--detach-sig", appimage_path]
|
||||||
|
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()
|
||||||
|
build_appimage(args.dist_path, args.version, args.filename)
|
18
packaging/AppImage/cura.appdata.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<component type="desktop-application">
|
||||||
|
<id>com.ultimaker.cura</id>
|
||||||
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
|
<project_license>LGPL-3.0</project_license>
|
||||||
|
<name>Ultimaker Cura</name>
|
||||||
|
<summary>Slicer to prepare your 3D printing projects</summary>
|
||||||
|
<description>
|
||||||
|
<p>Ultimaker Cura is a slicer, an application that prepares your model for 3D printing. Optimized, expert-tested profiles for 3D printers and materials mean you can start printing reliably in no time. And with industry-standard software integration, you can streamline your workflow for maximum efficiency.</p>
|
||||||
|
</description>
|
||||||
|
<url type="homepage">https://ultimaker.com/en/software/ultimaker-cura</url>
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<caption>Print preparation screen</caption>
|
||||||
|
<image>https://raw.githubusercontent.com/Ultimaker/Cura/master/screenshot.png</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
</component>
|
15
packaging/AppImage/cura.desktop.jinja
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Ultimaker Cura
|
||||||
|
Name[de]=Ultimaker Cura
|
||||||
|
GenericName=3D Printing Software
|
||||||
|
GenericName[de]=3D-Druck-Software
|
||||||
|
GenericName[nl]=3D-Print Software
|
||||||
|
Comment=Cura converts 3D models into paths for a 3D printer. It prepares your print for maximum accuracy, minimum printing time and good reliability with many extra features that make your print come out great.
|
||||||
|
Exec=Ultimaker-Cura %F
|
||||||
|
Icon=cura-icon
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;text/x-gcode;application/x-amf;application/x-ply;application/x-ctm;model/vnd.collada+xml;model/gltf-binary;model/gltf+json;model/vnd.collada+xml+zip;
|
||||||
|
Categories=Graphics;
|
||||||
|
Keywords=3D;Printing;
|
||||||
|
X-AppImage-Version={{ cura_version }}
|
194
packaging/NSIS/Ultimaker-Cura.nsi.jinja
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
# Copyright (c) 2022 Ultimaker B.V.
|
||||||
|
# Cura's build system is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
!define APP_NAME "{{ app_name }}"
|
||||||
|
!define COMP_NAME "{{ company }}"
|
||||||
|
!define WEB_SITE "{{ web_site }}"
|
||||||
|
!define VERSION "{{ version }}"
|
||||||
|
!define VIVERSION "{{ version_major }}.{{ version_minor }}.{{ version_patch }}.0"
|
||||||
|
!define COPYRIGHT "Copyright (c) {{ year }} {{ company }}"
|
||||||
|
!define DESCRIPTION "Application"
|
||||||
|
!define LICENSE_TXT "{{ cura_license_file }}"
|
||||||
|
!define INSTALLER_NAME "{{ destination }}"
|
||||||
|
!define MAIN_APP_EXE "{{ main_app }}"
|
||||||
|
!define INSTALL_TYPE "SetShellVarContext all"
|
||||||
|
!define REG_ROOT "HKCR"
|
||||||
|
!define REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\${APP_NAME}"
|
||||||
|
!define UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}"
|
||||||
|
|
||||||
|
!define REG_START_MENU "Start Menu Folder"
|
||||||
|
|
||||||
|
;Require administrator access
|
||||||
|
RequestExecutionLevel admin
|
||||||
|
|
||||||
|
var SM_Folder
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
VIProductVersion "${VIVERSION}"
|
||||||
|
VIAddVersionKey "ProductName" "{{ app_name }}"
|
||||||
|
VIAddVersionKey "CompanyName" "${COMP_NAME}"
|
||||||
|
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
|
||||||
|
VIAddVersionKey "FileDescription" "${DESCRIPTION}"
|
||||||
|
VIAddVersionKey "FileVersion" "${VIVERSION}"
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
SetCompressor {{ compression_method }}
|
||||||
|
Name "${APP_NAME}"
|
||||||
|
Caption "${APP_NAME}"
|
||||||
|
OutFile "${INSTALLER_NAME}"
|
||||||
|
BrandingText "${APP_NAME}"
|
||||||
|
InstallDir "$PROGRAMFILES64\${APP_NAME}"
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
!include "MUI2.nsh"
|
||||||
|
!include fileassoc.nsh
|
||||||
|
|
||||||
|
!define MUI_ABORTWARNING
|
||||||
|
!define MUI_UNABORTWARNING
|
||||||
|
|
||||||
|
!define MUI_ICON "{{ cura_icon }}"
|
||||||
|
|
||||||
|
!define MUI_WELCOMEFINISHPAGE_BITMAP "{{ cura_banner_img }}"
|
||||||
|
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "{{ cura_banner_img }}"
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
|
|
||||||
|
!ifdef LICENSE_TXT
|
||||||
|
!insertmacro MUI_PAGE_LICENSE "${LICENSE_TXT}"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
|
||||||
|
!ifdef REG_START_MENU
|
||||||
|
!define MUI_STARTMENUPAGE_NODISABLE
|
||||||
|
!define MUI_STARTMENUPAGE_DEFAULTFOLDER "Ultimaker Cura"
|
||||||
|
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "${REG_ROOT}"
|
||||||
|
!define MUI_STARTMENUPAGE_REGISTRY_KEY "${UNINSTALL_PATH}"
|
||||||
|
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "${REG_START_MENU}"
|
||||||
|
!insertmacro MUI_PAGE_STARTMENU Application $SM_Folder
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
|
||||||
|
# Set up explorer to run Cura instead of directly, so it's not executed elevated (with all negative consequences that brings for an unelevated user).
|
||||||
|
!define MUI_FINISHPAGE_RUN "$WINDIR\explorer.exe"
|
||||||
|
!define MUI_FINISHPAGE_RUN_PARAMETERS "$INSTDIR\${MAIN_APP_EXE}"
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
|
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
|
|
||||||
|
!insertmacro MUI_UNPAGE_FINISH
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGUAGE "English"
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
Section -MainProgram
|
||||||
|
${INSTALL_TYPE}
|
||||||
|
SetOverwrite ifnewer
|
||||||
|
{% for out_path, files in mapped_out_paths.items() %}SetOutPath "{{ out_path }}"{% for file in files %}
|
||||||
|
File "{{ file[0] }}"{% endfor %}
|
||||||
|
{% endfor %}SectionEnd
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
Section -Extension_Reg
|
||||||
|
!insertmacro APP_ASSOCIATE "stl" "Cura.model" "Standard Tessellation Language (STL) files" "$INSTDIR\${MAIN_APP_EXE},0" "Open with {{ app_name }}" "$INSTDIR\${MAIN_APP_EXE} $\"%1$\""
|
||||||
|
!insertmacro APP_ASSOCIATE "3mf" "Cura.project" "3D Manufacturing Format (3MF) files" "$INSTDIR\${MAIN_APP_EXE},0" "Open with {{ app_name }}" "$INSTDIR\${MAIN_APP_EXE} $\"%1$\""
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section -Icons_Reg
|
||||||
|
SetOutPath "$INSTDIR"
|
||||||
|
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||||
|
|
||||||
|
!ifdef REG_START_MENU
|
||||||
|
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
|
||||||
|
CreateDirectory "$SMPROGRAMS\$SM_Folder"
|
||||||
|
CreateShortCut "$SMPROGRAMS\$SM_Folder\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}"
|
||||||
|
CreateShortCut "$SMPROGRAMS\$SM_Folder\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe"
|
||||||
|
|
||||||
|
!ifdef WEB_SITE
|
||||||
|
WriteIniStr "$INSTDIR\Ultimaker Cura website.url" "InternetShortcut" "URL" "${WEB_SITE}"
|
||||||
|
CreateShortCut "$SMPROGRAMS\$SM_Folder\Ultimaker Cura website.lnk" "$INSTDIR\Ultimaker Cura website.url"
|
||||||
|
!endif
|
||||||
|
!insertmacro MUI_STARTMENU_WRITE_END
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifndef REG_START_MENU
|
||||||
|
CreateDirectory "$SMPROGRAMS\{{ app_name }}"
|
||||||
|
CreateShortCut "$SMPROGRAMS\{{ app_name }}\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}"
|
||||||
|
CreateShortCut "$SMPROGRAMS\{{ app_name }}\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe"
|
||||||
|
|
||||||
|
!ifdef WEB_SITE
|
||||||
|
WriteIniStr "$INSTDIR\Ultimaker Cura website.url" "InternetShortcut" "URL" "${WEB_SITE}"
|
||||||
|
CreateShortCut "$SMPROGRAMS\{{ app_name }}\Ultimaker Cura website.lnk" "$INSTDIR\Ultimaker Cura website.url"
|
||||||
|
!endif
|
||||||
|
!endif
|
||||||
|
|
||||||
|
WriteRegStr ${REG_ROOT} "${REG_APP_PATH}" "" "$INSTDIR\${MAIN_APP_EXE}"
|
||||||
|
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayName" "${APP_NAME}"
|
||||||
|
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "UninstallString" "$INSTDIR\uninstall.exe"
|
||||||
|
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayIcon" "$INSTDIR\${MAIN_APP_EXE}"
|
||||||
|
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayVersion" "${VERSION}"
|
||||||
|
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "Publisher" "${COMP_NAME}"
|
||||||
|
|
||||||
|
!ifdef WEB_SITE
|
||||||
|
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "URLInfoAbout" "${WEB_SITE}"
|
||||||
|
!endif
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
Section Uninstall
|
||||||
|
${INSTALL_TYPE}{% for files in mapped_out_paths.values() %}{% for file in files %}
|
||||||
|
Delete "{{ file[1] }}"{% endfor %}{% endfor %}{% for rem_dir in rmdir_paths %}
|
||||||
|
RmDir "{{ rem_dir }}"{% endfor %}
|
||||||
|
|
||||||
|
# FIXME: dirty solution, but for some reason these directories aren't removed
|
||||||
|
RmDir "$INSTDIR\share\cura\resources\scripts"
|
||||||
|
RmDir "$INSTDIR\share\cura\resources"
|
||||||
|
RmDir "$INSTDIR\share\cura"
|
||||||
|
RmDir "$INSTDIR\share\uranium\resources\scripts"
|
||||||
|
RmDir "$INSTDIR\share\uranium\resources"
|
||||||
|
RmDir "$INSTDIR\share\uranium"
|
||||||
|
RmDir "$INSTDIR\share"
|
||||||
|
|
||||||
|
Delete "$INSTDIR\uninstall.exe"
|
||||||
|
!ifdef WEB_SITE
|
||||||
|
Delete "$INSTDIR\${APP_NAME} website.url"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
RmDir "$INSTDIR"
|
||||||
|
|
||||||
|
!ifdef REG_START_MENU
|
||||||
|
!insertmacro MUI_STARTMENU_GETFOLDER "Application" $SM_Folder
|
||||||
|
Delete "$SMPROGRAMS\$SM_Folder\${APP_NAME}.lnk"
|
||||||
|
Delete "$SMPROGRAMS\$SM_Folder\Uninstall ${APP_NAME}.lnk"
|
||||||
|
!ifdef WEB_SITE
|
||||||
|
Delete "$SMPROGRAMS\$SM_Folder\Ultimaker Cura website.lnk"
|
||||||
|
!endif
|
||||||
|
RmDir "$SMPROGRAMS\$SM_Folder"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifndef REG_START_MENU
|
||||||
|
Delete "$SMPROGRAMS\{{ app_name }}\${APP_NAME}.lnk"
|
||||||
|
Delete "$SMPROGRAMS\{{ app_name }}\Uninstall ${APP_NAME}.lnk"
|
||||||
|
!ifdef WEB_SITE
|
||||||
|
Delete "$SMPROGRAMS\{{ app_name }}\Ultimaker Cura website.lnk"
|
||||||
|
!endif
|
||||||
|
RmDir "$SMPROGRAMS\{{ app_name }}"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!insertmacro APP_UNASSOCIATE "stl" "Cura.model"
|
||||||
|
!insertmacro APP_UNASSOCIATE "3mf" "Cura.project"
|
||||||
|
|
||||||
|
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
|
||||||
|
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
######################################################################
|
80
packaging/NSIS/create_windows_installer.py
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
# Copyright (c) 2022 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
import os
|
||||||
|
import argparse # Command line arguments parsing and help.
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from jinja2 import Template
|
||||||
|
|
||||||
|
|
||||||
|
def generate_nsi(source_path: str, dist_path: str, filename: str):
|
||||||
|
dist_loc = Path(os.getcwd(), dist_path)
|
||||||
|
source_loc = Path(os.getcwd(), source_path)
|
||||||
|
instdir = Path("$INSTDIR")
|
||||||
|
dist_paths = [p.relative_to(dist_loc.joinpath("Ultimaker-Cura")) for p in sorted(dist_loc.joinpath("Ultimaker-Cura").rglob("*")) if p.is_file()]
|
||||||
|
mapped_out_paths = {}
|
||||||
|
for dist_path in dist_paths:
|
||||||
|
if "__pycache__" not in dist_path.parts:
|
||||||
|
out_path = instdir.joinpath(dist_path).parent
|
||||||
|
if out_path not in mapped_out_paths:
|
||||||
|
mapped_out_paths[out_path] = [(dist_loc.joinpath("Ultimaker-Cura", dist_path), instdir.joinpath(dist_path))]
|
||||||
|
else:
|
||||||
|
mapped_out_paths[out_path].append((dist_loc.joinpath("Ultimaker-Cura", dist_path), instdir.joinpath(dist_path)))
|
||||||
|
|
||||||
|
rmdir_paths = set()
|
||||||
|
for rmdir_f in mapped_out_paths.values():
|
||||||
|
for _, rmdir_p in rmdir_f:
|
||||||
|
for rmdir in rmdir_p.parents:
|
||||||
|
rmdir_paths.add(rmdir)
|
||||||
|
|
||||||
|
rmdir_paths = sorted(list(rmdir_paths), reverse = True)[:-2] # Removes the `.` and `..` from the list
|
||||||
|
|
||||||
|
jinja_template_path = Path(source_loc.joinpath("packaging", "NSIS", "Ultimaker-Cura.nsi.jinja"))
|
||||||
|
with open(jinja_template_path, "r") as f:
|
||||||
|
template = Template(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
nsis_content = template.render(
|
||||||
|
app_name = f"Ultimaker Cura {os.getenv('CURA_VERSION_FULL')}",
|
||||||
|
main_app = "Ultimaker-Cura.exe",
|
||||||
|
version = os.getenv('CURA_VERSION_FULL'),
|
||||||
|
version_major = os.environ.get("CURA_VERSION_MAJOR"),
|
||||||
|
version_minor = os.environ.get("CURA_VERSION_MINOR"),
|
||||||
|
version_patch = os.environ.get("CURA_VERSION_PATCH"),
|
||||||
|
company = "Ultimaker B.V.",
|
||||||
|
web_site = "https://ultimaker.com",
|
||||||
|
year = datetime.now().year,
|
||||||
|
cura_license_file = str(source_loc.joinpath("packaging", "cura_license.txt")),
|
||||||
|
compression_method = "LZMA", # ZLIB, BZIP2 or LZMA
|
||||||
|
cura_banner_img = str(source_loc.joinpath("packaging", "NSIS", "cura_banner_nsis.bmp")),
|
||||||
|
cura_icon = str(source_loc.joinpath("packaging", "icons", "Cura.ico")),
|
||||||
|
mapped_out_paths = mapped_out_paths,
|
||||||
|
rmdir_paths = rmdir_paths,
|
||||||
|
destination = filename
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(dist_loc.joinpath("Ultimaker-Cura.nsi"), "w") as f:
|
||||||
|
f.write(nsis_content)
|
||||||
|
|
||||||
|
shutil.copy(source_loc.joinpath("packaging", "NSIS", "fileassoc.nsh"), dist_loc.joinpath("fileassoc.nsh"))
|
||||||
|
|
||||||
|
|
||||||
|
def build(dist_path: str):
|
||||||
|
dist_loc = Path(os.getcwd(), dist_path)
|
||||||
|
command = ["makensis", "/V2", "/P4", str(dist_loc.joinpath("Ultimaker-Cura.nsi"))]
|
||||||
|
subprocess.run(command)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description = "Create Windows exe installer of Cura.")
|
||||||
|
parser.add_argument("source_path", type=str, help="Path to Conan install Cura folder.")
|
||||||
|
parser.add_argument("dist_path", type=str, help="Path to Pyinstaller dist folder")
|
||||||
|
parser.add_argument("filename", type = str, help = "Filename of the exe (e.g. 'Ultimaker-Cura-5.1.0-beta-Windows-X64.exe')")
|
||||||
|
args = parser.parse_args()
|
||||||
|
generate_nsi(args.source_path, args.dist_path, args.filename)
|
||||||
|
build(args.dist_path)
|
BIN
packaging/NSIS/cura_banner_nsis.bmp
Normal file
After Width: | Height: | Size: 201 KiB |
134
packaging/NSIS/fileassoc.nsh
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
; fileassoc.nsh
|
||||||
|
; File association helper macros
|
||||||
|
; Written by Saivert
|
||||||
|
;
|
||||||
|
; Improved by Nikku<https://github.com/nikku>.
|
||||||
|
;
|
||||||
|
; Features automatic backup system and UPDATEFILEASSOC macro for
|
||||||
|
; shell change notification.
|
||||||
|
;
|
||||||
|
; |> How to use <|
|
||||||
|
; To associate a file with an application so you can double-click it in explorer, use
|
||||||
|
; the APP_ASSOCIATE macro like this:
|
||||||
|
;
|
||||||
|
; Example:
|
||||||
|
; !insertmacro APP_ASSOCIATE "txt" "myapp.textfile" "Description of txt files" \
|
||||||
|
; "$INSTDIR\myapp.exe,0" "Open with myapp" "$INSTDIR\myapp.exe $\"%1$\""
|
||||||
|
;
|
||||||
|
; Never insert the APP_ASSOCIATE macro multiple times, it is only ment
|
||||||
|
; to associate an application with a single file and using the
|
||||||
|
; the "open" verb as default. To add more verbs (actions) to a file
|
||||||
|
; use the APP_ASSOCIATE_ADDVERB macro.
|
||||||
|
;
|
||||||
|
; Example:
|
||||||
|
; !insertmacro APP_ASSOCIATE_ADDVERB "myapp.textfile" "edit" "Edit with myapp" \
|
||||||
|
; "$INSTDIR\myapp.exe /edit $\"%1$\""
|
||||||
|
;
|
||||||
|
; To have access to more options when registering the file association use the
|
||||||
|
; APP_ASSOCIATE_EX macro. Here you can specify the verb and what verb is to be the
|
||||||
|
; standard action (default verb).
|
||||||
|
;
|
||||||
|
; Note, that this script takes into account user versus global installs.
|
||||||
|
; To properly work you must initialize the SHELL_CONTEXT variable via SetShellVarContext.
|
||||||
|
;
|
||||||
|
; And finally: To remove the association from the registry use the APP_UNASSOCIATE
|
||||||
|
; macro. Here is another example just to wrap it up:
|
||||||
|
; !insertmacro APP_UNASSOCIATE "txt" "myapp.textfile"
|
||||||
|
;
|
||||||
|
; |> Note <|
|
||||||
|
; When defining your file class string always use the short form of your application title
|
||||||
|
; then a period (dot) and the type of file. This keeps the file class sort of unique.
|
||||||
|
; Examples:
|
||||||
|
; Winamp.Playlist
|
||||||
|
; NSIS.Script
|
||||||
|
; Photoshop.JPEGFile
|
||||||
|
;
|
||||||
|
; |> Tech info <|
|
||||||
|
; The registry key layout for a global file association is:
|
||||||
|
;
|
||||||
|
; HKEY_LOCAL_MACHINE\Software\Classes
|
||||||
|
; <".ext"> = <applicationID>
|
||||||
|
; <applicationID> = <"description">
|
||||||
|
; shell
|
||||||
|
; <verb> = <"menu-item text">
|
||||||
|
; command = <"command string">
|
||||||
|
;
|
||||||
|
;
|
||||||
|
; The registry key layout for a per-user file association is:
|
||||||
|
;
|
||||||
|
; HKEY_CURRENT_USER\Software\Classes
|
||||||
|
; <".ext"> = <applicationID>
|
||||||
|
; <applicationID> = <"description">
|
||||||
|
; shell
|
||||||
|
; <verb> = <"menu-item text">
|
||||||
|
; command = <"command string">
|
||||||
|
;
|
||||||
|
|
||||||
|
!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND
|
||||||
|
; Backup the previously associated file class
|
||||||
|
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" ""
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0"
|
||||||
|
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}"
|
||||||
|
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open"
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}`
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro APP_ASSOCIATE_EX EXT FILECLASS DESCRIPTION ICON VERB DEFAULTVERB SHELLNEW COMMANDTEXT COMMAND
|
||||||
|
; Backup the previously associated file class
|
||||||
|
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" ""
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0"
|
||||||
|
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}"
|
||||||
|
StrCmp "${SHELLNEW}" "0" +2
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}\ShellNew" "NullFile" ""
|
||||||
|
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" `${DEFAULTVERB}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\${VERB}" "" `${COMMANDTEXT}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\${VERB}\command" "" `${COMMAND}`
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro APP_ASSOCIATE_ADDVERB FILECLASS VERB COMMANDTEXT COMMAND
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\${VERB}" "" `${COMMANDTEXT}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\${VERB}\command" "" `${COMMAND}`
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro APP_ASSOCIATE_REMOVEVERB FILECLASS VERB
|
||||||
|
DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}\shell\${VERB}`
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!macro APP_UNASSOCIATE EXT FILECLASS
|
||||||
|
; Backup the previously associated file class
|
||||||
|
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0"
|
||||||
|
|
||||||
|
DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}`
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro APP_ASSOCIATE_GETFILECLASS OUTPUT EXT
|
||||||
|
ReadRegStr ${OUTPUT} SHELL_CONTEXT "Software\Classes\.${EXT}" ""
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
; !defines for use with SHChangeNotify
|
||||||
|
!ifdef SHCNE_ASSOCCHANGED
|
||||||
|
!undef SHCNE_ASSOCCHANGED
|
||||||
|
!endif
|
||||||
|
!define SHCNE_ASSOCCHANGED 0x08000000
|
||||||
|
!ifdef SHCNF_FLUSH
|
||||||
|
!undef SHCNF_FLUSH
|
||||||
|
!endif
|
||||||
|
!define SHCNF_FLUSH 0x1000
|
||||||
|
|
||||||
|
!macro UPDATEFILEASSOC
|
||||||
|
; Using the system.dll plugin to call the SHChangeNotify Win32 API function so we
|
||||||
|
; can update the shell.
|
||||||
|
System::Call "shell32::SHChangeNotify(i,i,i,i) (${SHCNE_ASSOCCHANGED}, ${SHCNF_FLUSH}, 0, 0)"
|
||||||
|
!macroend
|
165
packaging/cura_license.txt
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
12
packaging/dmg/cura.entitlements
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
BIN
packaging/dmg/cura_background_dmg.png
Normal file
After Width: | Height: | Size: 56 KiB |
67
packaging/dmg/dmg_sign_noterize.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import os
|
||||||
|
import argparse # Command line arguments parsing and help.
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
ULTIMAKER_CURA_DOMAIN = os.environ.get("ULTIMAKER_CURA_DOMAIN", "nl.ultimaker.cura")
|
||||||
|
|
||||||
|
|
||||||
|
def build_dmg(source_path: str, dist_path: str, filename: str) -> None:
|
||||||
|
create_dmg_executable = os.environ.get("CREATE_DMG_EXECUTABLE", "create-dmg")
|
||||||
|
|
||||||
|
arguments = [create_dmg_executable,
|
||||||
|
"--window-pos", "640", "360",
|
||||||
|
"--window-size", "690", "503",
|
||||||
|
"--app-drop-link", "520", "272",
|
||||||
|
"--volicon", f"{source_path}/packaging/icons/VolumeIcons_Cura.icns",
|
||||||
|
"--icon-size", "90",
|
||||||
|
"--icon", "Ultimaker-Cura.app", "169", "272",
|
||||||
|
"--eula", f"{source_path}/packaging/cura_license.txt",
|
||||||
|
"--background", f"{source_path}/packaging/dmg/cura_background_dmg.png",
|
||||||
|
f"{dist_path}/{filename}",
|
||||||
|
f"{dist_path}/Ultimaker-Cura.app"]
|
||||||
|
|
||||||
|
subprocess.run(arguments)
|
||||||
|
|
||||||
|
|
||||||
|
def sign(dist_path: str, filename: str) -> None:
|
||||||
|
codesign_executable = os.environ.get("CODESIGN", "codesign")
|
||||||
|
codesign_identity = os.environ.get("CODESIGN_IDENTITY")
|
||||||
|
|
||||||
|
arguments = [codesign_executable,
|
||||||
|
"-s", codesign_identity,
|
||||||
|
"--timestamp",
|
||||||
|
"-i", f"{ULTIMAKER_CURA_DOMAIN}.dmg", # TODO: check if this really should have the extra dmg. We seem to be doing this also in the old Rundeck scripts
|
||||||
|
f"{dist_path}/{filename}"]
|
||||||
|
|
||||||
|
subprocess.run(arguments)
|
||||||
|
|
||||||
|
|
||||||
|
def notarize(dist_path: str, filename: str) -> None:
|
||||||
|
notarize_user = os.environ.get("MAC_NOTARIZE_USER")
|
||||||
|
notarize_password = os.environ.get("MAC_NOTARIZE_PASS")
|
||||||
|
altool_executable = os.environ.get("ALTOOL_EXECUTABLE", "altool")
|
||||||
|
|
||||||
|
arguments = [
|
||||||
|
"xcrun", altool_executable,
|
||||||
|
"--notarize-app",
|
||||||
|
"--primary-bundle-id", ULTIMAKER_CURA_DOMAIN,
|
||||||
|
"--username", notarize_user,
|
||||||
|
"--password", notarize_password,
|
||||||
|
"--file", f"{dist_path}/{filename}"
|
||||||
|
]
|
||||||
|
|
||||||
|
subprocess.run(arguments)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description = "Create dmg of Cura.")
|
||||||
|
parser.add_argument("source_path", type=str, help="Path to Conan install Cura folder.")
|
||||||
|
parser.add_argument("dist_path", type=str, help="Path to Pyinstaller dist folder")
|
||||||
|
parser.add_argument("filename", type = str, help = "Filename of the dmg (e.g. 'Ultimaker-Cura-5.1.0-beta-Linux-X64.dmg')")
|
||||||
|
args = parser.parse_args()
|
||||||
|
build_dmg(args.source_path, args.dist_path, args.filename)
|
||||||
|
sign(args.dist_path, args.filename)
|
||||||
|
|
||||||
|
notarize_dmg = bool(os.environ.get("NOTARIZE_DMG", "TRUE"))
|
||||||
|
if notarize_dmg:
|
||||||
|
notarize(args.dist_path, args.filename)
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
BIN
packaging/icons/VolumeIcons_Cura.icns
Normal file
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
BIN
packaging/icons/cura-icon_128x128.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
packaging/icons/cura-icon_256x256.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
packaging/icons/cura-icon_64x64.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
packaging/icons/cura.icns
Normal file
|
@ -17,6 +17,7 @@ UM.Dialog
|
||||||
minimumWidth: UM.Theme.getSize("popup_dialog").width
|
minimumWidth: UM.Theme.getSize("popup_dialog").width
|
||||||
minimumHeight: UM.Theme.getSize("popup_dialog").height
|
minimumHeight: UM.Theme.getSize("popup_dialog").height
|
||||||
width: minimumWidth
|
width: minimumWidth
|
||||||
|
backgroundColor: UM.Theme.getColor("main_background")
|
||||||
margin: UM.Theme.getSize("default_margin").width
|
margin: UM.Theme.getSize("default_margin").width
|
||||||
property int comboboxHeight: UM.Theme.getSize("default_margin").height
|
property int comboboxHeight: UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
|
@ -486,7 +487,7 @@ UM.Dialog
|
||||||
UM.Label
|
UM.Label
|
||||||
{
|
{
|
||||||
id: warningText
|
id: warningText
|
||||||
text: "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", "The material used in this project is currently not installed in Cura.<br/>Install the material profile and reopen the project.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ from UM.Application import Application
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
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
|
||||||
|
@ -268,6 +270,10 @@ class ThreeMFWriter(MeshWriter):
|
||||||
# Don't export materials not in use
|
# Don't export materials not in use
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if isinstance(extruder.material, type(ContainerRegistry.getInstance().getEmptyInstanceContainer())):
|
||||||
|
# This is an empty material container, no material to export
|
||||||
|
continue
|
||||||
|
|
||||||
if package_manager.isMaterialBundled(extruder.material.getFileName(), extruder.material.getMetaDataEntry("GUID")):
|
if package_manager.isMaterialBundled(extruder.material.getFileName(), extruder.material.getMetaDataEntry("GUID")):
|
||||||
# Don't export bundled materials
|
# Don't export bundled materials
|
||||||
continue
|
continue
|
||||||
|
@ -275,14 +281,9 @@ class ThreeMFWriter(MeshWriter):
|
||||||
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
|
||||||
if not package_data:
|
if not package_data:
|
||||||
# We failed to find the package for this material
|
Logger.info(f"Could not find package for material in extruder {extruder.id}, skipping.")
|
||||||
|
|
||||||
message = Message(catalog.i18nc("@error:material",
|
|
||||||
"It was not possible to store material package information in project file: {material}. This project may not open correctly on other systems.".format(material=extruder.getName())),
|
|
||||||
title=catalog.i18nc("@info:title", "Failed to save material package information"),
|
|
||||||
message_type=Message.MessageType.WARNING)
|
|
||||||
message.show()
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
material_metadata = {"id": package_id,
|
material_metadata = {"id": package_id,
|
||||||
|
|
|
@ -139,5 +139,9 @@ message GCodePrefix {
|
||||||
bytes data = 2; //Header string to be prepended before the rest of the g-code sent from the engine.
|
bytes data = 2; //Header string to be prepended before the rest of the g-code sent from the engine.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SliceUUID {
|
||||||
|
string slice_uuid = 1; //The UUID of the slice.
|
||||||
|
}
|
||||||
|
|
||||||
message SlicingFinished {
|
message SlicingFinished {
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,6 +124,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._message_handlers["cura.proto.Progress"] = self._onProgressMessage
|
self._message_handlers["cura.proto.Progress"] = self._onProgressMessage
|
||||||
self._message_handlers["cura.proto.GCodeLayer"] = self._onGCodeLayerMessage
|
self._message_handlers["cura.proto.GCodeLayer"] = self._onGCodeLayerMessage
|
||||||
self._message_handlers["cura.proto.GCodePrefix"] = self._onGCodePrefixMessage
|
self._message_handlers["cura.proto.GCodePrefix"] = self._onGCodePrefixMessage
|
||||||
|
self._message_handlers["cura.proto.SliceUUID"] = self._onSliceUUIDMessage
|
||||||
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
|
||||||
|
|
||||||
|
@ -812,6 +813,10 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
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:
|
||||||
|
application = CuraApplication.getInstance()
|
||||||
|
application.getPrintInformation().slice_uuid = message.slice_uuid
|
||||||
|
|
||||||
def _createSocket(self, protocol_file: str = None) -> None:
|
def _createSocket(self, protocol_file: str = None) -> None:
|
||||||
"""Creates a new socket connection."""
|
"""Creates a new socket connection."""
|
||||||
|
|
||||||
|
|
|
@ -71,8 +71,6 @@ class DigitalFactoryApiClient:
|
||||||
has_access = response.library_max_private_projects == -1 or response.library_max_private_projects > 0
|
has_access = response.library_max_private_projects == -1 or response.library_max_private_projects > 0
|
||||||
callback(has_access)
|
callback(has_access)
|
||||||
self._library_max_private_projects = response.library_max_private_projects
|
self._library_max_private_projects = response.library_max_private_projects
|
||||||
# update the account with the additional user rights
|
|
||||||
self._account.updateAdditionalRight(df_access = has_access)
|
|
||||||
else:
|
else:
|
||||||
Logger.warning(f"Digital Factory: Response is not a feature budget, likely an error: {str(response)}")
|
Logger.warning(f"Digital Factory: Response is not a feature budget, likely an error: {str(response)}")
|
||||||
callback(False)
|
callback(False)
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
# Copyright (c) 2021 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.
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, List, Dict, Any
|
from typing import Optional, List, Dict, Any
|
||||||
|
|
||||||
from .BaseModel import BaseModel
|
from .BaseModel import BaseModel
|
||||||
from .DigitalFactoryFileResponse import DIGITAL_FACTORY_RESPONSE_DATETIME_FORMAT
|
from .DigitalFactoryFileResponse import DIGITAL_FACTORY_RESPONSE_DATETIME_FORMAT
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
class DigitalFactoryProjectResponse(BaseModel):
|
class DigitalFactoryProjectResponse(BaseModel):
|
||||||
"""Class representing a cloud project."""
|
"""Class representing a cloud project."""
|
||||||
|
@ -13,8 +16,8 @@ class DigitalFactoryProjectResponse(BaseModel):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
library_project_id: str,
|
library_project_id: str,
|
||||||
display_name: str,
|
display_name: str,
|
||||||
username: str,
|
username: str = catalog.i18nc("@text Placeholder for the username if it has been deleted", "deleted user"),
|
||||||
organization_shared: bool,
|
organization_shared: bool = False,
|
||||||
last_updated: Optional[str] = None,
|
last_updated: Optional[str] = None,
|
||||||
created_at: Optional[str] = None,
|
created_at: Optional[str] = None,
|
||||||
thumbnail_url: Optional[str] = None,
|
thumbnail_url: Optional[str] = None,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
<polygon fill="#000000" points="19 11 30 8 30 24 19 27" />
|
<polygon points="19 11 30 8 30 24 19 27" />
|
||||||
<path d="M10,19 C5.581722,19 2,15.418278 2,11 C2,6.581722 5.581722,3 10,3 C14.418278,3 18,6.581722 18,11 C18,15.418278 14.418278,19 10,19 Z M10,17 C13.3137085,17 16,14.3137085 16,11 C16,7.6862915 13.3137085,5 10,5 C6.6862915,5 4,7.6862915 4,11 C4,14.3137085 6.6862915,17 10,17 Z" fill="#000000" />
|
<path d="M10,19 C5.581722,19 2,15.418278 2,11 C2,6.581722 5.581722,3 10,3 C14.418278,3 18,6.581722 18,11 C18,15.418278 14.418278,19 10,19 Z M10,17 C13.3137085,17 16,14.3137085 16,11 C16,7.6862915 13.3137085,5 10,5 C6.6862915,5 4,7.6862915 4,11 C4,14.3137085 6.6862915,17 10,17 Z" />
|
||||||
<polygon fill="#000000" points="4.2 15 6 16.8 1.8 21 0 19.2" />
|
<polygon points="4.2 15 6 16.8 1.8 21 0 19.2" />
|
||||||
<path d="M18.7333454,8.81666365 C18.2107269,6.71940704 16.9524304,4.91317986 15.248379,3.68790525 L18,3 L30,6 L18.7333454,8.81666365 Z M17,16.6573343 L17,27 L6,24 L6,19.0644804 C7.20495897,19.6632939 8.56315852,20 10,20 C12.8272661,20 15.3500445,18.6963331 17,16.6573343 Z" fill="#000000" />
|
<path d="M18.7333454,8.81666365 C18.2107269,6.71940704 16.9524304,4.91317986 15.248379,3.68790525 L18,3 L30,6 L18.7333454,8.81666365 Z M17,16.6573343 L17,27 L6,24 L6,19.0644804 C7.20495897,19.6632939 8.56315852,20 10,20 C12.8272661,20 15.3500445,18.6963331 17,16.6573343 Z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 877 B After Width: | Height: | Size: 817 B |
|
@ -19,7 +19,7 @@ UM.Dialog
|
||||||
height: 500 * screenScaleFactor
|
height: 500 * screenScaleFactor
|
||||||
minimumWidth: 400 * screenScaleFactor
|
minimumWidth: 400 * screenScaleFactor
|
||||||
minimumHeight: 250 * screenScaleFactor
|
minimumHeight: 250 * screenScaleFactor
|
||||||
|
backgroundColor: UM.Theme.getColor("main_background")
|
||||||
onVisibleChanged:
|
onVisibleChanged:
|
||||||
{
|
{
|
||||||
// Whenever the window is closed (either via the "Close" button or the X on the window frame), we want to update it in the stack.
|
// Whenever the window is closed (either via the "Close" button or the X on the window frame), we want to update it in the stack.
|
||||||
|
@ -286,6 +286,7 @@ UM.Dialog
|
||||||
{
|
{
|
||||||
id: definitionsModel
|
id: definitionsModel
|
||||||
containerId: manager.selectedScriptDefinitionId
|
containerId: manager.selectedScriptDefinitionId
|
||||||
|
onContainerIdChanged: definitionsModel.setAllVisible(true)
|
||||||
showAll: true
|
showAll: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -432,7 +432,7 @@ class Stretcher:
|
||||||
"""
|
"""
|
||||||
dist_palp = self.line_width # Palpation distance to seek for a wall
|
dist_palp = self.line_width # Palpation distance to seek for a wall
|
||||||
mrot = np.array([[0, -1], [1, 0]]) # Rotation matrix for a quarter turn
|
mrot = np.array([[0, -1], [1, 0]]) # Rotation matrix for a quarter turn
|
||||||
for i in range(len(orig_seq)):
|
for i, _ in enumerate(orig_seq):
|
||||||
ibeg = i # Index of the first point of the segment
|
ibeg = i # Index of the first point of the segment
|
||||||
iend = i + 1 # Index of the last point of the segment
|
iend = i + 1 # Index of the last point of the segment
|
||||||
if iend == len(orig_seq):
|
if iend == len(orig_seq):
|
||||||
|
|
|
@ -133,6 +133,7 @@ class SliceInfo(QObject, Extension):
|
||||||
data["is_logged_in"] = self._application.getCuraAPI().account.isLoggedIn
|
data["is_logged_in"] = self._application.getCuraAPI().account.isLoggedIn
|
||||||
data["organization_id"] = org_id if org_id else None
|
data["organization_id"] = org_id if org_id else None
|
||||||
data["subscriptions"] = user_profile.get("subscriptions", []) if user_profile else []
|
data["subscriptions"] = user_profile.get("subscriptions", []) if user_profile else []
|
||||||
|
data["slice_uuid"] = print_information.slice_uuid
|
||||||
|
|
||||||
active_mode = self._application.getPreferences().getValue("cura/active_mode")
|
active_mode = self._application.getPreferences().getValue("cura/active_mode")
|
||||||
if active_mode == 0:
|
if active_mode == 0:
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<b>Using Custom Settings:</b> No<br/>
|
<b>Using Custom Settings:</b> No<br/>
|
||||||
<b>Is Logged In:</b> Yes<br/>
|
<b>Is Logged In:</b> Yes<br/>
|
||||||
<b>Organization ID (if any):</b> ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE=<br/>
|
<b>Organization ID (if any):</b> ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE=<br/>
|
||||||
|
<b>Slice ID:</b> aBcDeF01-2345-6789-aBcD-eF0123456789<br/>
|
||||||
<b>Subscriptions (if any):</b>
|
<b>Subscriptions (if any):</b>
|
||||||
<ul>
|
<ul>
|
||||||
<li><b>Level:</b> 10, <b>Type:</b> Enterprise, <b>Plan:</b> Basic</li>
|
<li><b>Level:</b> 10, <b>Type:</b> Enterprise, <b>Plan:</b> Basic</li>
|
||||||
|
|
|
@ -13,10 +13,98 @@ import Cura 1.6 as Cura
|
||||||
*/
|
*/
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
|
id: monitorContextMenu
|
||||||
property alias target: popUp.target
|
property alias target: popUp.target
|
||||||
|
|
||||||
property var printJob: null
|
property var printJob: null
|
||||||
|
|
||||||
|
//Everything in the pop-up only gets evaluated when showing the pop-up.
|
||||||
|
//However we want to show the button for showing the pop-up only if there is anything visible inside it.
|
||||||
|
//So compute here the visibility of the menu items, so that we can use it for the visibility of the button.
|
||||||
|
property bool sendToTopVisible:
|
||||||
|
{
|
||||||
|
if (printJob && printJob.state in ("queued", "error") && !isAssigned(printJob)) {
|
||||||
|
if (OutputDevice && OutputDevice.queuedPrintJobs[0] && OutputDevice.canWriteOthersPrintJobs) {
|
||||||
|
return OutputDevice.queuedPrintJobs[0].key != printJob.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool deleteVisible:
|
||||||
|
{
|
||||||
|
if(!printJob)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(printJob.isMine)
|
||||||
|
{
|
||||||
|
if(!OutputDevice.canWriteOwnPrintJobs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(!OutputDevice.canWriteOthersPrintJobs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var states = ["queued", "error", "sent_to_printer"];
|
||||||
|
return states.indexOf(printJob.state) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool pauseVisible:
|
||||||
|
{
|
||||||
|
if(!printJob)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(printJob.isMine)
|
||||||
|
{
|
||||||
|
if(!OutputDevice.canWriteOwnPrintJobs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(!OutputDevice.canWriteOthersPrintJobs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var states = ["printing", "pausing", "paused", "resuming"];
|
||||||
|
return states.indexOf(printJob.state) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool abortVisible:
|
||||||
|
{
|
||||||
|
if(!printJob)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(printJob.isMine)
|
||||||
|
{
|
||||||
|
if(!OutputDevice.canWriteOwnPrintJobs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(!OutputDevice.canWriteOthersPrintJobs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var states = ["pre_print", "printing", "pausing", "paused", "resuming"];
|
||||||
|
return states.indexOf(printJob.state) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool hasItems: sendToTopVisible || deleteVisible || pauseVisible || abortVisible
|
||||||
|
|
||||||
GenericPopUp
|
GenericPopUp
|
||||||
{
|
{
|
||||||
id: popUp
|
id: popUp
|
||||||
|
@ -46,56 +134,54 @@ Item
|
||||||
|
|
||||||
spacing: Math.floor(UM.Theme.getSize("default_margin").height / 2)
|
spacing: Math.floor(UM.Theme.getSize("default_margin").height / 2)
|
||||||
|
|
||||||
PrintJobContextMenuItem {
|
PrintJobContextMenuItem
|
||||||
onClicked: {
|
{
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
sendToTopConfirmationDialog.visible = true;
|
sendToTopConfirmationDialog.visible = true;
|
||||||
popUp.close();
|
popUp.close();
|
||||||
}
|
}
|
||||||
text: catalog.i18nc("@label", "Move to top");
|
text: catalog.i18nc("@label", "Move to top");
|
||||||
visible: {
|
visible: monitorContextMenu.sendToTopVisible
|
||||||
if (printJob && (printJob.state == "queued" || printJob.state == "error") && !isAssigned(printJob)) {
|
|
||||||
if (OutputDevice && OutputDevice.queuedPrintJobs[0]) {
|
|
||||||
return OutputDevice.queuedPrintJobs[0].key != printJob.key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintJobContextMenuItem {
|
PrintJobContextMenuItem
|
||||||
onClicked: {
|
{
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
deleteConfirmationDialog.visible = true;
|
deleteConfirmationDialog.visible = true;
|
||||||
popUp.close();
|
popUp.close();
|
||||||
}
|
}
|
||||||
text: catalog.i18nc("@label", "Delete");
|
text: catalog.i18nc("@label", "Delete");
|
||||||
visible: {
|
visible: monitorContextMenu.deleteVisible
|
||||||
if (!printJob) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var states = ["queued", "error", "sent_to_printer"];
|
|
||||||
return states.indexOf(printJob.state) !== -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintJobContextMenuItem {
|
PrintJobContextMenuItem
|
||||||
|
{
|
||||||
enabled: visible && !(printJob.state == "pausing" || printJob.state == "resuming");
|
enabled: visible && !(printJob.state == "pausing" || printJob.state == "resuming");
|
||||||
onClicked: {
|
onClicked:
|
||||||
if (printJob.state == "paused") {
|
{
|
||||||
|
if (printJob.state == "paused")
|
||||||
|
{
|
||||||
printJob.setState("resume");
|
printJob.setState("resume");
|
||||||
popUp.close();
|
popUp.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (printJob.state == "printing") {
|
if (printJob.state == "printing")
|
||||||
|
{
|
||||||
printJob.setState("pause");
|
printJob.setState("pause");
|
||||||
popUp.close();
|
popUp.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
text: {
|
text:
|
||||||
if (!printJob) {
|
{
|
||||||
|
if(!printJob)
|
||||||
|
{
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
switch(printJob.state) {
|
switch(printJob.state)
|
||||||
|
{
|
||||||
case "paused":
|
case "paused":
|
||||||
return catalog.i18nc("@label", "Resume");
|
return catalog.i18nc("@label", "Resume");
|
||||||
case "pausing":
|
case "pausing":
|
||||||
|
@ -106,29 +192,19 @@ Item
|
||||||
catalog.i18nc("@label", "Pause");
|
catalog.i18nc("@label", "Pause");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
visible: {
|
visible: monitorContextMenu.pauseVisible
|
||||||
if (!printJob) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var states = ["printing", "pausing", "paused", "resuming"];
|
|
||||||
return states.indexOf(printJob.state) !== -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintJobContextMenuItem {
|
PrintJobContextMenuItem
|
||||||
|
{
|
||||||
enabled: visible && printJob.state !== "aborting";
|
enabled: visible && printJob.state !== "aborting";
|
||||||
onClicked: {
|
onClicked:
|
||||||
|
{
|
||||||
abortConfirmationDialog.visible = true;
|
abortConfirmationDialog.visible = true;
|
||||||
popUp.close();
|
popUp.close();
|
||||||
}
|
}
|
||||||
text: printJob && printJob.state == "aborting" ? catalog.i18nc("@label", "Aborting...") : catalog.i18nc("@label", "Abort");
|
text: printJob && printJob.state == "aborting" ? catalog.i18nc("@label", "Aborting...") : catalog.i18nc("@label", "Abort");
|
||||||
visible: {
|
visible: monitorContextMenu.abortVisible
|
||||||
if (!printJob) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var states = ["pre_print", "printing", "pausing", "paused", "resuming"];
|
|
||||||
return states.indexOf(printJob.state) !== -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,7 +206,11 @@ Item
|
||||||
onClicked: enabled ? contextMenu.switchPopupState() : {}
|
onClicked: enabled ? contextMenu.switchPopupState() : {}
|
||||||
visible:
|
visible:
|
||||||
{
|
{
|
||||||
if (!printJob)
|
if(!printJob)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!contextMenu.hasItems)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,8 +209,13 @@ Item
|
||||||
onClicked: enabled ? contextMenu.switchPopupState() : {}
|
onClicked: enabled ? contextMenu.switchPopupState() : {}
|
||||||
visible:
|
visible:
|
||||||
{
|
{
|
||||||
if (!printer || !printer.activePrintJob) {
|
if(!printer || !printer.activePrintJob)
|
||||||
return false
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!contextMenu.hasItems)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
var states = ["queued", "error", "sent_to_printer", "pre_print", "printing", "pausing", "paused", "resuming"]
|
var states = ["queued", "error", "sent_to_printer", "pre_print", "printing", "pausing", "paused", "resuming"]
|
||||||
return states.indexOf(printer.activePrintJob.state) !== -1
|
return states.indexOf(printer.activePrintJob.state) !== -1
|
||||||
|
|
|
@ -39,6 +39,7 @@ Item
|
||||||
}
|
}
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
width: childrenRect.width
|
width: childrenRect.width
|
||||||
|
visible: OutputDevice.canReadPrinterDetails
|
||||||
|
|
||||||
UM.ColorImage
|
UM.ColorImage
|
||||||
{
|
{
|
||||||
|
|
|
@ -69,7 +69,7 @@ Component
|
||||||
top: printers.bottom
|
top: printers.bottom
|
||||||
topMargin: 48 * screenScaleFactor // TODO: Theme!
|
topMargin: 48 * screenScaleFactor // TODO: Theme!
|
||||||
}
|
}
|
||||||
visible: OutputDevice.supportsPrintJobQueue
|
visible: OutputDevice.supportsPrintJobQueue && OutputDevice.canReadPrintJobs
|
||||||
}
|
}
|
||||||
|
|
||||||
PrinterVideoStream
|
PrinterVideoStream
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) 2021 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.
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
@ -96,6 +96,8 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
# Trigger the printersChanged signal when the private signal is triggered.
|
# Trigger the printersChanged signal when the private signal is triggered.
|
||||||
self.printersChanged.connect(self._cloudClusterPrintersChanged)
|
self.printersChanged.connect(self._cloudClusterPrintersChanged)
|
||||||
|
# Trigger the permissionsChanged signal when the account's permissions change.
|
||||||
|
self._account.permissionsChanged.connect(self.permissionsChanged)
|
||||||
|
|
||||||
# Keep server string of the last generated time to avoid updating models more than once for the same response
|
# Keep server string of the last generated time to avoid updating models more than once for the same response
|
||||||
self._received_printers = None # type: Optional[List[ClusterPrinterStatus]]
|
self._received_printers = None # type: Optional[List[ClusterPrinterStatus]]
|
||||||
|
@ -340,6 +342,37 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
||||||
def openPrinterControlPanel(self) -> None:
|
def openPrinterControlPanel(self) -> None:
|
||||||
QDesktopServices.openUrl(QUrl(self.clusterCloudUrl + "?utm_source=cura&utm_medium=software&utm_campaign=monitor-manage-printer"))
|
QDesktopServices.openUrl(QUrl(self.clusterCloudUrl + "?utm_source=cura&utm_medium=software&utm_campaign=monitor-manage-printer"))
|
||||||
|
|
||||||
|
permissionsChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = permissionsChanged)
|
||||||
|
def canReadPrintJobs(self) -> bool:
|
||||||
|
"""
|
||||||
|
Whether this user can read the list of print jobs and their properties.
|
||||||
|
"""
|
||||||
|
return "digital-factory.print-job.read" in self._account.permissions
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = permissionsChanged)
|
||||||
|
def canWriteOthersPrintJobs(self) -> bool:
|
||||||
|
"""
|
||||||
|
Whether this user can change things about print jobs made by other
|
||||||
|
people.
|
||||||
|
"""
|
||||||
|
return "digital-factory.print-job.write" in self._account.permissions
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = permissionsChanged)
|
||||||
|
def canWriteOwnPrintJobs(self) -> bool:
|
||||||
|
"""
|
||||||
|
Whether this user can change things about print jobs made by themself.
|
||||||
|
"""
|
||||||
|
return "digital-factory.print-job.write.own" in self._account.permissions
|
||||||
|
|
||||||
|
@pyqtProperty(bool, constant = True)
|
||||||
|
def canReadPrinterDetails(self) -> bool:
|
||||||
|
"""
|
||||||
|
Whether this user can read the status of the printer.
|
||||||
|
"""
|
||||||
|
return "digital-factory.printer.read" in self._account.permissions
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def clusterData(self) -> CloudClusterResponse:
|
def clusterData(self) -> CloudClusterResponse:
|
||||||
"""Gets the cluster response from which this device was created."""
|
"""Gets the cluster response from which this device was created."""
|
||||||
|
|
|
@ -400,11 +400,13 @@ class CloudOutputDeviceManager:
|
||||||
# We do not use use MachineManager.addMachine here because we need to set the cluster ID before activating it.
|
# We do not use use MachineManager.addMachine here because we need to set the cluster ID before activating it.
|
||||||
new_machine = CuraStackBuilder.createMachine(device.name, device.printerType, show_warning_message=False)
|
new_machine = CuraStackBuilder.createMachine(device.name, device.printerType, show_warning_message=False)
|
||||||
if not new_machine:
|
if not new_machine:
|
||||||
Logger.log("e", "Failed creating a new machine")
|
Logger.error(f"Failed creating a new machine for {device.name}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._setOutputDeviceMetadata(device, new_machine)
|
self._setOutputDeviceMetadata(device, new_machine)
|
||||||
|
|
||||||
|
_abstract_machine = CuraStackBuilder.createAbstractMachine(device.printerType)
|
||||||
|
|
||||||
if activate:
|
if activate:
|
||||||
CuraApplication.getInstance().getMachineManager().setActiveMachine(new_machine.getId())
|
CuraApplication.getInstance().getMachineManager().setActiveMachine(new_machine.getId())
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) 2019 Ultimaker B.V.
|
# Copyright (c) 2020 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 os
|
||||||
from typing import Optional, Dict, List, Callable, Any
|
from typing import Optional, Dict, List, Callable, Any
|
||||||
|
|
|
@ -232,6 +232,9 @@ class LocalClusterOutputDeviceManager:
|
||||||
self._connectToOutputDevice(device, new_machine)
|
self._connectToOutputDevice(device, new_machine)
|
||||||
self._showCloudFlowMessage(device)
|
self._showCloudFlowMessage(device)
|
||||||
|
|
||||||
|
_abstract_machine = CuraStackBuilder.createAbstractMachine(device.printerType)
|
||||||
|
|
||||||
|
|
||||||
def _storeManualAddress(self, address: str) -> None:
|
def _storeManualAddress(self, address: str) -> None:
|
||||||
"""Add an address to the stored preferences."""
|
"""Add an address to the stored preferences."""
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) 2020 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 os
|
import os
|
||||||
from time import time
|
from time import time
|
||||||
|
@ -184,6 +184,42 @@ class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
def forceSendJob(self, print_job_uuid: str) -> None:
|
def forceSendJob(self, print_job_uuid: str) -> None:
|
||||||
raise NotImplementedError("forceSendJob must be implemented")
|
raise NotImplementedError("forceSendJob must be implemented")
|
||||||
|
|
||||||
|
@pyqtProperty(bool, constant = True)
|
||||||
|
def supportsPrintJobQueue(self) -> bool:
|
||||||
|
"""
|
||||||
|
Whether this printer knows about queueing print jobs.
|
||||||
|
"""
|
||||||
|
return True # This API always supports print job queueing.
|
||||||
|
|
||||||
|
@pyqtProperty(bool, constant = True)
|
||||||
|
def canReadPrintJobs(self) -> bool:
|
||||||
|
"""
|
||||||
|
Whether this user can read the list of print jobs and their properties.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@pyqtProperty(bool, constant = True)
|
||||||
|
def canWriteOthersPrintJobs(self) -> bool:
|
||||||
|
"""
|
||||||
|
Whether this user can change things about print jobs made by other
|
||||||
|
people.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@pyqtProperty(bool, constant = True)
|
||||||
|
def canWriteOwnPrintJobs(self) -> bool:
|
||||||
|
"""
|
||||||
|
Whether this user can change things about print jobs made by themself.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@pyqtProperty(bool, constant = True)
|
||||||
|
def canReadPrinterDetails(self) -> bool:
|
||||||
|
"""
|
||||||
|
Whether this user can read the status of the printer.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
@pyqtSlot(name="openPrintJobControlPanel")
|
@pyqtSlot(name="openPrintJobControlPanel")
|
||||||
def openPrintJobControlPanel(self) -> None:
|
def openPrintJobControlPanel(self) -> None:
|
||||||
raise NotImplementedError("openPrintJobControlPanel must be implemented")
|
raise NotImplementedError("openPrintJobControlPanel must be implemented")
|
||||||
|
|
|
@ -134,7 +134,7 @@ class X3DReader(MeshReader):
|
||||||
geometry = self.resolveDefUse(sub_node)
|
geometry = self.resolveDefUse(sub_node)
|
||||||
|
|
||||||
# TODO: appearance is completely ignored. At least apply the material color...
|
# TODO: appearance is completely ignored. At least apply the material color...
|
||||||
if not geometry is None:
|
if geometry is not None:
|
||||||
try:
|
try:
|
||||||
self.verts = self.faces = [] # Safeguard
|
self.verts = self.faces = [] # Safeguard
|
||||||
self.geometry_importers[geometry.tag](self, geometry)
|
self.geometry_importers[geometry.tag](self, geometry)
|
||||||
|
@ -493,12 +493,12 @@ class X3DReader(MeshReader):
|
||||||
# Columns are the unit vectors for the xz plane for the cross-section
|
# Columns are the unit vectors for the xz plane for the cross-section
|
||||||
if orient:
|
if orient:
|
||||||
mrot = orient[i] if len(orient) > 1 else orient[0]
|
mrot = orient[i] if len(orient) > 1 else orient[0]
|
||||||
if not mrot is None:
|
if mrot is not None:
|
||||||
m = m.dot(mrot) # Tested against X3DOM, the result matches, still not sure :(
|
m = m.dot(mrot) # Tested against X3DOM, the result matches, still not sure :(
|
||||||
|
|
||||||
if scale:
|
if scale:
|
||||||
mscale = scale[i] if len(scale) > 1 else scale[0]
|
mscale = scale[i] if len(scale) > 1 else scale[0]
|
||||||
if not mscale is None:
|
if mscale is not None:
|
||||||
m = m.dot(mscale)
|
m = m.dot(mscale)
|
||||||
|
|
||||||
# First the cross-section 2-vector is scaled,
|
# First the cross-section 2-vector is scaled,
|
||||||
|
@ -703,7 +703,7 @@ class X3DReader(MeshReader):
|
||||||
for c in node:
|
for c in node:
|
||||||
if c.tag == "Coordinate":
|
if c.tag == "Coordinate":
|
||||||
c = self.resolveDefUse(c)
|
c = self.resolveDefUse(c)
|
||||||
if not c is None:
|
if c is not None:
|
||||||
pt = c.attrib.get("point")
|
pt = c.attrib.get("point")
|
||||||
if pt:
|
if pt:
|
||||||
# allow the list of float values in 'point' attribute to
|
# allow the list of float values in 'point' attribute to
|
||||||
|
|
|
@ -152,12 +152,15 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
|
|
||||||
## Begin Metadata Block
|
## Begin Metadata Block
|
||||||
builder.start("metadata", {}) # type: ignore
|
builder.start("metadata", {}) # type: ignore
|
||||||
|
|
||||||
metadata = copy.deepcopy(self.getMetaData())
|
metadata = copy.deepcopy(self.getMetaData())
|
||||||
|
|
||||||
|
# Get the to reserialize keys from the metadata before they are deleted.
|
||||||
|
reserialize_settings = copy.deepcopy(metadata["reserialize_settings"])
|
||||||
|
|
||||||
# setting_version is derived from the "version" tag in the schema, so don't serialize it into a file
|
# setting_version is derived from the "version" tag in the schema, so don't serialize it into a file
|
||||||
if ignored_metadata_keys is None:
|
if ignored_metadata_keys is None:
|
||||||
ignored_metadata_keys = set()
|
ignored_metadata_keys = set()
|
||||||
ignored_metadata_keys |= {"setting_version", "definition", "status", "variant", "type", "base_file", "approximate_diameter", "id", "container_type", "name", "compatible"}
|
ignored_metadata_keys |= {"setting_version", "definition", "status", "variant", "type", "base_file", "approximate_diameter", "id", "container_type", "name", "compatible", "reserialize_settings"}
|
||||||
# remove the keys that we want to ignore in the metadata
|
# remove the keys that we want to ignore in the metadata
|
||||||
for key in ignored_metadata_keys:
|
for key in ignored_metadata_keys:
|
||||||
if key in metadata:
|
if key in metadata:
|
||||||
|
@ -304,6 +307,12 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
buildplate_dict["buildplate_recommended"] = material_container.getMetaDataEntry("buildplate_recommended")
|
buildplate_dict["buildplate_recommended"] = material_container.getMetaDataEntry("buildplate_recommended")
|
||||||
buildplate_dict["material_container"] = material_container
|
buildplate_dict["material_container"] = material_container
|
||||||
|
|
||||||
|
hotend_reserialize_settings = material_container.getMetaDataEntry("reserialize_settings")
|
||||||
|
for key, value in hotend_reserialize_settings.items():
|
||||||
|
builder.start("setting", {"key": key})
|
||||||
|
builder.data(value)
|
||||||
|
builder.end("setting")
|
||||||
|
|
||||||
builder.end("hotend")
|
builder.end("hotend")
|
||||||
|
|
||||||
if buildplate_dict:
|
if buildplate_dict:
|
||||||
|
@ -325,10 +334,27 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
builder.data("yes" if recommended else "no")
|
builder.data("yes" if recommended else "no")
|
||||||
builder.end("setting")
|
builder.end("setting")
|
||||||
|
|
||||||
|
buildplate_reserialize_settings = material_container.getMetaDataEntry("reserialize_settings")
|
||||||
|
for key, value in buildplate_reserialize_settings.items():
|
||||||
|
builder.start("setting", {"key": key})
|
||||||
|
builder.data(value)
|
||||||
|
builder.end("setting")
|
||||||
|
|
||||||
builder.end("buildplate")
|
builder.end("buildplate")
|
||||||
|
|
||||||
|
machine_reserialize_settings = container.getMetaDataEntry("reserialize_settings")
|
||||||
|
for key, value in machine_reserialize_settings.items():
|
||||||
|
builder.start("setting", {"key": key})
|
||||||
|
builder.data(value)
|
||||||
|
builder.end("setting")
|
||||||
|
|
||||||
builder.end("machine")
|
builder.end("machine")
|
||||||
|
|
||||||
|
for key, value in reserialize_settings.items():
|
||||||
|
builder.start("setting", {"key": key})
|
||||||
|
builder.data(value)
|
||||||
|
builder.end("setting")
|
||||||
|
|
||||||
builder.end("settings")
|
builder.end("settings")
|
||||||
## End Settings Block
|
## End Settings Block
|
||||||
|
|
||||||
|
@ -512,6 +538,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
meta_data["status"] = "unknown" # TODO: Add material verification
|
meta_data["status"] = "unknown" # TODO: Add material verification
|
||||||
meta_data["id"] = old_id
|
meta_data["id"] = old_id
|
||||||
meta_data["container_type"] = XmlMaterialProfile
|
meta_data["container_type"] = XmlMaterialProfile
|
||||||
|
meta_data["reserialize_settings"] = {}
|
||||||
|
|
||||||
common_setting_values = {}
|
common_setting_values = {}
|
||||||
|
|
||||||
|
@ -598,6 +625,8 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
elif key in self.__unmapped_settings:
|
elif key in self.__unmapped_settings:
|
||||||
if key == "hardware compatible":
|
if key == "hardware compatible":
|
||||||
common_compatibility = self._parseCompatibleValue(entry.text)
|
common_compatibility = self._parseCompatibleValue(entry.text)
|
||||||
|
elif key in self.__keep_serialized_settings:
|
||||||
|
meta_data["reserialize_settings"][key] = entry.text
|
||||||
|
|
||||||
# Add namespaced Cura-specific settings
|
# Add namespaced Cura-specific settings
|
||||||
settings = data.iterfind("./um:settings/cura:setting", self.__namespaces)
|
settings = data.iterfind("./um:settings/cura:setting", self.__namespaces)
|
||||||
|
@ -624,6 +653,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
machine_compatibility = common_compatibility
|
machine_compatibility = common_compatibility
|
||||||
machine_setting_values = {}
|
machine_setting_values = {}
|
||||||
settings = machine.iterfind("./um:setting", self.__namespaces)
|
settings = machine.iterfind("./um:setting", self.__namespaces)
|
||||||
|
machine_reserialize_settings = {}
|
||||||
for entry in settings:
|
for entry in settings:
|
||||||
key = entry.get("key")
|
key = entry.get("key")
|
||||||
if key in self.__material_settings_setting_map:
|
if key in self.__material_settings_setting_map:
|
||||||
|
@ -640,6 +670,8 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
elif key in self.__unmapped_settings:
|
elif key in self.__unmapped_settings:
|
||||||
if key == "hardware compatible":
|
if key == "hardware compatible":
|
||||||
machine_compatibility = self._parseCompatibleValue(entry.text)
|
machine_compatibility = self._parseCompatibleValue(entry.text)
|
||||||
|
elif key in self.__keep_serialized_settings:
|
||||||
|
machine_reserialize_settings[key] = entry.text
|
||||||
else:
|
else:
|
||||||
Logger.log("d", "Unsupported material setting %s", key)
|
Logger.log("d", "Unsupported material setting %s", key)
|
||||||
|
|
||||||
|
@ -694,6 +726,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
new_material.getMetaData()["compatible"] = machine_compatibility
|
new_material.getMetaData()["compatible"] = machine_compatibility
|
||||||
new_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
|
new_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
|
||||||
new_material.getMetaData()["definition"] = machine_id
|
new_material.getMetaData()["definition"] = machine_id
|
||||||
|
new_material.getMetaData()["reserialize_settings"] = machine_reserialize_settings
|
||||||
|
|
||||||
new_material.setCachedValues(cached_machine_setting_properties)
|
new_material.setCachedValues(cached_machine_setting_properties)
|
||||||
|
|
||||||
|
@ -709,7 +742,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
if hotend_name is None:
|
if hotend_name is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
hotend_mapped_settings, hotend_unmapped_settings = self._getSettingsDictForNode(hotend)
|
hotend_mapped_settings, hotend_unmapped_settings, hotend_reserialize_settings = self._getSettingsDictForNode(hotend)
|
||||||
hotend_compatibility = hotend_unmapped_settings.get("hardware compatible", machine_compatibility)
|
hotend_compatibility = hotend_unmapped_settings.get("hardware compatible", machine_compatibility)
|
||||||
|
|
||||||
# Generate container ID for the hotend-specific material container
|
# Generate container ID for the hotend-specific material container
|
||||||
|
@ -732,6 +765,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
|
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
|
||||||
new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
|
new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
|
||||||
new_hotend_material.getMetaData()["definition"] = machine_id
|
new_hotend_material.getMetaData()["definition"] = machine_id
|
||||||
|
new_hotend_material.getMetaData()["reserialize_settings"] = hotend_reserialize_settings
|
||||||
|
|
||||||
cached_hotend_setting_properties = cached_machine_setting_properties.copy()
|
cached_hotend_setting_properties = cached_machine_setting_properties.copy()
|
||||||
cached_hotend_setting_properties.update(hotend_mapped_settings)
|
cached_hotend_setting_properties.update(hotend_mapped_settings)
|
||||||
|
@ -753,9 +787,10 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
ContainerRegistry.getInstance().addContainer(container_to_add)
|
ContainerRegistry.getInstance().addContainer(container_to_add)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _getSettingsDictForNode(cls, node) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
def _getSettingsDictForNode(cls, node) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]:
|
||||||
node_mapped_settings_dict = dict() # type: Dict[str, Any]
|
node_mapped_settings_dict: Dict[str, Any] = dict()
|
||||||
node_unmapped_settings_dict = dict() # type: Dict[str, Any]
|
node_unmapped_settings_dict: Dict[str, Any] = dict()
|
||||||
|
node_reserialize_settings_dict: Dict[str, Any] = dict()
|
||||||
|
|
||||||
# Fetch settings in the "um" namespace
|
# Fetch settings in the "um" namespace
|
||||||
um_settings = node.iterfind("./um:setting", cls.__namespaces)
|
um_settings = node.iterfind("./um:setting", cls.__namespaces)
|
||||||
|
@ -781,6 +816,10 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
if setting_key in ("hardware compatible", "hardware recommended"):
|
if setting_key in ("hardware compatible", "hardware recommended"):
|
||||||
node_unmapped_settings_dict[setting_key] = cls._parseCompatibleValue(um_setting_entry.text)
|
node_unmapped_settings_dict[setting_key] = cls._parseCompatibleValue(um_setting_entry.text)
|
||||||
|
|
||||||
|
# Settings unused by Cura itself, but which need reserialization since they might be important to others.
|
||||||
|
elif setting_key in cls.__keep_serialized_settings:
|
||||||
|
node_reserialize_settings_dict[setting_key] = um_setting_entry.text
|
||||||
|
|
||||||
# Unknown settings
|
# Unknown settings
|
||||||
else:
|
else:
|
||||||
Logger.log("w", "Unsupported material setting %s", setting_key)
|
Logger.log("w", "Unsupported material setting %s", setting_key)
|
||||||
|
@ -798,7 +837,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
# Cura settings are all mapped
|
# Cura settings are all mapped
|
||||||
node_mapped_settings_dict[key] = value
|
node_mapped_settings_dict[key] = value
|
||||||
|
|
||||||
return node_mapped_settings_dict, node_unmapped_settings_dict
|
return node_mapped_settings_dict, node_unmapped_settings_dict, node_reserialize_settings_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def deserializeMetadata(cls, serialized: str, container_id: str) -> List[Dict[str, Any]]:
|
def deserializeMetadata(cls, serialized: str, container_id: str) -> List[Dict[str, Any]]:
|
||||||
|
@ -988,7 +1027,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
if buildplate_name is None:
|
if buildplate_name is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
buildplate_mapped_settings, buildplate_unmapped_settings = cls._getSettingsDictForNode(buildplate)
|
buildplate_mapped_settings, buildplate_unmapped_settings, buildplate_reserialize_settings = cls._getSettingsDictForNode(buildplate)
|
||||||
buildplate_compatibility = buildplate_unmapped_settings.get("hardware compatible",
|
buildplate_compatibility = buildplate_unmapped_settings.get("hardware compatible",
|
||||||
buildplate_map["buildplate_compatible"])
|
buildplate_map["buildplate_compatible"])
|
||||||
buildplate_recommended = buildplate_unmapped_settings.get("hardware recommended",
|
buildplate_recommended = buildplate_unmapped_settings.get("hardware recommended",
|
||||||
|
@ -1005,6 +1044,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
new_hotend_and_buildplate_material_metadata["compatible"] = buildplate_compatibility
|
new_hotend_and_buildplate_material_metadata["compatible"] = buildplate_compatibility
|
||||||
new_hotend_and_buildplate_material_metadata["buildplate_compatible"] = buildplate_compatibility
|
new_hotend_and_buildplate_material_metadata["buildplate_compatible"] = buildplate_compatibility
|
||||||
new_hotend_and_buildplate_material_metadata["buildplate_recommended"] = buildplate_recommended
|
new_hotend_and_buildplate_material_metadata["buildplate_recommended"] = buildplate_recommended
|
||||||
|
new_hotend_and_buildplate_material_metadata["reserialize_settings"] = buildplate_reserialize_settings
|
||||||
|
|
||||||
result_metadata.append(new_hotend_and_buildplate_material_metadata)
|
result_metadata.append(new_hotend_and_buildplate_material_metadata)
|
||||||
|
|
||||||
|
@ -1145,6 +1185,29 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
"hardware compatible",
|
"hardware compatible",
|
||||||
"hardware recommended"
|
"hardware recommended"
|
||||||
]
|
]
|
||||||
|
__keep_serialized_settings = { # Settings irrelevant to Cura, but that could be present in the files so we must store them and keep them serialized.
|
||||||
|
"relative extrusion",
|
||||||
|
"flow sensor detection margin",
|
||||||
|
"different material purge volume",
|
||||||
|
"same material purge volume",
|
||||||
|
"end of print purge volume",
|
||||||
|
"end of filament purge volume",
|
||||||
|
"purge anti ooze retract position",
|
||||||
|
"purge drop retract position",
|
||||||
|
"purge retract speed",
|
||||||
|
"purge unretract speed",
|
||||||
|
"purge anti ooze dwell time",
|
||||||
|
"purge drop dwell time",
|
||||||
|
"dwell time before break preparation move",
|
||||||
|
"pressure release dwell time",
|
||||||
|
"tainted print core max temperature",
|
||||||
|
"recommend cleaning after n prints",
|
||||||
|
"maximum heated bed temperature",
|
||||||
|
"material bed adhesion temperature",
|
||||||
|
"maximum heated chamber temperature",
|
||||||
|
"shrinkage percentage",
|
||||||
|
"move to die distance",
|
||||||
|
}
|
||||||
__material_properties_setting_map = {
|
__material_properties_setting_map = {
|
||||||
"diameter": "material_diameter"
|
"diameter": "material_diameter"
|
||||||
}
|
}
|
||||||
|
|
5
requirements-dev.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pytest
|
||||||
|
pyinstaller
|
||||||
|
pyinstaller-hooks-contrib
|
||||||
|
sip==6.5.1
|
||||||
|
jinja2
|
1
requirements-ultimaker.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
git+https://github.com/ultimaker/libcharon@master#egg=charon
|
276
requirements.txt
|
@ -1,36 +1,240 @@
|
||||||
appdirs==1.4.3
|
### Direct requirements for Uranium and libCharon ###
|
||||||
certifi==2019.11.28
|
PyQt6-sip==13.2.1 \
|
||||||
cffi==1.14.1
|
--hash=sha256:b7bce59900b2e0a04f70246de2ccf79ee7933036b6b9183cf039b62eeae2b858 \
|
||||||
chardet==3.0.4
|
--hash=sha256:8b52d42e42e6e9f934ac7528cd154ac0210a532bb33fa1edfb4a8bbfb73ff88b \
|
||||||
colorlog
|
--hash=sha256:0314d011633bc697e99f3f9897b484720e81a5f4ba0eaa5f05c5811e2e74ea53 \
|
||||||
cryptography==3.4.8
|
--hash=sha256:226e9e349aa16dc1132f106ca01fa99cf7cb8e59daee29304c2fea5fa33212ec
|
||||||
decorator==4.4.0
|
PyQt6==6.2.3 \
|
||||||
idna==2.8
|
--hash=sha256:a9bfcac198fe4b703706f809bb686c7cef5f60a7c802fc145c6b57929c7a6a34 \
|
||||||
importlib-metadata==4.10.0
|
--hash=sha256:11c039b07962b29246de2da0912f4f663786185fd74d48daac7a270a43c8d92a \
|
||||||
keyring==23.0.1
|
--hash=sha256:8a2f357b86fec8598f52f16d5f93416931017ca1986d5f68679c9565bfc21fff \
|
||||||
lxml==4.7.1
|
--hash=sha256:577334c9d4518022a4cb6f9799dfbd1b996167eb31404b5a63d6c43d603e6418
|
||||||
mypy==0.740
|
PyQt6-Qt6==6.2.4 \
|
||||||
netifaces==0.10.9
|
--hash=sha256:42c37475a50ec7e06e0445ac9ce39465f69a86af407ad9b28b183da178d401ee \
|
||||||
networkx==2.6.2
|
--hash=sha256:b68543e5d5a4f5d24c26b517569da3cd30b0fbe75390b841e142c160399b3c0a \
|
||||||
numpy==1.21.5
|
--hash=sha256:0aa93581b92e01deaf2dcaad88ed6718996a6d84de59ee88316bcba143f008c9 \
|
||||||
numpy-stl==2.10.1
|
--hash=sha256:48bc5b7400d6bca13d8c0a145f82295a6da317952ee1a3f107f1cd7d078c8140
|
||||||
packaging==18.0
|
PyQt6-NetworkAuth==6.2.0 \
|
||||||
pyclipper==1.3.0.post2
|
--hash=sha256:23e730cc0d6b828bec2f92d9fac3607871e6033a8af4620e5d4e3afc13bd6c3c \
|
||||||
pycollada==0.6
|
--hash=sha256:b85ee25b01d6cb38d6141df0052b96de2df7f6e69066eaddb22ae238f56be40b \
|
||||||
pycparser==2.20
|
--hash=sha256:e637781a00dd2032d0fd2025af09274898335033763e1dc765a5a99348f60c3b \
|
||||||
pyparsing==2.4.2
|
--hash=sha256:542e9d9a8a5bb78e1f26fa3d35ee01f45209bcf5a35b0cc367aaa85932c29750
|
||||||
PyQt5==5.15.6
|
PyQt6-NetworkAuth-Qt6==6.2.4 \
|
||||||
PyQt5-sip==12.9.0
|
--hash=sha256:c7996a9d8c4ce024529ec37981fbfd525ab1a2d497af1281f81f2b6054452d2e \
|
||||||
pyserial==3.4
|
--hash=sha256:1ae9e08e03bd9d5ebdb42dfaccf484a9cc62eeea7504621fe42c005ff1745e66 \
|
||||||
pytest
|
--hash=sha256:8ed4e5e0eaaa42a6f91aba6745eea23fb3ffcbddc6b162016936530ed28dd0ad
|
||||||
python-dateutil==2.8.0
|
PyQt6-sip==13.2.1 \
|
||||||
python-utils==2.3.0
|
--hash=sha256:b7bce59900b2e0a04f70246de2ccf79ee7933036b6b9183cf039b62eeae2b858 \
|
||||||
pywin32==303
|
--hash=sha256:8b52d42e42e6e9f934ac7528cd154ac0210a532bb33fa1edfb4a8bbfb73ff88b \
|
||||||
scipy==1.8.0rc2
|
--hash=sha256:0314d011633bc697e99f3f9897b484720e81a5f4ba0eaa5f05c5811e2e74ea53 \
|
||||||
sentry-sdk==0.13.5
|
--hash=sha256:226e9e349aa16dc1132f106ca01fa99cf7cb8e59daee29304c2fea5fa33212ec
|
||||||
six==1.12.0
|
certifi==2021.10.8 \
|
||||||
trimesh==3.9.36
|
--hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 \
|
||||||
twisted==21.2.0
|
--hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569
|
||||||
typing
|
cryptography==3.4.8; \
|
||||||
urllib3==1.25.9
|
--hash=sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14 \
|
||||||
zeroconf==0.31.0
|
--hash=sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc \
|
||||||
|
--hash=sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b
|
||||||
|
zeroconf==0.31.0 \
|
||||||
|
--hash=sha256:53a180248471c6f81bd1fffcbce03ed93d7d8eaf10905c9121ac1ea996d19844 \
|
||||||
|
--hash=sha256:5a468da018bc3f04bbce77ae247924d802df7aeb4c291bbbb5a9616d128800b0
|
||||||
|
importlib-metadata==4.10.0 \
|
||||||
|
--hash=sha256:b7cf7d3fef75f1e4c80a96ca660efbd51473d7e8f39b5ab9210febc7809012a4 \
|
||||||
|
--hash=sha256:92a8b58ce734b2a4494878e0ecf7d79ccd7a128b5fc6014c401e0b61f006f0f6
|
||||||
|
keyring==23.0.1 \
|
||||||
|
--hash=sha256:045703609dd3fccfcdb27da201684278823b72af515aedec1a8515719a038cb8 \
|
||||||
|
--hash=sha256:8f607d7d1cc502c43a932a275a56fe47db50271904513a379d39df1af277ac48
|
||||||
|
|
||||||
|
# Use Numpy wheel that is compiled with Intel optimizations (MKL). Obtained from https://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy
|
||||||
|
# We cache this at software.ultimaker.com since this website tends to remove older versions rather quickly.
|
||||||
|
https://software.ultimaker.com/cura-binary-dependencies/numpy-1.21.5+mkl-cp310-cp310-win_amd64.whl; \
|
||||||
|
sys_platform=="win32" \
|
||||||
|
--hash=sha256:fbd5d5126b730a151134d21994a951fe28df06464e0c9a2cba2a4132e542a5fc
|
||||||
|
numpy==1.21.5; \
|
||||||
|
sys_platform!="win32" \
|
||||||
|
--hash=sha256:301e408a052fdcda5cdcf03021ebafc3c6ea093021bf9d1aa47c54d48bdad166 \
|
||||||
|
--hash=sha256:a7e8f6216f180f3fd4efb73de5d1eaefb5f5a1ee5b645c67333033e39440e63a \
|
||||||
|
--hash=sha256:fc7a7d7b0ed72589fd8b8486b9b42a564f10b8762be8bd4d9df94b807af4a089 \
|
||||||
|
--hash=sha256:58ca1d7c8aef6e996112d0ce873ac9dfa1eaf4a1196b4ff7ff73880a09923ba7 \
|
||||||
|
--hash=sha256:dc4b2fb01f1b4ddbe2453468ea0719f4dbb1f5caa712c8b21bb3dd1480cd30d9 \
|
||||||
|
--hash=sha256:6a5928bc6241264dce5ed509e66f33676fc97f464e7a919edc672fb5532221ee
|
||||||
|
pyclipper==1.3.0.post2; \
|
||||||
|
--hash=sha256:c096703dc32f2e4700a1f7054e8b58c29fe86212fa7a2c2adecb0102cb639fb2 \
|
||||||
|
--hash=sha256:a1525051ced1ab74e8d32282299c24c68f3e31cd4b64e0b368720b5da65aad67 \
|
||||||
|
--hash=sha256:5960aaa012cb925ef44ecabe69528809564a3c95ceac874d95c6600f207138d3 \
|
||||||
|
--hash=sha256:d3954330c02a19f7566651a909ec4bc5733ba6c62a228ab26db4a90305748430 \
|
||||||
|
--hash=sha256:5175ee50772a7dcc0feaab19ccf5b979b6066f4753edb330700231cf70d0c918 \
|
||||||
|
--hash=sha256:19a6809d9cbd535d0fe922e9315babb8d70b5c7dcd43e0f89740d09c406b40f8 \
|
||||||
|
--hash=sha256:5c5d50498e335d7f969ca5ad5886e77c40088521dcabab4feb2f93727140251e
|
||||||
|
scipy==1.8.1; \
|
||||||
|
--hash=sha256:9e3fb1b0e896f14a85aa9a28d5f755daaeeb54c897b746df7a55ccb02b340f33 \
|
||||||
|
--hash=sha256:4e53a55f6a4f22de01ffe1d2f016e30adedb67a699a310cdcac312806807ca81 \
|
||||||
|
--hash=sha256:a0aa8220b89b2e3748a2836fbfa116194378910f1a6e78e4675a095bcd2c762d \
|
||||||
|
--hash=sha256:02b567e722d62bddd4ac253dafb01ce7ed8742cf8031aea030a41414b86c1125 \
|
||||||
|
--hash=sha256:65b77f20202599c51eb2771d11a6b899b97989159b7975e9b5259594f1d35ef4 \
|
||||||
|
--hash=sha256:9dd4012ac599a1e7eb63c114d1eee1bcfc6dc75a29b589ff0ad0bb3d9412034f \
|
||||||
|
--hash=sha256:70de2f11bf64ca9921fda018864c78af7147025e467ce9f4a11bc877266900a6 \
|
||||||
|
--hash=sha256:83606129247e7610b58d0e1e93d2c5133959e9cf93555d3c27e536892f1ba1f2 \
|
||||||
|
--hash=sha256:f3e7a8867f307e3359cc0ed2c63b61a1e33a19080f92fe377bc7d49f646f2ec1
|
||||||
|
trimesh==3.9.36 \
|
||||||
|
--hash=sha256:f01e8edab14d1999700c980c21a1546f37417216ad915a53be649d263130181e \
|
||||||
|
--hash=sha256:8ac8bea693b3ee119f11b022fc9b9481c9f1af06cb38bc859bf5d16bbbe49b23
|
||||||
|
sentry-sdk==0.13.5 \
|
||||||
|
--hash=sha256:05285942901d38c7ce2498aba50d8e87b361fc603281a5902dda98f3f8c5e145 \
|
||||||
|
--hash=sha256:c6b919623e488134a728f16326c6f0bcdab7e3f59e7f4c472a90eea4d6d8fe82
|
||||||
|
mypy==0.931 \
|
||||||
|
--hash=sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce \
|
||||||
|
--hash=sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d \
|
||||||
|
--hash=sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069 \
|
||||||
|
--hash=sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c \
|
||||||
|
--hash=sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d \
|
||||||
|
--hash=sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714 \
|
||||||
|
--hash=sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a \
|
||||||
|
--hash=sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d \
|
||||||
|
--hash=sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05 \
|
||||||
|
--hash=sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266 \
|
||||||
|
--hash=sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697 \
|
||||||
|
--hash=sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc \
|
||||||
|
--hash=sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799 \
|
||||||
|
--hash=sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd \
|
||||||
|
--hash=sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00 \
|
||||||
|
--hash=sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7 \
|
||||||
|
--hash=sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a \
|
||||||
|
--hash=sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0 \
|
||||||
|
--hash=sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0 \
|
||||||
|
--hash=sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166
|
||||||
|
pyserial==3.4 \
|
||||||
|
--hash=sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627 \
|
||||||
|
--hash=sha256:e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8
|
||||||
|
|
||||||
|
### Indirect requirements ###
|
||||||
|
chardet==3.0.4 \
|
||||||
|
--hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \
|
||||||
|
--hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691
|
||||||
|
idna==2.8 \
|
||||||
|
--hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \
|
||||||
|
--hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c
|
||||||
|
attrs==21.2.0 \
|
||||||
|
--hash=sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1 \
|
||||||
|
--hash=sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb
|
||||||
|
requests==2.22.0 \
|
||||||
|
--hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \
|
||||||
|
--hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31
|
||||||
|
# twisted
|
||||||
|
Twisted==21.2.0 \
|
||||||
|
--hash=sha256:77544a8945cf69b98d2946689bbe0c75de7d145cdf11f391dd487eae8fc95a12 \
|
||||||
|
--hash=sha256:aab38085ea6cda5b378b519a0ec99986874921ee8881318626b0a3414bb2631e
|
||||||
|
constantly==15.1.0 \
|
||||||
|
--hash=sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35 \
|
||||||
|
--hash=sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d
|
||||||
|
hyperlink==21.0.0 \
|
||||||
|
--hash=sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b \
|
||||||
|
--hash=sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4
|
||||||
|
incremental==21.3.0 \
|
||||||
|
--hash=sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57 \
|
||||||
|
--hash=sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321
|
||||||
|
zope.interface==5.4.0 \
|
||||||
|
--hash=sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3 \
|
||||||
|
--hash=sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54 \
|
||||||
|
--hash=sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e \
|
||||||
|
--hash=sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7 \
|
||||||
|
--hash=sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48 \
|
||||||
|
--hash=sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4
|
||||||
|
Automat==20.2.0 \
|
||||||
|
--hash=sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33 \
|
||||||
|
--hash=sha256:b6feb6455337df834f6c9962d6ccf771515b7d939bca142b29c20c2376bc6111
|
||||||
|
twisted-iocpsupport==1.0.2; \
|
||||||
|
sys_platform=="win32" \
|
||||||
|
--hash=sha256:306becd6e22ab6e8e4f36b6bdafd9c92e867c98a5ce517b27fdd27760ee7ae41 \
|
||||||
|
--hash=sha256:3c61742cb0bc6c1ac117a7e5f422c129832f0c295af49e01d8a6066df8cfc04d \
|
||||||
|
--hash=sha256:72068b206ee809c9c596b57b5287259ea41ddb4774d86725b19f35bf56aa32a9 \
|
||||||
|
--hash=sha256:7d972cfa8439bdcb35a7be78b7ef86d73b34b808c74be56dfa785c8a93b851bf \
|
||||||
|
--hash=sha256:81b3abe3527b367da0220482820cb12a16c661672b7bcfcde328902890d63323 \
|
||||||
|
--hash=sha256:851b3735ca7e8102e661872390e3bce88f8901bece95c25a0c8bb9ecb8a23d32 \
|
||||||
|
--hash=sha256:985c06a33f5c0dae92c71a036d1ea63872ee86a21dd9b01e1f287486f15524b4 \
|
||||||
|
--hash=sha256:9dbb8823b49f06d4de52721b47de4d3b3026064ef4788ce62b1a21c57c3fff6f \
|
||||||
|
--hash=sha256:b435857b9efcbfc12f8c326ef0383f26416272260455bbca2cd8d8eca470c546 \
|
||||||
|
--hash=sha256:b76b4eed9b27fd63ddb0877efdd2d15835fdcb6baa745cb85b66e5d016ac2878 \
|
||||||
|
--hash=sha256:b9fed67cf0f951573f06d560ac2f10f2a4bbdc6697770113a2fc396ea2cb2565 \
|
||||||
|
--hash=sha256:bf4133139d77fc706d8f572e6b7d82871d82ec7ef25d685c2351bdacfb701415
|
||||||
|
numpy-stl==2.10.1 \
|
||||||
|
--hash=sha256:f6b529b8a8112dfe456d4f7697c7aee0aca62be5a873879306afe4b26fca963c
|
||||||
|
python-utils==2.3.0 \
|
||||||
|
--hash=sha256:34aaf26b39b0b86628008f2ae0ac001b30e7986a8d303b61e1357dfcdad4f6d3 \
|
||||||
|
--hash=sha256:e25f840564554eaded56eaa395bca507b0b9e9f0ae5ecb13a8cb785305c56d25
|
||||||
|
six==1.12.0 \
|
||||||
|
--hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \
|
||||||
|
--hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73
|
||||||
|
shapely==1.8.2 \
|
||||||
|
--hash=sha256:572af9d5006fd5e3213e37ee548912b0341fb26724d6dc8a4e3950c10197ebb6 \
|
||||||
|
--hash=sha256:c60f3758212ec480675b820b13035dda8af8f7cc560d2cc67999b2717fb8faef \
|
||||||
|
--hash=sha256:6bdc7728f1e5df430d8c588661f79f1eed4a2728c8b689e12707cfec217f68f8 \
|
||||||
|
--hash=sha256:ce0b5c5f7acbccf98b3460eecaa40e9b18272b2a734f74fcddf1d7696e047e95 \
|
||||||
|
--hash=sha256:7c9e3400b716c51ba43eea1678c28272580114e009b6c78cdd00c44df3e325fa \
|
||||||
|
--hash=sha256:3423299254deec075e79fb7dc7909d702104e4167149de7f45510c3a6342eeea \
|
||||||
|
--hash=sha256:3423299254deec075e79fb7dc7909d702104e4167149de7f45510c3a6342eeea \
|
||||||
|
--hash=sha256:44d2832c1b706bf43101fda92831a083467cc4b4923a7ed17319ab599c1025d8 \
|
||||||
|
--hash=sha256:44d2832c1b706bf43101fda92831a083467cc4b4923a7ed17319ab599c1025d8 \
|
||||||
|
--hash=sha256:75042e8039c79dd01f102bb288beace9dc2f49fc44a2dea875f9b697aa8cd30d \
|
||||||
|
--hash=sha256:75042e8039c79dd01f102bb288beace9dc2f49fc44a2dea875f9b697aa8cd30d \
|
||||||
|
--hash=sha256:5254240eefc44139ab0d128faf671635d8bdd9c23955ee063d4d6b8f20073ae0
|
||||||
|
cython==0.29.26 \
|
||||||
|
--hash=sha256:af377d543a762867da11fcf6e558f7a4a535ff8693f30cce123fab10c00fa312 \
|
||||||
|
--hash=sha256:f5e15ff892c8afad64931ee3dd723c4755c2c516606f9aae7613bebfac62b0f6 \
|
||||||
|
--hash=sha256:2b834ff6e4d10ba6d7a0d676dd71c1b427a181ddbbbbf79e91d1861557aab59f \
|
||||||
|
--hash=sha256:c813799d533194b7d85203d881d8b4f567a8c644a67f50d47f1ffbf316df412f \
|
||||||
|
--hash=sha256:6773cce9d4b3b6168d8feb2b6f06b658ef1e11cbfec075041745666d8e2a5e45 \
|
||||||
|
--hash=sha256:362fbb9cb4627c7786231429768b54aaba5459a2a0e46c25e59f202ca6155437
|
||||||
|
pybind11==2.6.2 \
|
||||||
|
--hash=sha256:2d8aebe1709bc367e34e3b23d8eccbf3f387ee9d5640548c6260d33b59f02405 \
|
||||||
|
--hash=sha256:d0e0aed9279656f21501243b707eb6e3b951e89e10c3271dedf3ae41c365e5ed
|
||||||
|
wheel==0.37.1 \
|
||||||
|
--hash=sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4 \
|
||||||
|
--hash=sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a
|
||||||
|
setuptools==62.0.0 \
|
||||||
|
--hash=sha256:7999cbd87f1b6e1f33bf47efa368b224bed5e27b5ef2c4d46580186cbcb1a86a \
|
||||||
|
--hash=sha256:a65e3802053e99fc64c6b3b29c11132943d5b8c8facbcc461157511546510967
|
||||||
|
ifaddr==0.1.7 \
|
||||||
|
--hash=sha256:1f9e8a6ca6f16db5a37d3356f07b6e52344f6f9f7e806d618537731669eb1a94 \
|
||||||
|
--hash=sha256:d1f603952f0a71c9ab4e705754511e4e03b02565bc4cec7188ad6415ff534cd3
|
||||||
|
pycparser==2.20 \
|
||||||
|
--hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705
|
||||||
|
zipp==3.5.0 \
|
||||||
|
--hash=sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3 \
|
||||||
|
--hash=sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4
|
||||||
|
cffi==1.15.0 \
|
||||||
|
--hash=sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954 \
|
||||||
|
--hash=sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0 \
|
||||||
|
--hash=sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3 \
|
||||||
|
--hash=sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2
|
||||||
|
urllib3==1.25.9 \
|
||||||
|
--hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \
|
||||||
|
--hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115
|
||||||
|
mypy-extensions==0.4.3 \
|
||||||
|
--hash=sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d \
|
||||||
|
--hash=sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8
|
||||||
|
tomli==2.0.1 \
|
||||||
|
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
|
||||||
|
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
|
||||||
|
typing-extensions==3.10.0.2 \
|
||||||
|
--hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \
|
||||||
|
--hash=sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34
|
||||||
|
jeepney==0.7.1; \
|
||||||
|
--hash=sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac \
|
||||||
|
--hash=sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f
|
||||||
|
SecretStorage==3.3.1 \
|
||||||
|
--hash=sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f \
|
||||||
|
--hash=sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195
|
||||||
|
keyring==23.0.1 \
|
||||||
|
--hash=sha256:045703609dd3fccfcdb27da201684278823b72af515aedec1a8515719a038cb8 \
|
||||||
|
--hash=sha256:8f607d7d1cc502c43a932a275a56fe47db50271904513a379d39df1af277ac48
|
||||||
|
pywin32==303; \
|
||||||
|
sys_platform=="win32" \
|
||||||
|
--hash=sha256:51cb52c5ec6709f96c3f26e7795b0bf169ee0d8395b2c1d7eb2c029a5008ed51
|
||||||
|
pywin32-ctypes==0.2.0; \
|
||||||
|
sys_platform=="win32" \
|
||||||
|
--hash=sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942 \
|
||||||
|
--hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98
|
||||||
|
|
||||||
|
charset-normalizer==2.1.0; \
|
||||||
|
--hash=sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5
|
||||||
|
|