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
|
||||
description: Create a report to help us fix issues.
|
||||
labels: "Type: Bug"
|
||||
labels: ["Type: Bug", "Status: Triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
@ -14,7 +14,7 @@ body:
|
|||
attributes:
|
||||
label: Application Version
|
||||
description: The version of Cura this issue occurs with.
|
||||
placeholder: 4.9.0
|
||||
placeholder: 5.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
|
|
2
.github/ISSUE_TEMPLATE/featurerequest.yaml
vendored
2
.github/ISSUE_TEMPLATE/featurerequest.yaml
vendored
|
@ -1,6 +1,6 @@
|
|||
name: Feature Request
|
||||
description: Suggest an idea for this project.
|
||||
labels: "Type: New Feature"
|
||||
labels: ["Type: New Feature", "Status: Triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
|
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
|
||||
|
||||
#Externally located plug-ins commonly installed by our devs.
|
||||
plugins/BarbarianPlugin
|
||||
plugins/cura-big-flame-graph
|
||||
plugins/cura-camera-position
|
||||
plugins/cura-god-mode-plugin
|
||||
|
@ -64,6 +65,7 @@ plugins/CuraRemoteSupport
|
|||
plugins/ModelCutter
|
||||
plugins/PrintProfileCreator
|
||||
plugins/MultiPrintPlugin
|
||||
plugins/CuraOrientationPlugin
|
||||
|
||||
#Build stuff
|
||||
CMakeCache.txt
|
||||
|
@ -87,4 +89,13 @@ CuraEngine
|
|||
#Prevents import failures when plugin running tests
|
||||
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>
|
33
CITATION.cff
33
CITATION.cff
|
@ -1,11 +1,30 @@
|
|||
# YAML 1.2
|
||||
---
|
||||
cff-version: 1.2.0
|
||||
type: software
|
||||
message: >-
|
||||
If you use this software, please cite it using the
|
||||
metadata from this file.
|
||||
title: Ultimaker Cura
|
||||
abstract: >-
|
||||
A state-of-the-art slicer application built on top
|
||||
of the Uranium framework.
|
||||
authors:
|
||||
cff-version: "1.1.0"
|
||||
date-released: 2021-06-28
|
||||
license: "LGPL-3.0"
|
||||
message: "If you use this software, please cite it using these metadata."
|
||||
repository-code: "https://github.com/ultimaker/cura/"
|
||||
title: "Ultimaker Cura"
|
||||
version: "4.12.0"
|
||||
- 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)
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
|
||||
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_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)
|
||||
|
||||
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 "")
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${URANIUM_DIR}/cmake")
|
||||
endif()
|
||||
|
@ -57,47 +30,3 @@ if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "")
|
|||
CREATE_TRANSLATION_TARGETS()
|
||||
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
|
||||
------------
|
||||
For crashes and similar issues, please attach the following information:
|
||||
<div align = center>
|
||||
|
||||
* (On Windows) The log as produced by dxdiag (start -> run -> dxdiag -> save output)
|
||||
* The Cura GUI log file, located at
|
||||
* `%APPDATA%\cura\<Cura version>\cura.log` (Windows), or usually `C:\Users\<your username>\AppData\Roaming\cura\<Cura version>\cura.log`
|
||||
* `$HOME/Library/Application Support/cura/<Cura version>/cura.log` (OSX)
|
||||
* `$HOME/.local/share/cura/<Cura version>/cura.log` (Ubuntu/Linux)
|
||||
[![Badge Issues]][Issues]
|
||||
[![Badge PullRequests]][PullRequests]
|
||||
[![Badge Closed]][Closed]
|
||||
|
||||
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
|
||||
------------
|
||||
* [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.
|
||||
<br>
|
||||
<br>
|
||||
|
||||
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
|
||||
-------------
|
||||
Please check out [cura-build](https://github.com/Ultimaker/cura-build) for detailed building instructions.
|
||||
*State-of-the-art slicer app to prepare* <br>
|
||||
*your 3D models for your 3D printer.*
|
||||
|
||||
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
|
||||
-------------
|
||||
Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Running-Cura-from-Source) for details about running Cura from source.
|
||||
<br>
|
||||
<br>
|
||||
|
||||
Plugins
|
||||
-------------
|
||||
Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Plugin-Directory) for details about creating and using plugins.
|
||||
[![Button Building]][Building]
|
||||
[![Button Plugins]][Plugins]
|
||||
[![Button Machines]][Machines]
|
||||
|
||||
Supported printers
|
||||
-------------
|
||||
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 Report]][Report]
|
||||
[![Button Settings]][Settings]
|
||||
[![Button Localize]][Localize]
|
||||
|
||||
Configuring Cura
|
||||
----------------
|
||||
Please check out [Wiki page](https://github.com/Ultimaker/Cura/wiki/Cura-Settings) about configuration options for developers.
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<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.
|
||||
|
||||
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.
|
||||
|
||||
import enum
|
||||
from datetime import datetime
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer, Q_ENUMS
|
||||
from typing import Any, Optional, Dict, TYPE_CHECKING, Callable
|
||||
import json
|
||||
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.Message import Message
|
||||
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.Models import OAuth2Settings, UserProfile
|
||||
from cura.UltimakerCloud import UltimakerCloudConstants
|
||||
from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from PyQt6.QtNetwork import QNetworkReply
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class SyncState:
|
||||
class SyncState(enum.IntEnum):
|
||||
"""QML: Cura.AccountSyncState"""
|
||||
SYNCING = 0
|
||||
SUCCESS = 1
|
||||
|
@ -41,7 +48,7 @@ class Account(QObject):
|
|||
|
||||
# The interval in which sync services are automatically triggered
|
||||
SYNC_INTERVAL = 60.0 # seconds
|
||||
Q_ENUMS(SyncState)
|
||||
pyqtEnum(SyncState)
|
||||
|
||||
loginStateChanged = pyqtSignal(bool)
|
||||
"""Signal emitted when user logged in or out"""
|
||||
|
@ -78,6 +85,7 @@ class Account(QObject):
|
|||
self._logged_in = False
|
||||
self._user_profile: Optional[UserProfile] = None
|
||||
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._manual_sync_enabled = False
|
||||
self._update_packages_enabled = False
|
||||
|
@ -109,6 +117,7 @@ class Account(QObject):
|
|||
|
||||
self._sync_services: Dict[str, int] = {}
|
||||
"""contains entries "service_name" : SyncState"""
|
||||
self.syncRequested.connect(self._updatePermissions)
|
||||
|
||||
def initialize(self) -> None:
|
||||
self._authorization_service.initialize(self._application.getPreferences())
|
||||
|
@ -269,10 +278,10 @@ class Account(QObject):
|
|||
return self._authorization_service.getAccessToken()
|
||||
|
||||
@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 """
|
||||
if not self._user_profile:
|
||||
return None
|
||||
return {}
|
||||
return self._user_profile.__dict__
|
||||
|
||||
@pyqtProperty(str, notify=lastSyncDateTimeChanged)
|
||||
|
@ -311,13 +320,63 @@ class Account(QObject):
|
|||
|
||||
self._authorization_service.deleteAuthData()
|
||||
|
||||
@deprecated("Get permissions from the 'permissions' property", since = "5.2.0")
|
||||
def updateAdditionalRight(self, **kwargs) -> None:
|
||||
"""Update the additional rights of the account.
|
||||
The argument(s) are the rights that need to be set"""
|
||||
self._additional_rights.update(kwargs)
|
||||
self.additionalRightsChanged.emit(self._additional_rights)
|
||||
|
||||
@deprecated("Get permissions from the 'permissions' property", since = "5.2.0")
|
||||
@pyqtProperty("QVariantMap", notify = additionalRightsChanged)
|
||||
def additionalRights(self) -> Dict[str, Any]:
|
||||
"""A dictionary which can be queried for additional account 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 PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
|
||||
from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty
|
||||
|
||||
from UM.TaskManagement.HttpRequestManager import HttpRequestManager
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
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.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__))
|
||||
if application is None:
|
||||
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
|
||||
return cls.__instance
|
||||
return instance
|
||||
|
||||
def __init__(self, application: Optional["CuraApplication"] = None) -> None:
|
||||
super().__init__(parent = CuraAPI._application)
|
||||
CuraAPI.__instance = self
|
||||
|
||||
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.
|
||||
|
||||
# ---------
|
||||
|
@ -6,14 +6,22 @@
|
|||
# ---------
|
||||
DEFAULT_CURA_APP_NAME = "cura"
|
||||
DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura"
|
||||
DEFAULT_CURA_VERSION = "master"
|
||||
DEFAULT_CURA_VERSION = "dev"
|
||||
DEFAULT_CURA_BUILD_TYPE = ""
|
||||
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
|
||||
# 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.
|
||||
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:
|
||||
from cura.CuraVersion import CuraAppName # type: ignore
|
||||
|
@ -60,3 +68,14 @@ try:
|
|||
|
||||
except ImportError:
|
||||
CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME
|
||||
|
||||
DEPENDENCY_INFO = {}
|
||||
try:
|
||||
from pathlib import Path
|
||||
conan_install_info = Path(__file__).parent.parent.joinpath("conan_install_info.json")
|
||||
if conan_install_info.exists():
|
||||
import json
|
||||
with open(conan_install_info, "r") as f:
|
||||
DEPENDENCY_INFO = json.loads(f.read())
|
||||
except:
|
||||
pass
|
||||
|
|
|
@ -21,6 +21,7 @@ class ArrangeObjectsJob(Job):
|
|||
self._min_offset = min_offset
|
||||
|
||||
def run(self):
|
||||
found_solution_for_all = False
|
||||
status_message = Message(i18n_catalog.i18nc("@info:status", "Finding new location for objects"),
|
||||
lifetime = 0,
|
||||
dismissable = False,
|
||||
|
@ -28,18 +29,19 @@ class ArrangeObjectsJob(Job):
|
|||
title = i18n_catalog.i18nc("@info:title", "Finding Location"))
|
||||
status_message.show()
|
||||
|
||||
found_solution_for_all = None
|
||||
try:
|
||||
found_solution_for_all = arrange(self._nodes, Application.getInstance().getBuildVolume(), self._fixed_nodes)
|
||||
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.")
|
||||
|
||||
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(
|
||||
i18n_catalog.i18nc("@info:status",
|
||||
"Unable to find a location within the build volume for all objects"),
|
||||
title = i18n_catalog.i18nc("@info:title", "Can't Find Location"),
|
||||
message_type = Message.MessageType.ERROR)
|
||||
no_full_solution_message.show()
|
||||
|
||||
self.finished.emit(self)
|
||||
|
|
|
@ -74,14 +74,14 @@ class ShapeArray:
|
|||
# If the child-nodes are included, adjust convex hulls as well:
|
||||
if include_children:
|
||||
children = node.getAllChildren()
|
||||
if not children is None:
|
||||
if children is not None:
|
||||
for child in children:
|
||||
# 'Inefficient' combination of convex hulls through known code rather than mess it up:
|
||||
child_hull = child.callDecoration("getConvexHull")
|
||||
if not child_hull is None:
|
||||
if child_hull is not None:
|
||||
hull_verts = hull_verts.unionConvexHulls(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)
|
||||
|
||||
offset_verts = hull_head_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# 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 UM.Logger import Logger
|
||||
|
|
|
@ -136,7 +136,7 @@ class Backup:
|
|||
return False
|
||||
|
||||
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:
|
||||
# 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.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
from PyQt6.QtCore import QTimer
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -565,8 +565,8 @@ class BuildVolume(SceneNode):
|
|||
self._updateScaleFactor()
|
||||
|
||||
self._volume_aabb = AxisAlignedBox(
|
||||
minimum = Vector(min_w, min_h - 1.0, min_d).scale(self._scale_vector),
|
||||
maximum = Vector(max_w, max_h - self._raft_thickness - self._extra_z_clearance, max_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)
|
||||
)
|
||||
|
||||
bed_adhesion_size = self.getEdgeDisallowedSize()
|
||||
|
@ -575,8 +575,8 @@ class BuildVolume(SceneNode):
|
|||
# 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.
|
||||
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),
|
||||
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)
|
||||
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)
|
||||
)
|
||||
|
||||
self._application.getController().getScene()._maximum_bounds = scale_to_max_bounds # type: ignore
|
||||
|
@ -645,7 +645,7 @@ class BuildVolume(SceneNode):
|
|||
for extruder in extruders:
|
||||
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")
|
||||
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)
|
||||
|
@ -656,7 +656,7 @@ class BuildVolume(SceneNode):
|
|||
else:
|
||||
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
||||
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._updateDisallowedAreas()
|
||||
|
@ -752,8 +752,8 @@ class BuildVolume(SceneNode):
|
|||
return
|
||||
self._updateScaleFactor()
|
||||
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._depth = self._global_container_stack.getProperty("machine_depth", "value") * self._scale_vector.y
|
||||
self._width = self._global_container_stack.getProperty("machine_width", "value")
|
||||
self._depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||
self._shape = self._global_container_stack.getProperty("machine_shape", "value")
|
||||
|
||||
def _updateDisallowedAreasAndRebuild(self):
|
||||
|
@ -770,14 +770,6 @@ class BuildVolume(SceneNode):
|
|||
self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
|
||||
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:
|
||||
if not self._global_container_stack:
|
||||
return
|
||||
|
@ -818,11 +810,6 @@ class BuildVolume(SceneNode):
|
|||
break
|
||||
if prime_tower_collision: # Already found a collision.
|
||||
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:
|
||||
result_areas[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 = []
|
||||
for extruder_id in result_areas:
|
||||
self._scaleAreas(result_areas[extruder_id])
|
||||
self._disallowed_areas.extend(result_areas[extruder_id])
|
||||
self._disallowed_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])
|
||||
|
||||
def _computeDisallowedAreasPrinted(self, used_extruders):
|
||||
|
@ -850,9 +835,13 @@ class BuildVolume(SceneNode):
|
|||
|
||||
result = {}
|
||||
skirt_brim_extruder: ExtruderStack = None
|
||||
skirt_brim_extruder_nr = self._global_container_stack.getProperty("skirt_brim_extruder_nr", "value")
|
||||
|
||||
for extruder in used_extruders:
|
||||
if int(extruder.getProperty("extruder_nr", "value")) == int(self._global_container_stack.getProperty("skirt_brim_extruder_nr", "value")):
|
||||
skirt_brim_extruder = extruder
|
||||
if skirt_brim_extruder_nr == -1:
|
||||
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()] = []
|
||||
|
||||
# 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_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
|
||||
prime_tower_area = Polygon.approximatedCircle(radius, num_segments = 24)
|
||||
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_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 border_size - left_unreachable_border > 0:
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
|
@ -1083,7 +1066,7 @@ class BuildVolume(SceneNode):
|
|||
all_values[i] = 0
|
||||
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
|
||||
|
||||
:param adhesion_override: override adhesion type.
|
||||
|
@ -1093,52 +1076,12 @@ class BuildVolume(SceneNode):
|
|||
return None
|
||||
|
||||
container_stack = self._global_container_stack
|
||||
adhesion_type = adhesion_override
|
||||
if adhesion_type is None:
|
||||
adhesion_type = container_stack.getProperty("adhesion_type", "value")
|
||||
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
|
||||
# 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":
|
||||
if adhesion_type == "raft":
|
||||
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
|
||||
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(
|
||||
self._global_container_stack.getProperty("machine_width", "value"),
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
|
||||
from PyQt5.QtCore import QVariantAnimation, QEasingCurve
|
||||
from PyQt5.QtGui import QVector3D
|
||||
from PyQt6.QtCore import QVariantAnimation, QEasingCurve
|
||||
from PyQt6.QtGui import QVector3D
|
||||
|
||||
from UM.Math.Vector import Vector
|
||||
|
||||
|
@ -13,7 +13,7 @@ class CameraAnimation(QVariantAnimation):
|
|||
super().__init__(parent)
|
||||
self._camera_tool = None
|
||||
self.setDuration(300)
|
||||
self.setEasingCurve(QEasingCurve.OutQuad)
|
||||
self.setEasingCurve(QEasingCurve.Type.OutQuad)
|
||||
|
||||
def setCameraTool(self, camera_tool):
|
||||
self._camera_tool = camera_tool
|
||||
|
|
|
@ -20,9 +20,9 @@ try:
|
|||
except ImportError:
|
||||
with_sentry_sdk = False
|
||||
|
||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QUrl
|
||||
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt6.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QUrl
|
||||
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
|
||||
from PyQt6.QtGui import QDesktopServices
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
|
@ -136,8 +136,8 @@ class CrashHandler:
|
|||
|
||||
# "backup and start clean" and "close" buttons
|
||||
buttons = QDialogButtonBox()
|
||||
buttons.addButton(QDialogButtonBox.Close)
|
||||
buttons.addButton(catalog.i18nc("@action:button", "Backup and Reset Configuration"), QDialogButtonBox.AcceptRole)
|
||||
buttons.addButton(QDialogButtonBox.StandardButton.Close)
|
||||
buttons.addButton(catalog.i18nc("@action:button", "Backup and Reset Configuration"), QDialogButtonBox.ButtonRole.AcceptRole)
|
||||
buttons.rejected.connect(self._closeEarlyCrashDialog)
|
||||
buttons.accepted.connect(self._backupAndStartClean)
|
||||
|
||||
|
@ -161,7 +161,7 @@ class CrashHandler:
|
|||
QDesktopServices.openUrl(QUrl.fromLocalFile( path ))
|
||||
|
||||
def _showDetailedReport(self):
|
||||
self.dialog.exec_()
|
||||
self.dialog.exec()
|
||||
|
||||
def _createDialog(self):
|
||||
"""Creates a modal dialog."""
|
||||
|
@ -261,7 +261,7 @@ class CrashHandler:
|
|||
opengl_instance = OpenGL.getInstance()
|
||||
if not opengl_instance:
|
||||
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 += 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:
|
||||
with configure_scope() as scope:
|
||||
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_type", opengl_instance.getGPUType())
|
||||
scope.set_tag("active_machine", active_machine_definition_id)
|
||||
|
@ -409,12 +410,12 @@ class CrashHandler:
|
|||
|
||||
def _buttonsWidget(self):
|
||||
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
|
||||
# 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.
|
||||
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.rejected.connect(self.dialog.close)
|
||||
|
||||
|
@ -456,5 +457,5 @@ class CrashHandler:
|
|||
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
|
||||
if self.dialog:
|
||||
self.dialog.exec_()
|
||||
self.dialog.exec()
|
||||
os._exit(1)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt6.QtCore import QObject, QUrl
|
||||
from PyQt6.QtGui import QDesktopServices
|
||||
from typing import List, cast
|
||||
|
||||
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.
|
||||
|
||||
import enum
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
@ -8,10 +8,10 @@ import time
|
|||
from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict
|
||||
|
||||
import numpy
|
||||
from PyQt5.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
|
||||
from PyQt5.QtGui import QColor, QIcon
|
||||
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
from PyQt6.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, pyqtEnum, QCoreApplication
|
||||
from PyQt6.QtGui import QColor, QIcon
|
||||
from PyQt6.QtQml import qmlRegisterUncreatableType, qmlRegisterUncreatableMetaObject, qmlRegisterSingletonType, qmlRegisterType
|
||||
from PyQt6.QtWidgets import QMessageBox
|
||||
|
||||
import UM.Util
|
||||
import cura.Settings.cura_empty_instance_containers
|
||||
|
@ -115,6 +115,10 @@ from . import CuraActions
|
|||
from . import PlatformPhysics
|
||||
from . import PrintJobPreviewImageProvider
|
||||
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
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -122,16 +126,15 @@ if TYPE_CHECKING:
|
|||
|
||||
numpy.seterr(all = "ignore")
|
||||
|
||||
|
||||
class CuraApplication(QtApplication):
|
||||
# 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
|
||||
# changes of the settings.
|
||||
SettingVersion = 19
|
||||
SettingVersion = 20
|
||||
|
||||
Created = False
|
||||
|
||||
class ResourceTypes:
|
||||
class ResourceTypes(enum.IntEnum):
|
||||
QmlFiles = Resources.UserType + 1
|
||||
Firmware = Resources.UserType + 2
|
||||
QualityInstanceContainer = Resources.UserType + 3
|
||||
|
@ -145,12 +148,13 @@ class CuraApplication(QtApplication):
|
|||
SettingVisibilityPreset = Resources.UserType + 11
|
||||
IntentInstanceContainer = Resources.UserType + 12
|
||||
|
||||
Q_ENUMS(ResourceTypes)
|
||||
pyqtEnum(ResourceTypes)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(name = ApplicationMetadata.CuraAppName,
|
||||
app_display_name = ApplicationMetadata.CuraAppDisplayName,
|
||||
version = ApplicationMetadata.CuraVersion if not ApplicationMetadata.IsAlternateVersion else ApplicationMetadata.CuraBuildType,
|
||||
latest_url = ApplicationMetadata.CuraLatestURL,
|
||||
api_version = ApplicationMetadata.CuraSDKVersion,
|
||||
build_type = ApplicationMetadata.CuraBuildType,
|
||||
is_debug_mode = ApplicationMetadata.CuraDebugMode,
|
||||
|
@ -258,6 +262,7 @@ class CuraApplication(QtApplication):
|
|||
|
||||
from UM.CentralFileStorage import CentralFileStorage
|
||||
CentralFileStorage.setIsEnterprise(ApplicationMetadata.IsEnterpriseVersion)
|
||||
Resources.setIsEnterprise(ApplicationMetadata.IsEnterpriseVersion)
|
||||
|
||||
@pyqtProperty(str, constant=True)
|
||||
def ultimakerCloudApiRootUrl(self) -> str:
|
||||
|
@ -316,7 +321,7 @@ class CuraApplication(QtApplication):
|
|||
def initialize(self) -> None:
|
||||
self.__addExpectedResourceDirsAndSearchPaths() # Must be added before init of super
|
||||
|
||||
super().initialize()
|
||||
super().initialize(ApplicationMetadata.IsEnterpriseVersion)
|
||||
|
||||
self._preferences.addPreference("cura/single_instance", False)
|
||||
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)
|
||||
|
||||
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"):
|
||||
resource_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")
|
||||
Resources.addSearchPath(resource_path)
|
||||
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources"))
|
||||
|
||||
# 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
|
||||
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.
|
||||
self.getMachineManager().setActiveMachine(None)
|
||||
|
||||
QtApplication.getInstance().closeAllWindows()
|
||||
|
||||
main_window = self.getMainWindow()
|
||||
if main_window is not None:
|
||||
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.
|
||||
# Use this to close the application.
|
||||
|
@ -678,6 +692,22 @@ class CuraApplication(QtApplication):
|
|||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing Active Machine..."))
|
||||
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()
|
||||
|
||||
def discardOrKeepProfileChanges(self) -> bool:
|
||||
|
@ -793,6 +823,12 @@ class CuraApplication(QtApplication):
|
|||
def run(self):
|
||||
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")
|
||||
self._machine_error_checker = MachineErrorChecker(self)
|
||||
self._machine_error_checker.initialize()
|
||||
|
@ -858,7 +894,7 @@ class CuraApplication(QtApplication):
|
|||
self._auto_save = AutoSave(self)
|
||||
self._auto_save.initialize()
|
||||
|
||||
self.exec_()
|
||||
self.exec()
|
||||
|
||||
def __setUpSingleInstanceServer(self):
|
||||
if self._use_single_instance:
|
||||
|
@ -924,6 +960,7 @@ class CuraApplication(QtApplication):
|
|||
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
|
||||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing engine..."))
|
||||
self.initializeEngine()
|
||||
self.getTheme().setCheckIfTrusted(ApplicationMetadata.IsEnterpriseVersion)
|
||||
|
||||
# Initialize UI state
|
||||
controller.setActiveStage("PrepareStage")
|
||||
|
@ -1075,12 +1112,18 @@ class CuraApplication(QtApplication):
|
|||
def event(self, event):
|
||||
"""Handle Qt events"""
|
||||
|
||||
if event.type() == QEvent.FileOpen:
|
||||
if event.type() == QEvent.Type.FileOpen:
|
||||
if self._plugins_loaded:
|
||||
self._openFile(event.file())
|
||||
else:
|
||||
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)
|
||||
|
||||
def getAutoSave(self) -> Optional[AutoSave]:
|
||||
|
@ -1121,16 +1164,16 @@ class CuraApplication(QtApplication):
|
|||
engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
|
||||
|
||||
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()
|
||||
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 0, "SceneController", self.getCuraSceneController)
|
||||
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
|
||||
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
||||
qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, "IntentManager", self.getIntentManager)
|
||||
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
|
||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
|
||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
||||
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 0, self.getCuraSceneController, "SceneController")
|
||||
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, self.getExtruderManager, "ExtruderManager")
|
||||
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, self.getMachineManager, "MachineManager")
|
||||
qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, self.getIntentManager, "IntentManager")
|
||||
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, self.getSettingInheritanceManager, "SettingInheritanceManager")
|
||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, self.getSimpleModeSettingsManager, "SimpleModeSettingsManager")
|
||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, self.getMachineActionManager, "MachineActionManager")
|
||||
|
||||
self.processEvents()
|
||||
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
|
||||
|
@ -1148,24 +1191,28 @@ class CuraApplication(QtApplication):
|
|||
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
||||
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
||||
qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel")
|
||||
qmlRegisterType(MachineListModel, "Cura", 1, 0, "MachineListModel")
|
||||
qmlRegisterType(CompatibleMachineModel, "Cura", 1, 0, "CompatibleMachineModel")
|
||||
|
||||
self.processEvents()
|
||||
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
|
||||
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
|
||||
qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel")
|
||||
qmlRegisterSingletonType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel", self.getQualityManagementModel)
|
||||
qmlRegisterSingletonType(MaterialManagementModel, "Cura", 1, 5, "MaterialManagementModel", self.getMaterialManagementModel)
|
||||
qmlRegisterSingletonType(QualityManagementModel, "Cura", 1, 0, self.getQualityManagementModel, "QualityManagementModel")
|
||||
qmlRegisterSingletonType(MaterialManagementModel, "Cura", 1, 5, self.getMaterialManagementModel, "MaterialManagementModel")
|
||||
|
||||
self.processEvents()
|
||||
qmlRegisterType(DiscoveredPrintersModel, "Cura", 1, 0, "DiscoveredPrintersModel")
|
||||
qmlRegisterType(DiscoveredCloudPrintersModel, "Cura", 1, 7, "DiscoveredCloudPrintersModel")
|
||||
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
||||
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
|
||||
self.getQualityProfilesDropDownMenuModel, "QualityProfilesDropDownMenuModel")
|
||||
qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
||||
"CustomQualityProfilesDropDownMenuModel", self.getCustomQualityProfilesDropDownMenuModel)
|
||||
self.getCustomQualityProfilesDropDownMenuModel, "CustomQualityProfilesDropDownMenuModel")
|
||||
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
|
||||
qmlRegisterType(IntentModel, "Cura", 1, 6, "IntentModel")
|
||||
qmlRegisterType(IntentCategoryModel, "Cura", 1, 6, "IntentCategoryModel")
|
||||
qmlRegisterType(IntentSelectionModel, "Cura", 1, 7, "IntentSelectionModel")
|
||||
qmlRegisterType(ActiveIntentQualitiesModel, "Cura", 1, 7, "ActiveIntentQualitiesModel")
|
||||
|
||||
self.processEvents()
|
||||
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
||||
|
@ -1174,14 +1221,14 @@ class CuraApplication(QtApplication):
|
|||
qmlRegisterType(FirstStartMachineActionsModel, "Cura", 1, 0, "FirstStartMachineActionsModel")
|
||||
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
|
||||
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(PrinterOutputDevice, "Cura", 1, 0, "PrinterOutputDevice")
|
||||
|
||||
from cura.API import CuraAPI
|
||||
qmlRegisterSingletonType(CuraAPI, "Cura", 1, 1, "API", self.getCuraAPI)
|
||||
qmlRegisterUncreatableType(Account, "Cura", 1, 0, "AccountSyncState", "Could not create AccountSyncState")
|
||||
qmlRegisterSingletonType(CuraAPI, "Cura", 1, 1, self.getCuraAPI, "API")
|
||||
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.
|
||||
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()
|
||||
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
|
||||
if UM.Util.parseBool(node.getSetting(SceneNodeSettings.LockPosition)):
|
||||
if node.getSetting(SceneNodeSettings.LockPosition):
|
||||
locked_nodes.append(node)
|
||||
else:
|
||||
nodes_to_arrange.append(node)
|
||||
|
|
|
@ -1,19 +1,26 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# 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 UM.Logger import Logger
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from cura.CuraApplication import CuraApplication # To find some resource types.
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
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.i18n import i18nCatalog
|
||||
from urllib.parse import unquote_plus
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Qt.QtApplication import QtApplication
|
||||
from PyQt5.QtCore import QObject
|
||||
from PyQt6.QtCore import QObject
|
||||
|
||||
|
||||
class CuraPackageManager(PackageManager):
|
||||
|
@ -48,9 +55,62 @@ class CuraPackageManager(PackageManager):
|
|||
def initialize(self) -> None:
|
||||
self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer)
|
||||
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()
|
||||
|
||||
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]]]:
|
||||
"""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.
|
||||
# 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.View.View import View
|
||||
|
|
|
@ -24,9 +24,12 @@ class LayerPolygon:
|
|||
PrimeTowerType = 11
|
||||
__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
|
||||
|
||||
:param extruder: The position of the extruder
|
||||
|
@ -39,10 +42,12 @@ class LayerPolygon:
|
|||
|
||||
self._extruder = extruder
|
||||
self._types = line_types
|
||||
for i in range(len(self._types)):
|
||||
if self._types[i] >= self.__number_of_types: # Got faulty line data from the engine.
|
||||
Logger.log("w", "Found an unknown line type: %s", i)
|
||||
self._types[i] = self.NoneType
|
||||
unknown_types = numpy.where(self._types >= self.__number_of_types, self._types, None)
|
||||
if unknown_types.any():
|
||||
# Got faulty line data from the engine.
|
||||
for idx in unknown_types:
|
||||
Logger.warning(f"Found an unknown line type at: {idx}")
|
||||
self._types[idx] = self.NoneType
|
||||
self._data = data
|
||||
self._line_widths = line_widths
|
||||
self._line_thicknesses = line_thicknesses
|
||||
|
@ -63,9 +68,11 @@ class LayerPolygon:
|
|||
self._color_map = LayerPolygon.getColorMap()
|
||||
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.
|
||||
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_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
|
||||
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
|
||||
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_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
|
||||
|
||||
The arrays are either by vertex or by indices.
|
||||
|
@ -111,10 +120,11 @@ class LayerPolygon:
|
|||
line_mesh_mask = self._build_cache_line_mesh_mask
|
||||
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
|
||||
# start and end points for each line. For line segment n these are points n and n+1. Row n reads [n n+1]
|
||||
# Then 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))]
|
||||
# Index to the points we need to represent the line mesh.
|
||||
# This is constructed by generating simple start and end points for each line.
|
||||
# For line segment n, these are points n and n+1. Row n reads [n n+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.
|
||||
self._vertex_begin += vertex_offset
|
||||
|
@ -138,11 +148,12 @@ class LayerPolygon:
|
|||
# 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]
|
||||
|
||||
# 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_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.
|
||||
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.
|
||||
|
@ -214,13 +225,12 @@ class LayerPolygon:
|
|||
"""
|
||||
|
||||
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.
|
||||
# The call to numpy.roll shifts the entire array by one so that
|
||||
# we end up subtracting each next point from the current, wrapping
|
||||
# around. This gives us the edges from the next point to the current
|
||||
# point.
|
||||
# The call to numpy.roll shifts the entire array by one
|
||||
# so that we end up subtracting each next point from the current, wrapping around.
|
||||
# This gives us the edges from the next point to the current point.
|
||||
normals = numpy.diff(normals, 1, 0)
|
||||
|
||||
# Calculate the length of each edge using standard Pythagoras
|
||||
|
@ -245,17 +255,17 @@ class LayerPolygon:
|
|||
if cls.__color_map is None:
|
||||
theme = cast(Theme, QtApplication.getInstance().getTheme())
|
||||
cls.__color_map = numpy.array([
|
||||
theme.getColor("layerview_none").getRgbF(), # NoneType
|
||||
theme.getColor("layerview_inset_0").getRgbF(), # Inset0Type
|
||||
theme.getColor("layerview_inset_x").getRgbF(), # InsetXType
|
||||
theme.getColor("layerview_skin").getRgbF(), # SkinType
|
||||
theme.getColor("layerview_support").getRgbF(), # SupportType
|
||||
theme.getColor("layerview_skirt").getRgbF(), # SkirtType
|
||||
theme.getColor("layerview_infill").getRgbF(), # InfillType
|
||||
theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType
|
||||
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
|
||||
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
|
||||
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
|
||||
theme.getColor("layerview_none").getRgbF(), # NoneType
|
||||
theme.getColor("layerview_inset_0").getRgbF(), # Inset0Type
|
||||
theme.getColor("layerview_inset_x").getRgbF(), # InsetXType
|
||||
theme.getColor("layerview_skin").getRgbF(), # SkinType
|
||||
theme.getColor("layerview_support").getRgbF(), # SupportType
|
||||
theme.getColor("layerview_skirt").getRgbF(), # SkirtType
|
||||
theme.getColor("layerview_infill").getRgbF(), # InfillType
|
||||
theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType
|
||||
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
|
||||
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
|
||||
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
|
||||
theme.getColor("layerview_prime_tower").getRgbF() # PrimeTowerType
|
||||
])
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import os
|
||||
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.PluginObject import PluginObject
|
||||
|
@ -33,8 +33,11 @@ class MachineAction(QObject, PluginObject):
|
|||
self._qml_url = ""
|
||||
self._view = None
|
||||
self._finished = False
|
||||
self._open_as_dialog = True
|
||||
self._visible = True
|
||||
|
||||
labelChanged = pyqtSignal()
|
||||
visibilityChanged = pyqtSignal()
|
||||
onFinished = pyqtSignal()
|
||||
|
||||
def getKey(self) -> str:
|
||||
|
@ -79,6 +82,15 @@ class MachineAction(QObject, PluginObject):
|
|||
|
||||
pass
|
||||
|
||||
@pyqtSlot()
|
||||
def execute(self) -> None:
|
||||
self._execute()
|
||||
|
||||
def _execute(self) -> None:
|
||||
"""Protected implementation of execute."""
|
||||
|
||||
pass
|
||||
|
||||
@pyqtSlot()
|
||||
def setFinished(self) -> None:
|
||||
self._finished = True
|
||||
|
@ -94,7 +106,7 @@ class MachineAction(QObject, PluginObject):
|
|||
|
||||
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
||||
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
|
||||
path = os.path.join(plugin_path, self._qml_url)
|
||||
|
||||
|
@ -106,7 +118,7 @@ class MachineAction(QObject, PluginObject):
|
|||
def qmlPath(self) -> "QUrl":
|
||||
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
||||
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("")
|
||||
path = os.path.join(plugin_path, self._qml_url)
|
||||
return QUrl.fromLocalFile(path)
|
||||
|
@ -114,3 +126,30 @@ class MachineAction(QObject, PluginObject):
|
|||
@pyqtSlot(result = QObject)
|
||||
def getDisplayItem(self) -> Optional["QObject"]:
|
||||
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 PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
|
||||
from PyQt6.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
|
||||
from typing import Optional, Any, Set
|
||||
|
||||
from UM.Logger import Logger
|
||||
|
@ -43,16 +43,14 @@ class MachineErrorChecker(QObject):
|
|||
self._application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
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
|
||||
# changing settings.
|
||||
self._error_check_timer = QTimer(self)
|
||||
self._error_check_timer.setInterval(100)
|
||||
self._error_check_timer.setSingleShot(True)
|
||||
self._setCheckTimer()
|
||||
|
||||
self._keys_to_check = set() # type: Set[str]
|
||||
|
||||
self._num_keys_to_check_per_update = 10
|
||||
|
||||
def initialize(self) -> None:
|
||||
self._error_check_timer.timeout.connect(self._rescheduleCheck)
|
||||
|
||||
|
@ -64,6 +62,18 @@ class MachineErrorChecker(QObject):
|
|||
|
||||
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:
|
||||
if self._global_stack:
|
||||
self._global_stack.propertyChanged.disconnect(self.startErrorCheckPropertyChanged)
|
||||
|
@ -150,7 +160,7 @@ class MachineErrorChecker(QObject):
|
|||
self._stacks_and_keys_to_check.append((stack, key))
|
||||
|
||||
self._application.callLater(self._checkStack)
|
||||
self._start_time = time.time()
|
||||
self._check_start_time = time.time()
|
||||
Logger.log("d", "New error check scheduled.")
|
||||
|
||||
def _checkStack(self) -> None:
|
||||
|
@ -162,37 +172,37 @@ class MachineErrorChecker(QObject):
|
|||
|
||||
self._check_in_progress = True
|
||||
|
||||
# If there is nothing to check any more, it means there is no error.
|
||||
if not self._stacks_and_keys_to_check:
|
||||
# Finish
|
||||
self._setResult(False)
|
||||
return
|
||||
for i in range(self._num_keys_to_check_per_update):
|
||||
# If there is nothing to check any more, it means there is no error.
|
||||
if not self._stacks_and_keys_to_check:
|
||||
# Finish
|
||||
self._setResult(False)
|
||||
return
|
||||
|
||||
# Get the next stack and key to check
|
||||
stack, key = self._stacks_and_keys_to_check.popleft()
|
||||
# Get the next stack and key to check
|
||||
stack, key = self._stacks_and_keys_to_check.popleft()
|
||||
|
||||
enabled = stack.getProperty(key, "enabled")
|
||||
if not enabled:
|
||||
self._application.callLater(self._checkStack)
|
||||
return
|
||||
enabled = stack.getProperty(key, "enabled")
|
||||
if not enabled:
|
||||
continue
|
||||
|
||||
validation_state = stack.getProperty(key, "validationState")
|
||||
if validation_state is None:
|
||||
# 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
|
||||
# be an invalid setting.
|
||||
definition = stack.getSettingDefinition(key)
|
||||
validator_type = SettingDefinition.getValidatorForType(definition.type)
|
||||
if validator_type:
|
||||
validator = validator_type(key)
|
||||
validation_state = validator(stack)
|
||||
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
|
||||
# next check.
|
||||
keys_to_recheck = {setting_key for stack, setting_key in self._stacks_and_keys_to_check}
|
||||
keys_to_recheck.add(key)
|
||||
self._setResult(True, keys_to_recheck = keys_to_recheck)
|
||||
return
|
||||
validation_state = stack.getProperty(key, "validationState")
|
||||
if validation_state is None:
|
||||
# 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
|
||||
# be an invalid setting.
|
||||
definition = stack.getSettingDefinition(key)
|
||||
validator_type = SettingDefinition.getValidatorForType(definition.type)
|
||||
if validator_type:
|
||||
validator = validator_type(key)
|
||||
validation_state = validator(stack)
|
||||
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
|
||||
# next check.
|
||||
keys_to_recheck = {setting_key for stack, setting_key in self._stacks_and_keys_to_check}
|
||||
keys_to_recheck.add(key)
|
||||
self._setResult(True, keys_to_recheck = keys_to_recheck)
|
||||
return
|
||||
|
||||
# Schedule the check for the next key
|
||||
self._application.callLater(self._checkStack)
|
||||
|
@ -202,12 +212,10 @@ class MachineErrorChecker(QObject):
|
|||
self._has_errors = result
|
||||
self.hasErrorUpdated.emit()
|
||||
self._machine_manager.stacksValidationChanged.emit()
|
||||
if keys_to_recheck is None:
|
||||
self._keys_to_check = set()
|
||||
else:
|
||||
self._keys_to_check = keys_to_recheck
|
||||
self._keys_to_check = keys_to_recheck if keys_to_recheck else set()
|
||||
self._need_to_check = False
|
||||
self._check_in_progress = False
|
||||
self.needToWaitForResultChanged.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:
|
||||
# CURA-6599
|
||||
# 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.
|
||||
from cura.CuraApplication import CuraApplication
|
||||
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 PyQt5.QtCore import Qt, QTimer, pyqtSignal, pyqtProperty
|
||||
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, pyqtProperty
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Logger import Logger
|
||||
|
@ -61,22 +61,22 @@ class BaseMaterialsModel(ListModel):
|
|||
ContainerTree.getInstance().materialsChanged.connect(self._materialsListChanged)
|
||||
self._application.getMaterialManagementModel().favoritesChanged.connect(self._onChanged)
|
||||
|
||||
self.addRoleName(Qt.UserRole + 1, "root_material_id")
|
||||
self.addRoleName(Qt.UserRole + 2, "id")
|
||||
self.addRoleName(Qt.UserRole + 3, "GUID")
|
||||
self.addRoleName(Qt.UserRole + 4, "name")
|
||||
self.addRoleName(Qt.UserRole + 5, "brand")
|
||||
self.addRoleName(Qt.UserRole + 6, "description")
|
||||
self.addRoleName(Qt.UserRole + 7, "material")
|
||||
self.addRoleName(Qt.UserRole + 8, "color_name")
|
||||
self.addRoleName(Qt.UserRole + 9, "color_code")
|
||||
self.addRoleName(Qt.UserRole + 10, "density")
|
||||
self.addRoleName(Qt.UserRole + 11, "diameter")
|
||||
self.addRoleName(Qt.UserRole + 12, "approximate_diameter")
|
||||
self.addRoleName(Qt.UserRole + 13, "adhesion_info")
|
||||
self.addRoleName(Qt.UserRole + 14, "is_read_only")
|
||||
self.addRoleName(Qt.UserRole + 15, "container_node")
|
||||
self.addRoleName(Qt.UserRole + 16, "is_favorite")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 1, "root_material_id")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 2, "id")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 3, "GUID")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 4, "name")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 5, "brand")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 6, "description")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 7, "material")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 8, "color_name")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 9, "color_code")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 10, "density")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 11, "diameter")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 12, "approximate_diameter")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 13, "adhesion_info")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 14, "is_read_only")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 15, "container_node")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 16, "is_favorite")
|
||||
|
||||
def _onChanged(self) -> None:
|
||||
self._update_timer.start()
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# 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.Qt.ListModel import ListModel
|
||||
|
||||
|
||||
class BuildPlateModel(ListModel):
|
||||
NameRole = Qt.UserRole + 1
|
||||
ContainerNodeRole = Qt.UserRole + 2
|
||||
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||
ContainerNodeRole = Qt.ItemDataRole.UserRole + 2
|
||||
|
||||
def __init__(self, parent = None):
|
||||
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
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from PyQt5.QtCore import QObject
|
||||
from PyQt6.QtCore import QObject
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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
|
||||
|
||||
|
@ -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
|
||||
account """
|
||||
|
||||
DeviceKeyRole = Qt.UserRole + 1
|
||||
DeviceNameRole = Qt.UserRole + 2
|
||||
DeviceTypeRole = Qt.UserRole + 3
|
||||
DeviceFirmwareVersionRole = Qt.UserRole + 4
|
||||
DeviceKeyRole = Qt.ItemDataRole.UserRole + 1
|
||||
DeviceNameRole = Qt.ItemDataRole.UserRole + 2
|
||||
DeviceTypeRole = Qt.ItemDataRole.UserRole + 3
|
||||
DeviceFirmwareVersionRole = Qt.ItemDataRole.UserRole + 4
|
||||
|
||||
cloudPrintersDetectedChanged = pyqtSignal(bool)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
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.Logger import Logger
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# 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 UM.i18n import i18nCatalog
|
||||
|
@ -23,43 +23,43 @@ class ExtrudersModel(ListModel):
|
|||
"""
|
||||
|
||||
# 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."""
|
||||
|
||||
ColorRole = Qt.UserRole + 3
|
||||
ColorRole = Qt.ItemDataRole.UserRole + 3
|
||||
"""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.
|
||||
|
||||
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. """
|
||||
|
||||
# The ID of the definition of the extruder.
|
||||
DefinitionRole = Qt.UserRole + 5
|
||||
DefinitionRole = Qt.ItemDataRole.UserRole + 5
|
||||
|
||||
# The material of the extruder.
|
||||
MaterialRole = Qt.UserRole + 6
|
||||
MaterialRole = Qt.ItemDataRole.UserRole + 6
|
||||
|
||||
# The variant of the extruder.
|
||||
VariantRole = Qt.UserRole + 7
|
||||
StackRole = Qt.UserRole + 8
|
||||
VariantRole = Qt.ItemDataRole.UserRole + 7
|
||||
StackRole = Qt.ItemDataRole.UserRole + 8
|
||||
|
||||
MaterialBrandRole = Qt.UserRole + 9
|
||||
ColorNameRole = Qt.UserRole + 10
|
||||
MaterialBrandRole = Qt.ItemDataRole.UserRole + 9
|
||||
ColorNameRole = Qt.ItemDataRole.UserRole + 10
|
||||
|
||||
EnabledRole = Qt.UserRole + 11
|
||||
EnabledRole = Qt.ItemDataRole.UserRole + 11
|
||||
"""Is the extruder enabled?"""
|
||||
|
||||
MaterialTypeRole = Qt.UserRole + 12
|
||||
MaterialTypeRole = Qt.ItemDataRole.UserRole + 12
|
||||
"""The type of the material (e.g. PLA, ABS, PETG, etc.)."""
|
||||
|
||||
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
|
||||
"""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):
|
||||
"""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 PyQt5.QtCore import QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from PyQt6.QtCore import QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
|
@ -19,9 +19,9 @@ class FirstStartMachineActionsModel(ListModel):
|
|||
- action : the MachineAction object itself
|
||||
"""
|
||||
|
||||
TitleRole = Qt.UserRole + 1
|
||||
ContentRole = Qt.UserRole + 2
|
||||
ActionRole = Qt.UserRole + 3
|
||||
TitleRole = Qt.ItemDataRole.UserRole + 1
|
||||
ContentRole = Qt.ItemDataRole.UserRole + 2
|
||||
ActionRole = Qt.ItemDataRole.UserRole + 3
|
||||
|
||||
def __init__(self, application: "CuraApplication", parent: Optional[QObject] = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# 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 UM.Qt.ListModel import ListModel
|
||||
|
@ -15,14 +15,14 @@ from cura.UltimakerCloud.UltimakerCloudConstants import META_CAPABILITIES # To
|
|||
|
||||
|
||||
class GlobalStacksModel(ListModel):
|
||||
NameRole = Qt.UserRole + 1
|
||||
IdRole = Qt.UserRole + 2
|
||||
HasRemoteConnectionRole = Qt.UserRole + 3
|
||||
ConnectionTypeRole = Qt.UserRole + 4
|
||||
MetaDataRole = Qt.UserRole + 5
|
||||
DiscoverySourceRole = Qt.UserRole + 6 # For separating local and remote printers in the machine management page
|
||||
RemovalWarningRole = Qt.UserRole + 7
|
||||
IsOnlineRole = Qt.UserRole + 8
|
||||
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||
IdRole = Qt.ItemDataRole.UserRole + 2
|
||||
HasRemoteConnectionRole = Qt.ItemDataRole.UserRole + 3
|
||||
ConnectionTypeRole = Qt.ItemDataRole.UserRole + 4
|
||||
MetaDataRole = Qt.ItemDataRole.UserRole + 5
|
||||
DiscoverySourceRole = Qt.ItemDataRole.UserRole + 6 # For separating local and remote printers in the machine management page
|
||||
RemovalWarningRole = Qt.ItemDataRole.UserRole + 7
|
||||
IsOnlineRole = Qt.ItemDataRole.UserRole + 8
|
||||
|
||||
def __init__(self, parent = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
@ -44,6 +44,7 @@ class GlobalStacksModel(ListModel):
|
|||
self._filter_connection_type = None # type: Optional[ConnectionType]
|
||||
self._filter_online_only = False
|
||||
self._filter_capabilities: List[str] = [] # Required capabilities that all listed printers must have.
|
||||
self._filter_abstract_machines: Optional[bool] = None
|
||||
|
||||
# Listen to changes
|
||||
CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
|
||||
|
@ -54,6 +55,7 @@ class GlobalStacksModel(ListModel):
|
|||
filterConnectionTypeChanged = pyqtSignal()
|
||||
filterCapabilitiesChanged = pyqtSignal()
|
||||
filterOnlineOnlyChanged = pyqtSignal()
|
||||
filterAbstractMachinesChanged = pyqtSignal()
|
||||
|
||||
def setFilterConnectionType(self, new_filter: Optional[ConnectionType]) -> None:
|
||||
if self._filter_connection_type != new_filter:
|
||||
|
@ -98,6 +100,22 @@ class GlobalStacksModel(ListModel):
|
|||
"""
|
||||
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:
|
||||
"""Handler for container added/removed events from registry"""
|
||||
|
||||
|
@ -130,12 +148,16 @@ class GlobalStacksModel(ListModel):
|
|||
if self._filter_online_only and not is_online:
|
||||
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(","))
|
||||
if set(self._filter_capabilities) - capabilities: # Not all required capabilities are met.
|
||||
continue
|
||||
|
||||
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)
|
||||
|
||||
default_removal_warning = self._catalog.i18nc(
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
#Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import collections
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt6.QtCore import Qt, QTimer
|
||||
from typing import TYPE_CHECKING, Optional, Dict
|
||||
|
||||
from cura.Machines.Models.IntentModel import IntentModel
|
||||
from cura.Settings.IntentManager import IntentManager
|
||||
from UM.Qt.ListModel import ListModel
|
||||
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
|
||||
if TYPE_CHECKING:
|
||||
from UM.Settings.ContainerRegistry import ContainerInterface
|
||||
|
@ -21,11 +21,11 @@ catalog = i18nCatalog("cura")
|
|||
class IntentCategoryModel(ListModel):
|
||||
"""Lists the intent categories that are available for the current printer configuration. """
|
||||
|
||||
NameRole = Qt.UserRole + 1
|
||||
IntentCategoryRole = Qt.UserRole + 2
|
||||
WeightRole = Qt.UserRole + 3
|
||||
QualitiesRole = Qt.UserRole + 4
|
||||
DescriptionRole = Qt.UserRole + 5
|
||||
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||
IntentCategoryRole = Qt.ItemDataRole.UserRole + 2
|
||||
WeightRole = Qt.ItemDataRole.UserRole + 3
|
||||
QualitiesRole = Qt.ItemDataRole.UserRole + 4
|
||||
DescriptionRole = Qt.ItemDataRole.UserRole + 5
|
||||
|
||||
modelUpdated = pyqtSignal()
|
||||
|
||||
|
@ -111,7 +111,7 @@ class IntentCategoryModel(ListModel):
|
|||
except ValueError:
|
||||
weight = 99
|
||||
result.append({
|
||||
"name": IntentCategoryModel.translation(category, "name", category),
|
||||
"name": IntentCategoryModel.translation(category, "name", category.title()),
|
||||
"description": IntentCategoryModel.translation(category, "description", None),
|
||||
"intent_category": category,
|
||||
"weight": weight,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
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
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
@ -15,11 +15,11 @@ from cura.Machines.QualityGroup import QualityGroup
|
|||
|
||||
|
||||
class IntentModel(ListModel):
|
||||
NameRole = Qt.UserRole + 1
|
||||
QualityTypeRole = Qt.UserRole + 2
|
||||
LayerHeightRole = Qt.UserRole + 3
|
||||
AvailableRole = Qt.UserRole + 4
|
||||
IntentRole = Qt.UserRole + 5
|
||||
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||
QualityTypeRole = Qt.ItemDataRole.UserRole + 2
|
||||
LayerHeightRole = Qt.ItemDataRole.UserRole + 3
|
||||
AvailableRole = Qt.ItemDataRole.UserRole + 4
|
||||
IntentRole = Qt.ItemDataRole.UserRole + 5
|
||||
|
||||
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||
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.
|
||||
# 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 cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||
|
||||
|
@ -9,10 +11,11 @@ class MaterialTypesModel(ListModel):
|
|||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
QQmlEngine.setObjectOwnership(self, QQmlEngine.ObjectOwnership.CppOwnership)
|
||||
|
||||
self.addRoleName(Qt.UserRole + 1, "name")
|
||||
self.addRoleName(Qt.UserRole + 2, "brand")
|
||||
self.addRoleName(Qt.UserRole + 3, "colors")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 1, "name")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 2, "brand")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 3, "colors")
|
||||
|
||||
class MaterialBrandsModel(BaseMaterialsModel):
|
||||
|
||||
|
@ -20,9 +23,10 @@ class MaterialBrandsModel(BaseMaterialsModel):
|
|||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
QQmlEngine.setObjectOwnership(self, QQmlEngine.ObjectOwnership.CppOwnership)
|
||||
|
||||
self.addRoleName(Qt.UserRole + 1, "name")
|
||||
self.addRoleName(Qt.UserRole + 2, "material_types")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 1, "name")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 2, "material_types")
|
||||
|
||||
self._update()
|
||||
|
||||
|
@ -74,16 +78,15 @@ class MaterialBrandsModel(BaseMaterialsModel):
|
|||
material_type_item_list = []
|
||||
brand_item = {
|
||||
"name": brand,
|
||||
"material_types": MaterialTypesModel(self)
|
||||
"material_types": MaterialTypesModel()
|
||||
}
|
||||
|
||||
for material_type, material_list in material_dict.items():
|
||||
material_type_item = {
|
||||
"name": material_type,
|
||||
"brand": brand,
|
||||
"colors": BaseMaterialsModel(self)
|
||||
"colors": BaseMaterialsModel()
|
||||
}
|
||||
material_type_item["colors"].clear()
|
||||
|
||||
# Sort materials by name
|
||||
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.
|
||||
|
||||
import copy # To duplicate materials.
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt6.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
|
||||
from PyQt6.QtGui import QDesktopServices
|
||||
from typing import Any, Dict, Optional, TYPE_CHECKING
|
||||
import uuid # To generate new GUIDs for new materials.
|
||||
|
||||
|
@ -34,62 +34,6 @@ class MaterialManagementModel(QObject):
|
|||
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||
super().__init__(parent = parent)
|
||||
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)
|
||||
def canMaterialBeRemoved(self, material_node: "MaterialNode") -> bool:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# 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.Scene.Camera import Camera
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# 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.Qt.ListModel import ListModel
|
||||
|
@ -10,9 +10,9 @@ from cura.Machines.ContainerTree import ContainerTree
|
|||
|
||||
|
||||
class NozzleModel(ListModel):
|
||||
IdRole = Qt.UserRole + 1
|
||||
HotendNameRole = Qt.UserRole + 2
|
||||
ContainerNodeRole = Qt.UserRole + 3
|
||||
IdRole = Qt.ItemDataRole.UserRole + 1
|
||||
HotendNameRole = Qt.ItemDataRole.UserRole + 2
|
||||
ContainerNodeRole = Qt.ItemDataRole.UserRole + 3
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
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.Qt.ListModel import ListModel
|
||||
|
@ -29,13 +29,13 @@ if TYPE_CHECKING:
|
|||
class QualityManagementModel(ListModel):
|
||||
"""This the QML model for the quality management page."""
|
||||
|
||||
NameRole = Qt.UserRole + 1
|
||||
IsReadOnlyRole = Qt.UserRole + 2
|
||||
QualityGroupRole = Qt.UserRole + 3
|
||||
QualityTypeRole = Qt.UserRole + 4
|
||||
QualityChangesGroupRole = Qt.UserRole + 5
|
||||
IntentCategoryRole = Qt.UserRole + 6
|
||||
SectionNameRole = Qt.UserRole + 7
|
||||
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||
IsReadOnlyRole = Qt.ItemDataRole.UserRole + 2
|
||||
QualityGroupRole = Qt.ItemDataRole.UserRole + 3
|
||||
QualityTypeRole = Qt.ItemDataRole.UserRole + 4
|
||||
QualityChangesGroupRole = Qt.ItemDataRole.UserRole + 5
|
||||
IntentCategoryRole = Qt.ItemDataRole.UserRole + 6
|
||||
SectionNameRole = Qt.ItemDataRole.UserRole + 7
|
||||
|
||||
def __init__(self, parent: Optional["QObject"] = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
@ -358,8 +358,9 @@ class QualityManagementModel(ListModel):
|
|||
"quality_type": quality_type,
|
||||
"quality_changes_group": None,
|
||||
"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
|
||||
intent_translations_list = list(intent_translations)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# 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.
|
||||
from UM.Logger import Logger
|
||||
|
@ -13,14 +13,14 @@ from cura.Machines.Models.MachineModelUtils import fetchLayerHeight
|
|||
class QualityProfilesDropDownMenuModel(ListModel):
|
||||
"""QML Model for all built-in quality profiles. This model is used for the drop-down quality menu."""
|
||||
|
||||
NameRole = Qt.UserRole + 1
|
||||
QualityTypeRole = Qt.UserRole + 2
|
||||
LayerHeightRole = Qt.UserRole + 3
|
||||
LayerHeightUnitRole = Qt.UserRole + 4
|
||||
AvailableRole = Qt.UserRole + 5
|
||||
QualityGroupRole = Qt.UserRole + 6
|
||||
QualityChangesGroupRole = Qt.UserRole + 7
|
||||
IsExperimentalRole = Qt.UserRole + 8
|
||||
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||
QualityTypeRole = Qt.ItemDataRole.UserRole + 2
|
||||
LayerHeightRole = Qt.ItemDataRole.UserRole + 3
|
||||
LayerHeightUnitRole = Qt.ItemDataRole.UserRole + 4
|
||||
AvailableRole = Qt.ItemDataRole.UserRole + 5
|
||||
QualityGroupRole = Qt.ItemDataRole.UserRole + 6
|
||||
QualityChangesGroupRole = Qt.ItemDataRole.UserRole + 7
|
||||
IsExperimentalRole = Qt.ItemDataRole.UserRole + 8
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# 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
|
||||
|
||||
import cura.CuraApplication
|
||||
|
@ -17,13 +17,13 @@ import os
|
|||
class QualitySettingsModel(ListModel):
|
||||
"""This model is used to show details settings of the selected quality in the quality management page."""
|
||||
|
||||
KeyRole = Qt.UserRole + 1
|
||||
LabelRole = Qt.UserRole + 2
|
||||
UnitRole = Qt.UserRole + 3
|
||||
ProfileValueRole = Qt.UserRole + 4
|
||||
ProfileValueSourceRole = Qt.UserRole + 5
|
||||
UserValueRole = Qt.UserRole + 6
|
||||
CategoryRole = Qt.UserRole + 7
|
||||
KeyRole = Qt.ItemDataRole.UserRole + 1
|
||||
LabelRole = Qt.ItemDataRole.UserRole + 2
|
||||
UnitRole = Qt.ItemDataRole.UserRole + 3
|
||||
ProfileValueRole = Qt.ItemDataRole.UserRole + 4
|
||||
ProfileValueSourceRole = Qt.ItemDataRole.UserRole + 5
|
||||
UserValueRole = Qt.ItemDataRole.UserRole + 6
|
||||
CategoryRole = Qt.ItemDataRole.UserRole + 7
|
||||
|
||||
GLOBAL_STACK_POSITION = -1
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
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.Preferences import Preferences
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, Qt
|
||||
from PyQt6.QtCore import pyqtSlot, Qt
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
|
@ -15,12 +15,12 @@ from UM.Qt.ListModel import ListModel
|
|||
|
||||
|
||||
class UserChangesModel(ListModel):
|
||||
KeyRole = Qt.UserRole + 1
|
||||
LabelRole = Qt.UserRole + 2
|
||||
ExtruderRole = Qt.UserRole + 3
|
||||
OriginalValueRole = Qt.UserRole + 4
|
||||
UserValueRole = Qt.UserRole + 6
|
||||
CategoryRole = Qt.UserRole + 7
|
||||
KeyRole = Qt.ItemDataRole.UserRole + 1
|
||||
LabelRole = Qt.ItemDataRole.UserRole + 2
|
||||
ExtruderRole = Qt.ItemDataRole.UserRole + 3
|
||||
OriginalValueRole = Qt.ItemDataRole.UserRole + 4
|
||||
UserValueRole = Qt.ItemDataRole.UserRole + 6
|
||||
CategoryRole = Qt.ItemDataRole.UserRole + 7
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent = parent)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
|
||||
from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal
|
||||
|
||||
|
||||
class QualityChangesGroup(QObject):
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
from base64 import b64encode
|
||||
from datetime import datetime
|
||||
from hashlib import sha512
|
||||
from PyQt5.QtNetwork import QNetworkReply
|
||||
from PyQt6.QtNetwork import QNetworkReply
|
||||
import secrets
|
||||
from typing import Callable, Optional
|
||||
import urllib.parse
|
||||
|
|
|
@ -6,8 +6,8 @@ from datetime import datetime, timedelta
|
|||
from typing import Callable, Dict, Optional, TYPE_CHECKING, Union
|
||||
from urllib.parse import urlencode, quote_plus
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt6.QtCore import QUrl
|
||||
from PyQt6.QtGui import QDesktopServices
|
||||
|
||||
from UM.Logger import Logger
|
||||
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.
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
from PyQt6.QtCore import QTimer
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
|
@ -50,8 +50,13 @@ class PlatformPhysics:
|
|||
if not self._enabled:
|
||||
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()
|
||||
build_volume = Application.getInstance().getBuildVolume()
|
||||
build_volume = app_instance.getBuildVolume()
|
||||
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
|
||||
|
@ -75,7 +80,7 @@ class PlatformPhysics:
|
|||
# Move it downwards if bottom is above platform
|
||||
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
|
||||
move_vector = move_vector.set(y = -bbox.bottom + z_offset)
|
||||
|
||||
|
@ -84,7 +89,7 @@ class PlatformPhysics:
|
|||
node.addDecorator(ConvexHullDecorator())
|
||||
|
||||
# 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
|
||||
if node.getSetting(SceneNodeSettings.LockPosition):
|
||||
continue
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from PyQt5.QtGui import QImage
|
||||
from PyQt5.QtQuick import QQuickImageProvider
|
||||
from PyQt5.QtCore import QSize
|
||||
from PyQt6.QtGui import QImage
|
||||
from PyQt6.QtQuick import QQuickImageProvider
|
||||
from PyQt6.QtCore import QSize
|
||||
|
||||
from UM.Application import Application
|
||||
from typing import Tuple
|
||||
|
@ -8,7 +8,7 @@ from typing import Tuple
|
|||
|
||||
class PrintJobPreviewImageProvider(QQuickImageProvider):
|
||||
def __init__(self):
|
||||
super().__init__(QQuickImageProvider.Image)
|
||||
super().__init__(QQuickImageProvider.ImageType.Image)
|
||||
|
||||
def requestImage(self, id: str, size: QSize) -> Tuple[QImage, QSize]:
|
||||
"""Request a new image.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# 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 threading import Thread
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from typing import TYPE_CHECKING, Set, Union, Optional
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
from PyQt6.QtCore import QTimer
|
||||
|
||||
from .PrinterOutputController import PrinterOutputController
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
|
||||
from PyQt6.QtCore import pyqtProperty, QObject, pyqtSignal
|
||||
|
||||
from .MaterialOutputModel import MaterialOutputModel
|
||||
|
||||
|
@ -13,9 +13,9 @@ class ExtruderConfigurationModel(QObject):
|
|||
|
||||
def __init__(self, position: int = -1) -> None:
|
||||
super().__init__()
|
||||
self._position = position # type: int
|
||||
self._material = None # type: Optional[MaterialOutputModel]
|
||||
self._hotend_id = None # type: Optional[str]
|
||||
self._position: int = position
|
||||
self._material: Optional[MaterialOutputModel] = None
|
||||
self._hotend_id: Optional[str] = None
|
||||
|
||||
def setPosition(self, position: int) -> None:
|
||||
self._position = position
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, QObject
|
||||
from PyQt6.QtCore import pyqtProperty, 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.
|
||||
|
||||
from typing import Optional, TYPE_CHECKING, List
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot, QUrl
|
||||
from PyQt5.QtGui import QImage
|
||||
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot, QUrl
|
||||
from PyQt6.QtGui import QImage
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
if TYPE_CHECKING:
|
||||
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
|
||||
# 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
|
||||
return QUrl(temp, QUrl.TolerantMode)
|
||||
return QUrl(temp, QUrl.ParsingMode.TolerantMode)
|
||||
|
||||
def getPreviewImage(self) -> Optional[QImage]:
|
||||
return self._preview_image
|
||||
|
@ -86,6 +88,18 @@ class PrintJobOutputModel(QObject):
|
|||
self._owner = owner
|
||||
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)
|
||||
def assignedPrinter(self):
|
||||
return self._assigned_printer
|
||||
|
@ -119,16 +133,16 @@ class PrintJobOutputModel(QObject):
|
|||
|
||||
@pyqtProperty(int, notify = timeTotalChanged)
|
||||
def timeTotal(self) -> int:
|
||||
return self._time_total
|
||||
return int(self._time_total)
|
||||
|
||||
@pyqtProperty(int, notify = timeElapsedChanged)
|
||||
def timeElapsed(self) -> int:
|
||||
return self._time_elapsed
|
||||
return int(self._time_elapsed)
|
||||
|
||||
@pyqtProperty(int, notify = timeElapsedChanged)
|
||||
def timeRemaining(self) -> int:
|
||||
# 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)
|
||||
def progress(self) -> float:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# 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
|
||||
|
||||
MYPY = False
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# 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 UM.Math.Vector import Vector
|
||||
from cura.PrinterOutput.Peripheral import Peripheral
|
||||
|
@ -350,5 +350,6 @@ class PrinterOutputModel(QObject):
|
|||
self.availableConfigurationsChanged.emit()
|
||||
|
||||
def setAvailableConfigurations(self, new_configurations: List[PrinterConfigurationModel]) -> None:
|
||||
self._available_printer_configurations = new_configurations
|
||||
self.availableConfigurationsChanged.emit()
|
||||
if self._available_printer_configurations != new_configurations:
|
||||
self._available_printer_configurations = new_configurations
|
||||
self.availableConfigurationsChanged.emit()
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# Copyright (c) 2018 Aldo Hoeben / fieldOfView
|
||||
# NetworkMJPGImage is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QUrl, pyqtProperty, pyqtSignal, pyqtSlot, QRect, QByteArray
|
||||
from PyQt5.QtGui import QImage, QPainter
|
||||
from PyQt5.QtQuick import QQuickPaintedItem
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
|
||||
from PyQt6.QtCore import QUrl, pyqtProperty, pyqtSignal, pyqtSlot, QRect, QByteArray
|
||||
from PyQt6.QtGui import QImage, QPainter
|
||||
from PyQt6.QtQuick import QQuickPaintedItem
|
||||
from PyQt6.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
|
||||
|
||||
from UM.Logger import Logger
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ from cura.CuraApplication import CuraApplication
|
|||
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
|
||||
|
||||
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication
|
||||
from PyQt6.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator
|
||||
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication
|
||||
from time import time
|
||||
from typing import Callable, Dict, List, Optional, Union
|
||||
from enum import IntEnum
|
||||
|
@ -146,8 +146,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
url = QUrl("http://" + self._address + self._api_prefix + target)
|
||||
request = QNetworkRequest(url)
|
||||
if content_type is not None:
|
||||
request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
|
||||
request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
|
||||
request.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, content_type)
|
||||
request.setHeader(QNetworkRequest.KnownHeaders.UserAgentHeader, self._user_agent)
|
||||
return request
|
||||
|
||||
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;"):
|
||||
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:
|
||||
part.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
|
||||
part.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, content_type)
|
||||
|
||||
part.setBody(data)
|
||||
return part
|
||||
|
@ -290,7 +290,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
on_progress: Optional[Callable[[int, int], None]] = None) -> QNetworkReply:
|
||||
self._validateManager()
|
||||
request = self._createEmptyRequest(target, content_type=None)
|
||||
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
|
||||
multi_post_part = QHttpMultiPart(QHttpMultiPart.ContentType.FormDataType)
|
||||
for part in parts:
|
||||
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:
|
||||
post_part = QHttpPart()
|
||||
post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data)
|
||||
post_part.setHeader(QNetworkRequest.KnownHeaders.ContentDispositionHeader, header_data)
|
||||
post_part.setBody(body_data)
|
||||
|
||||
self.postFormWithParts(target, [post_part], on_finished, on_progress)
|
||||
|
@ -357,10 +357,10 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
def _handleOnFinished(self, reply: QNetworkReply) -> None:
|
||||
# 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.
|
||||
if reply.operation() == QNetworkAccessManager.PostOperation:
|
||||
if reply.operation() == QNetworkAccessManager.Operation.PostOperation:
|
||||
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.
|
||||
return
|
||||
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Peripheral:
|
||||
"""Data class that represents a peripheral for a printer.
|
||||
|
||||
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
|
||||
stage.
|
||||
|
||||
Args:
|
||||
type (string): A unique ID for the type of peripheral.
|
||||
name (string): A human-readable name for the peripheral.
|
||||
"""
|
||||
|
||||
def __init__(self, peripheral_type: str, name: str) -> None:
|
||||
"""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
|
||||
type: str
|
||||
name: str
|
||||
|
|
|
@ -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.
|
||||
|
||||
from enum import IntEnum
|
||||
from typing import Callable, List, Optional, Union
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
from PyQt6.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl
|
||||
from PyQt6.QtWidgets import QMessageBox
|
||||
|
||||
import cura.CuraApplication # Imported like this to prevent circular imports.
|
||||
from UM.Logger import Logger
|
||||
|
@ -50,13 +50,12 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
The assumption is made the printer is a FDM printer.
|
||||
|
||||
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.
|
||||
|
||||
For all other uses it should be used in the same way as a "regular" OutputDevice.
|
||||
"""
|
||||
|
||||
|
||||
printersChanged = pyqtSignal()
|
||||
connectionStateChanged = pyqtSignal(str)
|
||||
acceptsCommandsChanged = pyqtSignal()
|
||||
|
@ -137,7 +136,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
"""
|
||||
if self.connectionState != 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)
|
||||
|
||||
@pyqtProperty(int, constant = True)
|
||||
|
@ -179,8 +182,8 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
@pyqtProperty(QObject, constant = True)
|
||||
def monitorItem(self) -> QObject:
|
||||
# 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
|
||||
# create the item (and fail) every time.
|
||||
# It could be that it failed to actually create the qml item! If we check if the item was created, it will try
|
||||
# to create the item (and fail) every time.
|
||||
if not self._monitor_component:
|
||||
self._createMonitorViewFromQML()
|
||||
return self._monitor_item
|
||||
|
@ -233,9 +236,9 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
|
||||
self.acceptsCommandsChanged.emit()
|
||||
|
||||
# Returns the unique configurations of the printers within this output device
|
||||
@pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged)
|
||||
def uniqueConfigurations(self) -> List["PrinterConfigurationModel"]:
|
||||
""" Returns the unique configurations of the printers within this output device """
|
||||
return self._unique_configurations
|
||||
|
||||
def _updateUniqueConfigurations(self) -> None:
|
||||
|
@ -244,17 +247,19 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
if printer.printerConfiguration is not None and printer.printerConfiguration.hasAnyMaterialLoaded():
|
||||
all_configurations.add(printer.printerConfiguration)
|
||||
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!")
|
||||
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:
|
||||
self._unique_configurations = new_configurations
|
||||
self.uniqueConfigurationsChanged.emit()
|
||||
|
||||
# Returns the unique configurations of the printers within this output device
|
||||
@pyqtProperty("QStringList", notify = uniqueConfigurationsChanged)
|
||||
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])))
|
||||
|
||||
def _onPrintersChanged(self) -> None:
|
||||
|
|
|
@ -5,7 +5,7 @@ import enum
|
|||
import functools # For partial methods to use as callbacks with information pre-filled.
|
||||
import json # To serialise metadata for API calls.
|
||||
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 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
|
||||
if TYPE_CHECKING:
|
||||
from PyQt5.QtNetwork import QNetworkReply
|
||||
from PyQt6.QtNetwork import QNetworkReply
|
||||
from cura.UltimakerCloud.CloudMaterialSync import CloudMaterialSync
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# 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.Math.Polygon import Polygon
|
||||
|
@ -383,14 +383,14 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
# Shrinkage compensation.
|
||||
if not self._global_stack: # Should never happen.
|
||||
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
|
||||
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
|
||||
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.
|
||||
ancestor = self.getNode()
|
||||
while ancestor.getParent() != self._root:
|
||||
while ancestor.getParent() != self._root and ancestor.getParent() is not None:
|
||||
ancestor = ancestor.getParent()
|
||||
center = ancestor.getBoundingBox().center
|
||||
else:
|
||||
|
@ -498,7 +498,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
"adhesion_type", "raft_margin", "print_sequence",
|
||||
"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.
|
||||
|
||||
If these settings change, the convex hull should be recalculated.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from UM.Logger import Logger
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, QObject, QTimer
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt6.QtCore import Qt, pyqtSlot, QObject, QTimer
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
|
||||
from UM.Scene.Camera import Camera
|
||||
from cura.UI.ObjectsModel import ObjectsModel
|
||||
|
@ -107,8 +107,8 @@ class CuraSceneController(QObject):
|
|||
"""Either select or deselect an item"""
|
||||
|
||||
modifiers = QApplication.keyboardModifiers()
|
||||
ctrl_is_active = modifiers & Qt.ControlModifier
|
||||
shift_is_active = modifiers & Qt.ShiftModifier
|
||||
ctrl_is_active = modifiers & Qt.KeyboardModifier.ControlModifier
|
||||
shift_is_active = modifiers & Qt.KeyboardModifier.ShiftModifier
|
||||
|
||||
if ctrl_is_active:
|
||||
item = self._objects_model.getItem(index)
|
||||
|
@ -139,7 +139,7 @@ class CuraSceneController(QObject):
|
|||
def setActiveBuildPlate(self, nr):
|
||||
if nr == self._active_build_plate:
|
||||
return
|
||||
Logger.log("d", "Select build plate: %s" % nr)
|
||||
Logger.debug(f"Selected build plate: {nr}")
|
||||
self._active_build_plate = nr
|
||||
Selection.clear()
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ import urllib.parse
|
|||
import uuid
|
||||
from typing import Any, cast, Dict, List, TYPE_CHECKING, Union
|
||||
|
||||
from PyQt5.QtCore import QObject, QUrl
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
from PyQt6.QtCore import QObject, QUrl
|
||||
from PyQt6.QtWidgets import QMessageBox
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
|
@ -47,11 +47,11 @@ class ContainerManager(QObject):
|
|||
def __init__(self, application: "CuraApplication") -> None:
|
||||
if ContainerManager.__instance is not None:
|
||||
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
|
||||
ContainerManager.__instance = self
|
||||
try:
|
||||
super().__init__(parent = application)
|
||||
except TypeError:
|
||||
super().__init__()
|
||||
ContainerManager.__instance = self
|
||||
|
||||
self._container_name_filters = {} # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
|
@ -114,7 +114,7 @@ class ContainerManager(QObject):
|
|||
for _ in range(len(entries)):
|
||||
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
|
||||
item[entry_name] = entry_value
|
||||
|
||||
|
@ -206,7 +206,7 @@ class ContainerManager(QObject):
|
|||
if os.path.exists(file_url):
|
||||
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))
|
||||
if result == QMessageBox.No:
|
||||
if result == QMessageBox.StandardButton.No:
|
||||
return {"status": "cancelled", "message": "User cancelled"}
|
||||
|
||||
try:
|
||||
|
|
|
@ -6,7 +6,7 @@ import re
|
|||
import configparser
|
||||
|
||||
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.Settings.ContainerFormatError import ContainerFormatError
|
||||
|
@ -108,7 +108,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
:param container_type: :type{string} Type of the container (machine, quality, ...)
|
||||
: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 \
|
||||
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):
|
||||
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))
|
||||
if result == QMessageBox.No:
|
||||
if result == QMessageBox.StandardButton.No:
|
||||
return False
|
||||
|
||||
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