mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 22:47:29 -06:00
Merge branch 'main'
This commit is contained in:
commit
b1138e12d9
5455 changed files with 203722 additions and 208531 deletions
|
@ -1,4 +0,0 @@
|
||||||
.git
|
|
||||||
.github
|
|
||||||
resources/materials
|
|
||||||
CuraEngine
|
|
4
.github/ISSUE_TEMPLATE/bugreport.yaml
vendored
4
.github/ISSUE_TEMPLATE/bugreport.yaml
vendored
|
@ -1,6 +1,6 @@
|
||||||
name: Bug Report
|
name: Bug Report
|
||||||
description: Create a report to help us fix issues.
|
description: Create a report to help us fix issues.
|
||||||
labels: "Type: Bug"
|
labels: ["Type: Bug", "Status: Triage"]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
|
@ -14,7 +14,7 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Application Version
|
label: Application Version
|
||||||
description: The version of Cura this issue occurs with.
|
description: The version of Cura this issue occurs with.
|
||||||
placeholder: 4.9.0
|
placeholder: 5.0.0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
|
|
4
.github/ISSUE_TEMPLATE/featurerequest.yaml
vendored
4
.github/ISSUE_TEMPLATE/featurerequest.yaml
vendored
|
@ -1,6 +1,6 @@
|
||||||
name: Feature Request
|
name: Feature Request
|
||||||
description: Suggest an idea for this project.
|
description: Suggest an idea for this project.
|
||||||
labels: "Type: New Feature"
|
labels: ["Type: New Feature", "Status: Triage"]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
|
@ -41,4 +41,4 @@ body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Additional information & file uploads
|
label: Additional information & file uploads
|
||||||
description: You can add pictures or files to visualize your feature request in the comments below.
|
description: You can add pictures or files to visualize your feature request in the comments below.
|
||||||
|
|
13
.github/no-response.yml
vendored
13
.github/no-response.yml
vendored
|
@ -1,13 +0,0 @@
|
||||||
# Configuration for probot-no-response - https://github.com/probot/no-response
|
|
||||||
|
|
||||||
# Number of days of inactivity before an Issue is closed for lack of response
|
|
||||||
daysUntilClose: 14
|
|
||||||
# Label requiring a response
|
|
||||||
responseRequiredLabel: 'Status: Needs Info'
|
|
||||||
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
|
|
||||||
closeComment: >
|
|
||||||
This issue has been automatically closed because there has been no response
|
|
||||||
to our request for more information from the original author. With only the
|
|
||||||
information that is currently in the issue, we don't have enough information
|
|
||||||
to take action. Please reach out if you have or find the answers we need so
|
|
||||||
that we can investigate further.
|
|
21
.github/workflows/ci.yml
vendored
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
|
|
167
.github/workflows/conan-package-create.yml
vendored
Normal file
167
.github/workflows/conan-package-create.yml
vendored
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
name: Create and Upload Conan package
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
project_name:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
build_id:
|
||||||
|
required: true
|
||||||
|
type: number
|
||||||
|
|
||||||
|
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_build_info --v2 start ${{ inputs.project_name }} ${{ github.run_number }}000${{ inputs.build_id }}
|
||||||
|
conan lock create --reference ${{ inputs.recipe_id_full }} --build=missing --update
|
||||||
|
conan install ${{ inputs.recipe_id_full }} --build=missing --update --lockfile=conan.lock
|
||||||
|
conan_build_info --v2 create buildinfo.json --lockfile conan.lock --user ${{ secrets.CONAN_USER }} --password ${{ secrets.CONAN_PASS }}
|
||||||
|
conan_build_info --v2 publish buildinfo.json --url https://ultimaker.jfrog.io/artifactory --user ${{ secrets.CONAN_USER }} --password ${{ secrets.CONAN_PASS }}
|
||||||
|
conan_build_info --v2 stop
|
||||||
|
|
||||||
|
- 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
|
116
.github/workflows/conan-package.yml
vendored
Normal file
116
.github/workflows/conan-package.yml
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
---
|
||||||
|
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]'
|
||||||
|
- '[1-9].[0-9][0-9].[0-9]*'
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
jobs:
|
||||||
|
conan-recipe-version:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
uses: ultimaker/cura/.github/workflows/conan-recipe-version.yml@main
|
||||||
|
with:
|
||||||
|
project_name: cura
|
||||||
|
|
||||||
|
conan-package-export:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
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:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
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:
|
||||||
|
project_name: cura
|
||||||
|
build_id: 1
|
||||||
|
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
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
|
196
.github/workflows/conan-recipe-version.yml
vendored
Normal file
196
.github/workflows/conan-recipe-version.yml
vendored
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
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
|
||||||
|
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
|
- name: Checkout repo PR
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
|
|
||||||
|
- 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
|
||||||
|
ref_name = "${{ github.base_ref }}" if event_name == "pull_request" else "${{ github.ref_name }}"
|
||||||
|
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(ref_name)
|
||||||
|
is_release_branch = True
|
||||||
|
channel = "_"
|
||||||
|
user = "_"
|
||||||
|
actual_version = f"{branch_version}"
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
branch_version = tools.Version(repo.active_branch.name)
|
||||||
|
except ConanException:
|
||||||
|
branch_version = tools.Version('0.0.0')
|
||||||
|
if ref_name == f"{branch_version.major}.{branch_version.minor}":
|
||||||
|
channel = 'stable'
|
||||||
|
is_release_branch = True
|
||||||
|
elif ref_name in ("main", "master"):
|
||||||
|
channel = 'testing'
|
||||||
|
else:
|
||||||
|
channel = repo.active_branch.name.split("_")[0].replace("-", "_").lower()
|
||||||
|
|
||||||
|
if "pull_request" in event_name:
|
||||||
|
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():
|
||||||
|
if str(tag).startswith("firmware") or str(tag).startswith("master"):
|
||||||
|
continue # Quick-fix for the versioning scheme name of the embedded team in fdm_materials(_private) repo
|
||||||
|
try:
|
||||||
|
version = tools.Version(tag)
|
||||||
|
except ConanException:
|
||||||
|
continue
|
||||||
|
if version > latest_branch_version:
|
||||||
|
latest_branch_version = version
|
||||||
|
latest_branch_tag = repo.tag(tag)
|
||||||
|
|
||||||
|
if latest_branch_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
|
||||||
|
latest_branch_version_prerelease = latest_branch_version.prerelease
|
||||||
|
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 = f"{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}"
|
||||||
|
channel_metadata = f"{channel}_{no_commits}"
|
||||||
|
else:
|
||||||
|
if channel in ("stable", "_", ""):
|
||||||
|
channel_metadata = f"{no_commits}"
|
||||||
|
else:
|
||||||
|
channel_metadata = f"{channel}_{no_commits}"
|
||||||
|
if is_release_branch:
|
||||||
|
if latest_branch_version.prerelease == "":
|
||||||
|
# An actual full release has been created, we are working on patch
|
||||||
|
bump_up_patch = int(latest_branch_version.patch) + 1
|
||||||
|
actual_version = f"{latest_branch_version.major}.{latest_branch_version.minor}.{bump_up_patch}-beta.1+{buildmetadata}{channel_metadata}"
|
||||||
|
else:
|
||||||
|
# An beta release has been created we are working toward a next beta or full release
|
||||||
|
bump_up_release_tag = int(latest_branch_version.prerelease.split('.')[1]) + 1
|
||||||
|
actual_version = f"{latest_branch_version.major}.{latest_branch_version.minor}.{latest_branch_version.patch}-{latest_branch_version.prerelease.split('.')[0]}.{bump_up_release_tag}+{buildmetadata}{channel_metadata}"
|
||||||
|
else:
|
||||||
|
bump_up_minor = int(latest_branch_version.minor) + 1
|
||||||
|
reset_patch = 0
|
||||||
|
actual_version = f"{latest_branch_version.major}.{bump_up_minor}.{reset_patch}-alpha+{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"= {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
|
263
.github/workflows/cura-installer.yml
vendored
Normal file
263
.github/workflows/cura-installer.yml
vendored
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
name: Cura Installer
|
||||||
|
run-name: ${{ inputs.cura_conan_version }} by @${{ github.actor }}
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
cura_conan_version:
|
||||||
|
description: 'Cura Conan Version'
|
||||||
|
default: 'cura/latest@ultimaker/testing'
|
||||||
|
required: true
|
||||||
|
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: true
|
||||||
|
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 }}
|
||||||
|
CURA_CONAN_VERSION: ${{ inputs.cura_conan_version }}
|
||||||
|
ENTERPRISE: ${{ inputs.enterprise }}
|
||||||
|
STAGING: ${{ inputs.staging }}
|
||||||
|
|
||||||
|
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 (Bash)
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING --json "cura_inst/conan_install_info.json"
|
||||||
|
|
||||||
|
- name: Create the Packages (Powershell)
|
||||||
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
run: conan install $Env:CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$Env:ENTERPRISE -o cura:staging=$Env:STAGING --json "cura_inst/conan_install_info.json"
|
||||||
|
|
||||||
|
- name: Set Environment variables for Cura (bash)
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
run: |
|
||||||
|
. ./cura_inst/bin/activate_github_actions_env.sh
|
||||||
|
. ./cura_inst/bin/activate_github_actions_version_env.sh
|
||||||
|
|
||||||
|
- name: Set Environment variables for Cura (Powershell)
|
||||||
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
run: |
|
||||||
|
.\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
|
31
.github/workflows/no-response.yml
vendored
Normal file
31
.github/workflows/no-response.yml
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
name: No Response
|
||||||
|
|
||||||
|
# Both `issue_comment` and `scheduled` event types are required for this Action
|
||||||
|
# to work properly.
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
schedule:
|
||||||
|
# Schedule for ten minutes after the hour, every hour
|
||||||
|
- cron: '* */12 * * *'
|
||||||
|
|
||||||
|
# By specifying the access of one of the scopes, all of those that are not
|
||||||
|
# specified are set to 'none'.
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
noResponse:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: lee-dohm/no-response@v0.5.0
|
||||||
|
with:
|
||||||
|
token: ${{ github.token }}
|
||||||
|
daysUntilClose: 14
|
||||||
|
responseRequiredLabel: 'Status: Needs Info'
|
||||||
|
closeComment: >
|
||||||
|
This issue has been automatically closed because there has been no response
|
||||||
|
to our request for more information from the original author. With only the
|
||||||
|
information that is currently in the issue, we don't have enough information
|
||||||
|
to take action. Please reach out if you have or find the answers we need so
|
||||||
|
that we can investigate further.
|
54
.github/workflows/notify.yml
vendored
Normal file
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 }}
|
35
.github/workflows/notify_on_print_profile_change.yml
vendored
Normal file
35
.github/workflows/notify_on_print_profile_change.yml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
name: notify_on_print_profile_change
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
paths:
|
||||||
|
- 'resources/definitions/fdmprinter.def.json'
|
||||||
|
- 'resources/definitions/ultimaker**'
|
||||||
|
- 'resources/extruders/ultimaker**'
|
||||||
|
- 'resources/intent/ultimaker**'
|
||||||
|
- 'resources/quality/ultimaker**'
|
||||||
|
- 'resources/variants/ultimaker**'
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
paths:
|
||||||
|
- 'resources/definitions/fdmprinter.def.json'
|
||||||
|
- 'resources/definitions/ultimaker**'
|
||||||
|
- 'resources/extruders/ultimaker**'
|
||||||
|
- 'resources/intent/ultimaker**'
|
||||||
|
- 'resources/quality/ultimaker**'
|
||||||
|
- 'resources/variants/ultimaker**'
|
||||||
|
jobs:
|
||||||
|
slackNotification:
|
||||||
|
name: Slack Notification
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Ultimaker Print Profile Changed
|
||||||
|
uses: rtCamp/action-slack-notify@v2
|
||||||
|
env:
|
||||||
|
SLACK_CHANNEL: profile-changes
|
||||||
|
SLACK_USERNAME: ${{ github.repository }}
|
||||||
|
SLACK_COLOR: '#00FF00'
|
||||||
|
SLACK_TITLE: Print profiles changed
|
||||||
|
MSG_MINIMAL: commit
|
||||||
|
SLACK_WEBHOOK: ${{ secrets.SLACK_CURA_PPM_HOOK }}
|
2
.github/workflows/requirements-conan-package.txt
vendored
Normal file
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,!=1.52.0
|
||||||
|
sip
|
183
.github/workflows/unit-test.yml
vendored
Normal file
183
.github/workflows/unit-test.yml
vendored
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
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 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: 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:
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
checks: write
|
||||||
|
pull-requests: write # to comment on pull request
|
||||||
|
|
||||||
|
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 }}"
|
13
.gitignore
vendored
13
.gitignore
vendored
|
@ -38,6 +38,7 @@ cura.desktop
|
||||||
.settings
|
.settings
|
||||||
|
|
||||||
#Externally located plug-ins commonly installed by our devs.
|
#Externally located plug-ins commonly installed by our devs.
|
||||||
|
plugins/BarbarianPlugin
|
||||||
plugins/cura-big-flame-graph
|
plugins/cura-big-flame-graph
|
||||||
plugins/cura-camera-position
|
plugins/cura-camera-position
|
||||||
plugins/cura-god-mode-plugin
|
plugins/cura-god-mode-plugin
|
||||||
|
@ -64,6 +65,7 @@ plugins/CuraRemoteSupport
|
||||||
plugins/ModelCutter
|
plugins/ModelCutter
|
||||||
plugins/PrintProfileCreator
|
plugins/PrintProfileCreator
|
||||||
plugins/MultiPrintPlugin
|
plugins/MultiPrintPlugin
|
||||||
|
plugins/CuraOrientationPlugin
|
||||||
|
|
||||||
#Build stuff
|
#Build stuff
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
|
@ -87,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
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
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>
|
35
CITATION.cff
35
CITATION.cff
|
@ -1,11 +1,30 @@
|
||||||
# YAML 1.2
|
# YAML 1.2
|
||||||
---
|
---
|
||||||
authors:
|
cff-version: 1.2.0
|
||||||
cff-version: "1.1.0"
|
type: software
|
||||||
date-released: 2021-06-28
|
message: >-
|
||||||
license: "LGPL-3.0"
|
If you use this software, please cite it using the
|
||||||
message: "If you use this software, please cite it using these metadata."
|
metadata from this file.
|
||||||
repository-code: "https://github.com/ultimaker/cura/"
|
title: Ultimaker Cura
|
||||||
title: "Ultimaker Cura"
|
abstract: >-
|
||||||
version: "4.12.0"
|
A state-of-the-art slicer application built on top
|
||||||
|
of the Uranium framework.
|
||||||
|
authors:
|
||||||
|
- name: Ultimaker B.V.
|
||||||
|
contact:
|
||||||
|
- email: info@ultimaker.com
|
||||||
|
name: "Ultimaker B.V."
|
||||||
|
url: 'https://ultimaker.com/software/ultimaker-cura'
|
||||||
|
repository-code: 'https://github.com/Ultimaker/Cura'
|
||||||
|
license: LGPL-3.0
|
||||||
|
license-url: "https://github.com/Ultimaker/Cura/blob/main/LICENSE"
|
||||||
|
version: 5.0.0
|
||||||
|
date-released: '2022-05-17'
|
||||||
|
keywords:
|
||||||
|
- Ultimaker
|
||||||
|
- Cura
|
||||||
|
- Uranium
|
||||||
|
- Arachne
|
||||||
|
- 3DPrinting
|
||||||
|
- Slicer
|
||||||
...
|
...
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
|
# Copyright (c) 2022 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
# NOTE: This is only being used for translation scripts.
|
||||||
|
|
||||||
|
# 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.6)
|
cmake_minimum_required(VERSION 3.18)
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
@ -8,42 +15,8 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
||||||
set(URANIUM_DIR "${CMAKE_SOURCE_DIR}/../Uranium" CACHE PATH "The location of the Uranium repository")
|
set(URANIUM_DIR "${CMAKE_SOURCE_DIR}/../Uranium" CACHE PATH "The location of the Uranium repository")
|
||||||
set(URANIUM_SCRIPTS_DIR "${URANIUM_DIR}/scripts" CACHE PATH "The location of the scripts directory of the Uranium repository")
|
set(URANIUM_SCRIPTS_DIR "${URANIUM_DIR}/scripts" CACHE PATH "The location of the scripts directory of the Uranium repository")
|
||||||
|
|
||||||
# Tests
|
|
||||||
include(CuraTests)
|
|
||||||
|
|
||||||
option(CURA_DEBUGMODE "Enable debug dialog and other debug features" OFF)
|
|
||||||
if(CURA_DEBUGMODE)
|
|
||||||
set(_cura_debugmode "ON")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
option(GENERATE_TRANSLATIONS "Should the translations be generated?" ON)
|
option(GENERATE_TRANSLATIONS "Should the translations be generated?" ON)
|
||||||
|
|
||||||
set(CURA_APP_NAME "cura" CACHE STRING "Short name of Cura, used for configuration folder")
|
|
||||||
set(CURA_APP_DISPLAY_NAME "Ultimaker Cura" CACHE STRING "Display name of Cura")
|
|
||||||
set(CURA_VERSION "master" CACHE STRING "Version name of Cura")
|
|
||||||
set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'")
|
|
||||||
set(CURA_CLOUD_API_ROOT "" CACHE STRING "Alternative Cura cloud API root")
|
|
||||||
set(CURA_CLOUD_API_VERSION "" CACHE STRING "Alternative Cura cloud API version")
|
|
||||||
set(CURA_CLOUD_ACCOUNT_API_ROOT "" CACHE STRING "Alternative Cura cloud account API version")
|
|
||||||
set(CURA_MARKETPLACE_ROOT "" CACHE STRING "Alternative Marketplace location")
|
|
||||||
set(CURA_DIGITAL_FACTORY_URL "" CACHE STRING "Alternative Digital Factory location")
|
|
||||||
|
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/com.ultimaker.cura.desktop.in ${CMAKE_BINARY_DIR}/com.ultimaker.cura.desktop @ONLY)
|
|
||||||
|
|
||||||
configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY)
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME: The new FindPython3 finds the system's Python3.6 rather than the Python3.5 that we built for Cura's environment.
|
|
||||||
# So we're using the old method here, with FindPythonInterp for now.
|
|
||||||
find_package(PythonInterp 3 REQUIRED)
|
|
||||||
|
|
||||||
set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE})
|
|
||||||
|
|
||||||
set(Python3_VERSION ${PYTHON_VERSION_STRING})
|
|
||||||
set(Python3_VERSION_MAJOR ${PYTHON_VERSION_MAJOR})
|
|
||||||
set(Python3_VERSION_MINOR ${PYTHON_VERSION_MINOR})
|
|
||||||
set(Python3_VERSION_PATCH ${PYTHON_VERSION_PATCH})
|
|
||||||
|
|
||||||
if(NOT ${URANIUM_DIR} STREQUAL "")
|
if(NOT ${URANIUM_DIR} STREQUAL "")
|
||||||
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${URANIUM_DIR}/cmake")
|
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${URANIUM_DIR}/cmake")
|
||||||
endif()
|
endif()
|
||||||
|
@ -56,48 +29,4 @@ if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "")
|
||||||
if(${GENERATE_TRANSLATIONS})
|
if(${GENERATE_TRANSLATIONS})
|
||||||
CREATE_TRANSLATION_TARGETS()
|
CREATE_TRANSLATION_TARGETS()
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
install(DIRECTORY resources
|
|
||||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/cura)
|
|
||||||
|
|
||||||
include(CuraPluginInstall)
|
|
||||||
|
|
||||||
if(NOT APPLE AND NOT WIN32)
|
|
||||||
install(FILES cura_app.py
|
|
||||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
||||||
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
|
|
||||||
RENAME cura)
|
|
||||||
if(EXISTS /etc/debian_version)
|
|
||||||
install(DIRECTORY cura
|
|
||||||
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}/dist-packages
|
|
||||||
FILES_MATCHING PATTERN *.py)
|
|
||||||
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
|
|
||||||
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}/dist-packages/cura)
|
|
||||||
else()
|
|
||||||
install(DIRECTORY cura
|
|
||||||
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages
|
|
||||||
FILES_MATCHING PATTERN *.py)
|
|
||||||
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
|
|
||||||
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages/cura)
|
|
||||||
endif()
|
|
||||||
install(FILES ${CMAKE_BINARY_DIR}/com.ultimaker.cura.desktop
|
|
||||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
|
||||||
install(FILES ${CMAKE_SOURCE_DIR}/resources/images/cura-icon.png
|
|
||||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps/)
|
|
||||||
install(FILES com.ultimaker.cura.appdata.xml
|
|
||||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
|
|
||||||
install(FILES cura.sharedmimeinfo
|
|
||||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages/
|
|
||||||
RENAME cura.xml )
|
|
||||||
else()
|
|
||||||
install(FILES cura_app.py
|
|
||||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
||||||
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
|
||||||
install(DIRECTORY cura
|
|
||||||
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages
|
|
||||||
FILES_MATCHING PATTERN *.py)
|
|
||||||
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
|
|
||||||
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages/cura)
|
|
||||||
endif()
|
|
14
CuraVersion.py.jinja
Normal file
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
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"
|
|
74
Jenkinsfile
vendored
74
Jenkinsfile
vendored
|
@ -1,74 +0,0 @@
|
||||||
parallel_nodes(['linux && cura', 'windows && cura'])
|
|
||||||
{
|
|
||||||
timeout(time: 2, unit: "HOURS")
|
|
||||||
{
|
|
||||||
|
|
||||||
// Prepare building
|
|
||||||
stage('Prepare')
|
|
||||||
{
|
|
||||||
// Ensure we start with a clean build directory.
|
|
||||||
step([$class: 'WsCleanup'])
|
|
||||||
|
|
||||||
// Checkout whatever sources are linked to this pipeline.
|
|
||||||
checkout scm
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any error occurs during building, we want to catch it and continue with the "finale" stage.
|
|
||||||
catchError
|
|
||||||
{
|
|
||||||
// Building and testing should happen in a subdirectory.
|
|
||||||
dir('build')
|
|
||||||
{
|
|
||||||
// Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
|
|
||||||
stage('Build')
|
|
||||||
{
|
|
||||||
def branch = env.BRANCH_NAME
|
|
||||||
if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}"))
|
|
||||||
{
|
|
||||||
branch = "master"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure CMake is setup. Note that since this is Python code we do not really "build" it.
|
|
||||||
def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
|
|
||||||
cmake("..", "-DCMAKE_PREFIX_PATH=\"${env.CURA_ENVIRONMENT_PATH}/${branch}\" -DCMAKE_BUILD_TYPE=Release -DURANIUM_DIR=\"${uranium_dir}\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
|
|
||||||
stage('Unit Test')
|
|
||||||
{
|
|
||||||
if (isUnix())
|
|
||||||
{
|
|
||||||
// For Linux
|
|
||||||
try {
|
|
||||||
sh 'make CTEST_OUTPUT_ON_FAILURE=TRUE test'
|
|
||||||
} catch(e)
|
|
||||||
{
|
|
||||||
currentBuild.result = "UNSTABLE"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// For Windows
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// This also does code style checks.
|
|
||||||
bat 'ctest -V'
|
|
||||||
} catch(e)
|
|
||||||
{
|
|
||||||
currentBuild.result = "UNSTABLE"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform any post-build actions like notification and publishing of unit tests.
|
|
||||||
stage('Finalize')
|
|
||||||
{
|
|
||||||
// Publish the test results to Jenkins.
|
|
||||||
junit allowEmptyResults: true, testResults: 'build/junit*.xml'
|
|
||||||
|
|
||||||
notify_build_result(env.CURA_EMAIL_RECIPIENTS, '#cura-dev', ['master', '2.'])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
125
README.md
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
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
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
|
|
|
@ -1,105 +0,0 @@
|
||||||
# Copyright (c) 2019 Ultimaker B.V.
|
|
||||||
# CuraPluginInstall.cmake is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
#
|
|
||||||
# This module detects all plugins that need to be installed and adds them using the CMake install() command.
|
|
||||||
# It detects all plugin folder in the path "plugins/*" where there's a "plugin.json" in it.
|
|
||||||
#
|
|
||||||
# Plugins can be configured to NOT BE INSTALLED via the variable "CURA_NO_INSTALL_PLUGINS" as a list of string in the
|
|
||||||
# form of "a;b;c" or "a,b,c". By default all plugins will be installed.
|
|
||||||
#
|
|
||||||
|
|
||||||
option(PRINT_PLUGIN_LIST "Should the list of plugins that are installed be printed?" ON)
|
|
||||||
|
|
||||||
# FIXME: Remove the code for CMake <3.12 once we have switched over completely.
|
|
||||||
# FindPython3 is a new module since CMake 3.12. It deprecates FindPythonInterp and FindPythonLibs. The FindPython3
|
|
||||||
# module is copied from the CMake repository here so in CMake <3.12 we can still use it.
|
|
||||||
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
|
||||||
# Use FindPythonInterp and FindPythonLibs for CMake <3.12
|
|
||||||
find_package(PythonInterp 3 REQUIRED)
|
|
||||||
|
|
||||||
set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE})
|
|
||||||
else()
|
|
||||||
# Use FindPython3 for CMake >=3.12
|
|
||||||
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Options or configuration variables
|
|
||||||
set(CURA_NO_INSTALL_PLUGINS "" CACHE STRING "A list of plugins that should not be installed, separated with ';' or ','.")
|
|
||||||
|
|
||||||
file(GLOB_RECURSE _plugin_json_list ${CMAKE_SOURCE_DIR}/plugins/*/plugin.json)
|
|
||||||
list(LENGTH _plugin_json_list _plugin_json_list_len)
|
|
||||||
|
|
||||||
# Sort the lists alphabetically so we can handle cases like this:
|
|
||||||
# - plugins/my_plugin/plugin.json
|
|
||||||
# - plugins/my_plugin/my_module/plugin.json
|
|
||||||
# In this case, only "plugins/my_plugin" should be added via install().
|
|
||||||
set(_no_install_plugin_list ${CURA_NO_INSTALL_PLUGINS})
|
|
||||||
# Sanitize the string so the comparison will be case-insensitive.
|
|
||||||
string(STRIP "${_no_install_plugin_list}" _no_install_plugin_list)
|
|
||||||
string(TOLOWER "${_no_install_plugin_list}" _no_install_plugin_list)
|
|
||||||
|
|
||||||
# WORKAROUND counterpart of what's in cura-build.
|
|
||||||
string(REPLACE "," ";" _no_install_plugin_list "${_no_install_plugin_list}")
|
|
||||||
|
|
||||||
list(LENGTH _no_install_plugin_list _no_install_plugin_list_len)
|
|
||||||
|
|
||||||
if(_no_install_plugin_list_len GREATER 0)
|
|
||||||
list(SORT _no_install_plugin_list)
|
|
||||||
endif()
|
|
||||||
if(_plugin_json_list_len GREATER 0)
|
|
||||||
list(SORT _plugin_json_list)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Check all plugin directories and add them via install() if needed.
|
|
||||||
set(_install_plugin_list "")
|
|
||||||
foreach(_plugin_json_path ${_plugin_json_list})
|
|
||||||
get_filename_component(_plugin_dir ${_plugin_json_path} DIRECTORY)
|
|
||||||
file(RELATIVE_PATH _rel_plugin_dir ${CMAKE_CURRENT_SOURCE_DIR} ${_plugin_dir})
|
|
||||||
get_filename_component(_plugin_dir_name ${_plugin_dir} NAME)
|
|
||||||
|
|
||||||
# Make plugin name comparison case-insensitive
|
|
||||||
string(TOLOWER "${_plugin_dir_name}" _plugin_dir_name_lowercase)
|
|
||||||
|
|
||||||
# Check if this plugin needs to be skipped for installation
|
|
||||||
set(_add_plugin ON) # Indicates if this plugin should be added to the build or not.
|
|
||||||
set(_is_no_install_plugin OFF) # If this plugin will not be added, this indicates if it's because the plugin is
|
|
||||||
# specified in the NO_INSTALL_PLUGINS list.
|
|
||||||
if(_no_install_plugin_list)
|
|
||||||
if("${_plugin_dir_name_lowercase}" IN_LIST _no_install_plugin_list)
|
|
||||||
set(_add_plugin OFF)
|
|
||||||
set(_is_no_install_plugin ON)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Make sure this is not a subdirectory in a plugin that's already in the install list
|
|
||||||
if(_add_plugin)
|
|
||||||
foreach(_known_install_plugin_dir ${_install_plugin_list})
|
|
||||||
if(_plugin_dir MATCHES "${_known_install_plugin_dir}.+")
|
|
||||||
set(_add_plugin OFF)
|
|
||||||
break()
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(_add_plugin)
|
|
||||||
if(${PRINT_PLUGIN_LIST})
|
|
||||||
message(STATUS "[+] PLUGIN TO INSTALL: ${_rel_plugin_dir}")
|
|
||||||
endif()
|
|
||||||
get_filename_component(_rel_plugin_parent_dir ${_rel_plugin_dir} DIRECTORY)
|
|
||||||
install(DIRECTORY ${_rel_plugin_dir}
|
|
||||||
DESTINATION lib${LIB_SUFFIX}/cura/${_rel_plugin_parent_dir}
|
|
||||||
PATTERN "__pycache__" EXCLUDE
|
|
||||||
PATTERN "*.qmlc" EXCLUDE
|
|
||||||
)
|
|
||||||
list(APPEND _install_plugin_list ${_plugin_dir})
|
|
||||||
elseif(_is_no_install_plugin)
|
|
||||||
if(${PRINT_PLUGIN_LIST})
|
|
||||||
message(STATUS "[-] PLUGIN TO REMOVE : ${_rel_plugin_dir}")
|
|
||||||
endif()
|
|
||||||
execute_process(COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/mod_bundled_packages_json.py
|
|
||||||
-d ${CMAKE_CURRENT_SOURCE_DIR}/resources/bundled_packages
|
|
||||||
${_plugin_dir_name}
|
|
||||||
RESULT_VARIABLE _mod_json_result)
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
|
@ -1,83 +0,0 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
include(CTest)
|
|
||||||
include(CMakeParseArguments)
|
|
||||||
|
|
||||||
# FIXME: The new FindPython3 finds the system's Python3.6 rather than the Python3.5 that we built for Cura's environment.
|
|
||||||
# So we're using the old method here, with FindPythonInterp for now.
|
|
||||||
find_package(PythonInterp 3 REQUIRED)
|
|
||||||
|
|
||||||
set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE})
|
|
||||||
|
|
||||||
add_custom_target(test-verbose COMMAND ${CMAKE_CTEST_COMMAND} --verbose)
|
|
||||||
|
|
||||||
function(cura_add_test)
|
|
||||||
set(_single_args NAME DIRECTORY PYTHONPATH)
|
|
||||||
cmake_parse_arguments("" "" "${_single_args}" "" ${ARGN})
|
|
||||||
|
|
||||||
if(NOT _NAME)
|
|
||||||
message(FATAL_ERROR "cura_add_test requires a test name argument")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT _DIRECTORY)
|
|
||||||
message(FATAL_ERROR "cura_add_test requires a directory to test")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT _PYTHONPATH)
|
|
||||||
set(_PYTHONPATH ${_DIRECTORY})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
string(REPLACE "|" "\\;" _PYTHONPATH ${_PYTHONPATH})
|
|
||||||
set(_PYTHONPATH "${_PYTHONPATH}\\;$ENV{PYTHONPATH}")
|
|
||||||
else()
|
|
||||||
string(REPLACE "|" ":" _PYTHONPATH ${_PYTHONPATH})
|
|
||||||
set(_PYTHONPATH "${_PYTHONPATH}:$ENV{PYTHONPATH}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
get_test_property(${_NAME} ENVIRONMENT test_exists) #Find out if the test exists by getting a property from it that always exists (such as ENVIRONMENT because we set that ourselves).
|
|
||||||
if (NOT ${test_exists})
|
|
||||||
add_test(
|
|
||||||
NAME ${_NAME}
|
|
||||||
COMMAND ${Python3_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
|
|
||||||
)
|
|
||||||
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
|
|
||||||
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
|
|
||||||
else()
|
|
||||||
message(WARNING "Duplicate test ${_NAME}!")
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
|
|
||||||
#Add code style test.
|
|
||||||
add_test(
|
|
||||||
NAME "code-style"
|
|
||||||
COMMAND ${Python3_EXECUTABLE} run_mypy.py
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
#Add test for import statements which are not compatible with all builds
|
|
||||||
add_test(
|
|
||||||
NAME "invalid-imports"
|
|
||||||
COMMAND ${Python3_EXECUTABLE} scripts/check_invalid_imports.py
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
cura_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH "${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")
|
|
||||||
|
|
||||||
file(GLOB_RECURSE _plugins plugins/*/__init__.py)
|
|
||||||
foreach(_plugin ${_plugins})
|
|
||||||
get_filename_component(_plugin_directory ${_plugin} DIRECTORY)
|
|
||||||
if(EXISTS ${_plugin_directory}/tests)
|
|
||||||
get_filename_component(_plugin_name ${_plugin_directory} NAME)
|
|
||||||
cura_add_test(NAME pytest-${_plugin_name} DIRECTORY ${_plugin_directory} PYTHONPATH "${_plugin_directory}|${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
#Add test for whether the shortcut alt-keys are unique in every translation.
|
|
||||||
add_test(
|
|
||||||
NAME "shortcut-keys"
|
|
||||||
COMMAND ${Python3_EXECUTABLE} scripts/check_shortcut_keys.py
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
)
|
|
|
@ -1,73 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
#
|
|
||||||
# This script removes the given package entries in the bundled_packages JSON files. This is used by the PluginInstall
|
|
||||||
# CMake module.
|
|
||||||
#
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import collections
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def find_json_files(work_dir: str) -> list:
|
|
||||||
"""Finds all JSON files in the given directory recursively and returns a list of those files in absolute paths.
|
|
||||||
|
|
||||||
:param work_dir: The directory to look for JSON files recursively.
|
|
||||||
:return: A list of JSON files in absolute paths that are found in the given directory.
|
|
||||||
"""
|
|
||||||
|
|
||||||
json_file_list = []
|
|
||||||
for root, dir_names, file_names in os.walk(work_dir):
|
|
||||||
for file_name in file_names:
|
|
||||||
abs_path = os.path.abspath(os.path.join(root, file_name))
|
|
||||||
json_file_list.append(abs_path)
|
|
||||||
return json_file_list
|
|
||||||
|
|
||||||
|
|
||||||
def remove_entries_from_json_file(file_path: str, entries: list) -> None:
|
|
||||||
"""Removes the given entries from the given JSON file. The file will modified in-place.
|
|
||||||
|
|
||||||
:param file_path: The JSON file to modify.
|
|
||||||
:param entries: A list of strings as entries to remove.
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(file_path, "r", encoding = "utf-8") as f:
|
|
||||||
package_dict = json.load(f, object_hook = collections.OrderedDict)
|
|
||||||
except Exception as e:
|
|
||||||
msg = "Failed to load '{file_path}' as a JSON file. This file will be ignored Exception: {e}"\
|
|
||||||
.format(file_path = file_path, e = e)
|
|
||||||
sys.stderr.write(msg + os.linesep)
|
|
||||||
return
|
|
||||||
|
|
||||||
for entry in entries:
|
|
||||||
if entry in package_dict:
|
|
||||||
del package_dict[entry]
|
|
||||||
print("[INFO] Remove entry [{entry}] from [{file_path}]".format(file_path = file_path, entry = entry))
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(file_path, "w", encoding = "utf-8", newline = "\n") as f:
|
|
||||||
json.dump(package_dict, f, indent = 4)
|
|
||||||
except Exception as e:
|
|
||||||
msg = "Failed to write '{file_path}' as a JSON file. Exception: {e}".format(file_path = file_path, e = e)
|
|
||||||
raise IOError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
parser = argparse.ArgumentParser("mod_bundled_packages_json")
|
|
||||||
parser.add_argument("-d", "--dir", dest = "work_dir",
|
|
||||||
help = "The directory to look for bundled packages JSON files, recursively.")
|
|
||||||
parser.add_argument("entries", metavar = "ENTRIES", type = str, nargs = "+")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
json_file_list = find_json_files(args.work_dir)
|
|
||||||
for json_file_path in json_file_list:
|
|
||||||
remove_entries_from_json_file(json_file_path, args.entries)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -1,33 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Copyright 2016 Richard Hughes <richard@hughsie.com> -->
|
|
||||||
<component type="desktop">
|
|
||||||
<id>com.ultimaker.cura.desktop</id>
|
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
|
||||||
<project_license>LGPL-3.0 and CC-BY-SA-4.0</project_license>
|
|
||||||
<name>Cura</name>
|
|
||||||
<summary>The world's most advanced 3d printer software</summary>
|
|
||||||
<description>
|
|
||||||
<p>
|
|
||||||
Cura creates a seamless integration between hardware, software and
|
|
||||||
materials for the best 3D printing experience around.
|
|
||||||
Cura supports the 3MF, OBJ and STL file formats and is available on
|
|
||||||
Windows, Mac and Linux.
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>Novices can start printing right away</li>
|
|
||||||
<li>Experts are able to customize 300 settings to achieve the best results</li>
|
|
||||||
<li>Optimized profiles for Ultimaker materials</li>
|
|
||||||
<li>Supported by a global network of Ultimaker certified service partners</li>
|
|
||||||
<li>Print multiple objects at once with different settings for each object</li>
|
|
||||||
<li>Cura supports STL, 3MF and OBJ file formats</li>
|
|
||||||
<li>Open source and completely free</li>
|
|
||||||
</ul>
|
|
||||||
</description>
|
|
||||||
<screenshots>
|
|
||||||
<screenshot type="default">
|
|
||||||
<image>https://raw.githubusercontent.com/Ultimaker/Cura/master/screenshot.png</image>
|
|
||||||
</screenshot>
|
|
||||||
</screenshots>
|
|
||||||
<url type="homepage">https://ultimaker.com/software/ultimaker-cura?utm_source=cura&utm_medium=software&utm_campaign=cura-update-linux</url>
|
|
||||||
<translation type="gettext">Cura</translation>
|
|
||||||
</component>
|
|
739
conandata.yml
Normal file
739
conandata.yml
Normal file
|
@ -0,0 +1,739 @@
|
||||||
|
---
|
||||||
|
# Usage: defaults to the first entry in this conandata.yml file
|
||||||
|
# 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!
|
||||||
|
"5.3.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.2.0-beta.2":
|
||||||
|
requirements:
|
||||||
|
- "pyarcus/(latest)@ultimaker/stable"
|
||||||
|
- "curaengine/(latest)@ultimaker/stable"
|
||||||
|
- "pysavitar/(latest)@ultimaker/stable"
|
||||||
|
- "pynest2d/(latest)@ultimaker/stable"
|
||||||
|
- "uranium/(latest)@ultimaker/stable"
|
||||||
|
- "fdm_materials/(latest)@ultimaker/stable"
|
||||||
|
- "cura_binary_data/(latest)@ultimaker/stable"
|
||||||
|
- "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-beta.1":
|
||||||
|
requirements:
|
||||||
|
- "pyarcus/5.2.0-beta.1"
|
||||||
|
- "curaengine/5.2.0-beta.1"
|
||||||
|
- "pysavitar/5.2.0-beta.1"
|
||||||
|
- "pynest2d/5.2.0-beta.1"
|
||||||
|
- "uranium/5.2.0-beta.1"
|
||||||
|
- "fdm_materials/5.2.0-beta.1"
|
||||||
|
- "cura_binary_data/5.2.0-beta.1"
|
||||||
|
- "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":
|
||||||
|
requirements:
|
||||||
|
- "pyarcus/5.2.0"
|
||||||
|
- "curaengine/5.2.0"
|
||||||
|
- "pysavitar/5.2.0"
|
||||||
|
- "pynest2d/5.2.0"
|
||||||
|
- "uranium/5.2.0"
|
||||||
|
- "fdm_materials/5.2.0"
|
||||||
|
- "cura_binary_data/5.2.0"
|
||||||
|
- "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/5.2@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
|
457
conanfile.py
Normal file
457
conanfile.py
Normal file
|
@ -0,0 +1,457 @@
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from jinja2 import Template
|
||||||
|
|
||||||
|
from conan import ConanFile
|
||||||
|
from conan.tools.files import copy, rmdir, save
|
||||||
|
from conan.tools.env import VirtualRunEnv, Environment
|
||||||
|
from conan.tools.scm import Version
|
||||||
|
from conan.errors import ConanInvalidConfiguration, ConanException
|
||||||
|
|
||||||
|
required_conan_version = ">=1.50.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.7]@ultimaker/stable"
|
||||||
|
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 = 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())
|
||||||
|
|
||||||
|
# If you want a specific Cura version to show up on the splash screen add the user configuration `user.cura:version=VERSION`
|
||||||
|
# the global.conf, profile, package_info (of dependency) or via the cmd line `-c user.cura:version=VERSION`
|
||||||
|
cura_version = Version(self.conf.get("user.cura:version", default = self.version, check_type = str))
|
||||||
|
pre_tag = f"-{cura_version.pre}" if cura_version.pre else ""
|
||||||
|
build_tag = f"+{cura_version.build}" if cura_version.build else ""
|
||||||
|
internal_tag = f"+internal" if self.options.internal else ""
|
||||||
|
cura_version = f"{cura_version.major}.{cura_version.minor}.{cura_version.patch}{pre_tag}{build_tag}{internal_tag}"
|
||||||
|
|
||||||
|
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():
|
||||||
|
self.output.warning(f"Source path for binary {binary['binary']} does not exist")
|
||||||
|
continue
|
||||||
|
|
||||||
|
for bin in src_path.glob(binary["binary"] + "*[.exe|.dll|.so|.dylib|.so.]*"):
|
||||||
|
binaries.append((str(bin), binary["dst"]))
|
||||||
|
for bin in src_path.glob(binary["binary"]):
|
||||||
|
binaries.append((str(bin), binary["dst"]))
|
||||||
|
|
||||||
|
# Make sure all Conan dependencies which are shared are added to the binary list for pyinstaller
|
||||||
|
for _, dependency in self.dependencies.host.items():
|
||||||
|
for bin_paths in dependency.cpp_info.bindirs:
|
||||||
|
binaries.extend([(f"{p}", ".") for p in Path(bin_paths).glob("**/*.dll")])
|
||||||
|
for lib_paths in dependency.cpp_info.libdirs:
|
||||||
|
binaries.extend([(f"{p}", ".") for p in Path(lib_paths).glob("**/*.so*")])
|
||||||
|
binaries.extend([(f"{p}", ".") for p in Path(lib_paths).glob("**/*.dylib*")])
|
||||||
|
|
||||||
|
# Copy dynamic libs from lib path
|
||||||
|
binaries.extend([(f"{p}", ".") for p in Path(self._base_dir.joinpath("lib")).glob("**/*.dylib*")])
|
||||||
|
binaries.extend([(f"{p}", ".") for p in Path(self._base_dir.joinpath("lib")).glob("**/*.so*")])
|
||||||
|
|
||||||
|
# Collect all dll's from PyQt6 and place them in the root
|
||||||
|
binaries.extend([(f"{p}", ".") for p in Path(self._site_packages, "PyQt6", "Qt6").glob("**/*.dll")])
|
||||||
|
|
||||||
|
with open(Path(__file__).parent.joinpath("Ultimaker-Cura.spec.jinja"), "r") as f:
|
||||||
|
pyinstaller = Template(f.read())
|
||||||
|
|
||||||
|
version = self.conf_info.get("user.cura:version", default = self.version, check_type = str)
|
||||||
|
cura_version = Version(version)
|
||||||
|
|
||||||
|
with open(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"'{version}'",
|
||||||
|
short_version = f"'{cura_version.major}.{cura_version.minor}.{cura_version.patch}'",
|
||||||
|
))
|
||||||
|
|
||||||
|
def set_version(self):
|
||||||
|
if self.version is None:
|
||||||
|
self.version = self._umdefault_version()
|
||||||
|
|
||||||
|
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):
|
||||||
|
version = self.conf_info.get("user.cura:version", default = self.version, check_type = str)
|
||||||
|
if version and Version(version) <= 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)
|
||||||
|
|
||||||
|
rmdir(self, os.path.join(self.source_folder, "resources", "materials"))
|
||||||
|
self.copy("*.fdm_material", root_package = "fdm_materials", src = "@resdirs", dst = "resources/materials", keep_path = False)
|
||||||
|
self.copy("*.sig", root_package = "fdm_materials", src = "@resdirs", dst = "resources/materials", keep_path = False)
|
||||||
|
|
||||||
|
if self.options.internal:
|
||||||
|
self.copy("*.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
|
||||||
|
version = self.conf_info.get("user.cura:version", default = self.version, check_type = str)
|
||||||
|
cura_version = Version(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"
|
||||||
|
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.
|
|
@ -16,4 +16,6 @@ Making pull requests
|
||||||
--------------------
|
--------------------
|
||||||
If you want to propose a change to Cura's source code, please create a pull request in the appropriate repository (being [Cura](https://github.com/Ultimaker/Cura), [Uranium](https://github.com/Ultimaker/Uranium), [CuraEngine](https://github.com/Ultimaker/CuraEngine), [fdm_materials](https://github.com/Ultimaker/fdm_materials), [libArcus](https://github.com/Ultimaker/libArcus), [cura-build](https://github.com/Ultimaker/cura-build), [cura-build-environment](https://github.com/Ultimaker/cura-build-environment), [libSavitar](https://github.com/Ultimaker/libSavitar), [libCharon](https://github.com/Ultimaker/libCharon) or [cura-binary-data](https://github.com/Ultimaker/cura-binary-data)) and if your change requires changes on multiple of these repositories, please link them together so that we know to merge them together.
|
If you want to propose a change to Cura's source code, please create a pull request in the appropriate repository (being [Cura](https://github.com/Ultimaker/Cura), [Uranium](https://github.com/Ultimaker/Uranium), [CuraEngine](https://github.com/Ultimaker/CuraEngine), [fdm_materials](https://github.com/Ultimaker/fdm_materials), [libArcus](https://github.com/Ultimaker/libArcus), [cura-build](https://github.com/Ultimaker/cura-build), [cura-build-environment](https://github.com/Ultimaker/cura-build-environment), [libSavitar](https://github.com/Ultimaker/libSavitar), [libCharon](https://github.com/Ultimaker/libCharon) or [cura-binary-data](https://github.com/Ultimaker/cura-binary-data)) and if your change requires changes on multiple of these repositories, please link them together so that we know to merge them together.
|
||||||
|
|
||||||
Some of these repositories will have automated tests running when you create a pull request, indicated by green check marks or red crosses in the Github web page. If you see a red cross, that means that a test has failed. If the test doesn't fail on the Master branch but does fail on your branch, that indicates that you've probably made a mistake and you need to do that. Click on the cross for more details, or run the test locally by running `cmake . && ctest --verbose`.
|
The style guide for code contributions to Cura and other Ultimaker projects can be found [here](https://github.com/Ultimaker/Meta/blob/master/general/generic_code_conventions.md).
|
||||||
|
|
||||||
|
Some of these repositories will have automated tests running when you create a pull request, indicated by green check marks or red crosses in the Github web page. If you see a red cross, that means that a test has failed. If the test doesn't fail on the Master branch but does fail on your branch, that indicates that you've probably made a mistake and you need to do that. Click on the cross for more details, or run the test locally by running `cmake . && ctest --verbose`.
|
||||||
|
|
BIN
cura-logo-dark.PNG
Normal file
BIN
cura-logo-dark.PNG
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
BIN
cura-logo.PNG
BIN
cura-logo.PNG
Binary file not shown.
Before Width: | Height: | Size: 520 KiB After Width: | Height: | Size: 1 MiB |
|
@ -1,24 +1,31 @@
|
||||||
# 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
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer, Q_ENUMS
|
import json
|
||||||
from typing import Any, Optional, Dict, TYPE_CHECKING, Callable
|
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer, pyqtEnum
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
class SyncState:
|
class SyncState(enum.IntEnum):
|
||||||
"""QML: Cura.AccountSyncState"""
|
"""QML: Cura.AccountSyncState"""
|
||||||
SYNCING = 0
|
SYNCING = 0
|
||||||
SUCCESS = 1
|
SUCCESS = 1
|
||||||
|
@ -41,7 +48,7 @@ class Account(QObject):
|
||||||
|
|
||||||
# The interval in which sync services are automatically triggered
|
# The interval in which sync services are automatically triggered
|
||||||
SYNC_INTERVAL = 60.0 # seconds
|
SYNC_INTERVAL = 60.0 # seconds
|
||||||
Q_ENUMS(SyncState)
|
pyqtEnum(SyncState)
|
||||||
|
|
||||||
loginStateChanged = pyqtSignal(bool)
|
loginStateChanged = pyqtSignal(bool)
|
||||||
"""Signal emitted when user logged in or out"""
|
"""Signal emitted when user logged in or out"""
|
||||||
|
@ -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())
|
||||||
|
@ -269,10 +278,10 @@ class Account(QObject):
|
||||||
return self._authorization_service.getAccessToken()
|
return self._authorization_service.getAccessToken()
|
||||||
|
|
||||||
@pyqtProperty("QVariantMap", notify = userProfileChanged)
|
@pyqtProperty("QVariantMap", notify = userProfileChanged)
|
||||||
def userProfile(self) -> Optional[Dict[str, Optional[str]]]:
|
def userProfile(self) -> Dict[str, Optional[str]]:
|
||||||
"""None if no user is logged in otherwise the logged in user as a dict containing containing user_id, username and profile_image_url """
|
"""None if no user is logged in otherwise the logged in user as a dict containing containing user_id, username and profile_image_url """
|
||||||
if not self._user_profile:
|
if not self._user_profile:
|
||||||
return None
|
return {}
|
||||||
return self._user_profile.__dict__
|
return self._user_profile.__dict__
|
||||||
|
|
||||||
@pyqtProperty(str, notify=lastSyncDateTimeChanged)
|
@pyqtProperty(str, notify=lastSyncDateTimeChanged)
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
|
from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
from UM.TaskManagement.HttpRequestManager import HttpRequestManager
|
from UM.TaskManagement.HttpRequestManager import HttpRequestManager
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# 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
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtProperty
|
from PyQt6.QtCore import QObject, pyqtProperty
|
||||||
|
|
||||||
from cura.API.Backups import Backups
|
from cura.API.Backups import Backups
|
||||||
from cura.API.ConnectionStatus import ConnectionStatus
|
from cura.API.ConnectionStatus import ConnectionStatus
|
||||||
|
@ -34,12 +34,13 @@ class CuraAPI(QObject):
|
||||||
raise RuntimeError("Tried to create singleton '{class_name}' more than once.".format(class_name = CuraAPI.__name__))
|
raise RuntimeError("Tried to create singleton '{class_name}' more than once.".format(class_name = CuraAPI.__name__))
|
||||||
if application is None:
|
if application is None:
|
||||||
raise RuntimeError("Upon first time creation, the application must be set.")
|
raise RuntimeError("Upon first time creation, the application must be set.")
|
||||||
cls.__instance = super(CuraAPI, cls).__new__(cls)
|
instance = super(CuraAPI, cls).__new__(cls)
|
||||||
cls._application = application
|
cls._application = application
|
||||||
return cls.__instance
|
return instance
|
||||||
|
|
||||||
def __init__(self, application: Optional["CuraApplication"] = None) -> None:
|
def __init__(self, application: Optional["CuraApplication"] = None) -> None:
|
||||||
super().__init__(parent = CuraAPI._application)
|
super().__init__(parent = CuraAPI._application)
|
||||||
|
CuraAPI.__instance = self
|
||||||
|
|
||||||
self._account = Account(self._application)
|
self._account = Account(self._application)
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
# ---------
|
# ---------
|
||||||
|
@ -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 = "7.9.0"
|
CuraSDKVersion = "8.2.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
|
||||||
|
|
|
@ -21,6 +21,7 @@ class ArrangeObjectsJob(Job):
|
||||||
self._min_offset = min_offset
|
self._min_offset = min_offset
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
found_solution_for_all = False
|
||||||
status_message = Message(i18n_catalog.i18nc("@info:status", "Finding new location for objects"),
|
status_message = Message(i18n_catalog.i18nc("@info:status", "Finding new location for objects"),
|
||||||
lifetime = 0,
|
lifetime = 0,
|
||||||
dismissable = False,
|
dismissable = False,
|
||||||
|
@ -28,18 +29,19 @@ class ArrangeObjectsJob(Job):
|
||||||
title = i18n_catalog.i18nc("@info:title", "Finding Location"))
|
title = i18n_catalog.i18nc("@info:title", "Finding Location"))
|
||||||
status_message.show()
|
status_message.show()
|
||||||
|
|
||||||
found_solution_for_all = None
|
|
||||||
try:
|
try:
|
||||||
found_solution_for_all = arrange(self._nodes, Application.getInstance().getBuildVolume(), self._fixed_nodes)
|
found_solution_for_all = arrange(self._nodes, Application.getInstance().getBuildVolume(), self._fixed_nodes)
|
||||||
except: # If the thread crashes, the message should still close
|
except: # If the thread crashes, the message should still close
|
||||||
Logger.logException("e", "Unable to arrange the objects on the buildplate. The arrange algorithm has crashed.")
|
Logger.logException("e", "Unable to arrange the objects on the buildplate. The arrange algorithm has crashed.")
|
||||||
|
|
||||||
status_message.hide()
|
status_message.hide()
|
||||||
if found_solution_for_all is not None and not found_solution_for_all:
|
|
||||||
|
if not found_solution_for_all:
|
||||||
no_full_solution_message = Message(
|
no_full_solution_message = Message(
|
||||||
i18n_catalog.i18nc("@info:status",
|
i18n_catalog.i18nc("@info:status",
|
||||||
"Unable to find a location within the build volume for all objects"),
|
"Unable to find a location within the build volume for all objects"),
|
||||||
title = i18n_catalog.i18nc("@info:title", "Can't Find Location"),
|
title = i18n_catalog.i18nc("@info:title", "Can't Find Location"),
|
||||||
message_type = Message.MessageType.ERROR)
|
message_type = Message.MessageType.ERROR)
|
||||||
no_full_solution_message.show()
|
no_full_solution_message.show()
|
||||||
|
|
||||||
self.finished.emit(self)
|
self.finished.emit(self)
|
||||||
|
|
|
@ -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))
|
||||||
|
@ -159,4 +159,4 @@ class ShapeArray:
|
||||||
|
|
||||||
max_col_idx = (idxs[0] - p1[0]) / (p2[0] - p1[0]) * (p2[1] - p1[1]) + p1[1]
|
max_col_idx = (idxs[0] - p1[0]) / (p2[0] - p1[0]) * (p2[1] - p1[1]) + p1[1]
|
||||||
sign = numpy.sign(p2[0] - p1[0])
|
sign = numpy.sign(p2[0] - p1[0])
|
||||||
return idxs[1] * sign <= max_col_idx * sign
|
return idxs[1] * sign <= max_col_idx * sign
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2021 Ultimaker B.V.
|
# Copyright (c) 2021 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 PyQt5.QtCore import QTimer
|
from PyQt6.QtCore import QTimer
|
||||||
from typing import Any, TYPE_CHECKING
|
from typing import Any, TYPE_CHECKING
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -31,7 +31,7 @@ from cura.Settings.GlobalStack import GlobalStack
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
from PyQt5.QtCore import QTimer
|
from PyQt6.QtCore import QTimer
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -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
|
||||||
|
@ -818,11 +810,6 @@ class BuildVolume(SceneNode):
|
||||||
break
|
break
|
||||||
if prime_tower_collision: # Already found a collision.
|
if prime_tower_collision: # Already found a collision.
|
||||||
break
|
break
|
||||||
if self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and self._global_container_stack.getProperty("adhesion_type", "value") != "raft":
|
|
||||||
brim_size = self._calculateBedAdhesionSize(used_extruders, "brim")
|
|
||||||
# Use 2x the brim size, since we need 1x brim size distance due to the object brim and another
|
|
||||||
# times the brim due to the brim of the prime tower
|
|
||||||
prime_tower_areas[extruder_id][area_index] = prime_tower_area.getMinkowskiHull(Polygon.approximatedCircle(2 * brim_size, num_segments = 24))
|
|
||||||
if not prime_tower_collision:
|
if not prime_tower_collision:
|
||||||
result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
|
result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
|
||||||
result_areas_no_brim[extruder_id].extend(prime_tower_areas[extruder_id])
|
result_areas_no_brim[extruder_id].extend(prime_tower_areas[extruder_id])
|
||||||
|
@ -833,11 +820,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):
|
||||||
|
@ -850,9 +835,13 @@ class BuildVolume(SceneNode):
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
skirt_brim_extruder: ExtruderStack = None
|
skirt_brim_extruder: ExtruderStack = None
|
||||||
|
skirt_brim_extruder_nr = self._global_container_stack.getProperty("skirt_brim_extruder_nr", "value")
|
||||||
|
|
||||||
for extruder in used_extruders:
|
for extruder in used_extruders:
|
||||||
if int(extruder.getProperty("extruder_nr", "value")) == int(self._global_container_stack.getProperty("skirt_brim_extruder_nr", "value")):
|
if skirt_brim_extruder_nr == -1:
|
||||||
skirt_brim_extruder = extruder
|
skirt_brim_extruder = used_extruders[0] # The prime tower brim is always printed with the first extruder
|
||||||
|
elif int(extruder.getProperty("extruder_nr", "value")) == int(skirt_brim_extruder_nr):
|
||||||
|
skirt_brim_extruder = extruder
|
||||||
result[extruder.getId()] = []
|
result[extruder.getId()] = []
|
||||||
|
|
||||||
# Currently, the only normally printed object is the prime tower.
|
# Currently, the only normally printed object is the prime tower.
|
||||||
|
@ -866,15 +855,6 @@ class BuildVolume(SceneNode):
|
||||||
prime_tower_x = prime_tower_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left.
|
prime_tower_x = prime_tower_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left.
|
||||||
prime_tower_y = prime_tower_y + machine_depth / 2
|
prime_tower_y = prime_tower_y + machine_depth / 2
|
||||||
|
|
||||||
if skirt_brim_extruder is not None and self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and self._global_container_stack.getProperty("adhesion_type", "value") != "raft":
|
|
||||||
brim_size = (
|
|
||||||
skirt_brim_extruder.getProperty("brim_line_count", "value") *
|
|
||||||
skirt_brim_extruder.getProperty("skirt_brim_line_width", "value") / 100.0 *
|
|
||||||
skirt_brim_extruder.getProperty("initial_layer_line_width_factor", "value")
|
|
||||||
)
|
|
||||||
prime_tower_x -= brim_size
|
|
||||||
prime_tower_y += brim_size
|
|
||||||
|
|
||||||
radius = prime_tower_size / 2
|
radius = prime_tower_size / 2
|
||||||
prime_tower_area = Polygon.approximatedCircle(radius, num_segments = 24)
|
prime_tower_area = Polygon.approximatedCircle(radius, num_segments = 24)
|
||||||
prime_tower_area = prime_tower_area.translate(prime_tower_x - radius, prime_tower_y - radius)
|
prime_tower_area = prime_tower_area.translate(prime_tower_x - radius, prime_tower_y - radius)
|
||||||
|
@ -993,6 +973,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([
|
||||||
|
@ -1083,7 +1066,7 @@ class BuildVolume(SceneNode):
|
||||||
all_values[i] = 0
|
all_values[i] = 0
|
||||||
return all_values
|
return all_values
|
||||||
|
|
||||||
def _calculateBedAdhesionSize(self, used_extruders, adhesion_override = None):
|
def _calculateBedAdhesionSize(self, used_extruders):
|
||||||
"""Get the bed adhesion size for the global container stack and used extruders
|
"""Get the bed adhesion size for the global container stack and used extruders
|
||||||
|
|
||||||
:param adhesion_override: override adhesion type.
|
:param adhesion_override: override adhesion type.
|
||||||
|
@ -1093,52 +1076,12 @@ class BuildVolume(SceneNode):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
container_stack = self._global_container_stack
|
container_stack = self._global_container_stack
|
||||||
adhesion_type = adhesion_override
|
adhesion_type = container_stack.getProperty("adhesion_type", "value")
|
||||||
if adhesion_type is None:
|
|
||||||
adhesion_type = container_stack.getProperty("adhesion_type", "value")
|
|
||||||
|
|
||||||
# Skirt_brim_line_width is a bit of an odd one out. The primary bit of the skirt/brim is printed
|
if adhesion_type == "raft":
|
||||||
# with the adhesion extruder, but it also prints one extra line by all other extruders. As such, the
|
|
||||||
# setting does *not* have a limit_to_extruder setting (which means that we can't ask the global extruder what
|
|
||||||
# the value is.
|
|
||||||
skirt_brim_extruder_nr = self._global_container_stack.getProperty("skirt_brim_extruder_nr", "value")
|
|
||||||
try:
|
|
||||||
skirt_brim_stack = self._global_container_stack.extruderList[int(skirt_brim_extruder_nr)]
|
|
||||||
except IndexError:
|
|
||||||
Logger.warning(f"Couldn't find extruder with index '{skirt_brim_extruder_nr}', defaulting to 0 instead.")
|
|
||||||
skirt_brim_stack = self._global_container_stack.extruderList[0]
|
|
||||||
skirt_brim_line_width = skirt_brim_stack.getProperty("skirt_brim_line_width", "value")
|
|
||||||
|
|
||||||
initial_layer_line_width_factor = skirt_brim_stack.getProperty("initial_layer_line_width_factor", "value")
|
|
||||||
# Use brim width if brim is enabled OR the prime tower has a brim.
|
|
||||||
if adhesion_type == "brim":
|
|
||||||
brim_line_count = skirt_brim_stack.getProperty("brim_line_count", "value")
|
|
||||||
brim_gap = skirt_brim_stack.getProperty("brim_gap", "value")
|
|
||||||
bed_adhesion_size = brim_gap + skirt_brim_line_width * brim_line_count * initial_layer_line_width_factor / 100.0
|
|
||||||
|
|
||||||
for extruder_stack in used_extruders:
|
|
||||||
bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0
|
|
||||||
|
|
||||||
# We don't create an additional line for the extruder we're printing the brim with.
|
|
||||||
bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
|
|
||||||
elif adhesion_type == "skirt":
|
|
||||||
skirt_distance = skirt_brim_stack.getProperty("skirt_gap", "value")
|
|
||||||
skirt_line_count = skirt_brim_stack.getProperty("skirt_line_count", "value")
|
|
||||||
|
|
||||||
bed_adhesion_size = skirt_distance + (
|
|
||||||
skirt_brim_line_width * skirt_line_count) * initial_layer_line_width_factor / 100.0
|
|
||||||
|
|
||||||
for extruder_stack in used_extruders:
|
|
||||||
bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0
|
|
||||||
|
|
||||||
# We don't create an additional line for the extruder we're printing the skirt with.
|
|
||||||
bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
|
|
||||||
elif adhesion_type == "raft":
|
|
||||||
bed_adhesion_size = self._global_container_stack.getProperty("raft_margin", "value") # Should refer to the raft extruder if set.
|
bed_adhesion_size = self._global_container_stack.getProperty("raft_margin", "value") # Should refer to the raft extruder if set.
|
||||||
elif adhesion_type == "none":
|
else: # raft, brim or skirt. Those last two are handled by CuraEngine.
|
||||||
bed_adhesion_size = 0
|
bed_adhesion_size = 0
|
||||||
else:
|
|
||||||
raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?")
|
|
||||||
|
|
||||||
max_length_available = 0.5 * min(
|
max_length_available = 0.5 * min(
|
||||||
self._global_container_stack.getProperty("machine_width", "value"),
|
self._global_container_stack.getProperty("machine_width", "value"),
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QVariantAnimation, QEasingCurve
|
from PyQt6.QtCore import QVariantAnimation, QEasingCurve
|
||||||
from PyQt5.QtGui import QVector3D
|
from PyQt6.QtGui import QVector3D
|
||||||
|
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ class CameraAnimation(QVariantAnimation):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._camera_tool = None
|
self._camera_tool = None
|
||||||
self.setDuration(300)
|
self.setDuration(300)
|
||||||
self.setEasingCurve(QEasingCurve.OutQuad)
|
self.setEasingCurve(QEasingCurve.Type.OutQuad)
|
||||||
|
|
||||||
def setCameraTool(self, camera_tool):
|
def setCameraTool(self, camera_tool):
|
||||||
self._camera_tool = camera_tool
|
self._camera_tool = camera_tool
|
||||||
|
|
|
@ -20,9 +20,9 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
with_sentry_sdk = False
|
with_sentry_sdk = False
|
||||||
|
|
||||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QUrl
|
from PyQt6.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QUrl
|
||||||
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
|
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt6.QtGui import QDesktopServices
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -136,8 +136,8 @@ class CrashHandler:
|
||||||
|
|
||||||
# "backup and start clean" and "close" buttons
|
# "backup and start clean" and "close" buttons
|
||||||
buttons = QDialogButtonBox()
|
buttons = QDialogButtonBox()
|
||||||
buttons.addButton(QDialogButtonBox.Close)
|
buttons.addButton(QDialogButtonBox.StandardButton.Close)
|
||||||
buttons.addButton(catalog.i18nc("@action:button", "Backup and Reset Configuration"), QDialogButtonBox.AcceptRole)
|
buttons.addButton(catalog.i18nc("@action:button", "Backup and Reset Configuration"), QDialogButtonBox.ButtonRole.AcceptRole)
|
||||||
buttons.rejected.connect(self._closeEarlyCrashDialog)
|
buttons.rejected.connect(self._closeEarlyCrashDialog)
|
||||||
buttons.accepted.connect(self._backupAndStartClean)
|
buttons.accepted.connect(self._backupAndStartClean)
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ class CrashHandler:
|
||||||
QDesktopServices.openUrl(QUrl.fromLocalFile( path ))
|
QDesktopServices.openUrl(QUrl.fromLocalFile( path ))
|
||||||
|
|
||||||
def _showDetailedReport(self):
|
def _showDetailedReport(self):
|
||||||
self.dialog.exec_()
|
self.dialog.exec()
|
||||||
|
|
||||||
def _createDialog(self):
|
def _createDialog(self):
|
||||||
"""Creates a modal dialog."""
|
"""Creates a modal dialog."""
|
||||||
|
@ -261,7 +261,7 @@ class CrashHandler:
|
||||||
opengl_instance = OpenGL.getInstance()
|
opengl_instance = OpenGL.getInstance()
|
||||||
if not opengl_instance:
|
if not opengl_instance:
|
||||||
self.data["opengl"] = {"version": "n/a", "vendor": "n/a", "type": "n/a"}
|
self.data["opengl"] = {"version": "n/a", "vendor": "n/a", "type": "n/a"}
|
||||||
return catalog.i18nc("@label", "Not yet initialized<br/>")
|
return catalog.i18nc("@label", "Not yet initialized") + "<br />"
|
||||||
|
|
||||||
info = "<ul>"
|
info = "<ul>"
|
||||||
info += catalog.i18nc("@label OpenGL version", "<li>OpenGL Version: {version}</li>").format(version = opengl_instance.getOpenGLVersion())
|
info += catalog.i18nc("@label OpenGL version", "<li>OpenGL Version: {version}</li>").format(version = opengl_instance.getOpenGLVersion())
|
||||||
|
@ -291,6 +291,7 @@ class CrashHandler:
|
||||||
if with_sentry_sdk:
|
if with_sentry_sdk:
|
||||||
with configure_scope() as scope:
|
with configure_scope() as scope:
|
||||||
scope.set_tag("opengl_version", opengl_instance.getOpenGLVersion())
|
scope.set_tag("opengl_version", opengl_instance.getOpenGLVersion())
|
||||||
|
scope.set_tag("opengl_version_short", opengl_instance.getOpenGLVersionShort())
|
||||||
scope.set_tag("gpu_vendor", opengl_instance.getGPUVendorName())
|
scope.set_tag("gpu_vendor", opengl_instance.getGPUVendorName())
|
||||||
scope.set_tag("gpu_type", opengl_instance.getGPUType())
|
scope.set_tag("gpu_type", opengl_instance.getGPUType())
|
||||||
scope.set_tag("active_machine", active_machine_definition_id)
|
scope.set_tag("active_machine", active_machine_definition_id)
|
||||||
|
@ -409,12 +410,12 @@ class CrashHandler:
|
||||||
|
|
||||||
def _buttonsWidget(self):
|
def _buttonsWidget(self):
|
||||||
buttons = QDialogButtonBox()
|
buttons = QDialogButtonBox()
|
||||||
buttons.addButton(QDialogButtonBox.Close)
|
buttons.addButton(QDialogButtonBox.StandardButton.Close)
|
||||||
# Like above, this will be served as a separate detailed report dialog if the application has not yet been
|
# Like above, this will be served as a separate detailed report dialog if the application has not yet been
|
||||||
# fully loaded. In this case, "send report" will be a check box in the early crash dialog, so there is no
|
# fully loaded. In this case, "send report" will be a check box in the early crash dialog, so there is no
|
||||||
# need for this extra button.
|
# need for this extra button.
|
||||||
if self.has_started:
|
if self.has_started:
|
||||||
buttons.addButton(catalog.i18nc("@action:button", "Send report"), QDialogButtonBox.AcceptRole)
|
buttons.addButton(catalog.i18nc("@action:button", "Send report"), QDialogButtonBox.ButtonRole.AcceptRole)
|
||||||
buttons.accepted.connect(self._sendCrashReport)
|
buttons.accepted.connect(self._sendCrashReport)
|
||||||
buttons.rejected.connect(self.dialog.close)
|
buttons.rejected.connect(self.dialog.close)
|
||||||
|
|
||||||
|
@ -456,5 +457,5 @@ class CrashHandler:
|
||||||
def _show(self):
|
def _show(self):
|
||||||
# When the exception is in the skip_exception_types list, the dialog is not created, so we don't need to show it
|
# When the exception is in the skip_exception_types list, the dialog is not created, so we don't need to show it
|
||||||
if self.dialog:
|
if self.dialog:
|
||||||
self.dialog.exec_()
|
self.dialog.exec()
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 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 PyQt5.QtCore import QObject, QUrl
|
from PyQt6.QtCore import QObject, QUrl
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt6.QtGui import QDesktopServices
|
||||||
from typing import List, cast
|
from typing import List, cast
|
||||||
|
|
||||||
from UM.Event import CallFunctionEvent
|
from UM.Event import CallFunctionEvent
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# 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 os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -8,10 +8,10 @@ import time
|
||||||
from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict
|
from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from PyQt5.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
|
from PyQt6.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, pyqtEnum, QCoreApplication
|
||||||
from PyQt5.QtGui import QColor, QIcon
|
from PyQt6.QtGui import QColor, QIcon
|
||||||
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
from PyQt6.QtQml import qmlRegisterUncreatableType, qmlRegisterUncreatableMetaObject, qmlRegisterSingletonType, qmlRegisterType
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
import UM.Util
|
import UM.Util
|
||||||
import cura.Settings.cura_empty_instance_containers
|
import cura.Settings.cura_empty_instance_containers
|
||||||
|
@ -115,6 +115,10 @@ 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.CompatibleMachineModel import CompatibleMachineModel
|
||||||
|
from .Machines.Models.MachineListModel import MachineListModel
|
||||||
|
from .Machines.Models.ActiveIntentQualitiesModel import ActiveIntentQualitiesModel
|
||||||
|
from .Machines.Models.IntentSelectionModel import IntentSelectionModel
|
||||||
from .SingleInstance import SingleInstance
|
from .SingleInstance import SingleInstance
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -122,16 +126,15 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
numpy.seterr(all = "ignore")
|
numpy.seterr(all = "ignore")
|
||||||
|
|
||||||
|
|
||||||
class CuraApplication(QtApplication):
|
class CuraApplication(QtApplication):
|
||||||
# SettingVersion represents the set of settings available in the machine/extruder definitions.
|
# SettingVersion represents the set of settings available in the machine/extruder definitions.
|
||||||
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
|
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
|
||||||
# changes of the settings.
|
# changes of the settings.
|
||||||
SettingVersion = 19
|
SettingVersion = 20
|
||||||
|
|
||||||
Created = False
|
Created = False
|
||||||
|
|
||||||
class ResourceTypes:
|
class ResourceTypes(enum.IntEnum):
|
||||||
QmlFiles = Resources.UserType + 1
|
QmlFiles = Resources.UserType + 1
|
||||||
Firmware = Resources.UserType + 2
|
Firmware = Resources.UserType + 2
|
||||||
QualityInstanceContainer = Resources.UserType + 3
|
QualityInstanceContainer = Resources.UserType + 3
|
||||||
|
@ -145,12 +148,13 @@ class CuraApplication(QtApplication):
|
||||||
SettingVisibilityPreset = Resources.UserType + 11
|
SettingVisibilityPreset = Resources.UserType + 11
|
||||||
IntentInstanceContainer = Resources.UserType + 12
|
IntentInstanceContainer = Resources.UserType + 12
|
||||||
|
|
||||||
Q_ENUMS(ResourceTypes)
|
pyqtEnum(ResourceTypes)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
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,
|
||||||
|
@ -258,6 +262,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
from UM.CentralFileStorage import CentralFileStorage
|
from UM.CentralFileStorage import CentralFileStorage
|
||||||
CentralFileStorage.setIsEnterprise(ApplicationMetadata.IsEnterpriseVersion)
|
CentralFileStorage.setIsEnterprise(ApplicationMetadata.IsEnterpriseVersion)
|
||||||
|
Resources.setIsEnterprise(ApplicationMetadata.IsEnterpriseVersion)
|
||||||
|
|
||||||
@pyqtProperty(str, constant=True)
|
@pyqtProperty(str, constant=True)
|
||||||
def ultimakerCloudApiRootUrl(self) -> str:
|
def ultimakerCloudApiRootUrl(self) -> str:
|
||||||
|
@ -316,7 +321,7 @@ class CuraApplication(QtApplication):
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
self.__addExpectedResourceDirsAndSearchPaths() # Must be added before init of super
|
self.__addExpectedResourceDirsAndSearchPaths() # Must be added before init of super
|
||||||
|
|
||||||
super().initialize()
|
super().initialize(ApplicationMetadata.IsEnterpriseVersion)
|
||||||
|
|
||||||
self._preferences.addPreference("cura/single_instance", False)
|
self._preferences.addPreference("cura/single_instance", False)
|
||||||
self._use_single_instance = self._preferences.getValue("cura/single_instance") or self._cli_args.single_instance
|
self._use_single_instance = self._preferences.getValue("cura/single_instance") or self._cli_args.single_instance
|
||||||
|
@ -349,12 +354,18 @@ class CuraApplication(QtApplication):
|
||||||
Resources.addExpectedDirNameInData(dir_name)
|
Resources.addExpectedDirNameInData(dir_name)
|
||||||
|
|
||||||
app_root = os.path.abspath(os.path.join(os.path.dirname(sys.executable)))
|
app_root = os.path.abspath(os.path.join(os.path.dirname(sys.executable)))
|
||||||
Resources.addSearchPath(os.path.join(app_root, "share", "cura", "resources"))
|
Resources.addSecureSearchPath(os.path.join(app_root, "share", "cura", "resources"))
|
||||||
|
|
||||||
Resources.addSearchPath(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.addSearchPath(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):
|
||||||
|
@ -622,11 +633,14 @@ class CuraApplication(QtApplication):
|
||||||
# the QML code then gets `null` as the global stack and can deal with that as it deems fit.
|
# the QML code then gets `null` as the global stack and can deal with that as it deems fit.
|
||||||
self.getMachineManager().setActiveMachine(None)
|
self.getMachineManager().setActiveMachine(None)
|
||||||
|
|
||||||
|
QtApplication.getInstance().closeAllWindows()
|
||||||
|
|
||||||
main_window = self.getMainWindow()
|
main_window = self.getMainWindow()
|
||||||
if main_window is not None:
|
if main_window is not None:
|
||||||
main_window.close()
|
main_window.close()
|
||||||
else:
|
|
||||||
self.exit(0)
|
QtApplication.closeAllWindows()
|
||||||
|
QCoreApplication.quit()
|
||||||
|
|
||||||
# This function first performs all upon-exit checks such as USB printing that is in progress.
|
# This function first performs all upon-exit checks such as USB printing that is in progress.
|
||||||
# Use this to close the application.
|
# Use this to close the application.
|
||||||
|
@ -678,6 +692,22 @@ class CuraApplication(QtApplication):
|
||||||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing Active Machine..."))
|
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing Active Machine..."))
|
||||||
super().setGlobalContainerStack(stack)
|
super().setGlobalContainerStack(stack)
|
||||||
|
|
||||||
|
showMessageBox = pyqtSignal(str,str, str, str, int, int,
|
||||||
|
arguments = ["title", "text", "informativeText", "detailedText","buttons", "icon"])
|
||||||
|
"""A reusable dialogbox"""
|
||||||
|
|
||||||
|
def messageBox(self, title, text,
|
||||||
|
informativeText = "",
|
||||||
|
detailedText = "",
|
||||||
|
buttons = QMessageBox.StandardButton.Ok,
|
||||||
|
icon = QMessageBox.Icon.NoIcon,
|
||||||
|
callback = None,
|
||||||
|
callback_arguments = []
|
||||||
|
):
|
||||||
|
self._message_box_callback = callback
|
||||||
|
self._message_box_callback_arguments = callback_arguments
|
||||||
|
self.showMessageBox.emit(title, text, informativeText, detailedText, buttons, icon)
|
||||||
|
|
||||||
showDiscardOrKeepProfileChanges = pyqtSignal()
|
showDiscardOrKeepProfileChanges = pyqtSignal()
|
||||||
|
|
||||||
def discardOrKeepProfileChanges(self) -> bool:
|
def discardOrKeepProfileChanges(self) -> bool:
|
||||||
|
@ -793,6 +823,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()
|
||||||
|
@ -858,7 +894,7 @@ class CuraApplication(QtApplication):
|
||||||
self._auto_save = AutoSave(self)
|
self._auto_save = AutoSave(self)
|
||||||
self._auto_save.initialize()
|
self._auto_save.initialize()
|
||||||
|
|
||||||
self.exec_()
|
self.exec()
|
||||||
|
|
||||||
def __setUpSingleInstanceServer(self):
|
def __setUpSingleInstanceServer(self):
|
||||||
if self._use_single_instance:
|
if self._use_single_instance:
|
||||||
|
@ -924,6 +960,7 @@ class CuraApplication(QtApplication):
|
||||||
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
|
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
|
||||||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing engine..."))
|
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing engine..."))
|
||||||
self.initializeEngine()
|
self.initializeEngine()
|
||||||
|
self.getTheme().setCheckIfTrusted(ApplicationMetadata.IsEnterpriseVersion)
|
||||||
|
|
||||||
# Initialize UI state
|
# Initialize UI state
|
||||||
controller.setActiveStage("PrepareStage")
|
controller.setActiveStage("PrepareStage")
|
||||||
|
@ -1075,12 +1112,18 @@ class CuraApplication(QtApplication):
|
||||||
def event(self, event):
|
def event(self, event):
|
||||||
"""Handle Qt events"""
|
"""Handle Qt events"""
|
||||||
|
|
||||||
if event.type() == QEvent.FileOpen:
|
if event.type() == QEvent.Type.FileOpen:
|
||||||
if self._plugins_loaded:
|
if self._plugins_loaded:
|
||||||
self._openFile(event.file())
|
self._openFile(event.file())
|
||||||
else:
|
else:
|
||||||
self._open_file_queue.append(event.file())
|
self._open_file_queue.append(event.file())
|
||||||
|
|
||||||
|
if int(event.type()) == 20: # 'QEvent.Type.Quit' enum isn't there, even though it should be according to docs.
|
||||||
|
# Once we're at this point, everything should have been flushed already (past OnExitCallbackManager).
|
||||||
|
# It's more difficult to call sys.exit(0): That requires that it happens as the result of a pyqtSignal-emit.
|
||||||
|
# (See https://doc.qt.io/qt-6/qcoreapplication.html#quit)
|
||||||
|
os._exit(0)
|
||||||
|
|
||||||
return super().event(event)
|
return super().event(event)
|
||||||
|
|
||||||
def getAutoSave(self) -> Optional[AutoSave]:
|
def getAutoSave(self) -> Optional[AutoSave]:
|
||||||
|
@ -1121,16 +1164,16 @@ class CuraApplication(QtApplication):
|
||||||
engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
|
engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
|
||||||
|
|
||||||
self.processEvents()
|
self.processEvents()
|
||||||
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
qmlRegisterUncreatableMetaObject(CuraApplication.staticMetaObject, "Cura", 1, 0, "ResourceTypes", "ResourceTypes is an enum-only type")
|
||||||
|
|
||||||
self.processEvents()
|
self.processEvents()
|
||||||
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 0, "SceneController", self.getCuraSceneController)
|
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 0, self.getCuraSceneController, "SceneController")
|
||||||
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
|
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, self.getExtruderManager, "ExtruderManager")
|
||||||
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, self.getMachineManager, "MachineManager")
|
||||||
qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, "IntentManager", self.getIntentManager)
|
qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, self.getIntentManager, "IntentManager")
|
||||||
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
|
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, self.getSettingInheritanceManager, "SettingInheritanceManager")
|
||||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
|
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, self.getSimpleModeSettingsManager, "SimpleModeSettingsManager")
|
||||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, self.getMachineActionManager, "MachineActionManager")
|
||||||
|
|
||||||
self.processEvents()
|
self.processEvents()
|
||||||
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
|
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
|
||||||
|
@ -1148,24 +1191,28 @@ 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")
|
||||||
|
qmlRegisterType(CompatibleMachineModel, "Cura", 1, 0, "CompatibleMachineModel")
|
||||||
|
|
||||||
self.processEvents()
|
self.processEvents()
|
||||||
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
|
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
|
||||||
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
|
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
|
||||||
qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel")
|
qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel")
|
||||||
qmlRegisterSingletonType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel", self.getQualityManagementModel)
|
qmlRegisterSingletonType(QualityManagementModel, "Cura", 1, 0, self.getQualityManagementModel, "QualityManagementModel")
|
||||||
qmlRegisterSingletonType(MaterialManagementModel, "Cura", 1, 5, "MaterialManagementModel", self.getMaterialManagementModel)
|
qmlRegisterSingletonType(MaterialManagementModel, "Cura", 1, 5, self.getMaterialManagementModel, "MaterialManagementModel")
|
||||||
|
|
||||||
self.processEvents()
|
self.processEvents()
|
||||||
qmlRegisterType(DiscoveredPrintersModel, "Cura", 1, 0, "DiscoveredPrintersModel")
|
qmlRegisterType(DiscoveredPrintersModel, "Cura", 1, 0, "DiscoveredPrintersModel")
|
||||||
qmlRegisterType(DiscoveredCloudPrintersModel, "Cura", 1, 7, "DiscoveredCloudPrintersModel")
|
qmlRegisterType(DiscoveredCloudPrintersModel, "Cura", 1, 7, "DiscoveredCloudPrintersModel")
|
||||||
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
||||||
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
|
self.getQualityProfilesDropDownMenuModel, "QualityProfilesDropDownMenuModel")
|
||||||
qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
||||||
"CustomQualityProfilesDropDownMenuModel", self.getCustomQualityProfilesDropDownMenuModel)
|
self.getCustomQualityProfilesDropDownMenuModel, "CustomQualityProfilesDropDownMenuModel")
|
||||||
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
|
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
|
||||||
qmlRegisterType(IntentModel, "Cura", 1, 6, "IntentModel")
|
qmlRegisterType(IntentModel, "Cura", 1, 6, "IntentModel")
|
||||||
qmlRegisterType(IntentCategoryModel, "Cura", 1, 6, "IntentCategoryModel")
|
qmlRegisterType(IntentCategoryModel, "Cura", 1, 6, "IntentCategoryModel")
|
||||||
|
qmlRegisterType(IntentSelectionModel, "Cura", 1, 7, "IntentSelectionModel")
|
||||||
|
qmlRegisterType(ActiveIntentQualitiesModel, "Cura", 1, 7, "ActiveIntentQualitiesModel")
|
||||||
|
|
||||||
self.processEvents()
|
self.processEvents()
|
||||||
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
||||||
|
@ -1174,14 +1221,14 @@ class CuraApplication(QtApplication):
|
||||||
qmlRegisterType(FirstStartMachineActionsModel, "Cura", 1, 0, "FirstStartMachineActionsModel")
|
qmlRegisterType(FirstStartMachineActionsModel, "Cura", 1, 0, "FirstStartMachineActionsModel")
|
||||||
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
|
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
|
||||||
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
|
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
|
||||||
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance)
|
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, ContainerManager.getInstance, "ContainerManager")
|
||||||
qmlRegisterType(SidebarCustomMenuItemsModel, "Cura", 1, 0, "SidebarCustomMenuItemsModel")
|
qmlRegisterType(SidebarCustomMenuItemsModel, "Cura", 1, 0, "SidebarCustomMenuItemsModel")
|
||||||
|
|
||||||
qmlRegisterType(PrinterOutputDevice, "Cura", 1, 0, "PrinterOutputDevice")
|
qmlRegisterType(PrinterOutputDevice, "Cura", 1, 0, "PrinterOutputDevice")
|
||||||
|
|
||||||
from cura.API import CuraAPI
|
from cura.API import CuraAPI
|
||||||
qmlRegisterSingletonType(CuraAPI, "Cura", 1, 1, "API", self.getCuraAPI)
|
qmlRegisterSingletonType(CuraAPI, "Cura", 1, 1, self.getCuraAPI, "API")
|
||||||
qmlRegisterUncreatableType(Account, "Cura", 1, 0, "AccountSyncState", "Could not create AccountSyncState")
|
qmlRegisterUncreatableMetaObject(CuraApplication.staticMetaObject, "Cura", 1, 0, "AccountSyncState", "AccountSyncState is an enum-only type")
|
||||||
|
|
||||||
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
|
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
|
||||||
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
|
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
|
||||||
|
@ -1400,7 +1447,7 @@ class CuraApplication(QtApplication):
|
||||||
bounding_box = node.getBoundingBox()
|
bounding_box = node.getBoundingBox()
|
||||||
if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth:
|
if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth:
|
||||||
# Arrange only the unlocked nodes and keep the locked ones in place
|
# Arrange only the unlocked nodes and keep the locked ones in place
|
||||||
if UM.Util.parseBool(node.getSetting(SceneNodeSettings.LockPosition)):
|
if node.getSetting(SceneNodeSettings.LockPosition):
|
||||||
locked_nodes.append(node)
|
locked_nodes.append(node)
|
||||||
else:
|
else:
|
||||||
nodes_to_arrange.append(node)
|
nodes_to_arrange.append(node)
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 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 glob
|
||||||
|
import os
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, cast, Dict, List, Set, Tuple, TYPE_CHECKING, Optional
|
from typing import Any, cast, Dict, List, Set, Tuple, TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from cura.CuraApplication import CuraApplication # To find some resource types.
|
from cura.CuraApplication import CuraApplication # To find some resource types.
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
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")
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from UM.Qt.QtApplication import QtApplication
|
from UM.Qt.QtApplication import QtApplication
|
||||||
from PyQt5.QtCore import QObject
|
from PyQt6.QtCore import QObject
|
||||||
|
|
||||||
|
|
||||||
class CuraPackageManager(PackageManager):
|
class CuraPackageManager(PackageManager):
|
||||||
|
@ -48,9 +55,62 @@ 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()
|
||||||
|
|
||||||
|
def isMaterialBundled(self, file_name: str, guid: str):
|
||||||
|
""" Check if there is a bundled material name with file_name and guid """
|
||||||
|
for path in Resources.getSecureSearchPaths():
|
||||||
|
# Secure search paths are install directory paths, if a material is in here it must be bundled.
|
||||||
|
|
||||||
|
paths = [Path(p) for p in glob.glob(path + '/**/*.xml.fdm_material', recursive=True)]
|
||||||
|
for material in paths:
|
||||||
|
if material.name == file_name:
|
||||||
|
Logger.info(f"Found bundled material: {material.name}. Located in path: {str(material)}")
|
||||||
|
with open(material, encoding="utf-8") as f:
|
||||||
|
# Make sure the file we found has the same guid as our material
|
||||||
|
# Parsing this xml would be better but the namespace is needed to search it.
|
||||||
|
parsed_guid = PluginRegistry.getInstance().getPluginObject(
|
||||||
|
"XmlMaterialProfile").getMetadataFromSerialized(
|
||||||
|
f.read(), "GUID")
|
||||||
|
if guid == parsed_guid:
|
||||||
|
# The material we found matches both filename and GUID
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getMaterialFilePackageId(self, file_name: str, guid: str) -> str:
|
||||||
|
"""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()]:
|
||||||
|
package_id = material_package.name
|
||||||
|
|
||||||
|
for root, _, file_names in os.walk(material_package.path):
|
||||||
|
if file_name not in file_names:
|
||||||
|
# File with the name we are looking for is not in this directory
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(os.path.join(root, file_name), encoding="utf-8") as f:
|
||||||
|
# Make sure the file we found has the same guid as our material
|
||||||
|
# Parsing this xml would be better but the namespace is needed to search it.
|
||||||
|
parsed_guid = PluginRegistry.getInstance().getPluginObject("XmlMaterialProfile").getMetadataFromSerialized(
|
||||||
|
f.read(), "GUID")
|
||||||
|
|
||||||
|
if guid == parsed_guid:
|
||||||
|
return package_id
|
||||||
|
|
||||||
|
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 ""
|
||||||
|
|
||||||
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]]]:
|
||||||
"""Returns a list of where the package is used
|
"""Returns a list of where the package is used
|
||||||
|
|
||||||
|
|
|
@ -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@"
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 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 PyQt5.QtCore import pyqtProperty, QUrl
|
from PyQt6.QtCore import pyqtProperty, QUrl
|
||||||
|
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
from UM.View.View import View
|
from UM.View.View import View
|
||||||
|
|
|
@ -24,9 +24,12 @@ class LayerPolygon:
|
||||||
PrimeTowerType = 11
|
PrimeTowerType = 11
|
||||||
__number_of_types = 12
|
__number_of_types = 12
|
||||||
|
|
||||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
|
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType,
|
||||||
|
numpy.arange(__number_of_types) == MoveCombingType),
|
||||||
|
numpy.arange(__number_of_types) == MoveRetractionType)
|
||||||
|
|
||||||
def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray, line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None:
|
def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray,
|
||||||
|
line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None:
|
||||||
"""LayerPolygon, used in ProcessSlicedLayersJob
|
"""LayerPolygon, used in ProcessSlicedLayersJob
|
||||||
|
|
||||||
:param extruder: The position of the extruder
|
:param extruder: The position of the extruder
|
||||||
|
@ -39,10 +42,12 @@ class LayerPolygon:
|
||||||
|
|
||||||
self._extruder = extruder
|
self._extruder = extruder
|
||||||
self._types = line_types
|
self._types = line_types
|
||||||
for i in range(len(self._types)):
|
unknown_types = numpy.where(self._types >= self.__number_of_types, self._types, None)
|
||||||
if self._types[i] >= self.__number_of_types: # Got faulty line data from the engine.
|
if unknown_types.any():
|
||||||
Logger.log("w", "Found an unknown line type: %s", i)
|
# Got faulty line data from the engine.
|
||||||
self._types[i] = self.NoneType
|
for idx in unknown_types:
|
||||||
|
Logger.warning(f"Found an unknown line type at: {idx}")
|
||||||
|
self._types[idx] = self.NoneType
|
||||||
self._data = data
|
self._data = data
|
||||||
self._line_widths = line_widths
|
self._line_widths = line_widths
|
||||||
self._line_thicknesses = line_thicknesses
|
self._line_thicknesses = line_thicknesses
|
||||||
|
@ -58,14 +63,16 @@ class LayerPolygon:
|
||||||
self._mesh_line_count = len(self._types) - self._jump_count
|
self._mesh_line_count = len(self._types) - self._jump_count
|
||||||
self._vertex_count = self._mesh_line_count + numpy.sum(self._types[1:] == self._types[:-1])
|
self._vertex_count = self._mesh_line_count + numpy.sum(self._types[1:] == self._types[:-1])
|
||||||
|
|
||||||
# Buffering the colors shouldn't be necessary as it is not
|
# Buffering the colors shouldn't be necessary as it is not
|
||||||
# re-used and can save a lot of memory usage.
|
# re-used and can save a lot of memory usage.
|
||||||
self._color_map = LayerPolygon.getColorMap()
|
self._color_map = LayerPolygon.getColorMap()
|
||||||
self._colors = self._color_map[self._types] # type: numpy.ndarray
|
self._colors = self._color_map[self._types] # type: numpy.ndarray
|
||||||
|
|
||||||
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
|
# When type is used as index returns true if type == LayerPolygon.InfillType
|
||||||
|
# or type == LayerPolygon.SkinType
|
||||||
|
# or type == LayerPolygon.SupportInfillType
|
||||||
# Should be generated in better way, not hardcoded.
|
# Should be generated in better way, not hardcoded.
|
||||||
self._is_infill_or_skin_type_map = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype = bool)
|
self._is_infill_or_skin_type_map = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype=bool)
|
||||||
|
|
||||||
self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
|
self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
|
||||||
self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
|
self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
|
||||||
|
@ -80,12 +87,14 @@ class LayerPolygon:
|
||||||
# Only if the type of line segment changes do we need to add an extra vertex to change colors
|
# Only if the type of line segment changes do we need to add an extra vertex to change colors
|
||||||
self._build_cache_needed_points[1:, 0][:, numpy.newaxis] = self._types[1:] != self._types[:-1]
|
self._build_cache_needed_points[1:, 0][:, numpy.newaxis] = self._types[1:] != self._types[:-1]
|
||||||
# Mark points as unneeded if they are of types we don't want in the line mesh according to the calculated mask
|
# Mark points as unneeded if they are of types we don't want in the line mesh according to the calculated mask
|
||||||
numpy.logical_and(self._build_cache_needed_points, self._build_cache_line_mesh_mask, self._build_cache_needed_points )
|
numpy.logical_and(self._build_cache_needed_points, self._build_cache_line_mesh_mask, self._build_cache_needed_points)
|
||||||
|
|
||||||
self._vertex_begin = 0
|
self._vertex_begin = 0
|
||||||
self._vertex_end = cast(int, numpy.sum(self._build_cache_needed_points))
|
self._vertex_end = cast(int, numpy.sum(self._build_cache_needed_points))
|
||||||
|
|
||||||
def build(self, vertex_offset: int, index_offset: int, vertices: numpy.ndarray, colors: numpy.ndarray, line_dimensions: numpy.ndarray, feedrates: numpy.ndarray, extruders: numpy.ndarray, line_types: numpy.ndarray, indices: numpy.ndarray) -> None:
|
def build(self, vertex_offset: int, index_offset: int, vertices: numpy.ndarray,
|
||||||
|
colors: numpy.ndarray, line_dimensions: numpy.ndarray, feedrates: numpy.ndarray,
|
||||||
|
extruders: numpy.ndarray, line_types: numpy.ndarray, indices: numpy.ndarray) -> None:
|
||||||
"""Set all the arrays provided by the function caller, representing the LayerPolygon
|
"""Set all the arrays provided by the function caller, representing the LayerPolygon
|
||||||
|
|
||||||
The arrays are either by vertex or by indices.
|
The arrays are either by vertex or by indices.
|
||||||
|
@ -111,19 +120,20 @@ class LayerPolygon:
|
||||||
line_mesh_mask = self._build_cache_line_mesh_mask
|
line_mesh_mask = self._build_cache_line_mesh_mask
|
||||||
needed_points_list = self._build_cache_needed_points
|
needed_points_list = self._build_cache_needed_points
|
||||||
|
|
||||||
# Index to the points we need to represent the line mesh. This is constructed by generating simple
|
# Index to the points we need to represent the line mesh.
|
||||||
# start and end points for each line. For line segment n these are points n and n+1. Row n reads [n n+1]
|
# This is constructed by generating simple start and end points for each line.
|
||||||
# Then then the indices for the points we don't need are thrown away based on the pre-calculated list.
|
# For line segment n, these are points n and n+1. Row n reads [n n+1]
|
||||||
index_list = ( numpy.arange(len(self._types)).reshape((-1, 1)) + numpy.array([[0, 1]]) ).reshape((-1, 1))[needed_points_list.reshape((-1, 1))]
|
# Then the indices for the points we don't need are thrown away based on the pre-calculated list.
|
||||||
|
index_list = (numpy.arange(len(self._types)).reshape((-1, 1)) + numpy.array([[0, 1]])).reshape((-1, 1))[needed_points_list.reshape((-1, 1))]
|
||||||
|
|
||||||
# The relative values of begin and end indices have already been set in buildCache, so we only need to offset them to the parents offset.
|
# The relative values of begin and end indices have already been set in buildCache, so we only need to offset them to the parents offset.
|
||||||
self._vertex_begin += vertex_offset
|
self._vertex_begin += vertex_offset
|
||||||
self._vertex_end += vertex_offset
|
self._vertex_end += vertex_offset
|
||||||
|
|
||||||
# Points are picked based on the index list to get the vertices needed.
|
# Points are picked based on the index list to get the vertices needed.
|
||||||
vertices[self._vertex_begin:self._vertex_end, :] = self._data[index_list, :]
|
vertices[self._vertex_begin:self._vertex_end, :] = self._data[index_list, :]
|
||||||
|
|
||||||
# Create an array with colors for each vertex and remove the color data for the points that has been thrown away.
|
# Create an array with colors for each vertex and remove the color data for the points that has been thrown away.
|
||||||
colors[self._vertex_begin:self._vertex_end, :] = numpy.tile(self._colors, (1, 2)).reshape((-1, 4))[needed_points_list.ravel()]
|
colors[self._vertex_begin:self._vertex_end, :] = numpy.tile(self._colors, (1, 2)).reshape((-1, 4))[needed_points_list.ravel()]
|
||||||
|
|
||||||
# Create an array with line widths and thicknesses for each vertex.
|
# Create an array with line widths and thicknesses for each vertex.
|
||||||
|
@ -138,14 +148,15 @@ class LayerPolygon:
|
||||||
# Convert type per vertex to type per line
|
# Convert type per vertex to type per line
|
||||||
line_types[self._vertex_begin:self._vertex_end] = numpy.tile(self._types, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
|
line_types[self._vertex_begin:self._vertex_end] = numpy.tile(self._types, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
|
||||||
|
|
||||||
# The relative values of begin and end indices have already been set in buildCache, so we only need to offset them to the parents offset.
|
# The relative values of begin and end indices have already been set in buildCache,
|
||||||
|
# so we only need to offset them to the parents offset.
|
||||||
self._index_begin += index_offset
|
self._index_begin += index_offset
|
||||||
self._index_end += index_offset
|
self._index_end += index_offset
|
||||||
|
|
||||||
indices[self._index_begin:self._index_end, :] = numpy.arange(self._index_end-self._index_begin, dtype = numpy.int32).reshape((-1, 1))
|
indices[self._index_begin:self._index_end, :] = numpy.arange(self._index_end-self._index_begin, dtype=numpy.int32).reshape((-1, 1))
|
||||||
# When the line type changes the index needs to be increased by 2.
|
# When the line type changes the index needs to be increased by 2.
|
||||||
indices[self._index_begin:self._index_end, :] += numpy.cumsum(needed_points_list[line_mesh_mask.ravel(), 0], dtype = numpy.int32).reshape((-1, 1))
|
indices[self._index_begin:self._index_end, :] += numpy.cumsum(needed_points_list[line_mesh_mask.ravel(), 0], dtype = numpy.int32).reshape((-1, 1))
|
||||||
# Each line segment goes from it's starting point p to p+1, offset by the vertex index.
|
# Each line segment goes from it's starting point p to p+1, offset by the vertex index.
|
||||||
# The -1 is to compensate for the necessarily True value of needed_points_list[0,0] which causes an unwanted +1 in cumsum above.
|
# The -1 is to compensate for the necessarily True value of needed_points_list[0,0] which causes an unwanted +1 in cumsum above.
|
||||||
indices[self._index_begin:self._index_end, :] += numpy.array([self._vertex_begin - 1, self._vertex_begin])
|
indices[self._index_begin:self._index_end, :] += numpy.array([self._vertex_begin - 1, self._vertex_begin])
|
||||||
|
|
||||||
|
@ -214,13 +225,12 @@ class LayerPolygon:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
normals = numpy.copy(self._data)
|
normals = numpy.copy(self._data)
|
||||||
normals[:, 1] = 0.0 # We are only interested in 2D normals
|
normals[:, 1] = 0.0 # We are only interested in 2D normals
|
||||||
|
|
||||||
# Calculate the edges between points.
|
# Calculate the edges between points.
|
||||||
# The call to numpy.roll shifts the entire array by one so that
|
# The call to numpy.roll shifts the entire array by one
|
||||||
# we end up subtracting each next point from the current, wrapping
|
# so that we end up subtracting each next point from the current, wrapping around.
|
||||||
# around. This gives us the edges from the next point to the current
|
# This gives us the edges from the next point to the current point.
|
||||||
# point.
|
|
||||||
normals = numpy.diff(normals, 1, 0)
|
normals = numpy.diff(normals, 1, 0)
|
||||||
|
|
||||||
# Calculate the length of each edge using standard Pythagoras
|
# Calculate the length of each edge using standard Pythagoras
|
||||||
|
@ -245,17 +255,17 @@ class LayerPolygon:
|
||||||
if cls.__color_map is None:
|
if cls.__color_map is None:
|
||||||
theme = cast(Theme, QtApplication.getInstance().getTheme())
|
theme = cast(Theme, QtApplication.getInstance().getTheme())
|
||||||
cls.__color_map = numpy.array([
|
cls.__color_map = numpy.array([
|
||||||
theme.getColor("layerview_none").getRgbF(), # NoneType
|
theme.getColor("layerview_none").getRgbF(), # NoneType
|
||||||
theme.getColor("layerview_inset_0").getRgbF(), # Inset0Type
|
theme.getColor("layerview_inset_0").getRgbF(), # Inset0Type
|
||||||
theme.getColor("layerview_inset_x").getRgbF(), # InsetXType
|
theme.getColor("layerview_inset_x").getRgbF(), # InsetXType
|
||||||
theme.getColor("layerview_skin").getRgbF(), # SkinType
|
theme.getColor("layerview_skin").getRgbF(), # SkinType
|
||||||
theme.getColor("layerview_support").getRgbF(), # SupportType
|
theme.getColor("layerview_support").getRgbF(), # SupportType
|
||||||
theme.getColor("layerview_skirt").getRgbF(), # SkirtType
|
theme.getColor("layerview_skirt").getRgbF(), # SkirtType
|
||||||
theme.getColor("layerview_infill").getRgbF(), # InfillType
|
theme.getColor("layerview_infill").getRgbF(), # InfillType
|
||||||
theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType
|
theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType
|
||||||
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
|
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
|
||||||
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
|
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
|
||||||
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
|
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
|
||||||
theme.getColor("layerview_prime_tower").getRgbF() # PrimeTowerType
|
theme.getColor("layerview_prime_tower").getRgbF() # PrimeTowerType
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import os
|
import os
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QUrl, pyqtSlot, pyqtProperty, pyqtSignal
|
from PyQt6.QtCore import QObject, QUrl, pyqtSlot, pyqtProperty, pyqtSignal
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.PluginObject import PluginObject
|
from UM.PluginObject import PluginObject
|
||||||
|
@ -33,8 +33,11 @@ class MachineAction(QObject, PluginObject):
|
||||||
self._qml_url = ""
|
self._qml_url = ""
|
||||||
self._view = None
|
self._view = None
|
||||||
self._finished = False
|
self._finished = False
|
||||||
|
self._open_as_dialog = True
|
||||||
|
self._visible = True
|
||||||
|
|
||||||
labelChanged = pyqtSignal()
|
labelChanged = pyqtSignal()
|
||||||
|
visibilityChanged = pyqtSignal()
|
||||||
onFinished = pyqtSignal()
|
onFinished = pyqtSignal()
|
||||||
|
|
||||||
def getKey(self) -> str:
|
def getKey(self) -> str:
|
||||||
|
@ -79,6 +82,15 @@ class MachineAction(QObject, PluginObject):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def execute(self) -> None:
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
def _execute(self) -> None:
|
||||||
|
"""Protected implementation of execute."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def setFinished(self) -> None:
|
def setFinished(self) -> None:
|
||||||
self._finished = True
|
self._finished = True
|
||||||
|
@ -94,7 +106,7 @@ class MachineAction(QObject, PluginObject):
|
||||||
|
|
||||||
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
||||||
if plugin_path is None:
|
if plugin_path is None:
|
||||||
Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId())
|
Logger.error(f"Cannot create QML view: cannot find plugin path for plugin {self.getPluginId()}")
|
||||||
return None
|
return None
|
||||||
path = os.path.join(plugin_path, self._qml_url)
|
path = os.path.join(plugin_path, self._qml_url)
|
||||||
|
|
||||||
|
@ -106,7 +118,7 @@ class MachineAction(QObject, PluginObject):
|
||||||
def qmlPath(self) -> "QUrl":
|
def qmlPath(self) -> "QUrl":
|
||||||
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
||||||
if plugin_path is None:
|
if plugin_path is None:
|
||||||
Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId())
|
Logger.error(f"Cannot create QML view: cannot find plugin path for plugin {self.getPluginId()}")
|
||||||
return QUrl("")
|
return QUrl("")
|
||||||
path = os.path.join(plugin_path, self._qml_url)
|
path = os.path.join(plugin_path, self._qml_url)
|
||||||
return QUrl.fromLocalFile(path)
|
return QUrl.fromLocalFile(path)
|
||||||
|
@ -114,3 +126,30 @@ class MachineAction(QObject, PluginObject):
|
||||||
@pyqtSlot(result = QObject)
|
@pyqtSlot(result = QObject)
|
||||||
def getDisplayItem(self) -> Optional["QObject"]:
|
def getDisplayItem(self) -> Optional["QObject"]:
|
||||||
return self._createViewFromQML()
|
return self._createViewFromQML()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, constant=True)
|
||||||
|
def shouldOpenAsDialog(self) -> bool:
|
||||||
|
"""Whether this action will show a dialog.
|
||||||
|
|
||||||
|
If not, the action will directly run the function inside execute().
|
||||||
|
|
||||||
|
:return: Defaults to true to be in line with the old behaviour.
|
||||||
|
"""
|
||||||
|
return self._open_as_dialog
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def setVisible(self, visible: bool) -> None:
|
||||||
|
if self._visible != visible:
|
||||||
|
self._visible = visible
|
||||||
|
self.visibilityChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = visibilityChanged)
|
||||||
|
def visible(self) -> bool:
|
||||||
|
"""Whether this action button will be visible.
|
||||||
|
|
||||||
|
Example: Show only when isLoggedIn
|
||||||
|
|
||||||
|
:return: Defaults to true to be in line with the old behaviour.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._visible
|
|
@ -5,7 +5,7 @@ import time
|
||||||
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
|
from PyQt6.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
|
||||||
from typing import Optional, Any, Set
|
from typing import Optional, Any, Set
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -43,16 +43,14 @@ class MachineErrorChecker(QObject):
|
||||||
self._application = cura.CuraApplication.CuraApplication.getInstance()
|
self._application = cura.CuraApplication.CuraApplication.getInstance()
|
||||||
self._machine_manager = self._application.getMachineManager()
|
self._machine_manager = self._application.getMachineManager()
|
||||||
|
|
||||||
self._start_time = 0. # measure checking time
|
self._check_start_time = time.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]
|
||||||
|
|
||||||
|
self._num_keys_to_check_per_update = 10
|
||||||
|
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
self._error_check_timer.timeout.connect(self._rescheduleCheck)
|
self._error_check_timer.timeout.connect(self._rescheduleCheck)
|
||||||
|
|
||||||
|
@ -64,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)
|
||||||
|
@ -150,7 +160,7 @@ class MachineErrorChecker(QObject):
|
||||||
self._stacks_and_keys_to_check.append((stack, key))
|
self._stacks_and_keys_to_check.append((stack, key))
|
||||||
|
|
||||||
self._application.callLater(self._checkStack)
|
self._application.callLater(self._checkStack)
|
||||||
self._start_time = time.time()
|
self._check_start_time = time.time()
|
||||||
Logger.log("d", "New error check scheduled.")
|
Logger.log("d", "New error check scheduled.")
|
||||||
|
|
||||||
def _checkStack(self) -> None:
|
def _checkStack(self) -> None:
|
||||||
|
@ -162,37 +172,37 @@ class MachineErrorChecker(QObject):
|
||||||
|
|
||||||
self._check_in_progress = True
|
self._check_in_progress = True
|
||||||
|
|
||||||
# If there is nothing to check any more, it means there is no error.
|
for i in range(self._num_keys_to_check_per_update):
|
||||||
if not self._stacks_and_keys_to_check:
|
# If there is nothing to check any more, it means there is no error.
|
||||||
# Finish
|
if not self._stacks_and_keys_to_check:
|
||||||
self._setResult(False)
|
# Finish
|
||||||
return
|
self._setResult(False)
|
||||||
|
return
|
||||||
|
|
||||||
# Get the next stack and key to check
|
# Get the next stack and key to check
|
||||||
stack, key = self._stacks_and_keys_to_check.popleft()
|
stack, key = self._stacks_and_keys_to_check.popleft()
|
||||||
|
|
||||||
enabled = stack.getProperty(key, "enabled")
|
enabled = stack.getProperty(key, "enabled")
|
||||||
if not enabled:
|
if not enabled:
|
||||||
self._application.callLater(self._checkStack)
|
continue
|
||||||
return
|
|
||||||
|
|
||||||
validation_state = stack.getProperty(key, "validationState")
|
validation_state = stack.getProperty(key, "validationState")
|
||||||
if validation_state is None:
|
if validation_state is None:
|
||||||
# Setting is not validated. This can happen if there is only a setting definition.
|
# Setting is not validated. This can happen if there is only a setting definition.
|
||||||
# We do need to validate it, because a setting definitions value can be set by a function, which could
|
# We do need to validate it, because a setting definitions value can be set by a function, which could
|
||||||
# be an invalid setting.
|
# be an invalid setting.
|
||||||
definition = stack.getSettingDefinition(key)
|
definition = stack.getSettingDefinition(key)
|
||||||
validator_type = SettingDefinition.getValidatorForType(definition.type)
|
validator_type = SettingDefinition.getValidatorForType(definition.type)
|
||||||
if validator_type:
|
if validator_type:
|
||||||
validator = validator_type(key)
|
validator = validator_type(key)
|
||||||
validation_state = validator(stack)
|
validation_state = validator(stack)
|
||||||
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid):
|
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid):
|
||||||
# Since we don't know if any of the settings we didn't check is has an error value, store the list for the
|
# Since we don't know if any of the settings we didn't check is has an error value, store the list for the
|
||||||
# next check.
|
# next check.
|
||||||
keys_to_recheck = {setting_key for stack, setting_key in self._stacks_and_keys_to_check}
|
keys_to_recheck = {setting_key for stack, setting_key in self._stacks_and_keys_to_check}
|
||||||
keys_to_recheck.add(key)
|
keys_to_recheck.add(key)
|
||||||
self._setResult(True, keys_to_recheck = keys_to_recheck)
|
self._setResult(True, keys_to_recheck = keys_to_recheck)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Schedule the check for the next key
|
# Schedule the check for the next key
|
||||||
self._application.callLater(self._checkStack)
|
self._application.callLater(self._checkStack)
|
||||||
|
@ -202,12 +212,10 @@ class MachineErrorChecker(QObject):
|
||||||
self._has_errors = result
|
self._has_errors = result
|
||||||
self.hasErrorUpdated.emit()
|
self.hasErrorUpdated.emit()
|
||||||
self._machine_manager.stacksValidationChanged.emit()
|
self._machine_manager.stacksValidationChanged.emit()
|
||||||
if keys_to_recheck is None:
|
self._keys_to_check = keys_to_recheck if keys_to_recheck else set()
|
||||||
self._keys_to_check = set()
|
|
||||||
else:
|
|
||||||
self._keys_to_check = keys_to_recheck
|
|
||||||
self._need_to_check = False
|
self._need_to_check = False
|
||||||
self._check_in_progress = False
|
self._check_in_progress = False
|
||||||
self.needToWaitForResultChanged.emit()
|
self.needToWaitForResultChanged.emit()
|
||||||
self.errorCheckFinished.emit()
|
self.errorCheckFinished.emit()
|
||||||
Logger.log("i", "Error check finished, result = %s, time = %0.1fs", result, time.time() - self._start_time)
|
execution_time = time.time() - self._check_start_time
|
||||||
|
Logger.info(f"Error check finished, result = {result}, time = {execution_time:.2f}s")
|
||||||
|
|
|
@ -129,7 +129,7 @@ class MachineNode(ContainerNode):
|
||||||
if name not in groups_by_name:
|
if name not in groups_by_name:
|
||||||
# CURA-6599
|
# CURA-6599
|
||||||
# For some reason, QML will get null or fail to convert type for MachineManager.activeQualityChangesGroup() to
|
# For some reason, QML will get null or fail to convert type for MachineManager.activeQualityChangesGroup() to
|
||||||
# a QObject. Setting the object ownership to QQmlEngine.CppOwnership doesn't work, but setting the object
|
# a QObject. Setting the object ownership to QQmlEngine.ObjectOwnership.CppOwnership doesn't work, but setting the object
|
||||||
# parent to application seems to work.
|
# parent to application seems to work.
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
groups_by_name[name] = QualityChangesGroup(name, quality_type = quality_changes["quality_type"],
|
groups_by_name[name] = QualityChangesGroup(name, quality_type = quality_changes["quality_type"],
|
||||||
|
|
131
cura/Machines/Models/ActiveIntentQualitiesModel.py
Normal file
131
cura/Machines/Models/ActiveIntentQualitiesModel.py
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
# Copyright (c) 2022 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
from typing import Optional, Set, Dict, List, Any
|
||||||
|
|
||||||
|
from PyQt6.QtCore import Qt, QObject, QTimer
|
||||||
|
|
||||||
|
import cura.CuraApplication
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
from cura.Machines.ContainerTree import ContainerTree
|
||||||
|
from cura.Machines.Models.MachineModelUtils import fetchLayerHeight
|
||||||
|
from cura.Machines.MaterialNode import MaterialNode
|
||||||
|
from cura.Machines.QualityGroup import QualityGroup
|
||||||
|
from cura.Settings.IntentManager import IntentManager
|
||||||
|
|
||||||
|
|
||||||
|
class ActiveIntentQualitiesModel(ListModel):
|
||||||
|
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||||
|
DisplayTextRole = Qt.ItemDataRole.UserRole + 2
|
||||||
|
QualityTypeRole = Qt.ItemDataRole.UserRole + 3
|
||||||
|
LayerHeightRole = Qt.ItemDataRole.UserRole + 4
|
||||||
|
IntentCategeoryRole = Qt.ItemDataRole.UserRole + 5
|
||||||
|
|
||||||
|
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(self.NameRole, "name")
|
||||||
|
self.addRoleName(self.QualityTypeRole, "quality_type")
|
||||||
|
self.addRoleName(self.LayerHeightRole, "layer_height")
|
||||||
|
self.addRoleName(self.DisplayTextRole, "display_text")
|
||||||
|
self.addRoleName(self.IntentCategeoryRole, "intent_category")
|
||||||
|
|
||||||
|
self._intent_category = ""
|
||||||
|
|
||||||
|
IntentManager.intentCategoryChangedSignal.connect(self._update)
|
||||||
|
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
|
||||||
|
machine_manager.activeQualityGroupChanged.connect(self._update)
|
||||||
|
machine_manager.globalContainerChanged.connect(self._updateDelayed)
|
||||||
|
machine_manager.extruderChanged.connect(self._updateDelayed) # We also need to update if an extruder gets disabled
|
||||||
|
|
||||||
|
self._update_timer = QTimer()
|
||||||
|
self._update_timer.setInterval(100)
|
||||||
|
self._update_timer.setSingleShot(True)
|
||||||
|
self._update_timer.timeout.connect(self._update)
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _updateDelayed(self):
|
||||||
|
self._update_timer.start()
|
||||||
|
|
||||||
|
def _onChanged(self, container: ContainerStack) -> None:
|
||||||
|
if container.getMetaDataEntry("type") == "intent":
|
||||||
|
self._updateDelayed()
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
active_extruder_stack = cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeStack
|
||||||
|
if active_extruder_stack:
|
||||||
|
self._intent_category = active_extruder_stack.intent.getMetaDataEntry("intent_category", "")
|
||||||
|
|
||||||
|
new_items: List[Dict[str, Any]] = []
|
||||||
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
|
if not global_stack:
|
||||||
|
self.setItems(new_items)
|
||||||
|
return
|
||||||
|
quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
|
||||||
|
|
||||||
|
material_nodes = self._getActiveMaterials()
|
||||||
|
|
||||||
|
added_quality_type_set: Set[str] = set()
|
||||||
|
for material_node in material_nodes:
|
||||||
|
intents = self._getIntentsForMaterial(material_node, quality_groups)
|
||||||
|
for intent in intents:
|
||||||
|
if intent["quality_type"] not in added_quality_type_set:
|
||||||
|
new_items.append(intent)
|
||||||
|
added_quality_type_set.add(intent["quality_type"])
|
||||||
|
|
||||||
|
new_items = sorted(new_items, key=lambda x: x["layer_height"])
|
||||||
|
self.setItems(new_items)
|
||||||
|
|
||||||
|
def _getActiveMaterials(self) -> Set["MaterialNode"]:
|
||||||
|
"""Get the active materials for all extruders. No duplicates will be returned"""
|
||||||
|
|
||||||
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
|
if global_stack is None:
|
||||||
|
return set()
|
||||||
|
|
||||||
|
container_tree = ContainerTree.getInstance()
|
||||||
|
machine_node = container_tree.machines[global_stack.definition.getId()]
|
||||||
|
nodes: Set[MaterialNode] = set()
|
||||||
|
|
||||||
|
for extruder in global_stack.extruderList:
|
||||||
|
active_variant_name = extruder.variant.getMetaDataEntry("name")
|
||||||
|
if active_variant_name not in machine_node.variants:
|
||||||
|
Logger.log("w", "Could not find the variant %s", active_variant_name)
|
||||||
|
continue
|
||||||
|
active_variant_node = machine_node.variants[active_variant_name]
|
||||||
|
active_material_node = active_variant_node.materials.get(extruder.material.getMetaDataEntry("base_file"))
|
||||||
|
if active_material_node is None:
|
||||||
|
Logger.log("w", "Could not find the material %s", extruder.material.getMetaDataEntry("base_file"))
|
||||||
|
continue
|
||||||
|
nodes.add(active_material_node)
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
def _getIntentsForMaterial(self, active_material_node: "MaterialNode", quality_groups: Dict[str, "QualityGroup"]) -> List[Dict[str, Any]]:
|
||||||
|
extruder_intents: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
for quality_id, quality_node in active_material_node.qualities.items():
|
||||||
|
if quality_node.quality_type not in quality_groups: # Don't add the empty quality type (or anything else that would crash, defensively).
|
||||||
|
continue
|
||||||
|
quality_group = quality_groups[quality_node.quality_type]
|
||||||
|
|
||||||
|
if not quality_group.is_available:
|
||||||
|
continue
|
||||||
|
|
||||||
|
layer_height = fetchLayerHeight(quality_group)
|
||||||
|
|
||||||
|
for intent_id, intent_node in quality_node.intents.items():
|
||||||
|
if intent_node.intent_category != self._intent_category:
|
||||||
|
continue
|
||||||
|
|
||||||
|
extruder_intents.append({"name": quality_group.name,
|
||||||
|
"display_text": f"<b>{quality_group.name}</b> - {layer_height}mm",
|
||||||
|
"quality_type": quality_group.quality_type,
|
||||||
|
"layer_height": layer_height,
|
||||||
|
"intent_category": self._intent_category
|
||||||
|
})
|
||||||
|
return extruder_intents
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from typing import Dict, Set
|
from typing import Dict, Set
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, pyqtProperty
|
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -61,22 +61,22 @@ class BaseMaterialsModel(ListModel):
|
||||||
ContainerTree.getInstance().materialsChanged.connect(self._materialsListChanged)
|
ContainerTree.getInstance().materialsChanged.connect(self._materialsListChanged)
|
||||||
self._application.getMaterialManagementModel().favoritesChanged.connect(self._onChanged)
|
self._application.getMaterialManagementModel().favoritesChanged.connect(self._onChanged)
|
||||||
|
|
||||||
self.addRoleName(Qt.UserRole + 1, "root_material_id")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 1, "root_material_id")
|
||||||
self.addRoleName(Qt.UserRole + 2, "id")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 2, "id")
|
||||||
self.addRoleName(Qt.UserRole + 3, "GUID")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 3, "GUID")
|
||||||
self.addRoleName(Qt.UserRole + 4, "name")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 4, "name")
|
||||||
self.addRoleName(Qt.UserRole + 5, "brand")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 5, "brand")
|
||||||
self.addRoleName(Qt.UserRole + 6, "description")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 6, "description")
|
||||||
self.addRoleName(Qt.UserRole + 7, "material")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 7, "material")
|
||||||
self.addRoleName(Qt.UserRole + 8, "color_name")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 8, "color_name")
|
||||||
self.addRoleName(Qt.UserRole + 9, "color_code")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 9, "color_code")
|
||||||
self.addRoleName(Qt.UserRole + 10, "density")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 10, "density")
|
||||||
self.addRoleName(Qt.UserRole + 11, "diameter")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 11, "diameter")
|
||||||
self.addRoleName(Qt.UserRole + 12, "approximate_diameter")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 12, "approximate_diameter")
|
||||||
self.addRoleName(Qt.UserRole + 13, "adhesion_info")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 13, "adhesion_info")
|
||||||
self.addRoleName(Qt.UserRole + 14, "is_read_only")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 14, "is_read_only")
|
||||||
self.addRoleName(Qt.UserRole + 15, "container_node")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 15, "container_node")
|
||||||
self.addRoleName(Qt.UserRole + 16, "is_favorite")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 16, "is_favorite")
|
||||||
|
|
||||||
def _onChanged(self) -> None:
|
def _onChanged(self) -> None:
|
||||||
self._update_timer.start()
|
self._update_timer.start()
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
# 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 PyQt5.QtCore import Qt
|
from PyQt6.QtCore import Qt
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
|
||||||
class BuildPlateModel(ListModel):
|
class BuildPlateModel(ListModel):
|
||||||
NameRole = Qt.UserRole + 1
|
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||||
ContainerNodeRole = Qt.UserRole + 2
|
ContainerNodeRole = Qt.ItemDataRole.UserRole + 2
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
81
cura/Machines/Models/CompatibleMachineModel.py
Normal file
81
cura/Machines/Models/CompatibleMachineModel.py
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
# Copyright (c) 2022 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from PyQt6.QtCore import Qt, QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||||
|
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
|
||||||
|
|
||||||
|
class CompatibleMachineModel(ListModel):
|
||||||
|
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||||
|
UniqueIdRole = Qt.ItemDataRole.UserRole + 2
|
||||||
|
ExtrudersRole = Qt.ItemDataRole.UserRole + 3
|
||||||
|
|
||||||
|
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self._catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
self.addRoleName(self.NameRole, "name")
|
||||||
|
self.addRoleName(self.UniqueIdRole, "unique_id")
|
||||||
|
self.addRoleName(self.ExtrudersRole, "extruders")
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||||
|
machine_manager.globalContainerChanged.connect(self._update)
|
||||||
|
machine_manager.outputDevicesChanged.connect(self._update)
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def forceUpdate(self):
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _update(self) -> None:
|
||||||
|
self.clear()
|
||||||
|
|
||||||
|
def _makeMaterial(brand, name, color):
|
||||||
|
if name.lower() in ["", "empty"]:
|
||||||
|
return {"brand": "", "name": "(empty)", "hexcolor": "#ffffff"}
|
||||||
|
else:
|
||||||
|
return {"brand": brand, "name": name, "hexcolor": color}
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||||
|
|
||||||
|
# Loop over the output-devices, not the stacks; need all applicable configurations, not just the current loaded one.
|
||||||
|
for output_device in machine_manager.printerOutputDevices:
|
||||||
|
for printer in output_device.printers:
|
||||||
|
extruder_configs = dict()
|
||||||
|
|
||||||
|
# initialize & add current active material:
|
||||||
|
for extruder in printer.extruders:
|
||||||
|
materials = [_makeMaterial(
|
||||||
|
extruder.activeMaterial.brand, extruder.activeMaterial.name, extruder.activeMaterial.color)]
|
||||||
|
extruder_configs[extruder.getPosition()] = {
|
||||||
|
"position": extruder.getPosition(),
|
||||||
|
"core": extruder.hotendID,
|
||||||
|
"materials": materials
|
||||||
|
}
|
||||||
|
|
||||||
|
# add currently inactive, but possible materials:
|
||||||
|
for configuration in printer.availableConfigurations:
|
||||||
|
for extruder in configuration.extruderConfigurations:
|
||||||
|
if not extruder.position in extruder_configs:
|
||||||
|
Logger.log("w", f"No active extruder for position {extruder.position}.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
entry = _makeMaterial(extruder.material.brand, extruder.material.name, extruder.material.color)
|
||||||
|
if entry not in extruder_configs[extruder.position]["materials"]:
|
||||||
|
extruder_configs[extruder.position]["materials"].append(entry)
|
||||||
|
|
||||||
|
if any([len(extruder["materials"]) > 0 for extruder in extruder_configs.values()]):
|
||||||
|
self.appendItem({
|
||||||
|
"name": printer.name,
|
||||||
|
"unique_id": printer.name, # <- Can assume the cloud doesn't have duplicate names?
|
||||||
|
"extruders": list(extruder_configs.values())
|
||||||
|
})
|
|
@ -10,7 +10,7 @@ from cura.Machines.ContainerTree import ContainerTree
|
||||||
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
|
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from PyQt5.QtCore import QObject
|
from PyQt6.QtCore import QObject
|
||||||
from UM.Settings.Interfaces import ContainerInterface
|
from UM.Settings.Interfaces import ContainerInterface
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from typing import Optional, TYPE_CHECKING, List, Dict
|
from typing import Optional, TYPE_CHECKING, List, Dict
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSlot, Qt, pyqtSignal, pyqtProperty
|
from PyQt6.QtCore import QObject, pyqtSlot, Qt, pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
@ -12,10 +12,10 @@ class DiscoveredCloudPrintersModel(ListModel):
|
||||||
"""Model used to inform the application about newly added cloud printers, which are discovered from the user's
|
"""Model used to inform the application about newly added cloud printers, which are discovered from the user's
|
||||||
account """
|
account """
|
||||||
|
|
||||||
DeviceKeyRole = Qt.UserRole + 1
|
DeviceKeyRole = Qt.ItemDataRole.UserRole + 1
|
||||||
DeviceNameRole = Qt.UserRole + 2
|
DeviceNameRole = Qt.ItemDataRole.UserRole + 2
|
||||||
DeviceTypeRole = Qt.UserRole + 3
|
DeviceTypeRole = Qt.ItemDataRole.UserRole + 3
|
||||||
DeviceFirmwareVersionRole = Qt.UserRole + 4
|
DeviceFirmwareVersionRole = Qt.ItemDataRole.UserRole + 4
|
||||||
|
|
||||||
cloudPrintersDetectedChanged = pyqtSignal(bool)
|
cloudPrintersDetectedChanged = pyqtSignal(bool)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from typing import Callable, Dict, List, Optional, TYPE_CHECKING
|
from typing import Callable, Dict, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot, pyqtProperty, pyqtSignal, QObject, QTimer
|
from PyQt6.QtCore import pyqtSlot, pyqtProperty, pyqtSignal, QObject, QTimer
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 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 PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer
|
from PyQt6.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer
|
||||||
from typing import Iterable, TYPE_CHECKING
|
from typing import Iterable, TYPE_CHECKING
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
@ -23,43 +23,43 @@ class ExtrudersModel(ListModel):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The ID of the container stack for the extruder.
|
# The ID of the container stack for the extruder.
|
||||||
IdRole = Qt.UserRole + 1
|
IdRole = Qt.ItemDataRole.UserRole + 1
|
||||||
|
|
||||||
NameRole = Qt.UserRole + 2
|
NameRole = Qt.ItemDataRole.UserRole + 2
|
||||||
"""Human-readable name of the extruder."""
|
"""Human-readable name of the extruder."""
|
||||||
|
|
||||||
ColorRole = Qt.UserRole + 3
|
ColorRole = Qt.ItemDataRole.UserRole + 3
|
||||||
"""Colour of the material loaded in the extruder."""
|
"""Colour of the material loaded in the extruder."""
|
||||||
|
|
||||||
IndexRole = Qt.UserRole + 4
|
IndexRole = Qt.ItemDataRole.UserRole + 4
|
||||||
"""Index of the extruder, which is also the value of the setting itself.
|
"""Index of the extruder, which is also the value of the setting itself.
|
||||||
|
|
||||||
An index of 0 indicates the first extruder, an index of 1 the second one, and so on. This is the value that will
|
An index of 0 indicates the first extruder, an index of 1 the second one, and so on. This is the value that will
|
||||||
be saved in instance containers. """
|
be saved in instance containers. """
|
||||||
|
|
||||||
# The ID of the definition of the extruder.
|
# The ID of the definition of the extruder.
|
||||||
DefinitionRole = Qt.UserRole + 5
|
DefinitionRole = Qt.ItemDataRole.UserRole + 5
|
||||||
|
|
||||||
# The material of the extruder.
|
# The material of the extruder.
|
||||||
MaterialRole = Qt.UserRole + 6
|
MaterialRole = Qt.ItemDataRole.UserRole + 6
|
||||||
|
|
||||||
# The variant of the extruder.
|
# The variant of the extruder.
|
||||||
VariantRole = Qt.UserRole + 7
|
VariantRole = Qt.ItemDataRole.UserRole + 7
|
||||||
StackRole = Qt.UserRole + 8
|
StackRole = Qt.ItemDataRole.UserRole + 8
|
||||||
|
|
||||||
MaterialBrandRole = Qt.UserRole + 9
|
MaterialBrandRole = Qt.ItemDataRole.UserRole + 9
|
||||||
ColorNameRole = Qt.UserRole + 10
|
ColorNameRole = Qt.ItemDataRole.UserRole + 10
|
||||||
|
|
||||||
EnabledRole = Qt.UserRole + 11
|
EnabledRole = Qt.ItemDataRole.UserRole + 11
|
||||||
"""Is the extruder enabled?"""
|
"""Is the extruder enabled?"""
|
||||||
|
|
||||||
MaterialTypeRole = Qt.UserRole + 12
|
MaterialTypeRole = Qt.ItemDataRole.UserRole + 12
|
||||||
"""The type of the material (e.g. PLA, ABS, PETG, etc.)."""
|
"""The type of the material (e.g. PLA, ABS, PETG, etc.)."""
|
||||||
|
|
||||||
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
|
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
|
||||||
"""List of colours to display if there is no material or the material has no known colour. """
|
"""List of colours to display if there is no material or the material has no known colour. """
|
||||||
|
|
||||||
MaterialNameRole = Qt.UserRole + 13
|
MaterialNameRole = Qt.ItemDataRole.UserRole + 13
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
"""Initialises the extruders model, defining the roles and listening for changes in the data.
|
"""Initialises the extruders model, defining the roles and listening for changes in the data.
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from typing import Optional, Dict, Any, TYPE_CHECKING
|
from typing import Optional, Dict, Any, TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
|
from PyQt6.QtCore import QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@ class FirstStartMachineActionsModel(ListModel):
|
||||||
- action : the MachineAction object itself
|
- action : the MachineAction object itself
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TitleRole = Qt.UserRole + 1
|
TitleRole = Qt.ItemDataRole.UserRole + 1
|
||||||
ContentRole = Qt.UserRole + 2
|
ContentRole = Qt.ItemDataRole.UserRole + 2
|
||||||
ActionRole = Qt.UserRole + 3
|
ActionRole = Qt.ItemDataRole.UserRole + 3
|
||||||
|
|
||||||
def __init__(self, application: "CuraApplication", parent: Optional[QObject] = None) -> None:
|
def __init__(self, application: "CuraApplication", parent: Optional[QObject] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2021 Ultimaker B.V.
|
# Copyright (c) 2021 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 PyQt5.QtCore import Qt, QTimer, pyqtProperty, pyqtSignal
|
from PyQt6.QtCore import Qt, QTimer, pyqtProperty, pyqtSignal
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
@ -15,14 +15,14 @@ from cura.UltimakerCloud.UltimakerCloudConstants import META_CAPABILITIES # To
|
||||||
|
|
||||||
|
|
||||||
class GlobalStacksModel(ListModel):
|
class GlobalStacksModel(ListModel):
|
||||||
NameRole = Qt.UserRole + 1
|
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||||
IdRole = Qt.UserRole + 2
|
IdRole = Qt.ItemDataRole.UserRole + 2
|
||||||
HasRemoteConnectionRole = Qt.UserRole + 3
|
HasRemoteConnectionRole = Qt.ItemDataRole.UserRole + 3
|
||||||
ConnectionTypeRole = Qt.UserRole + 4
|
ConnectionTypeRole = Qt.ItemDataRole.UserRole + 4
|
||||||
MetaDataRole = Qt.UserRole + 5
|
MetaDataRole = Qt.ItemDataRole.UserRole + 5
|
||||||
DiscoverySourceRole = Qt.UserRole + 6 # For separating local and remote printers in the machine management page
|
DiscoverySourceRole = Qt.ItemDataRole.UserRole + 6 # For separating local and remote printers in the machine management page
|
||||||
RemovalWarningRole = Qt.UserRole + 7
|
RemovalWarningRole = Qt.ItemDataRole.UserRole + 7
|
||||||
IsOnlineRole = Qt.UserRole + 8
|
IsOnlineRole = Qt.ItemDataRole.UserRole + 8
|
||||||
|
|
||||||
def __init__(self, parent = None) -> None:
|
def __init__(self, parent = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
@ -44,6 +44,7 @@ class GlobalStacksModel(ListModel):
|
||||||
self._filter_connection_type = None # type: Optional[ConnectionType]
|
self._filter_connection_type = None # type: Optional[ConnectionType]
|
||||||
self._filter_online_only = False
|
self._filter_online_only = False
|
||||||
self._filter_capabilities: List[str] = [] # Required capabilities that all listed printers must have.
|
self._filter_capabilities: List[str] = [] # Required capabilities that all listed printers must have.
|
||||||
|
self._filter_abstract_machines: Optional[bool] = None
|
||||||
|
|
||||||
# Listen to changes
|
# Listen to changes
|
||||||
CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
|
CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
|
||||||
|
@ -54,6 +55,7 @@ class GlobalStacksModel(ListModel):
|
||||||
filterConnectionTypeChanged = pyqtSignal()
|
filterConnectionTypeChanged = pyqtSignal()
|
||||||
filterCapabilitiesChanged = pyqtSignal()
|
filterCapabilitiesChanged = pyqtSignal()
|
||||||
filterOnlineOnlyChanged = pyqtSignal()
|
filterOnlineOnlyChanged = pyqtSignal()
|
||||||
|
filterAbstractMachinesChanged = pyqtSignal()
|
||||||
|
|
||||||
def setFilterConnectionType(self, new_filter: Optional[ConnectionType]) -> None:
|
def setFilterConnectionType(self, new_filter: Optional[ConnectionType]) -> None:
|
||||||
if self._filter_connection_type != new_filter:
|
if self._filter_connection_type != new_filter:
|
||||||
|
@ -98,6 +100,22 @@ class GlobalStacksModel(ListModel):
|
||||||
"""
|
"""
|
||||||
return self._filter_capabilities
|
return self._filter_capabilities
|
||||||
|
|
||||||
|
def setFilterAbstractMachines(self, new_filter: Optional[bool]) -> None:
|
||||||
|
if self._filter_abstract_machines != new_filter:
|
||||||
|
self._filter_abstract_machines = new_filter
|
||||||
|
self.filterAbstractMachinesChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, fset = setFilterAbstractMachines, notify = filterAbstractMachinesChanged)
|
||||||
|
def filterAbstractMachines(self) -> Optional[bool]:
|
||||||
|
"""
|
||||||
|
Weather we include abstract printers, non-abstract printers or both
|
||||||
|
|
||||||
|
if this is set to None both abstract and non-abstract printers will be included in the list
|
||||||
|
set to True will only include abstract printers
|
||||||
|
set to False will only inclde non-abstract printers
|
||||||
|
"""
|
||||||
|
return self._filter_abstract_machines
|
||||||
|
|
||||||
def _onContainerChanged(self, container) -> None:
|
def _onContainerChanged(self, container) -> None:
|
||||||
"""Handler for container added/removed events from registry"""
|
"""Handler for container added/removed events from registry"""
|
||||||
|
|
||||||
|
@ -130,12 +148,16 @@ class GlobalStacksModel(ListModel):
|
||||||
if self._filter_online_only and not is_online:
|
if self._filter_online_only and not is_online:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
is_abstract_machine = parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False))
|
||||||
|
if self._filter_abstract_machines is not None and self._filter_abstract_machines is not is_abstract_machine:
|
||||||
|
continue
|
||||||
|
|
||||||
capabilities = set(container_stack.getMetaDataEntry(META_CAPABILITIES, "").split(","))
|
capabilities = set(container_stack.getMetaDataEntry(META_CAPABILITIES, "").split(","))
|
||||||
if set(self._filter_capabilities) - capabilities: # Not all required capabilities are met.
|
if set(self._filter_capabilities) - capabilities: # Not all required capabilities are met.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
device_name = container_stack.getMetaDataEntry("group_name", container_stack.getName())
|
device_name = container_stack.getMetaDataEntry("group_name", container_stack.getName())
|
||||||
section_name = "Connected printers" if has_remote_connection else "Preset printers"
|
section_name = self._catalog.i18nc("@label", "Connected printers") if has_remote_connection else self._catalog.i18nc("@label", "Preset printers")
|
||||||
section_name = self._catalog.i18nc("@info:title", section_name)
|
section_name = self._catalog.i18nc("@info:title", section_name)
|
||||||
|
|
||||||
default_removal_warning = self._catalog.i18nc(
|
default_removal_warning = self._catalog.i18nc(
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
#Cura is released under the terms of the LGPLv3 or higher.
|
#Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
from PyQt5.QtCore import Qt, QTimer
|
from PyQt6.QtCore import Qt, QTimer
|
||||||
from typing import TYPE_CHECKING, Optional, Dict
|
from typing import TYPE_CHECKING, Optional, Dict
|
||||||
|
|
||||||
from cura.Machines.Models.IntentModel import IntentModel
|
from cura.Machines.Models.IntentModel import IntentModel
|
||||||
from cura.Settings.IntentManager import IntentManager
|
from cura.Settings.IntentManager import IntentManager
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes.
|
from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes.
|
||||||
from PyQt5.QtCore import pyqtSignal
|
from PyQt6.QtCore import pyqtSignal
|
||||||
import cura.CuraApplication
|
import cura.CuraApplication
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from UM.Settings.ContainerRegistry import ContainerInterface
|
from UM.Settings.ContainerRegistry import ContainerInterface
|
||||||
|
@ -21,11 +21,11 @@ catalog = i18nCatalog("cura")
|
||||||
class IntentCategoryModel(ListModel):
|
class IntentCategoryModel(ListModel):
|
||||||
"""Lists the intent categories that are available for the current printer configuration. """
|
"""Lists the intent categories that are available for the current printer configuration. """
|
||||||
|
|
||||||
NameRole = Qt.UserRole + 1
|
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||||
IntentCategoryRole = Qt.UserRole + 2
|
IntentCategoryRole = Qt.ItemDataRole.UserRole + 2
|
||||||
WeightRole = Qt.UserRole + 3
|
WeightRole = Qt.ItemDataRole.UserRole + 3
|
||||||
QualitiesRole = Qt.UserRole + 4
|
QualitiesRole = Qt.ItemDataRole.UserRole + 4
|
||||||
DescriptionRole = Qt.UserRole + 5
|
DescriptionRole = Qt.ItemDataRole.UserRole + 5
|
||||||
|
|
||||||
modelUpdated = pyqtSignal()
|
modelUpdated = pyqtSignal()
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ class IntentCategoryModel(ListModel):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
weight = 99
|
weight = 99
|
||||||
result.append({
|
result.append({
|
||||||
"name": IntentCategoryModel.translation(category, "name", category),
|
"name": IntentCategoryModel.translation(category, "name", category.title()),
|
||||||
"description": IntentCategoryModel.translation(category, "description", None),
|
"description": IntentCategoryModel.translation(category, "description", None),
|
||||||
"intent_category": category,
|
"intent_category": category,
|
||||||
"weight": weight,
|
"weight": weight,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# 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, Dict, Any, Set, List
|
from typing import Optional, Dict, Any, Set, List
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal, QTimer
|
from PyQt6.QtCore import Qt, QObject, pyqtProperty, pyqtSignal, QTimer
|
||||||
|
|
||||||
import cura.CuraApplication
|
import cura.CuraApplication
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
@ -15,11 +15,11 @@ from cura.Machines.QualityGroup import QualityGroup
|
||||||
|
|
||||||
|
|
||||||
class IntentModel(ListModel):
|
class IntentModel(ListModel):
|
||||||
NameRole = Qt.UserRole + 1
|
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||||
QualityTypeRole = Qt.UserRole + 2
|
QualityTypeRole = Qt.ItemDataRole.UserRole + 2
|
||||||
LayerHeightRole = Qt.UserRole + 3
|
LayerHeightRole = Qt.ItemDataRole.UserRole + 3
|
||||||
AvailableRole = Qt.UserRole + 4
|
AvailableRole = Qt.ItemDataRole.UserRole + 4
|
||||||
IntentRole = Qt.UserRole + 5
|
IntentRole = Qt.ItemDataRole.UserRole + 5
|
||||||
|
|
||||||
def __init__(self, parent: Optional[QObject] = None) -> None:
|
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
129
cura/Machines/Models/IntentSelectionModel.py
Normal file
129
cura/Machines/Models/IntentSelectionModel.py
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
# Copyright (c) 2022 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
import collections
|
||||||
|
from typing import OrderedDict, Optional
|
||||||
|
|
||||||
|
from PyQt6.QtCore import Qt, QTimer, QObject
|
||||||
|
|
||||||
|
import cura
|
||||||
|
from UM import i18nCatalog
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
from UM.Settings.Interfaces import ContainerInterface
|
||||||
|
from cura.Settings.IntentManager import IntentManager
|
||||||
|
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
|
class IntentSelectionModel(ListModel):
|
||||||
|
|
||||||
|
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||||
|
IntentCategoryRole = Qt.ItemDataRole.UserRole + 2
|
||||||
|
WeightRole = Qt.ItemDataRole.UserRole + 3
|
||||||
|
DescriptionRole = Qt.ItemDataRole.UserRole + 4
|
||||||
|
IconRole = Qt.ItemDataRole.UserRole + 5
|
||||||
|
|
||||||
|
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(self.NameRole, "name")
|
||||||
|
self.addRoleName(self.IntentCategoryRole, "intent_category")
|
||||||
|
self.addRoleName(self.WeightRole, "weight")
|
||||||
|
self.addRoleName(self.DescriptionRole, "description")
|
||||||
|
self.addRoleName(self.IconRole, "icon")
|
||||||
|
|
||||||
|
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||||
|
|
||||||
|
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange)
|
||||||
|
ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange)
|
||||||
|
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
|
||||||
|
machine_manager.activeMaterialChanged.connect(self._update)
|
||||||
|
machine_manager.activeVariantChanged.connect(self._update)
|
||||||
|
machine_manager.extruderChanged.connect(self._update)
|
||||||
|
|
||||||
|
extruder_manager = application.getExtruderManager()
|
||||||
|
extruder_manager.extrudersChanged.connect(self._update)
|
||||||
|
|
||||||
|
self._update_timer: QTimer = QTimer()
|
||||||
|
self._update_timer.setInterval(100)
|
||||||
|
self._update_timer.setSingleShot(True)
|
||||||
|
self._update_timer.timeout.connect(self._update)
|
||||||
|
|
||||||
|
self._onChange()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _getDefaultProfileInformation() -> OrderedDict[str, dict]:
|
||||||
|
""" Default information user-visible string. Ordered by weight. """
|
||||||
|
default_profile_information = collections.OrderedDict()
|
||||||
|
default_profile_information["default"] = {
|
||||||
|
"name": catalog.i18nc("@label", "Default"),
|
||||||
|
"icon": "GearCheck"
|
||||||
|
}
|
||||||
|
default_profile_information["visual"] = {
|
||||||
|
"name": catalog.i18nc("@label", "Visual"),
|
||||||
|
"description": catalog.i18nc("@text", "The visual profile is designed to print visual prototypes and models with the intent of high visual and surface quality."),
|
||||||
|
"icon" : "Visual"
|
||||||
|
}
|
||||||
|
default_profile_information["engineering"] = {
|
||||||
|
"name": catalog.i18nc("@label", "Engineering"),
|
||||||
|
"description": catalog.i18nc("@text", "The engineering profile is designed to print functional prototypes and end-use parts with the intent of better accuracy and for closer tolerances."),
|
||||||
|
"icon": "Nut"
|
||||||
|
}
|
||||||
|
default_profile_information["quick"] = {
|
||||||
|
"name": catalog.i18nc("@label", "Draft"),
|
||||||
|
"description": catalog.i18nc("@text", "The draft profile is designed to print initial prototypes and concept validation with the intent of significant print time reduction."),
|
||||||
|
"icon": "SpeedOMeter"
|
||||||
|
}
|
||||||
|
return default_profile_information
|
||||||
|
|
||||||
|
def _onContainerChange(self, container: ContainerInterface) -> None:
|
||||||
|
"""Updates the list of intents if an intent profile was added or removed."""
|
||||||
|
|
||||||
|
if container.getMetaDataEntry("type") == "intent":
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _onChange(self) -> None:
|
||||||
|
self._update_timer.start()
|
||||||
|
|
||||||
|
def _update(self) -> None:
|
||||||
|
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
||||||
|
|
||||||
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
|
if global_stack is None:
|
||||||
|
self.setItems([])
|
||||||
|
Logger.log("d", "No active GlobalStack, set quality profile model as empty.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check for material compatibility
|
||||||
|
if not cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeMaterialsCompatible():
|
||||||
|
Logger.log("d", "No active material compatibility, set quality profile model as empty.")
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
default_profile_info = self._getDefaultProfileInformation()
|
||||||
|
|
||||||
|
available_categories = IntentManager.getInstance().currentAvailableIntentCategories()
|
||||||
|
result = []
|
||||||
|
for i, category in enumerate(available_categories):
|
||||||
|
profile_info = default_profile_info.get(category, {})
|
||||||
|
|
||||||
|
try:
|
||||||
|
weight = list(default_profile_info.keys()).index(category)
|
||||||
|
except ValueError:
|
||||||
|
weight = len(available_categories) + i
|
||||||
|
|
||||||
|
result.append({
|
||||||
|
"name": profile_info.get("name", category.title()),
|
||||||
|
"description": profile_info.get("description", None),
|
||||||
|
"icon" : profile_info.get("icon", ""),
|
||||||
|
"intent_category": category,
|
||||||
|
"weight": weight,
|
||||||
|
})
|
||||||
|
|
||||||
|
result.sort(key=lambda k: k["weight"])
|
||||||
|
|
||||||
|
self.setItems(result)
|
||||||
|
|
||||||
|
|
144
cura/Machines/Models/MachineListModel.py
Normal file
144
cura/Machines/Models/MachineListModel.py
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
# 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 typing import Optional
|
||||||
|
|
||||||
|
from PyQt6.QtCore import Qt, QTimer, QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||||
|
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
from UM.Settings.Interfaces import ContainerInterface
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
from UM.Util import parseBool
|
||||||
|
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
|
||||||
|
|
||||||
|
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
|
||||||
|
IsAbstractMachineRole = Qt.ItemDataRole.UserRole + 7
|
||||||
|
ComponentTypeRole = Qt.ItemDataRole.UserRole + 8
|
||||||
|
|
||||||
|
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self._show_cloud_printers = False
|
||||||
|
|
||||||
|
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.IsAbstractMachineRole, "isAbstractMachine")
|
||||||
|
self.addRoleName(self.ComponentTypeRole, "componentType")
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
showCloudPrintersChanged = pyqtSignal(bool)
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=showCloudPrintersChanged)
|
||||||
|
def showCloudPrinters(self) -> bool:
|
||||||
|
return self._show_cloud_printers
|
||||||
|
|
||||||
|
@pyqtSlot(bool)
|
||||||
|
def setShowCloudPrinters(self, show_cloud_printers: bool) -> None:
|
||||||
|
self._show_cloud_printers = show_cloud_printers
|
||||||
|
self._updateDelayed()
|
||||||
|
self.showCloudPrintersChanged.emit(show_cloud_printers)
|
||||||
|
|
||||||
|
def _onContainerChanged(self, container: ContainerInterface) -> 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.clear()
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
machines_manager = CuraApplication.getInstance().getMachineManager()
|
||||||
|
|
||||||
|
other_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type="machine")
|
||||||
|
other_machine_stacks.sort(key = lambda machine: machine.getName().upper())
|
||||||
|
|
||||||
|
abstract_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(is_abstract_machine = "True")
|
||||||
|
abstract_machine_stacks.sort(key = lambda machine: machine.getName().upper(), reverse = True)
|
||||||
|
for abstract_machine in abstract_machine_stacks:
|
||||||
|
definition_id = abstract_machine.definition.getId()
|
||||||
|
online_machine_stacks = machines_manager.getMachinesWithDefinition(definition_id, online_only = True)
|
||||||
|
|
||||||
|
online_machine_stacks = list(filter(lambda machine: machine.hasNetworkedConnection(), online_machine_stacks))
|
||||||
|
online_machine_stacks.sort(key=lambda machine: machine.getName().upper())
|
||||||
|
|
||||||
|
other_machine_stacks.remove(abstract_machine)
|
||||||
|
if abstract_machine in online_machine_stacks:
|
||||||
|
online_machine_stacks.remove(abstract_machine)
|
||||||
|
|
||||||
|
# Create a list item for abstract machine
|
||||||
|
self.addItem(abstract_machine, True, len(online_machine_stacks))
|
||||||
|
|
||||||
|
# Create list of machines that are children of the abstract machine
|
||||||
|
for stack in online_machine_stacks:
|
||||||
|
if self._show_cloud_printers:
|
||||||
|
self.addItem(stack, True)
|
||||||
|
# Remove this machine from the other stack list
|
||||||
|
if stack in other_machine_stacks:
|
||||||
|
other_machine_stacks.remove(stack)
|
||||||
|
|
||||||
|
if len(abstract_machine_stacks) > 0:
|
||||||
|
if self._show_cloud_printers:
|
||||||
|
self.appendItem({"componentType": "HIDE_BUTTON",
|
||||||
|
"isOnline": True,
|
||||||
|
"isAbstractMachine": False,
|
||||||
|
"machineCount": 0
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
self.appendItem({"componentType": "SHOW_BUTTON",
|
||||||
|
"isOnline": True,
|
||||||
|
"isAbstractMachine": False,
|
||||||
|
"machineCount": 0
|
||||||
|
})
|
||||||
|
|
||||||
|
for stack in other_machine_stacks:
|
||||||
|
self.addItem(stack, False)
|
||||||
|
|
||||||
|
def addItem(self, container_stack: ContainerStack, is_online: bool, machine_count: int = 0) -> None:
|
||||||
|
if parseBool(container_stack.getMetaDataEntry("hidden", False)):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.appendItem({
|
||||||
|
"componentType": "MACHINE",
|
||||||
|
"name": container_stack.getName(),
|
||||||
|
"id": container_stack.getId(),
|
||||||
|
"metadata": container_stack.getMetaData().copy(),
|
||||||
|
"isOnline": is_online,
|
||||||
|
"isAbstractMachine": parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False)),
|
||||||
|
"machineCount": machine_count,
|
||||||
|
})
|
|
@ -1,7 +1,9 @@
|
||||||
# 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 PyQt5.QtCore import Qt, pyqtSignal
|
from PyQt6.QtCore import Qt, pyqtSignal
|
||||||
|
from PyQt6.QtQml import QQmlEngine
|
||||||
|
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||||
|
|
||||||
|
@ -9,10 +11,11 @@ class MaterialTypesModel(ListModel):
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
QQmlEngine.setObjectOwnership(self, QQmlEngine.ObjectOwnership.CppOwnership)
|
||||||
|
|
||||||
self.addRoleName(Qt.UserRole + 1, "name")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 1, "name")
|
||||||
self.addRoleName(Qt.UserRole + 2, "brand")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 2, "brand")
|
||||||
self.addRoleName(Qt.UserRole + 3, "colors")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 3, "colors")
|
||||||
|
|
||||||
class MaterialBrandsModel(BaseMaterialsModel):
|
class MaterialBrandsModel(BaseMaterialsModel):
|
||||||
|
|
||||||
|
@ -20,9 +23,10 @@ class MaterialBrandsModel(BaseMaterialsModel):
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
QQmlEngine.setObjectOwnership(self, QQmlEngine.ObjectOwnership.CppOwnership)
|
||||||
|
|
||||||
self.addRoleName(Qt.UserRole + 1, "name")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 1, "name")
|
||||||
self.addRoleName(Qt.UserRole + 2, "material_types")
|
self.addRoleName(Qt.ItemDataRole.UserRole + 2, "material_types")
|
||||||
|
|
||||||
self._update()
|
self._update()
|
||||||
|
|
||||||
|
@ -74,16 +78,15 @@ class MaterialBrandsModel(BaseMaterialsModel):
|
||||||
material_type_item_list = []
|
material_type_item_list = []
|
||||||
brand_item = {
|
brand_item = {
|
||||||
"name": brand,
|
"name": brand,
|
||||||
"material_types": MaterialTypesModel(self)
|
"material_types": MaterialTypesModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
for material_type, material_list in material_dict.items():
|
for material_type, material_list in material_dict.items():
|
||||||
material_type_item = {
|
material_type_item = {
|
||||||
"name": material_type,
|
"name": material_type,
|
||||||
"brand": brand,
|
"brand": brand,
|
||||||
"colors": BaseMaterialsModel(self)
|
"colors": BaseMaterialsModel()
|
||||||
}
|
}
|
||||||
material_type_item["colors"].clear()
|
|
||||||
|
|
||||||
# Sort materials by name
|
# Sort materials by name
|
||||||
material_list = sorted(material_list, key = lambda x: x["name"].upper())
|
material_list = sorted(material_list, key = lambda x: x["name"].upper())
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import copy # To duplicate materials.
|
import copy # To duplicate materials.
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
|
from PyQt6.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt6.QtGui import QDesktopServices
|
||||||
from typing import Any, Dict, Optional, TYPE_CHECKING
|
from typing import Any, Dict, Optional, TYPE_CHECKING
|
||||||
import uuid # To generate new GUIDs for new materials.
|
import uuid # To generate new GUIDs for new materials.
|
||||||
|
|
||||||
|
@ -34,62 +34,6 @@ class MaterialManagementModel(QObject):
|
||||||
def __init__(self, parent: Optional[QObject] = None) -> None:
|
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||||
super().__init__(parent = parent)
|
super().__init__(parent = parent)
|
||||||
self._material_sync = CloudMaterialSync(parent=self)
|
self._material_sync = CloudMaterialSync(parent=self)
|
||||||
self._checkIfNewMaterialsWereInstalled()
|
|
||||||
|
|
||||||
def _checkIfNewMaterialsWereInstalled(self) -> None:
|
|
||||||
"""
|
|
||||||
Checks whether new material packages were installed in the latest startup. If there were, then it shows
|
|
||||||
a message prompting the user to sync the materials with their printers.
|
|
||||||
"""
|
|
||||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
|
||||||
for package_id, package_data in application.getPackageManager().getPackagesInstalledOnStartup().items():
|
|
||||||
if package_data["package_info"]["package_type"] == "material":
|
|
||||||
# At least one new material was installed
|
|
||||||
# TODO: This should be enabled again once CURA-8609 is merged
|
|
||||||
#self._showSyncNewMaterialsMessage()
|
|
||||||
break
|
|
||||||
|
|
||||||
def _showSyncNewMaterialsMessage(self) -> None:
|
|
||||||
sync_materials_message = Message(
|
|
||||||
text = catalog.i18nc("@action:button",
|
|
||||||
"Please sync the material profiles with your printers before starting to print."),
|
|
||||||
title = catalog.i18nc("@action:button", "New materials installed"),
|
|
||||||
message_type = Message.MessageType.WARNING,
|
|
||||||
lifetime = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
sync_materials_message.addAction(
|
|
||||||
"sync",
|
|
||||||
name = catalog.i18nc("@action:button", "Sync materials"),
|
|
||||||
icon = "",
|
|
||||||
description = "Sync your newly installed materials with your printers.",
|
|
||||||
button_align = Message.ActionButtonAlignment.ALIGN_RIGHT
|
|
||||||
)
|
|
||||||
|
|
||||||
sync_materials_message.addAction(
|
|
||||||
"learn_more",
|
|
||||||
name = catalog.i18nc("@action:button", "Learn more"),
|
|
||||||
icon = "",
|
|
||||||
description = "Learn more about syncing your newly installed materials with your printers.",
|
|
||||||
button_align = Message.ActionButtonAlignment.ALIGN_LEFT,
|
|
||||||
button_style = Message.ActionButtonStyle.LINK
|
|
||||||
)
|
|
||||||
sync_materials_message.actionTriggered.connect(self._onSyncMaterialsMessageActionTriggered)
|
|
||||||
|
|
||||||
# Show the message only if there are printers that support material export
|
|
||||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
|
||||||
global_stacks = container_registry.findContainerStacks(type = "machine")
|
|
||||||
if any([stack.supportsMaterialExport for stack in global_stacks]):
|
|
||||||
sync_materials_message.show()
|
|
||||||
|
|
||||||
def _onSyncMaterialsMessageActionTriggered(self, sync_message: Message, sync_message_action: str):
|
|
||||||
if sync_message_action == "sync":
|
|
||||||
QDesktopServices.openUrl(QUrl("https://example.com/openSyncAllWindow"))
|
|
||||||
# self.openSyncAllWindow()
|
|
||||||
sync_message.hide()
|
|
||||||
elif sync_message_action == "learn_more":
|
|
||||||
QDesktopServices.openUrl(QUrl("https://support.ultimaker.com/hc/en-us/articles/360013137919?utm_source=cura&utm_medium=software&utm_campaign=sync-material-printer-message"))
|
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot("QVariant", result = bool)
|
@pyqtSlot("QVariant", result = bool)
|
||||||
def canMaterialBeRemoved(self, material_node: "MaterialNode") -> bool:
|
def canMaterialBeRemoved(self, material_node: "MaterialNode") -> bool:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 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 PyQt5.QtCore import QTimer, pyqtSignal, pyqtProperty
|
from PyQt6.QtCore import QTimer, pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Scene.Camera import Camera
|
from UM.Scene.Camera import Camera
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# 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 PyQt5.QtCore import Qt
|
from PyQt6.QtCore import Qt
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
@ -10,9 +10,9 @@ from cura.Machines.ContainerTree import ContainerTree
|
||||||
|
|
||||||
|
|
||||||
class NozzleModel(ListModel):
|
class NozzleModel(ListModel):
|
||||||
IdRole = Qt.UserRole + 1
|
IdRole = Qt.ItemDataRole.UserRole + 1
|
||||||
HotendNameRole = Qt.UserRole + 2
|
HotendNameRole = Qt.ItemDataRole.UserRole + 2
|
||||||
ContainerNodeRole = Qt.UserRole + 3
|
ContainerNodeRole = Qt.ItemDataRole.UserRole + 3
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# 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 Any, cast, Dict, Optional, TYPE_CHECKING
|
from typing import Any, cast, Dict, Optional, TYPE_CHECKING
|
||||||
from PyQt5.QtCore import pyqtSlot, QObject, Qt, QTimer
|
from PyQt6.QtCore import pyqtSlot, QObject, Qt, QTimer
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
@ -29,13 +29,13 @@ if TYPE_CHECKING:
|
||||||
class QualityManagementModel(ListModel):
|
class QualityManagementModel(ListModel):
|
||||||
"""This the QML model for the quality management page."""
|
"""This the QML model for the quality management page."""
|
||||||
|
|
||||||
NameRole = Qt.UserRole + 1
|
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||||
IsReadOnlyRole = Qt.UserRole + 2
|
IsReadOnlyRole = Qt.ItemDataRole.UserRole + 2
|
||||||
QualityGroupRole = Qt.UserRole + 3
|
QualityGroupRole = Qt.ItemDataRole.UserRole + 3
|
||||||
QualityTypeRole = Qt.UserRole + 4
|
QualityTypeRole = Qt.ItemDataRole.UserRole + 4
|
||||||
QualityChangesGroupRole = Qt.UserRole + 5
|
QualityChangesGroupRole = Qt.ItemDataRole.UserRole + 5
|
||||||
IntentCategoryRole = Qt.UserRole + 6
|
IntentCategoryRole = Qt.ItemDataRole.UserRole + 6
|
||||||
SectionNameRole = Qt.UserRole + 7
|
SectionNameRole = Qt.ItemDataRole.UserRole + 7
|
||||||
|
|
||||||
def __init__(self, parent: Optional["QObject"] = None) -> None:
|
def __init__(self, parent: Optional["QObject"] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
@ -358,8 +358,9 @@ class QualityManagementModel(ListModel):
|
||||||
"quality_type": quality_type,
|
"quality_type": quality_type,
|
||||||
"quality_changes_group": None,
|
"quality_changes_group": None,
|
||||||
"intent_category": intent_category,
|
"intent_category": intent_category,
|
||||||
"section_name": catalog.i18nc("@label", intent_translations.get(intent_category, {}).get("name", catalog.i18nc("@label", "Unknown"))),
|
"section_name": catalog.i18nc("@label", intent_translations.get(intent_category, {}).get("name", catalog.i18nc("@label", intent_category.title()))),
|
||||||
})
|
})
|
||||||
|
|
||||||
# Sort by quality_type for each intent category
|
# Sort by quality_type for each intent category
|
||||||
intent_translations_list = list(intent_translations)
|
intent_translations_list = list(intent_translations)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# 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 PyQt5.QtCore import Qt, QTimer
|
from PyQt6.QtCore import Qt, QTimer
|
||||||
|
|
||||||
import cura.CuraApplication # Imported this way to prevent circular dependencies.
|
import cura.CuraApplication # Imported this way to prevent circular dependencies.
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -13,14 +13,14 @@ from cura.Machines.Models.MachineModelUtils import fetchLayerHeight
|
||||||
class QualityProfilesDropDownMenuModel(ListModel):
|
class QualityProfilesDropDownMenuModel(ListModel):
|
||||||
"""QML Model for all built-in quality profiles. This model is used for the drop-down quality menu."""
|
"""QML Model for all built-in quality profiles. This model is used for the drop-down quality menu."""
|
||||||
|
|
||||||
NameRole = Qt.UserRole + 1
|
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||||
QualityTypeRole = Qt.UserRole + 2
|
QualityTypeRole = Qt.ItemDataRole.UserRole + 2
|
||||||
LayerHeightRole = Qt.UserRole + 3
|
LayerHeightRole = Qt.ItemDataRole.UserRole + 3
|
||||||
LayerHeightUnitRole = Qt.UserRole + 4
|
LayerHeightUnitRole = Qt.ItemDataRole.UserRole + 4
|
||||||
AvailableRole = Qt.UserRole + 5
|
AvailableRole = Qt.ItemDataRole.UserRole + 5
|
||||||
QualityGroupRole = Qt.UserRole + 6
|
QualityGroupRole = Qt.ItemDataRole.UserRole + 6
|
||||||
QualityChangesGroupRole = Qt.UserRole + 7
|
QualityChangesGroupRole = Qt.ItemDataRole.UserRole + 7
|
||||||
IsExperimentalRole = Qt.UserRole + 8
|
IsExperimentalRole = Qt.ItemDataRole.UserRole + 8
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2022 Ultimaker B.V.
|
# Copyright (c) 2022 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
|
from PyQt6.QtCore import pyqtProperty, pyqtSignal, Qt
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
|
||||||
import cura.CuraApplication
|
import cura.CuraApplication
|
||||||
|
@ -17,13 +17,13 @@ import os
|
||||||
class QualitySettingsModel(ListModel):
|
class QualitySettingsModel(ListModel):
|
||||||
"""This model is used to show details settings of the selected quality in the quality management page."""
|
"""This model is used to show details settings of the selected quality in the quality management page."""
|
||||||
|
|
||||||
KeyRole = Qt.UserRole + 1
|
KeyRole = Qt.ItemDataRole.UserRole + 1
|
||||||
LabelRole = Qt.UserRole + 2
|
LabelRole = Qt.ItemDataRole.UserRole + 2
|
||||||
UnitRole = Qt.UserRole + 3
|
UnitRole = Qt.ItemDataRole.UserRole + 3
|
||||||
ProfileValueRole = Qt.UserRole + 4
|
ProfileValueRole = Qt.ItemDataRole.UserRole + 4
|
||||||
ProfileValueSourceRole = Qt.UserRole + 5
|
ProfileValueSourceRole = Qt.ItemDataRole.UserRole + 5
|
||||||
UserValueRole = Qt.UserRole + 6
|
UserValueRole = Qt.ItemDataRole.UserRole + 6
|
||||||
CategoryRole = Qt.UserRole + 7
|
CategoryRole = Qt.ItemDataRole.UserRole + 7
|
||||||
|
|
||||||
GLOBAL_STACK_POSITION = -1
|
GLOBAL_STACK_POSITION = -1
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Preferences import Preferences
|
from UM.Preferences import Preferences
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import os
|
import os
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot, Qt
|
from PyQt6.QtCore import pyqtSlot, Qt
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -15,12 +15,12 @@ from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
|
||||||
class UserChangesModel(ListModel):
|
class UserChangesModel(ListModel):
|
||||||
KeyRole = Qt.UserRole + 1
|
KeyRole = Qt.ItemDataRole.UserRole + 1
|
||||||
LabelRole = Qt.UserRole + 2
|
LabelRole = Qt.ItemDataRole.UserRole + 2
|
||||||
ExtruderRole = Qt.UserRole + 3
|
ExtruderRole = Qt.ItemDataRole.UserRole + 3
|
||||||
OriginalValueRole = Qt.UserRole + 4
|
OriginalValueRole = Qt.ItemDataRole.UserRole + 4
|
||||||
UserValueRole = Qt.UserRole + 6
|
UserValueRole = Qt.ItemDataRole.UserRole + 6
|
||||||
CategoryRole = Qt.UserRole + 7
|
CategoryRole = Qt.ItemDataRole.UserRole + 7
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent = parent)
|
super().__init__(parent = parent)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
|
from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal
|
||||||
|
|
||||||
|
|
||||||
class QualityChangesGroup(QObject):
|
class QualityChangesGroup(QObject):
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from hashlib import sha512
|
from hashlib import sha512
|
||||||
from PyQt5.QtNetwork import QNetworkReply
|
from PyQt6.QtNetwork import QNetworkReply
|
||||||
import secrets
|
import secrets
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
|
@ -6,8 +6,8 @@ from datetime import datetime, timedelta
|
||||||
from typing import Callable, Dict, Optional, TYPE_CHECKING, Union
|
from typing import Callable, Dict, Optional, TYPE_CHECKING, Union
|
||||||
from urllib.parse import urlencode, quote_plus
|
from urllib.parse import urlencode, quote_plus
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl
|
from PyQt6.QtCore import QUrl
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt6.QtGui import QDesktopServices
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# 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 PyQt5.QtCore import QTimer
|
from PyQt6.QtCore import QTimer
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -50,8 +50,13 @@ class PlatformPhysics:
|
||||||
if not self._enabled:
|
if not self._enabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
app_instance = Application.getInstance()
|
||||||
|
app_preferences = app_instance.getPreferences()
|
||||||
|
app_automatic_drop_down = app_preferences.getValue("physics/automatic_drop_down")
|
||||||
|
app_automatic_push_free = app_preferences.getValue("physics/automatic_push_free")
|
||||||
|
|
||||||
root = self._controller.getScene().getRoot()
|
root = self._controller.getScene().getRoot()
|
||||||
build_volume = Application.getInstance().getBuildVolume()
|
build_volume = app_instance.getBuildVolume()
|
||||||
build_volume.updateNodeBoundaryCheck()
|
build_volume.updateNodeBoundaryCheck()
|
||||||
|
|
||||||
# Keep a list of nodes that are moving. We use this so that we don't move two intersecting objects in the
|
# Keep a list of nodes that are moving. We use this so that we don't move two intersecting objects in the
|
||||||
|
@ -75,7 +80,7 @@ class PlatformPhysics:
|
||||||
# Move it downwards if bottom is above platform
|
# Move it downwards if bottom is above platform
|
||||||
move_vector = Vector()
|
move_vector = Vector()
|
||||||
|
|
||||||
if Application.getInstance().getPreferences().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down
|
if node.getSetting(SceneNodeSettings.AutoDropDown, app_automatic_drop_down) and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down
|
||||||
z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
|
z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
|
||||||
move_vector = move_vector.set(y = -bbox.bottom + z_offset)
|
move_vector = move_vector.set(y = -bbox.bottom + z_offset)
|
||||||
|
|
||||||
|
@ -84,7 +89,7 @@ class PlatformPhysics:
|
||||||
node.addDecorator(ConvexHullDecorator())
|
node.addDecorator(ConvexHullDecorator())
|
||||||
|
|
||||||
# only push away objects if this node is a printing mesh
|
# only push away objects if this node is a printing mesh
|
||||||
if not node.callDecoration("isNonPrintingMesh") and Application.getInstance().getPreferences().getValue("physics/automatic_push_free"):
|
if not node.callDecoration("isNonPrintingMesh") and app_automatic_push_free:
|
||||||
# Do not move locked nodes
|
# Do not move locked nodes
|
||||||
if node.getSetting(SceneNodeSettings.LockPosition):
|
if node.getSetting(SceneNodeSettings.LockPosition):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from PyQt5.QtGui import QImage
|
from PyQt6.QtGui import QImage
|
||||||
from PyQt5.QtQuick import QQuickImageProvider
|
from PyQt6.QtQuick import QQuickImageProvider
|
||||||
from PyQt5.QtCore import QSize
|
from PyQt6.QtCore import QSize
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
@ -8,7 +8,7 @@ from typing import Tuple
|
||||||
|
|
||||||
class PrintJobPreviewImageProvider(QQuickImageProvider):
|
class PrintJobPreviewImageProvider(QQuickImageProvider):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(QQuickImageProvider.Image)
|
super().__init__(QQuickImageProvider.ImageType.Image)
|
||||||
|
|
||||||
def requestImage(self, id: str, size: QSize) -> Tuple[QImage, QSize]:
|
def requestImage(self, id: str, size: QSize) -> Tuple[QImage, QSize]:
|
||||||
"""Request a new image.
|
"""Request a new image.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 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 PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
|
from PyQt6.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Set, Union, Optional
|
from typing import TYPE_CHECKING, Set, Union, Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import QTimer
|
from PyQt6.QtCore import QTimer
|
||||||
|
|
||||||
from .PrinterOutputController import PrinterOutputController
|
from .PrinterOutputController import PrinterOutputController
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# 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
|
from typing import Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
|
from PyQt6.QtCore import pyqtProperty, QObject, pyqtSignal
|
||||||
|
|
||||||
from .MaterialOutputModel import MaterialOutputModel
|
from .MaterialOutputModel import MaterialOutputModel
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ class ExtruderConfigurationModel(QObject):
|
||||||
|
|
||||||
def __init__(self, position: int = -1) -> None:
|
def __init__(self, position: int = -1) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._position = position # type: int
|
self._position: int = position
|
||||||
self._material = None # type: Optional[MaterialOutputModel]
|
self._material: Optional[MaterialOutputModel] = None
|
||||||
self._hotend_id = None # type: Optional[str]
|
self._hotend_id: Optional[str] = None
|
||||||
|
|
||||||
def setPosition(self, position: int) -> None:
|
def setPosition(self, position: int) -> None:
|
||||||
self._position = position
|
self._position = position
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
||||||
|
|
||||||
from .ExtruderConfigurationModel import ExtruderConfigurationModel
|
from .ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, QObject
|
from PyQt6.QtCore import pyqtProperty, QObject
|
||||||
|
|
||||||
|
|
||||||
class MaterialOutputModel(QObject):
|
class MaterialOutputModel(QObject):
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
# 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
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot, QUrl
|
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot, QUrl
|
||||||
from PyQt5.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
|
||||||
|
@ -58,7 +60,7 @@ class PrintJobOutputModel(QObject):
|
||||||
# requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
|
# requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
|
||||||
# as new (instead of relying on cached version and thus forces an update.
|
# as new (instead of relying on cached version and thus forces an update.
|
||||||
temp = "image://print_job_preview/" + str(self._preview_image_id) + "/" + self._key
|
temp = "image://print_job_preview/" + str(self._preview_image_id) + "/" + self._key
|
||||||
return QUrl(temp, QUrl.TolerantMode)
|
return QUrl(temp, QUrl.ParsingMode.TolerantMode)
|
||||||
|
|
||||||
def getPreviewImage(self) -> Optional[QImage]:
|
def getPreviewImage(self) -> Optional[QImage]:
|
||||||
return self._preview_image
|
return self._preview_image
|
||||||
|
@ -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
|
||||||
|
@ -119,16 +133,16 @@ class PrintJobOutputModel(QObject):
|
||||||
|
|
||||||
@pyqtProperty(int, notify = timeTotalChanged)
|
@pyqtProperty(int, notify = timeTotalChanged)
|
||||||
def timeTotal(self) -> int:
|
def timeTotal(self) -> int:
|
||||||
return self._time_total
|
return int(self._time_total)
|
||||||
|
|
||||||
@pyqtProperty(int, notify = timeElapsedChanged)
|
@pyqtProperty(int, notify = timeElapsedChanged)
|
||||||
def timeElapsed(self) -> int:
|
def timeElapsed(self) -> int:
|
||||||
return self._time_elapsed
|
return int(self._time_elapsed)
|
||||||
|
|
||||||
@pyqtProperty(int, notify = timeElapsedChanged)
|
@pyqtProperty(int, notify = timeElapsedChanged)
|
||||||
def timeRemaining(self) -> int:
|
def timeRemaining(self) -> int:
|
||||||
# Never get a negative time remaining
|
# Never get a negative time remaining
|
||||||
return max(self.timeTotal - self.timeElapsed, 0)
|
return int(max(self.timeTotal - self.timeElapsed, 0))
|
||||||
|
|
||||||
@pyqtProperty(float, notify = timeElapsedChanged)
|
@pyqtProperty(float, notify = timeElapsedChanged)
|
||||||
def progress(self) -> float:
|
def progress(self) -> float:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 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 PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
|
from PyQt6.QtCore import pyqtProperty, QObject, pyqtSignal
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
MYPY = False
|
MYPY = False
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# 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 PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot, QUrl
|
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot, QUrl
|
||||||
from typing import List, Dict, Optional, TYPE_CHECKING
|
from typing import List, Dict, Optional, TYPE_CHECKING
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from cura.PrinterOutput.Peripheral import Peripheral
|
from cura.PrinterOutput.Peripheral import Peripheral
|
||||||
|
@ -350,5 +350,6 @@ class PrinterOutputModel(QObject):
|
||||||
self.availableConfigurationsChanged.emit()
|
self.availableConfigurationsChanged.emit()
|
||||||
|
|
||||||
def setAvailableConfigurations(self, new_configurations: List[PrinterConfigurationModel]) -> None:
|
def setAvailableConfigurations(self, new_configurations: List[PrinterConfigurationModel]) -> None:
|
||||||
self._available_printer_configurations = new_configurations
|
if self._available_printer_configurations != new_configurations:
|
||||||
self.availableConfigurationsChanged.emit()
|
self._available_printer_configurations = new_configurations
|
||||||
|
self.availableConfigurationsChanged.emit()
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# Copyright (c) 2018 Aldo Hoeben / fieldOfView
|
# Copyright (c) 2018 Aldo Hoeben / fieldOfView
|
||||||
# NetworkMJPGImage is released under the terms of the LGPLv3 or higher.
|
# NetworkMJPGImage is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl, pyqtProperty, pyqtSignal, pyqtSlot, QRect, QByteArray
|
from PyQt6.QtCore import QUrl, pyqtProperty, pyqtSignal, pyqtSlot, QRect, QByteArray
|
||||||
from PyQt5.QtGui import QImage, QPainter
|
from PyQt6.QtGui import QImage, QPainter
|
||||||
from PyQt5.QtQuick import QQuickPaintedItem
|
from PyQt6.QtQuick import QQuickPaintedItem
|
||||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
|
from PyQt6.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,8 @@ from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
|
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
|
||||||
|
|
||||||
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator
|
from PyQt6.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication
|
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Callable, Dict, List, Optional, Union
|
from typing import Callable, Dict, List, Optional, Union
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
@ -146,8 +146,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
url = QUrl("http://" + self._address + self._api_prefix + target)
|
url = QUrl("http://" + self._address + self._api_prefix + target)
|
||||||
request = QNetworkRequest(url)
|
request = QNetworkRequest(url)
|
||||||
if content_type is not None:
|
if content_type is not None:
|
||||||
request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
|
request.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, content_type)
|
||||||
request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
|
request.setHeader(QNetworkRequest.KnownHeaders.UserAgentHeader, self._user_agent)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
def createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart:
|
def createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart:
|
||||||
|
@ -162,10 +162,10 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
|
|
||||||
if not content_header.startswith("form-data;"):
|
if not content_header.startswith("form-data;"):
|
||||||
content_header = "form-data; " + content_header
|
content_header = "form-data; " + content_header
|
||||||
part.setHeader(QNetworkRequest.ContentDispositionHeader, content_header)
|
part.setHeader(QNetworkRequest.KnownHeaders.ContentDispositionHeader, content_header)
|
||||||
|
|
||||||
if content_type is not None:
|
if content_type is not None:
|
||||||
part.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
|
part.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, content_type)
|
||||||
|
|
||||||
part.setBody(data)
|
part.setBody(data)
|
||||||
return part
|
return part
|
||||||
|
@ -290,7 +290,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
on_progress: Optional[Callable[[int, int], None]] = None) -> QNetworkReply:
|
on_progress: Optional[Callable[[int, int], None]] = None) -> QNetworkReply:
|
||||||
self._validateManager()
|
self._validateManager()
|
||||||
request = self._createEmptyRequest(target, content_type=None)
|
request = self._createEmptyRequest(target, content_type=None)
|
||||||
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
|
multi_post_part = QHttpMultiPart(QHttpMultiPart.ContentType.FormDataType)
|
||||||
for part in parts:
|
for part in parts:
|
||||||
multi_post_part.append(part)
|
multi_post_part.append(part)
|
||||||
|
|
||||||
|
@ -311,7 +311,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
|
|
||||||
def postForm(self, target: str, header_data: str, body_data: bytes, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
|
def postForm(self, target: str, header_data: str, body_data: bytes, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
|
||||||
post_part = QHttpPart()
|
post_part = QHttpPart()
|
||||||
post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data)
|
post_part.setHeader(QNetworkRequest.KnownHeaders.ContentDispositionHeader, header_data)
|
||||||
post_part.setBody(body_data)
|
post_part.setBody(body_data)
|
||||||
|
|
||||||
self.postFormWithParts(target, [post_part], on_finished, on_progress)
|
self.postFormWithParts(target, [post_part], on_finished, on_progress)
|
||||||
|
@ -357,10 +357,10 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
def _handleOnFinished(self, reply: QNetworkReply) -> None:
|
def _handleOnFinished(self, reply: QNetworkReply) -> None:
|
||||||
# Due to garbage collection, we need to cache certain bits of post operations.
|
# Due to garbage collection, we need to cache certain bits of post operations.
|
||||||
# As we don't want to keep them around forever, delete them if we get a reply.
|
# As we don't want to keep them around forever, delete them if we get a reply.
|
||||||
if reply.operation() == QNetworkAccessManager.PostOperation:
|
if reply.operation() == QNetworkAccessManager.Operation.PostOperation:
|
||||||
self._clearCachedMultiPart(reply)
|
self._clearCachedMultiPart(reply)
|
||||||
|
|
||||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) is None:
|
if reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute) is None:
|
||||||
# No status code means it never even reached remote.
|
# No status code means it never even reached remote.
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# 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 enum import IntEnum
|
from enum import IntEnum
|
||||||
from typing import Callable, List, Optional, Union
|
from typing import Callable, List, Optional, Union
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl
|
from PyQt6.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
import cura.CuraApplication # Imported like this to prevent circular imports.
|
import cura.CuraApplication # Imported like this to prevent circular imports.
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -50,13 +50,12 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
The assumption is made the printer is a FDM printer.
|
The assumption is made the printer is a FDM printer.
|
||||||
|
|
||||||
Note that a number of settings are marked as "final". This is because decorators
|
Note that a number of settings are marked as "final". This is because decorators
|
||||||
are not inherited by children. To fix this we use the private counter part of those
|
are not inherited by children. To fix this we use the private counterpart of those
|
||||||
functions to actually have the implementation.
|
functions to actually have the implementation.
|
||||||
|
|
||||||
For all other uses it should be used in the same way as a "regular" OutputDevice.
|
For all other uses it should be used in the same way as a "regular" OutputDevice.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
printersChanged = pyqtSignal()
|
printersChanged = pyqtSignal()
|
||||||
connectionStateChanged = pyqtSignal(str)
|
connectionStateChanged = pyqtSignal(str)
|
||||||
acceptsCommandsChanged = pyqtSignal()
|
acceptsCommandsChanged = pyqtSignal()
|
||||||
|
@ -137,7 +136,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
"""
|
"""
|
||||||
if self.connectionState != connection_state:
|
if self.connectionState != connection_state:
|
||||||
self._connection_state = connection_state
|
self._connection_state = connection_state
|
||||||
cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().setMetaDataEntry("is_online", self.isConnected())
|
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||||
|
if application is not None: # Might happen during the closing of Cura or in a test.
|
||||||
|
global_stack = application.getGlobalContainerStack()
|
||||||
|
if global_stack is not None:
|
||||||
|
global_stack.setMetaDataEntry("is_online", self.isConnected())
|
||||||
self.connectionStateChanged.emit(self._id)
|
self.connectionStateChanged.emit(self._id)
|
||||||
|
|
||||||
@pyqtProperty(int, constant = True)
|
@pyqtProperty(int, constant = True)
|
||||||
|
@ -179,8 +182,8 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
@pyqtProperty(QObject, constant = True)
|
@pyqtProperty(QObject, constant = True)
|
||||||
def monitorItem(self) -> QObject:
|
def monitorItem(self) -> QObject:
|
||||||
# Note that we specifically only check if the monitor component is created.
|
# Note that we specifically only check if the monitor component is created.
|
||||||
# It could be that it failed to actually create the qml item! If we check if the item was created, it will try to
|
# It could be that it failed to actually create the qml item! If we check if the item was created, it will try
|
||||||
# create the item (and fail) every time.
|
# to create the item (and fail) every time.
|
||||||
if not self._monitor_component:
|
if not self._monitor_component:
|
||||||
self._createMonitorViewFromQML()
|
self._createMonitorViewFromQML()
|
||||||
return self._monitor_item
|
return self._monitor_item
|
||||||
|
@ -233,9 +236,9 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
|
|
||||||
self.acceptsCommandsChanged.emit()
|
self.acceptsCommandsChanged.emit()
|
||||||
|
|
||||||
# Returns the unique configurations of the printers within this output device
|
|
||||||
@pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged)
|
@pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged)
|
||||||
def uniqueConfigurations(self) -> List["PrinterConfigurationModel"]:
|
def uniqueConfigurations(self) -> List["PrinterConfigurationModel"]:
|
||||||
|
""" Returns the unique configurations of the printers within this output device """
|
||||||
return self._unique_configurations
|
return self._unique_configurations
|
||||||
|
|
||||||
def _updateUniqueConfigurations(self) -> None:
|
def _updateUniqueConfigurations(self) -> None:
|
||||||
|
@ -244,17 +247,19 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
if printer.printerConfiguration is not None and printer.printerConfiguration.hasAnyMaterialLoaded():
|
if printer.printerConfiguration is not None and printer.printerConfiguration.hasAnyMaterialLoaded():
|
||||||
all_configurations.add(printer.printerConfiguration)
|
all_configurations.add(printer.printerConfiguration)
|
||||||
all_configurations.update(printer.availableConfigurations)
|
all_configurations.update(printer.availableConfigurations)
|
||||||
if None in all_configurations: # Shouldn't happen, but it does. I don't see how it could ever happen. Skip adding that configuration. List could end up empty!
|
if None in all_configurations:
|
||||||
|
# Shouldn't happen, but it does. I don't see how it could ever happen. Skip adding that configuration.
|
||||||
|
# List could end up empty!
|
||||||
Logger.log("e", "Found a broken configuration in the synced list!")
|
Logger.log("e", "Found a broken configuration in the synced list!")
|
||||||
all_configurations.remove(None)
|
all_configurations.remove(None)
|
||||||
new_configurations = sorted(all_configurations, key = lambda config: config.printerType or "")
|
new_configurations = sorted(all_configurations, key = lambda config: config.printerType or "", reverse = True)
|
||||||
if new_configurations != self._unique_configurations:
|
if new_configurations != self._unique_configurations:
|
||||||
self._unique_configurations = new_configurations
|
self._unique_configurations = new_configurations
|
||||||
self.uniqueConfigurationsChanged.emit()
|
self.uniqueConfigurationsChanged.emit()
|
||||||
|
|
||||||
# Returns the unique configurations of the printers within this output device
|
|
||||||
@pyqtProperty("QStringList", notify = uniqueConfigurationsChanged)
|
@pyqtProperty("QStringList", notify = uniqueConfigurationsChanged)
|
||||||
def uniquePrinterTypes(self) -> List[str]:
|
def uniquePrinterTypes(self) -> List[str]:
|
||||||
|
""" Returns the unique configurations of the printers within this output device """
|
||||||
return list(sorted(set([configuration.printerType or "" for configuration in self._unique_configurations])))
|
return list(sorted(set([configuration.printerType or "" for configuration in self._unique_configurations])))
|
||||||
|
|
||||||
def _onPrintersChanged(self) -> None:
|
def _onPrintersChanged(self) -> None:
|
||||||
|
|
|
@ -5,7 +5,7 @@ import enum
|
||||||
import functools # For partial methods to use as callbacks with information pre-filled.
|
import functools # For partial methods to use as callbacks with information pre-filled.
|
||||||
import json # To serialise metadata for API calls.
|
import json # To serialise metadata for API calls.
|
||||||
import os # To delete the archive when we're done.
|
import os # To delete the archive when we're done.
|
||||||
from PyQt5.QtCore import QUrl
|
from PyQt6.QtCore import QUrl
|
||||||
import tempfile # To create an archive before we upload it.
|
import tempfile # To create an archive before we upload it.
|
||||||
|
|
||||||
import cura.CuraApplication # Imported like this to prevent circular imports.
|
import cura.CuraApplication # Imported like this to prevent circular imports.
|
||||||
|
@ -21,7 +21,7 @@ from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
|
||||||
|
|
||||||
from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING
|
from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from PyQt5.QtNetwork import QNetworkReply
|
from PyQt6.QtNetwork import QNetworkReply
|
||||||
from cura.UltimakerCloud.CloudMaterialSync import CloudMaterialSync
|
from cura.UltimakerCloud.CloudMaterialSync import CloudMaterialSync
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2020 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.
|
||||||
|
|
||||||
from PyQt5.QtCore import QTimer
|
from PyQt6.QtCore import QTimer
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Math.Polygon import Polygon
|
from UM.Math.Polygon import Polygon
|
||||||
|
@ -383,14 +383,14 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
# Shrinkage compensation.
|
# Shrinkage compensation.
|
||||||
if not self._global_stack: # Should never happen.
|
if not self._global_stack: # Should never happen.
|
||||||
return convex_hull
|
return convex_hull
|
||||||
scale_factor = self._global_stack.getProperty("material_shrinkage_percentage", "value") / 100.0
|
scale_factor = self._global_stack.getProperty("material_shrinkage_percentage_xy", "value") / 100.0
|
||||||
result = convex_hull
|
result = convex_hull
|
||||||
if scale_factor != 1.0 and not self.getNode().callDecoration("isGroup"):
|
if scale_factor != 1.0 and scale_factor > 0 and not self.getNode().callDecoration("isGroup"):
|
||||||
center = None
|
center = None
|
||||||
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||||
# Find the root node that's placed in the scene; the root of the mesh group.
|
# Find the root node that's placed in the scene; the root of the mesh group.
|
||||||
ancestor = self.getNode()
|
ancestor = self.getNode()
|
||||||
while ancestor.getParent() != self._root:
|
while ancestor.getParent() != self._root and ancestor.getParent() is not None:
|
||||||
ancestor = ancestor.getParent()
|
ancestor = ancestor.getParent()
|
||||||
center = ancestor.getBoundingBox().center
|
center = ancestor.getBoundingBox().center
|
||||||
else:
|
else:
|
||||||
|
@ -498,7 +498,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
"adhesion_type", "raft_margin", "print_sequence",
|
"adhesion_type", "raft_margin", "print_sequence",
|
||||||
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]
|
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]
|
||||||
|
|
||||||
_influencing_settings = {"xy_offset", "xy_offset_layer_0", "mold_enabled", "mold_width", "anti_overhang_mesh", "infill_mesh", "cutting_mesh", "material_shrinkage_percentage"}
|
_influencing_settings = {"xy_offset", "xy_offset_layer_0", "mold_enabled", "mold_width", "anti_overhang_mesh", "infill_mesh", "cutting_mesh", "material_shrinkage_percentage_xy"}
|
||||||
"""Settings that change the convex hull.
|
"""Settings that change the convex hull.
|
||||||
|
|
||||||
If these settings change, the convex hull should be recalculated.
|
If these settings change, the convex hull should be recalculated.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, pyqtSlot, QObject, QTimer
|
from PyQt6.QtCore import Qt, pyqtSlot, QObject, QTimer
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
|
||||||
from UM.Scene.Camera import Camera
|
from UM.Scene.Camera import Camera
|
||||||
from cura.UI.ObjectsModel import ObjectsModel
|
from cura.UI.ObjectsModel import ObjectsModel
|
||||||
|
@ -107,8 +107,8 @@ class CuraSceneController(QObject):
|
||||||
"""Either select or deselect an item"""
|
"""Either select or deselect an item"""
|
||||||
|
|
||||||
modifiers = QApplication.keyboardModifiers()
|
modifiers = QApplication.keyboardModifiers()
|
||||||
ctrl_is_active = modifiers & Qt.ControlModifier
|
ctrl_is_active = modifiers & Qt.KeyboardModifier.ControlModifier
|
||||||
shift_is_active = modifiers & Qt.ShiftModifier
|
shift_is_active = modifiers & Qt.KeyboardModifier.ShiftModifier
|
||||||
|
|
||||||
if ctrl_is_active:
|
if ctrl_is_active:
|
||||||
item = self._objects_model.getItem(index)
|
item = self._objects_model.getItem(index)
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ import urllib.parse
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any, cast, Dict, List, TYPE_CHECKING, Union
|
from typing import Any, cast, Dict, List, TYPE_CHECKING, Union
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QUrl
|
from PyQt6.QtCore import QObject, QUrl
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
|
@ -47,11 +47,11 @@ class ContainerManager(QObject):
|
||||||
def __init__(self, application: "CuraApplication") -> None:
|
def __init__(self, application: "CuraApplication") -> None:
|
||||||
if ContainerManager.__instance is not None:
|
if ContainerManager.__instance is not None:
|
||||||
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
|
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
|
||||||
ContainerManager.__instance = self
|
|
||||||
try:
|
try:
|
||||||
super().__init__(parent = application)
|
super().__init__(parent = application)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
ContainerManager.__instance = self
|
||||||
|
|
||||||
self._container_name_filters = {} # type: Dict[str, Dict[str, Any]]
|
self._container_name_filters = {} # type: Dict[str, Dict[str, Any]]
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ class ContainerManager(QObject):
|
||||||
for _ in range(len(entries)):
|
for _ in range(len(entries)):
|
||||||
item = item.get(entries.pop(0), {})
|
item = item.get(entries.pop(0), {})
|
||||||
|
|
||||||
if item[entry_name] != entry_value:
|
if entry_name not in item or item[entry_name] != entry_value:
|
||||||
sub_item_changed = True
|
sub_item_changed = True
|
||||||
item[entry_name] = entry_value
|
item[entry_name] = entry_value
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ class ContainerManager(QObject):
|
||||||
if os.path.exists(file_url):
|
if os.path.exists(file_url):
|
||||||
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
|
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
|
||||||
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_url))
|
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_url))
|
||||||
if result == QMessageBox.No:
|
if result == QMessageBox.StandardButton.No:
|
||||||
return {"status": "cancelled", "message": "User cancelled"}
|
return {"status": "cancelled", "message": "User cancelled"}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -6,7 +6,7 @@ import re
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
from typing import Any, cast, Dict, Optional, List, Union, Tuple
|
from typing import Any, cast, Dict, Optional, List, Union, Tuple
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
from UM.Settings.ContainerFormatError import ContainerFormatError
|
from UM.Settings.ContainerFormatError import ContainerFormatError
|
||||||
|
@ -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)
|
||||||
|
@ -139,7 +139,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
if os.path.exists(file_name):
|
if os.path.exists(file_name):
|
||||||
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
|
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
|
||||||
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
|
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
|
||||||
if result == QMessageBox.No:
|
if result == QMessageBox.StandardButton.No:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
profile_writer = self._findProfileWriter(extension, description)
|
profile_writer = self._findProfileWriter(extension, description)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue