Merge branch 'Ultimaker:main' into master
|
@ -1,4 +0,0 @@
|
|||
.git
|
||||
.github
|
||||
resources/materials
|
||||
CuraEngine
|
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
|
||||
|
|
4
.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:
|
||||
|
@ -41,4 +41,4 @@ body:
|
|||
- type: textarea
|
||||
attributes:
|
||||
label: Additional information & file uploads
|
||||
description: You can add pictures or files to visualize your feature request in the comments below.
|
||||
description: You can add pictures or files to visualize your feature request in the comments below.
|
||||
|
|
13
.github/no-response.yml
vendored
|
@ -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
|
@ -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
|
173
.github/workflows/conan-package-create.yml
vendored
Normal file
|
@ -0,0 +1,173 @@
|
|||
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: Use GCC-10 on ubuntu-20.04
|
||||
if: ${{ startsWith(inputs.runs_on, 'ubuntu-20.04') }}
|
||||
run: |
|
||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 10
|
||||
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 10
|
||||
|
||||
- name: Create the default Conan profile
|
||||
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
|
@ -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
|
@ -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
|
@ -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
|
269
.github/workflows/cura-installer.yml
vendored
Normal file
|
@ -0,0 +1,269 @@
|
|||
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: ${{ 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: Use GCC-10 on ubuntu-20.04
|
||||
if: ${{ startsWith(inputs.runs_on, 'ubuntu-20.04') }}
|
||||
run: |
|
||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 10
|
||||
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 10
|
||||
|
||||
- name: Create the default Conan profile
|
||||
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
|
@ -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
|
@ -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 }}
|
36
.github/workflows/notify_on_print_profile_change.yml
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
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**'
|
||||
permissions: {}
|
||||
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
|
@ -0,0 +1,2 @@
|
|||
conan!=1.51.0,!=1.51.1,!=1.51.2,!=1.51.3,!=1.52.0
|
||||
sip
|
180
.github/workflows/unit-test.yml
vendored
Normal file
|
@ -0,0 +1,180 @@
|
|||
---
|
||||
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 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: Use GCC-10 on ubuntu-20.04
|
||||
run: |
|
||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 10
|
||||
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 10
|
||||
|
||||
- name: 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
|
@ -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
|
@ -0,0 +1,25 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="{{ name }}" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
|
||||
<module name="{{ module_name }}" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />{% for key, value in env_vars.items() %}
|
||||
<env name="{{ key }}" value="{{ value }}" />{% endfor %}
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="{{ sdk_path }}" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/{{ script_name }}" />
|
||||
<option name="PARAMETERS" value="{{ parameters }}" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
23
.run_templates/pycharm_cura_test.run.xml.jinja
Normal file
|
@ -0,0 +1,23 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="{{ name }}" type="tests" factoryName="py.test" nameIsGenerated="true">
|
||||
<module name="{{ module_name }}" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />{% for key, value in env_vars.items() %}
|
||||
<env name="{{ key }}" value="{{ value }}" />{% endfor %}
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="{{ sdk_path }}" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/tests" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||
<option name="_new_keywords" value="""" />
|
||||
<option name="_new_parameters" value="""" />
|
||||
<option name="_new_additionalArguments" value="""" />
|
||||
<option name="_new_target" value=""$PROJECT_DIR$/{{ script_name }}"" />
|
||||
<option name="_new_targetType" value=""PATH"" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
35
CITATION.cff
|
@ -1,11 +1,30 @@
|
|||
# YAML 1.2
|
||||
---
|
||||
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"
|
||||
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:
|
||||
- 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,6 +1,10 @@
|
|||
# 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.18)
|
||||
|
||||
|
@ -11,47 +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")
|
||||
|
||||
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)
|
||||
|
||||
if(NOT DEFINED Python_VERSION)
|
||||
set(Python_VERSION
|
||||
3.10
|
||||
CACHE STRING "Python Version" FORCE)
|
||||
message(STATUS "Setting Python version to ${Python_VERSION}. Set Python_VERSION if you want to compile against an other version.")
|
||||
endif()
|
||||
if(APPLE)
|
||||
set(Python_FIND_FRAMEWORK NEVER)
|
||||
endif()
|
||||
find_package(Python ${Python_VERSION} EXACT REQUIRED COMPONENTS Interpreter)
|
||||
message(STATUS "Linking and building ${project_name} against Python ${Python_VERSION}")
|
||||
if(NOT DEFINED Python_SITELIB_LOCAL)
|
||||
set(Python_SITELIB_LOCAL
|
||||
"${Python_SITELIB}"
|
||||
CACHE PATH "Local alternative site-package location to install Cura" FORCE)
|
||||
endif()
|
||||
|
||||
# Tests
|
||||
include(CuraTests)
|
||||
|
||||
if(NOT ${URANIUM_DIR} STREQUAL "")
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${URANIUM_DIR}/cmake")
|
||||
endif()
|
||||
|
@ -64,24 +29,4 @@ if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "")
|
|||
if(${GENERATE_TRANSLATIONS})
|
||||
CREATE_TRANSLATION_TARGETS()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
install(DIRECTORY resources DESTINATION ${CMAKE_INSTALL_DATADIR}/cura)
|
||||
|
||||
include(CuraPluginInstall)
|
||||
|
||||
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 "${Python_SITELIB_LOCAL}")
|
||||
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py DESTINATION "${Python_SITELIB_LOCAL}/cura/")
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
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 )
|
||||
endif()
|
||||
endif()
|
14
CuraVersion.py.jinja
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
CuraAppName = "{{ cura_app_name }}"
|
||||
CuraAppDisplayName = "{{ cura_app_display_name }}"
|
||||
CuraVersion = "{{ cura_version }}"
|
||||
CuraBuildType = "{{ cura_build_type }}"
|
||||
CuraDebugMode = {{ cura_debug_mode }}
|
||||
CuraCloudAPIRoot = "{{ cura_cloud_api_root }}"
|
||||
CuraCloudAPIVersion = "{{ cura_cloud_api_version }}"
|
||||
CuraCloudAccountAPIRoot = "{{ cura_cloud_account_api_root }}"
|
||||
CuraMarketplaceRoot = "{{ cura_marketplace_root }}"
|
||||
CuraDigitalFactoryURL = "{{ cura_digital_factory_url }}"
|
||||
CuraLatestURL = "{{ cura_latest_url }}"
|
45
Dockerfile
|
@ -1,45 +0,0 @@
|
|||
FROM ultimaker/cura-build-environment:1
|
||||
|
||||
# Environment vars for easy configuration
|
||||
ENV CURA_APP_DIR=/srv/cura
|
||||
|
||||
# Ensure our sources dir exists
|
||||
RUN mkdir $CURA_APP_DIR
|
||||
|
||||
# Setup CuraEngine
|
||||
ENV CURA_ENGINE_BRANCH=master
|
||||
WORKDIR $CURA_APP_DIR
|
||||
RUN git clone -b $CURA_ENGINE_BRANCH --depth 1 https://github.com/Ultimaker/CuraEngine
|
||||
WORKDIR $CURA_APP_DIR/CuraEngine
|
||||
RUN mkdir build
|
||||
WORKDIR $CURA_APP_DIR/CuraEngine/build
|
||||
RUN cmake3 ..
|
||||
RUN make
|
||||
RUN make install
|
||||
|
||||
# TODO: setup libCharon
|
||||
|
||||
# Setup Uranium
|
||||
ENV URANIUM_BRANCH=master
|
||||
WORKDIR $CURA_APP_DIR
|
||||
RUN git clone -b $URANIUM_BRANCH --depth 1 https://github.com/Ultimaker/Uranium
|
||||
|
||||
# Setup materials
|
||||
ENV MATERIALS_BRANCH=master
|
||||
WORKDIR $CURA_APP_DIR
|
||||
RUN git clone -b $MATERIALS_BRANCH --depth 1 https://github.com/Ultimaker/fdm_materials materials
|
||||
|
||||
# Setup Cura
|
||||
WORKDIR $CURA_APP_DIR/Cura
|
||||
ADD . .
|
||||
RUN mv $CURA_APP_DIR/materials resources/materials
|
||||
|
||||
# Make sure Cura can find CuraEngine
|
||||
RUN ln -s /usr/local/bin/CuraEngine $CURA_APP_DIR/Cura
|
||||
|
||||
# Run Cura
|
||||
WORKDIR $CURA_APP_DIR/Cura
|
||||
ENV PYTHONPATH=${PYTHONPATH}:$CURA_APP_DIR/Uranium
|
||||
RUN chmod +x ./CuraEngine
|
||||
RUN chmod +x ./run_in_docker.sh
|
||||
CMD "./run_in_docker.sh"
|
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
|
@ -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
|
@ -0,0 +1,273 @@
|
|||
# -*- mode: python ; coding: utf-8 -*-
|
||||
import os
|
||||
from pathlib import Path
|
||||
from PyInstaller.utils.hooks import collect_all
|
||||
|
||||
|
||||
datas = {{ datas }}
|
||||
binaries = {{ binaries }}
|
||||
|
||||
hiddenimports = {{ hiddenimports }}
|
||||
|
||||
{% for value in collect_all %}tmp_ret = collect_all('{{ value }}')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
{% endfor %}
|
||||
|
||||
# Add dynamic libs in the venv bin/Script Path. This is needed because we might copy some additional libs
|
||||
# e.q.: OpenSSL 1.1.1l in that directory with a separate:
|
||||
# `conan install openssl@1.1.1l -g deploy && cp openssl/bin/*.so cura_inst/bin`
|
||||
binaries.extend([(str(bin), ".") for bin in Path("{{ venv_script_path }}").glob("*.so*")])
|
||||
binaries.extend([(str(bin), ".") for bin in Path("{{ venv_script_path }}").glob("*.dll")])
|
||||
binaries.extend([(str(bin), ".") for bin in Path("{{ venv_script_path }}").glob("*.dylib")])
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
[{{ entrypoint }}],
|
||||
pathex=[],
|
||||
binaries=binaries,
|
||||
datas=datas,
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name=r'{{ name }}',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip={{ strip }},
|
||||
upx={{ upx }},
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch={{ target_arch }},
|
||||
codesign_identity=os.getenv('CODESIGN_IDENTITY', None),
|
||||
entitlements_file={{ entitlements_file }},
|
||||
icon={{ icon }}
|
||||
)
|
||||
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name=r'{{ name }}'
|
||||
)
|
||||
|
||||
{% if macos == true %}
|
||||
# PyInstaller seems to copy everything in the resource folder for the MacOS, this causes issues with codesigning and notarizing
|
||||
# The folder structure should adhere to the one specified in Table 2-5
|
||||
# https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1
|
||||
# The class below is basically ducktyping the BUNDLE class of PyInstaller and using our own `assemble` method for more fine-grain and specific
|
||||
# control. Some code of the method below is copied from:
|
||||
# https://github.com/pyinstaller/pyinstaller/blob/22d1d2a5378228744cc95f14904dae1664df32c4/PyInstaller/building/osx.py#L115
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2005-2022, PyInstaller Development Team.
|
||||
#
|
||||
# Distributed under the terms of the GNU General Public License (version 2
|
||||
# or later) with exception for distributing the bootloader.
|
||||
#
|
||||
# The full license is in the file COPYING.txt, distributed with this software.
|
||||
#
|
||||
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
import plistlib
|
||||
import shutil
|
||||
import PyInstaller.utils.osx as osxutils
|
||||
from pathlib import Path
|
||||
from PyInstaller.building.osx import BUNDLE
|
||||
from PyInstaller.building.utils import (_check_path_overlap, _rmtree, add_suffix_to_extension, checkCache)
|
||||
from PyInstaller.building.datastruct import logger
|
||||
from PyInstaller.building.icon import normalize_icon_type
|
||||
|
||||
|
||||
class UMBUNDLE(BUNDLE):
|
||||
def assemble(self):
|
||||
from PyInstaller.config import CONF
|
||||
|
||||
if _check_path_overlap(self.name) and os.path.isdir(self.name):
|
||||
_rmtree(self.name)
|
||||
logger.info("Building BUNDLE %s", self.tocbasename)
|
||||
|
||||
# Create a minimal Mac bundle structure.
|
||||
macos_path = Path(self.name, "Contents", "MacOS")
|
||||
resources_path = Path(self.name, "Contents", "Resources")
|
||||
frameworks_path = Path(self.name, "Contents", "Frameworks")
|
||||
os.makedirs(macos_path)
|
||||
os.makedirs(resources_path)
|
||||
os.makedirs(frameworks_path)
|
||||
|
||||
# Makes sure the icon exists and attempts to convert to the proper format if applicable
|
||||
self.icon = normalize_icon_type(self.icon, ("icns",), "icns", CONF["workpath"])
|
||||
|
||||
# Ensure icon path is absolute
|
||||
self.icon = os.path.abspath(self.icon)
|
||||
|
||||
# Copy icns icon to Resources directory.
|
||||
shutil.copy(self.icon, os.path.join(self.name, 'Contents', 'Resources'))
|
||||
|
||||
# Key/values for a minimal Info.plist file
|
||||
info_plist_dict = {
|
||||
"CFBundleDisplayName": self.appname,
|
||||
"CFBundleName": self.appname,
|
||||
|
||||
# Required by 'codesign' utility.
|
||||
# The value for CFBundleIdentifier is used as the default unique name of your program for Code Signing
|
||||
# purposes. It even identifies the APP for access to restricted OS X areas like Keychain.
|
||||
#
|
||||
# The identifier used for signing must be globally unique. The usual form for this identifier is a
|
||||
# hierarchical name in reverse DNS notation, starting with the toplevel domain, followed by the company
|
||||
# name, followed by the department within the company, and ending with the product name. Usually in the
|
||||
# form: com.mycompany.department.appname
|
||||
# CLI option --osx-bundle-identifier sets this value.
|
||||
"CFBundleIdentifier": self.bundle_identifier,
|
||||
"CFBundleExecutable": os.path.basename(self.exename),
|
||||
"CFBundleIconFile": os.path.basename(self.icon),
|
||||
"CFBundleInfoDictionaryVersion": "6.0",
|
||||
"CFBundlePackageType": "APPL",
|
||||
"CFBundleShortVersionString": self.version,
|
||||
}
|
||||
|
||||
# Set some default values. But they still can be overwritten by the user.
|
||||
if self.console:
|
||||
# Setting EXE console=True implies LSBackgroundOnly=True.
|
||||
info_plist_dict['LSBackgroundOnly'] = True
|
||||
else:
|
||||
# Let's use high resolution by default.
|
||||
info_plist_dict['NSHighResolutionCapable'] = True
|
||||
|
||||
# Merge info_plist settings from spec file
|
||||
if isinstance(self.info_plist, dict) and self.info_plist:
|
||||
info_plist_dict.update(self.info_plist)
|
||||
|
||||
plist_filename = os.path.join(self.name, "Contents", "Info.plist")
|
||||
with open(plist_filename, "wb") as plist_fh:
|
||||
plistlib.dump(info_plist_dict, plist_fh)
|
||||
|
||||
links = []
|
||||
_QT_BASE_PATH = {'PySide2', 'PySide6', 'PyQt5', 'PyQt6', 'PySide6'}
|
||||
for inm, fnm, typ in self.toc:
|
||||
# Adjust name for extensions, if applicable
|
||||
inm, fnm, typ = add_suffix_to_extension(inm, fnm, typ)
|
||||
inm = Path(inm)
|
||||
fnm = Path(fnm)
|
||||
# Copy files from cache. This ensures that are used files with relative paths to dynamic library
|
||||
# dependencies (@executable_path)
|
||||
if typ in ('EXTENSION', 'BINARY') or (typ == 'DATA' and inm.suffix == '.so'):
|
||||
if any(['.' in p for p in inm.parent.parts]):
|
||||
inm = Path(inm.name)
|
||||
fnm = Path(checkCache(
|
||||
str(fnm),
|
||||
strip = self.strip,
|
||||
upx = self.upx,
|
||||
upx_exclude = self.upx_exclude,
|
||||
dist_nm = str(inm),
|
||||
target_arch = self.target_arch,
|
||||
codesign_identity = self.codesign_identity,
|
||||
entitlements_file = self.entitlements_file,
|
||||
strict_arch_validation = (typ == 'EXTENSION'),
|
||||
))
|
||||
frame_dst = frameworks_path.joinpath(inm)
|
||||
if not frame_dst.exists():
|
||||
if frame_dst.is_dir():
|
||||
os.makedirs(frame_dst, exist_ok = True)
|
||||
else:
|
||||
os.makedirs(frame_dst.parent, exist_ok = True)
|
||||
shutil.copy(fnm, frame_dst, follow_symlinks = True)
|
||||
macos_dst = macos_path.joinpath(inm)
|
||||
if not macos_dst.exists():
|
||||
if macos_dst.is_dir():
|
||||
os.makedirs(macos_dst, exist_ok = True)
|
||||
else:
|
||||
os.makedirs(macos_dst.parent, exist_ok = True)
|
||||
|
||||
# Create relative symlink to the framework
|
||||
symlink_to = Path(*[".." for p in macos_dst.relative_to(macos_path).parts], "Frameworks").joinpath(
|
||||
frame_dst.relative_to(frameworks_path))
|
||||
try:
|
||||
macos_dst.symlink_to(symlink_to)
|
||||
except FileExistsError:
|
||||
pass
|
||||
else:
|
||||
if typ == 'DATA':
|
||||
if any(['.' in p for p in inm.parent.parts]) or inm.suffix == '.so':
|
||||
# Skip info dist egg and some not needed folders in tcl and tk, since they all contain dots in their files
|
||||
logger.warning(f"Skipping DATA file {inm}")
|
||||
continue
|
||||
res_dst = resources_path.joinpath(inm)
|
||||
if not res_dst.exists():
|
||||
if res_dst.is_dir():
|
||||
os.makedirs(res_dst, exist_ok = True)
|
||||
else:
|
||||
os.makedirs(res_dst.parent, exist_ok = True)
|
||||
shutil.copy(fnm, res_dst, follow_symlinks = True)
|
||||
macos_dst = macos_path.joinpath(inm)
|
||||
if not macos_dst.exists():
|
||||
if macos_dst.is_dir():
|
||||
os.makedirs(macos_dst, exist_ok = True)
|
||||
else:
|
||||
os.makedirs(macos_dst.parent, exist_ok = True)
|
||||
|
||||
# Create relative symlink to the resource
|
||||
symlink_to = Path(*[".." for p in macos_dst.relative_to(macos_path).parts], "Resources").joinpath(
|
||||
res_dst.relative_to(resources_path))
|
||||
try:
|
||||
macos_dst.symlink_to(symlink_to)
|
||||
except FileExistsError:
|
||||
pass
|
||||
else:
|
||||
macos_dst = macos_path.joinpath(inm)
|
||||
if not macos_dst.exists():
|
||||
if macos_dst.is_dir():
|
||||
os.makedirs(macos_dst, exist_ok = True)
|
||||
else:
|
||||
os.makedirs(macos_dst.parent, exist_ok = True)
|
||||
shutil.copy(fnm, macos_dst, follow_symlinks = True)
|
||||
|
||||
# Sign the bundle
|
||||
logger.info('Signing the BUNDLE...')
|
||||
try:
|
||||
osxutils.sign_binary(self.name, self.codesign_identity, self.entitlements_file, deep = True)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error while signing the bundle: {e}")
|
||||
logger.warning("You will need to sign the bundle manually!")
|
||||
|
||||
logger.info(f"Building BUNDLE {self.tocbasename} completed successfully.")
|
||||
|
||||
app = UMBUNDLE(
|
||||
coll,
|
||||
name='{{ name }}.app',
|
||||
icon={{ icon }},
|
||||
bundle_identifier={{ osx_bundle_identifier }},
|
||||
version={{ version }},
|
||||
info_plist={
|
||||
'CFBundleDisplayName': '{{ display_name }}',
|
||||
'NSPrincipalClass': 'NSApplication',
|
||||
'CFBundleDevelopmentRegion': 'English',
|
||||
'CFBundleExecutable': '{{ name }}',
|
||||
'CFBundleInfoDictionaryVersion': '6.0',
|
||||
'CFBundlePackageType': 'APPL',
|
||||
'CFBundleShortVersionString': {{ short_version }},
|
||||
'CFBundleDocumentTypes': [{
|
||||
'CFBundleTypeRole': 'Viewer',
|
||||
'CFBundleTypeExtensions': ['*'],
|
||||
'CFBundleTypeName': 'Model Files',
|
||||
}]
|
||||
},
|
||||
){% endif %}
|
34
build.sh
|
@ -1,34 +0,0 @@
|
|||
set -e
|
||||
set -u
|
||||
|
||||
export OLD_PWD=`pwd`
|
||||
export CMAKE=/c/software/PCL/cmake-3.0.1-win32-x86/bin/cmake.exe
|
||||
export MAKE=mingw32-make.exe
|
||||
export PATH=/c/mingw-w64/i686-4.9.2-posix-dwarf-rt_v3-rev1/mingw32/bin:$PATH
|
||||
|
||||
mkdir -p /c/software/protobuf/_build
|
||||
cd /c/software/protobuf/_build
|
||||
$CMAKE ../
|
||||
$MAKE install
|
||||
|
||||
mkdir -p /c/software/libArcus/_build
|
||||
cd /c/software/libArcus/_build
|
||||
$CMAKE ../
|
||||
$MAKE install
|
||||
|
||||
mkdir -p /c/software/PinkUnicornEngine/_build
|
||||
cd /c/software/PinkUnicornEngine/_build
|
||||
$CMAKE ../
|
||||
$MAKE
|
||||
|
||||
cd $OLD_PWD
|
||||
export PYTHONPATH=`pwd`/../libArcus/python:/c/Software/Uranium/
|
||||
/c/python34/python setup.py py2exe
|
||||
|
||||
cp /c/software/PinkUnicornEngine/_build/CuraEngine.exe dist/
|
||||
cp /c/software/libArcus/_install/bin/libArcus.dll dist/
|
||||
cp /c/mingw-w64/i686-4.9.2-posix-dwarf-rt_v3-rev1/mingw32/bin/libgcc_s_dw2-1.dll dist/
|
||||
cp /c/mingw-w64/i686-4.9.2-posix-dwarf-rt_v3-rev1/mingw32/bin/libwinpthread-1.dll dist/
|
||||
cp /c/mingw-w64/i686-4.9.2-posix-dwarf-rt_v3-rev1/mingw32/bin/libstdc++-6.dll dist/
|
||||
|
||||
/c/program\ files\ \(x86\)/NSIS/makensis.exe installer.nsi
|
|
@ -1,92 +0,0 @@
|
|||
# Copyright (c) 2022 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)
|
||||
|
||||
# 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 ${Python_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,77 +0,0 @@
|
|||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
include(CTest)
|
||||
include(CMakeParseArguments)
|
||||
|
||||
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 ${Python_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 ${Python_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 ${Python_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 ${Python_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>
|
844
conandata.yml
Normal file
|
@ -0,0 +1,844 @@
|
|||
---
|
||||
# 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.1":
|
||||
requirements:
|
||||
- "pyarcus/5.2.0"
|
||||
- "curaengine/5.2.1"
|
||||
- "pysavitar/5.2.0"
|
||||
- "pynest2d/5.2.0"
|
||||
- "uranium/5.2.0"
|
||||
- "fdm_materials/5.2.0"
|
||||
- "cura_binary_data/5.2.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-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
|
@ -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.
|
||||
|
||||
Some of these repositories will have automated tests running when you create a pull request, indicated by green check marks or red crosses in the Github web page. If you see a red cross, that means that a test has failed. If the test doesn't fail on the Master branch but does fail on your branch, that indicates that you've probably made a mistake and you need to do that. Click on the cross for more details, or run the test locally by running `cmake . && ctest --verbose`.
|
||||
The style guide for code contributions to Cura and other Ultimaker projects can be found [here](https://github.com/Ultimaker/Meta/blob/master/general/generic_code_conventions.md).
|
||||
|
||||
Some of these repositories will have automated tests running when you create a pull request, indicated by green check marks or red crosses in the Github web page. If you see a red cross, that means that a test has failed. If the test doesn't fail on the Master branch but does fail on your branch, that indicates that you've probably made a mistake and you need to do that. Click on the cross for more details, or run the test locally by running `cmake . && ctest --verbose`.
|
||||
|
|
BIN
cura-logo-dark.PNG
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
cura-logo.PNG
Before Width: | Height: | Size: 520 KiB After Width: | Height: | Size: 1 MiB |
|
@ -1,19 +1,26 @@
|
|||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import enum
|
||||
from datetime import datetime
|
||||
import json
|
||||
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer, pyqtEnum
|
||||
from typing import Any, Optional, Dict, TYPE_CHECKING, Callable
|
||||
from PyQt6.QtNetwork import QNetworkRequest
|
||||
from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING
|
||||
|
||||
from UM.Decorators import deprecated
|
||||
from UM.Logger import Logger
|
||||
from UM.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")
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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 = "8.0.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))
|
||||
|
@ -159,4 +159,4 @@ class ShapeArray:
|
|||
|
||||
max_col_idx = (idxs[0] - p1[0]) / (p2[0] - p1[0]) * (p2[1] - p1[1]) + p1[1]
|
||||
sign = numpy.sign(p2[0] - p1[0])
|
||||
return idxs[1] * sign <= max_col_idx * sign
|
||||
return idxs[1] * sign <= max_col_idx * sign
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
@ -150,6 +154,7 @@ class CuraApplication(QtApplication):
|
|||
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,
|
||||
|
@ -257,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:
|
||||
|
@ -315,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
|
||||
|
@ -348,13 +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.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "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):
|
||||
|
@ -812,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()
|
||||
|
@ -943,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")
|
||||
|
@ -1173,6 +1191,8 @@ 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")
|
||||
|
@ -1191,6 +1211,8 @@ class CuraApplication(QtApplication):
|
|||
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")
|
||||
|
@ -1425,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,14 +1,21 @@
|
|||
# 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:
|
||||
|
@ -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@"
|
|
@ -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
|
||||
|
@ -58,14 +63,16 @@ class LayerPolygon:
|
|||
self._mesh_line_count = len(self._types) - self._jump_count
|
||||
self._vertex_count = self._mesh_line_count + numpy.sum(self._types[1:] == self._types[:-1])
|
||||
|
||||
# Buffering the colors shouldn't be necessary as it is not
|
||||
# Buffering the colors shouldn't be necessary as it is not
|
||||
# re-used and can save a lot of memory usage.
|
||||
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,19 +120,20 @@ 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
|
||||
self._vertex_end += vertex_offset
|
||||
|
||||
# Points are picked based on the index list to get the vertices needed.
|
||||
# Points are picked based on the index list to get the vertices needed.
|
||||
vertices[self._vertex_begin:self._vertex_end, :] = self._data[index_list, :]
|
||||
|
||||
# Create an array with colors for each vertex and remove the color data for the points that has been thrown away.
|
||||
# Create an array with colors for each vertex and remove the color data for the points that has been thrown away.
|
||||
colors[self._vertex_begin:self._vertex_end, :] = numpy.tile(self._colors, (1, 2)).reshape((-1, 4))[needed_points_list.ravel()]
|
||||
|
||||
# Create an array with line widths and thicknesses for each vertex.
|
||||
|
@ -138,14 +148,15 @@ class LayerPolygon:
|
|||
# Convert type per vertex to type per line
|
||||
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.
|
||||
# Each line segment goes from it's starting point p to p+1, offset by the vertex index.
|
||||
# The -1 is to compensate for the necessarily True value of needed_points_list[0,0] which causes an unwanted +1 in cumsum above.
|
||||
indices[self._index_begin:self._index_end, :] += numpy.array([self._vertex_begin - 1, self._vertex_begin])
|
||||
|
||||
|
@ -214,13 +225,12 @@ class LayerPolygon:
|
|||
"""
|
||||
|
||||
normals = numpy.copy(self._data)
|
||||
normals[:, 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
|
||||
])
|
||||
|
||||
|
|
|
@ -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
|
|
@ -43,13 +43,9 @@ 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]
|
||||
|
||||
|
@ -66,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)
|
||||
|
@ -152,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:
|
||||
|
@ -194,7 +202,7 @@ class MachineErrorChecker(QObject):
|
|||
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)
|
||||
continue
|
||||
return
|
||||
|
||||
# Schedule the check for the next key
|
||||
self._application.callLater(self._checkStack)
|
||||
|
@ -204,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")
|
||||
|
|
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
|
||||
|
||||
|
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())
|
||||
})
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
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
|
@ -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,
|
||||
})
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional, TYPE_CHECKING, List
|
||||
|
@ -6,6 +6,8 @@ from typing import Optional, TYPE_CHECKING, List
|
|||
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot, QUrl
|
||||
from PyQt6.QtGui import QImage
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
||||
|
@ -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
|
||||
|
|
|
@ -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,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
|
||||
|
|
|
@ -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()
|
||||
|
@ -183,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
|
||||
|
@ -237,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:
|
||||
|
@ -248,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:
|
||||
|
|
|
@ -390,7 +390,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
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:
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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.ButtonRole.NoRole:
|
||||
if result == QMessageBox.StandardButton.No:
|
||||
return {"status": "cancelled", "message": "User cancelled"}
|
||||
|
||||
try:
|
||||
|
|
|
@ -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.ButtonRole.NoRole:
|
||||
if result == QMessageBox.StandardButton.No:
|
||||
return False
|
||||
|
||||
profile_writer = self._findProfileWriter(extension, description)
|
||||
|
|
|
@ -49,7 +49,7 @@ class CuraContainerStack(ContainerStack):
|
|||
self._empty_material = cura_empty_instance_containers.empty_material_container #type: InstanceContainer
|
||||
self._empty_variant = cura_empty_instance_containers.empty_variant_container #type: InstanceContainer
|
||||
|
||||
self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] #type: List[ContainerInterface]
|
||||
self._containers: List[ContainerInterface] = [self._empty_instance_container for i in _ContainerIndexes.IndexTypeMap]
|
||||
self._containers[_ContainerIndexes.QualityChanges] = self._empty_quality_changes
|
||||
self._containers[_ContainerIndexes.Quality] = self._empty_quality
|
||||
self._containers[_ContainerIndexes.Material] = self._empty_material
|
||||
|
@ -427,4 +427,4 @@ class _ContainerIndexes:
|
|||
}
|
||||
|
||||
# Reverse lookup: type -> index
|
||||
TypeIndexMap = dict([(v, k) for k, v in IndexTypeMap.items()])
|
||||
TypeIndexMap = {v: k for k, v in IndexTypeMap.items()}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional
|
||||
import copy
|
||||
|
||||
from typing import Optional, cast
|
||||
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
from UM.Logger import Logger
|
||||
|
@ -17,7 +19,7 @@ class CuraStackBuilder:
|
|||
"""Contains helper functions to create new machines."""
|
||||
|
||||
@classmethod
|
||||
def createMachine(cls, name: str, definition_id: str, machine_extruder_count: Optional[int] = None) -> Optional[GlobalStack]:
|
||||
def createMachine(cls, name: str, definition_id: str, machine_extruder_count: Optional[int] = None, show_warning_message: bool = True) -> Optional[GlobalStack]:
|
||||
"""Create a new instance of a machine.
|
||||
|
||||
:param name: The name of the new machine.
|
||||
|
@ -27,14 +29,15 @@ class CuraStackBuilder:
|
|||
:return: The new global stack or None if an error occurred.
|
||||
"""
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.CuraApplication import CuraApplication # inline import needed due to circular import
|
||||
application = CuraApplication.getInstance()
|
||||
registry = application.getContainerRegistry()
|
||||
container_tree = ContainerTree.getInstance()
|
||||
|
||||
definitions = registry.findDefinitionContainers(id = definition_id)
|
||||
if not definitions:
|
||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(definition_id)
|
||||
if show_warning_message:
|
||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(definition_id)
|
||||
Logger.log("w", "Definition {definition} was not found!", definition = definition_id)
|
||||
return None
|
||||
|
||||
|
@ -90,7 +93,7 @@ class CuraStackBuilder:
|
|||
:param extruder_position: The position of the current extruder.
|
||||
"""
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.CuraApplication import CuraApplication # inline import needed due to circular import
|
||||
application = CuraApplication.getInstance()
|
||||
registry = application.getContainerRegistry()
|
||||
|
||||
|
@ -198,13 +201,21 @@ class CuraStackBuilder:
|
|||
|
||||
:return: A new Global stack instance with the specified parameters.
|
||||
"""
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = CuraApplication.getInstance()
|
||||
registry = application.getContainerRegistry()
|
||||
|
||||
stack = GlobalStack(new_stack_id)
|
||||
stack.setDefinition(definition)
|
||||
cls.createUserContainer(new_stack_id, definition, stack, variant_container, material_container, quality_container)
|
||||
return stack
|
||||
|
||||
@classmethod
|
||||
def createUserContainer(cls, new_stack_id: str, definition: DefinitionContainerInterface,
|
||||
stack: GlobalStack,
|
||||
variant_container: "InstanceContainer",
|
||||
material_container: "InstanceContainer",
|
||||
quality_container: "InstanceContainer") -> None:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = CuraApplication.getInstance()
|
||||
|
||||
registry = application.getContainerRegistry()
|
||||
|
||||
# Create user container
|
||||
user_container = cls.createUserChangesContainer(new_stack_id + "_user", definition.getId(), new_stack_id,
|
||||
|
@ -220,8 +231,6 @@ class CuraStackBuilder:
|
|||
|
||||
registry.addContainer(user_container)
|
||||
|
||||
return stack
|
||||
|
||||
@classmethod
|
||||
def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str,
|
||||
is_global_stack: bool) -> "InstanceContainer":
|
||||
|
@ -258,3 +267,36 @@ class CuraStackBuilder:
|
|||
container_stack.definitionChanges = definition_changes_container
|
||||
|
||||
return definition_changes_container
|
||||
|
||||
@classmethod
|
||||
def createAbstractMachine(cls, definition_id: str) -> Optional[GlobalStack]:
|
||||
"""Create a new instance of an abstract machine.
|
||||
|
||||
:param definition_id: The ID of the machine definition to use.
|
||||
|
||||
:return: The new Abstract Machine or None if an error occurred.
|
||||
"""
|
||||
abstract_machine_id = f"{definition_id}_abstract_machine"
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = CuraApplication.getInstance()
|
||||
registry = application.getContainerRegistry()
|
||||
|
||||
abstract_machines = registry.findContainerStacks(id = abstract_machine_id)
|
||||
if abstract_machines:
|
||||
return cast(GlobalStack, abstract_machines[0])
|
||||
definitions = registry.findDefinitionContainers(id=definition_id)
|
||||
|
||||
name = ""
|
||||
|
||||
if definitions:
|
||||
name = definitions[0].getName()
|
||||
stack = cls.createMachine(abstract_machine_id, definition_id, show_warning_message=False)
|
||||
if not stack:
|
||||
return None
|
||||
|
||||
stack.setName(name)
|
||||
|
||||
stack.setMetaDataEntry("is_abstract_machine", True)
|
||||
stack.setMetaDataEntry("is_online", True)
|
||||
|
||||
return stack
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
|
||||
|
@ -275,7 +275,7 @@ class ExtruderManager(QObject):
|
|||
for extruder_setting in used_adhesion_extruders:
|
||||
extruder_str_nr = str(global_stack.getProperty(extruder_setting, "value"))
|
||||
if extruder_str_nr == "-1":
|
||||
extruder_str_nr = self._application.getMachineManager().defaultExtruderPosition
|
||||
continue # An optional extruder doesn't force any extruder to be used if it isn't used already
|
||||
if extruder_str_nr in self.extruderIds:
|
||||
used_extruder_stack_ids.add(self.extruderIds[extruder_str_nr])
|
||||
|
||||
|
@ -298,7 +298,7 @@ class ExtruderManager(QObject):
|
|||
# Starts with the adhesion extruder.
|
||||
adhesion_type = global_stack.getProperty("adhesion_type", "value")
|
||||
if adhesion_type in {"skirt", "brim"}:
|
||||
return global_stack.getProperty("skirt_brim_extruder_nr", "value")
|
||||
return max(0, int(global_stack.getProperty("skirt_brim_extruder_nr", "value"))) # optional skirt/brim extruder defaults to zero
|
||||
if adhesion_type == "raft":
|
||||
return global_stack.getProperty("raft_base_extruder_nr", "value")
|
||||
|
||||
|
@ -382,7 +382,10 @@ class ExtruderManager(QObject):
|
|||
# "fdmextruder". We need to check a machine here so its extruder definition is correct according to this.
|
||||
def fixSingleExtrusionMachineExtruderDefinition(self, global_stack: "GlobalStack") -> None:
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
expected_extruder_definition_0_id = global_stack.getMetaDataEntry("machine_extruder_trains")["0"]
|
||||
expected_extruder_stack = global_stack.getMetaDataEntry("machine_extruder_trains")
|
||||
if expected_extruder_stack is None:
|
||||
return
|
||||
expected_extruder_definition_0_id = expected_extruder_stack["0"]
|
||||
try:
|
||||
extruder_stack_0 = global_stack.extruderList[0]
|
||||
except IndexError:
|
||||
|
|
|
@ -142,8 +142,6 @@ class ExtruderStack(CuraContainerStack):
|
|||
|
||||
limit_to_extruder = super().getProperty(key, "limit_to_extruder", context)
|
||||
if limit_to_extruder is not None:
|
||||
if limit_to_extruder == -1:
|
||||
limit_to_extruder = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition)
|
||||
limit_to_extruder = str(limit_to_extruder)
|
||||
|
||||
if (limit_to_extruder is not None and limit_to_extruder != "-1") and self.getMetaDataEntry("position") != str(limit_to_extruder):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from collections import defaultdict
|
||||
|
@ -8,10 +8,9 @@ import uuid
|
|||
|
||||
from PyQt6.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
|
||||
|
||||
from UM.Decorators import deprecated, override
|
||||
from UM.Decorators import override
|
||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.SettingInstance import InstanceState
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.Interfaces import PropertyEvaluationContext
|
||||
from UM.Logger import Logger
|
||||
|
@ -91,7 +90,6 @@ class GlobalStack(CuraContainerStack):
|
|||
@pyqtProperty("QVariantList", notify=configuredConnectionTypesChanged)
|
||||
def configuredConnectionTypes(self) -> List[int]:
|
||||
"""The configured connection types can be used to find out if the global
|
||||
|
||||
stack is configured to be connected with a printer, without having to
|
||||
know all the details as to how this is exactly done (and without
|
||||
actually setting the stack to be active).
|
||||
|
@ -228,8 +226,6 @@ class GlobalStack(CuraContainerStack):
|
|||
# Handle the "limit_to_extruder" property.
|
||||
limit_to_extruder = super().getProperty(key, "limit_to_extruder", context)
|
||||
if limit_to_extruder is not None:
|
||||
if limit_to_extruder == -1:
|
||||
limit_to_extruder = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition)
|
||||
limit_to_extruder = str(limit_to_extruder)
|
||||
if limit_to_extruder is not None and limit_to_extruder != "-1" and limit_to_extruder in self._extruders:
|
||||
if super().getProperty(key, "settable_per_extruder", context):
|
||||
|
@ -293,7 +289,6 @@ class GlobalStack(CuraContainerStack):
|
|||
for extruder_train in extruder_trains:
|
||||
extruder_position = extruder_train.getMetaDataEntry("position")
|
||||
extruder_check_position.add(extruder_position)
|
||||
|
||||
for check_position in range(machine_extruder_count):
|
||||
if str(check_position) not in extruder_check_position:
|
||||
return False
|
||||
|
@ -344,13 +339,17 @@ class GlobalStack(CuraContainerStack):
|
|||
def getName(self) -> str:
|
||||
return self._metadata.get("group_name", self._metadata.get("name", ""))
|
||||
|
||||
def setName(self, name: "str") -> None:
|
||||
def setName(self, name: str) -> None:
|
||||
super().setName(name)
|
||||
|
||||
nameChanged = pyqtSignal()
|
||||
name = pyqtProperty(str, fget=getName, fset=setName, notify=nameChanged)
|
||||
|
||||
|
||||
def hasNetworkedConnection(self) -> bool:
|
||||
has_connection = False
|
||||
for connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value]:
|
||||
has_connection |= connection_type in self.configuredConnectionTypes
|
||||
return has_connection
|
||||
|
||||
## private:
|
||||
global_stack_mime = MimeType(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
|
@ -8,6 +8,7 @@ from UM.Logger import Logger
|
|||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
|
||||
import cura.CuraApplication
|
||||
from UM.Signal import Signal
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Settings.cura_empty_instance_containers import empty_intent_container
|
||||
|
||||
|
@ -29,6 +30,7 @@ class IntentManager(QObject):
|
|||
return cls.__instance
|
||||
|
||||
intentCategoryChanged = pyqtSignal() #Triggered when we switch categories.
|
||||
intentCategoryChangedSignal = Signal()
|
||||
|
||||
def intentMetadatas(self, definition_id: str, nozzle_name: str, material_base_file: str) -> List[Dict[str, Any]]:
|
||||
"""Gets the metadata dictionaries of all intent profiles for a given
|
||||
|
@ -189,3 +191,4 @@ class IntentManager(QObject):
|
|||
application.getMachineManager().setQualityGroupByQualityType(quality_type)
|
||||
if old_intent_category != intent_category:
|
||||
self.intentCategoryChanged.emit()
|
||||
self.intentCategoryChangedSignal.emit()
|
||||
|
|
|
@ -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.
|
||||
|
||||
import time
|
||||
|
@ -19,6 +19,7 @@ from UM.Logger import Logger
|
|||
from UM.Message import Message
|
||||
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Signal import postponeSignals, CompressTechnique
|
||||
|
||||
import cura.CuraApplication # Imported like this to prevent circular references.
|
||||
|
@ -98,7 +99,7 @@ class MachineManager(QObject):
|
|||
|
||||
self._application.getPreferences().addPreference("cura/active_machine", "")
|
||||
|
||||
self._printer_output_devices = [] # type: List[PrinterOutputDevice]
|
||||
self._printer_output_devices: List[PrinterOutputDevice] = []
|
||||
self._application.getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
|
||||
# There might already be some output devices by the time the signal is connected
|
||||
self._onOutputDevicesChanged()
|
||||
|
@ -111,7 +112,7 @@ class MachineManager(QObject):
|
|||
|
||||
self._application.callLater(self.setInitialActiveMachine)
|
||||
|
||||
containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) # type: List[InstanceContainer]
|
||||
containers: List[InstanceContainer] = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId)
|
||||
if containers:
|
||||
containers[0].nameChanged.connect(self._onMaterialNameChanged)
|
||||
|
||||
|
@ -186,6 +187,32 @@ class MachineManager(QObject):
|
|||
|
||||
self.outputDevicesChanged.emit()
|
||||
|
||||
def getMachinesWithDefinition(self, definition_id: str, online_only=False) -> List[ContainerStack]:
|
||||
""" Fetches all container stacks that match definition_id.
|
||||
|
||||
:param definition_id: The id of the machine definition.
|
||||
:return: A list of Containers that match definition_id
|
||||
"""
|
||||
from cura.CuraApplication import CuraApplication # In function to avoid circular import
|
||||
application = CuraApplication.getInstance()
|
||||
registry = application.getContainerRegistry()
|
||||
|
||||
machines = registry.findContainerStacks(type="machine")
|
||||
# Filter machines that match definition
|
||||
machines = filter(lambda machine: machine.definition.id == definition_id, machines)
|
||||
# Filter only LAN and Cloud printers
|
||||
machines = filter(lambda machine: ConnectionType.CloudConnection in machine.configuredConnectionTypes or
|
||||
ConnectionType.NetworkConnection in machine.configuredConnectionTypes,
|
||||
machines)
|
||||
if online_only:
|
||||
# LAN printers can have is_online = False but should still be included,
|
||||
# their online status is only checked when they are the active printer.
|
||||
machines = filter(lambda machine: parseBool(machine.getMetaDataEntry("is_online", False) or
|
||||
ConnectionType.NetworkConnection in machine.configuredConnectionTypes),
|
||||
machines)
|
||||
|
||||
return list(machines)
|
||||
|
||||
@pyqtProperty(QObject, notify = currentConfigurationChanged)
|
||||
def currentConfiguration(self) -> PrinterConfigurationModel:
|
||||
return self._current_printer_configuration
|
||||
|
@ -332,6 +359,7 @@ class MachineManager(QObject):
|
|||
extruder_manager = ExtruderManager.getInstance()
|
||||
extruder_manager.fixSingleExtrusionMachineExtruderDefinition(global_stack)
|
||||
if not global_stack.isValid():
|
||||
Logger.warning("Global stack isn't valid, adding it to faulty container list")
|
||||
# Mark global stack as invalid
|
||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(global_stack.getId())
|
||||
return # We're done here
|
||||
|
@ -503,6 +531,10 @@ class MachineManager(QObject):
|
|||
def printerConnected(self) -> bool:
|
||||
return bool(self._printer_output_devices)
|
||||
|
||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||
def activeMachineIsAbstractCloudPrinter(self) -> bool:
|
||||
return len(self._printer_output_devices) == 1 and self._printer_output_devices[0].__class__.__name__ == "AbstractCloudOutputDevice"
|
||||
|
||||
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
|
||||
def activeMachineIsGroup(self) -> bool:
|
||||
if self.activeMachine is None:
|
||||
|
@ -872,7 +904,8 @@ class MachineManager(QObject):
|
|||
|
||||
if self._global_container_stack is None \
|
||||
or self._global_container_stack.getProperty(setting_key, "value") == new_value \
|
||||
or self.numberExtrudersEnabled < 2:
|
||||
or self._global_container_stack.definitionChanges.getProperty("extruders_enabled_count", "value") is None \
|
||||
or self._global_container_stack.definitionChanges.getProperty("extruders_enabled_count", "value") < 2:
|
||||
return
|
||||
|
||||
user_changes_container = self._global_container_stack.userChanges
|
||||
|
@ -977,7 +1010,7 @@ class MachineManager(QObject):
|
|||
for position, extruder in enumerate(self._global_container_stack.extruderList):
|
||||
if extruder.isEnabled and int(position) < machine_extruder_count:
|
||||
extruder_count += 1
|
||||
if self.numberExtrudersEnabled != extruder_count:
|
||||
if self._global_container_stack.definitionChanges.getProperty("extruders_enabled_count", "value") != extruder_count:
|
||||
definition_changes_container.setProperty("extruders_enabled_count", "value", extruder_count)
|
||||
self.numberExtrudersEnabledChanged.emit()
|
||||
|
||||
|
@ -1611,7 +1644,7 @@ class MachineManager(QObject):
|
|||
if intent_category != "default":
|
||||
intent_display_name = IntentCategoryModel.translation(intent_category,
|
||||
"name",
|
||||
catalog.i18nc("@label", "Unknown"))
|
||||
intent_category.title())
|
||||
display_name = "{intent_name} - {the_rest}".format(intent_name = intent_display_name,
|
||||
the_rest = display_name)
|
||||
|
||||
|
@ -1778,3 +1811,31 @@ class MachineManager(QObject):
|
|||
abbr_machine += stripped_word
|
||||
|
||||
return abbr_machine
|
||||
|
||||
@pyqtSlot(str, str, result = bool)
|
||||
def intentCategoryHasQuality(self, intent_category: str, quality_type: str) -> bool:
|
||||
""" Checks if there are any quality groups for active extruders that have an intent category """
|
||||
quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
|
||||
|
||||
if quality_type in quality_groups:
|
||||
quality_group = quality_groups[quality_type]
|
||||
for node in quality_group.nodes_for_extruders.values():
|
||||
if any(intent.intent_category == intent_category for intent in node.intents.values()):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
def getDefaultQualityTypeForIntent(self, intent_category) -> str:
|
||||
""" If there is an intent category for the default machine quality return it, otherwise return the first quality for this intent category """
|
||||
machine = ContainerTree.getInstance().machines.get(self._global_container_stack.definition.getId())
|
||||
|
||||
if self.intentCategoryHasQuality(intent_category, machine.preferred_quality_type):
|
||||
return machine.preferred_quality_type
|
||||
|
||||
for quality_type, quality_group in ContainerTree.getInstance().getCurrentQualityGroups().items():
|
||||
for node in quality_group.nodes_for_extruders.values():
|
||||
if any(intent.intent_category == intent_category for intent in node.intents.values()):
|
||||
return quality_type
|
||||
|
||||
return ""
|
||||
|
|
|
@ -10,10 +10,13 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
#
|
||||
# This class manages a all registered upon-exit checks that need to be perform when the application tries to exit.
|
||||
# For example, to show a confirmation dialog when there is USB printing in progress, etc. All callbacks will be called
|
||||
# in the order of when they got registered. If all callbacks "passes", that is, for example, if the user clicks "yes"
|
||||
# on the exit confirmation dialog or nothing that's blocking the exit, then the application will quit after that.
|
||||
# This class manages all registered upon-exit checks
|
||||
# that need to be performed when the application tries to exit.
|
||||
# For example, show a confirmation dialog when there is USB printing in progress.
|
||||
# All callbacks will be called in the order of when they were registered.
|
||||
# If all callbacks "pass", for example:
|
||||
# if the user clicks "yes" on the exit confirmation dialog
|
||||
# and nothing else is blocking the exit, then the application will quit.
|
||||
#
|
||||
class OnExitCallbackManager:
|
||||
|
||||
|
@ -35,10 +38,12 @@ class OnExitCallbackManager:
|
|||
def getIsAllChecksPassed(self) -> bool:
|
||||
return self._is_all_checks_passed
|
||||
|
||||
# Trigger the next callback if available. If not, it means that all callbacks have "passed", which means we should
|
||||
# not block the application to quit, and it will call the application to actually quit.
|
||||
# Trigger the next callback if there is one.
|
||||
# If not, all callbacks have "passed",
|
||||
# which means we should not prevent the application from quitting,
|
||||
# and we call the application to actually quit.
|
||||
def triggerNextCallback(self) -> None:
|
||||
# Get the next callback and schedule that if
|
||||
# Get the next callback and schedule it
|
||||
this_callback = None
|
||||
if self._current_callback_idx < len(self._on_exit_callback_list):
|
||||
this_callback = self._on_exit_callback_list[self._current_callback_idx]
|
||||
|
@ -55,10 +60,11 @@ class OnExitCallbackManager:
|
|||
# Tell the application to exit
|
||||
self._application.callLater(self._application.closeApplication)
|
||||
|
||||
# This is the callback function which an on-exit callback should call when it finishes, it should provide the
|
||||
# "should_proceed" flag indicating whether this check has "passed", or in other words, whether quitting the
|
||||
# application should be blocked. If the last on-exit callback doesn't block the quitting, it will call the next
|
||||
# registered on-exit callback if available.
|
||||
# Callback function which an on-exit callback calls when it finishes.
|
||||
# It provides a "should_proceed" flag indicating whether the check has "passed",
|
||||
# or whether quitting the application should be blocked.
|
||||
# If the last on-exit callback doesn't block quitting, it will call the next
|
||||
# registered on-exit callback if one is available.
|
||||
def onCurrentCallbackFinished(self, should_proceed: bool = True) -> None:
|
||||
if not should_proceed:
|
||||
Logger.log("d", "on-app-exit callback finished and we should not proceed.")
|
||||
|
|
|
@ -12,7 +12,7 @@ class AddPrinterPagesModel(WelcomePagesModel):
|
|||
|
||||
def initialize(self, cancellable: bool = True) -> None:
|
||||
self._pages.append({"id": "add_network_or_local_printer",
|
||||
"page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"),
|
||||
"page_url": self._getBuiltinWelcomePagePath("AddUltimakerOrThirdPartyPrinterStack.qml"),
|
||||
"next_page_id": "machine_actions",
|
||||
"next_page_button_text": self._catalog.i18nc("@action:button", "Add"),
|
||||
})
|
||||
|
|
|
@ -23,9 +23,9 @@ catalog = i18nCatalog("cura")
|
|||
|
||||
|
||||
class PrintInformation(QObject):
|
||||
"""A class for processing and the print times per build plate as well as managing the job name
|
||||
"""A class for processing the print times per build plate and managing the job name
|
||||
|
||||
This class also mangles the current machine name and the filename of the first loaded mesh into a job name.
|
||||
This class also combines the current machine name and the filename of the first loaded mesh into a job name.
|
||||
This job name is requested by the JobSpecs qml file.
|
||||
"""
|
||||
|
||||
|
@ -38,6 +38,8 @@ class PrintInformation(QObject):
|
|||
|
||||
self.initializeCuraMessagePrintTimeProperties()
|
||||
|
||||
self.slice_uuid: Optional[str] = None
|
||||
|
||||
# Indexed by build plate number
|
||||
self._material_lengths = {} # type: Dict[int, List[float]]
|
||||
self._material_weights = {} # type: Dict[int, List[float]]
|
||||
|
@ -184,7 +186,7 @@ class PrintInformation(QObject):
|
|||
|
||||
if time != time: # Check for NaN. Engine can sometimes give us weird values.
|
||||
duration.setDuration(0)
|
||||
Logger.log("w", "Received NaN for print duration message")
|
||||
Logger.warning("Received NaN for print duration message")
|
||||
continue
|
||||
|
||||
total_estimated_time += time
|
||||
|
@ -366,7 +368,7 @@ class PrintInformation(QObject):
|
|||
mime_type = MimeTypeDatabase.getMimeTypeForFile(name)
|
||||
data = mime_type.stripExtension(name)
|
||||
except MimeTypeNotFoundError:
|
||||
Logger.log("w", "Unsupported Mime Type Database file extension %s", name)
|
||||
Logger.warning(f"Unsupported Mime Type Database file extension {name}")
|
||||
|
||||
if data is not None and check_name is not None:
|
||||
self._base_name = data
|
||||
|
@ -390,7 +392,7 @@ class PrintInformation(QObject):
|
|||
return self._base_name
|
||||
|
||||
def _defineAbbreviatedMachineName(self) -> None:
|
||||
"""Created an acronym-like abbreviated machine name from the currently active machine name.
|
||||
"""Creates an abbreviated machine name from the currently active machine name.
|
||||
|
||||
Called each time the global stack is switched.
|
||||
"""
|
||||
|
@ -444,7 +446,7 @@ class PrintInformation(QObject):
|
|||
self.setToZeroPrintInformation(self._active_build_plate)
|
||||
|
||||
def _onOutputStart(self, output_device: OutputDevice) -> None:
|
||||
"""If this is the sort of output 'device' (like local or online file storage, rather than a printer),
|
||||
"""If this is a sort of output 'device' (like local or online file storage, rather than a printer),
|
||||
the user could have altered the file-name, and thus the project name should be altered as well."""
|
||||
if isinstance(output_device, ProjectOutputDevice):
|
||||
new_name = output_device.getLastOutputName()
|
||||
|
|
|
@ -265,7 +265,7 @@ class WelcomePagesModel(ListModel):
|
|||
"should_show_function": self.shouldShowCloudPage,
|
||||
},
|
||||
{"id": "add_network_or_local_printer",
|
||||
"page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"),
|
||||
"page_url": self._getBuiltinWelcomePagePath("AddUltimakerOrThirdPartyPrinterStack.qml"),
|
||||
"next_page_id": "machine_actions",
|
||||
},
|
||||
{"id": "add_printer_by_ip",
|
||||
|
|
|
@ -62,15 +62,21 @@ class WhatsNewPagesModel(WelcomePagesModel):
|
|||
|
||||
def initialize(self) -> None:
|
||||
self._pages = []
|
||||
self._pages.append({"id": "whats_new",
|
||||
"page_url": self._getBuiltinWelcomePagePath("WhatsNewContent.qml"),
|
||||
"next_page_button_text": self._catalog.i18nc("@action:button", "Skip"),
|
||||
"next_page_id": "changelog"
|
||||
})
|
||||
self._pages.append({"id": "changelog",
|
||||
"page_url": self._getBuiltinWelcomePagePath("ChangelogContent.qml"),
|
||||
"next_page_button_text": self._catalog.i18nc("@action:button", "Close"),
|
||||
})
|
||||
try:
|
||||
self._pages.append({"id": "whats_new",
|
||||
"page_url": self._getBuiltinWelcomePagePath("WhatsNewContent.qml"),
|
||||
"next_page_button_text": self._catalog.i18nc("@action:button", "Skip"),
|
||||
"next_page_id": "changelog"
|
||||
})
|
||||
except FileNotFoundError:
|
||||
Logger.warning("Unable to find what's new page")
|
||||
try:
|
||||
self._pages.append({"id": "changelog",
|
||||
"page_url": self._getBuiltinWelcomePagePath("ChangelogContent.qml"),
|
||||
"next_page_button_text": self._catalog.i18nc("@action:button", "Close"),
|
||||
})
|
||||
except FileNotFoundError:
|
||||
Logger.warning("Unable to find changelog page")
|
||||
self.setItems(self._pages)
|
||||
|
||||
images, max_image = WhatsNewPagesModel._collectOrdinalFiles(Resources.Images, WhatsNewPagesModel.image_formats)
|
||||
|
|
|
@ -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.
|
||||
|
||||
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl
|
||||
|
@ -18,6 +18,7 @@ if TYPE_CHECKING:
|
|||
from UM.Signal import Signal
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class CloudMaterialSync(QObject):
|
||||
"""
|
||||
Handles the synchronisation of material profiles with cloud accounts.
|
||||
|
@ -44,7 +45,6 @@ class CloudMaterialSync(QObject):
|
|||
break
|
||||
|
||||
def openSyncAllWindow(self):
|
||||
|
||||
self.reset()
|
||||
|
||||
if self.sync_all_dialog is None:
|
||||
|
|
|
@ -18,6 +18,7 @@ import os
|
|||
if sys.platform != "linux": # Turns out the Linux build _does_ use this, but we're not making an Enterprise release for that system anyway.
|
||||
os.environ["QT_PLUGIN_PATH"] = "" # Security workaround: Don't need it, and introduces an attack vector, so set to nul.
|
||||
os.environ["QML2_IMPORT_PATH"] = "" # Security workaround: Don't need it, and introduces an attack vector, so set to nul.
|
||||
os.environ["QT_OPENGL_DLL"] = "" # Security workaround: Don't need it, and introduces an attack vector, so set to nul.
|
||||
|
||||
from PyQt6.QtNetwork import QSslConfiguration, QSslSocket
|
||||
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Abort at the first error.
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
PROJECT_DIR="$( cd "${SCRIPT_DIR}/.." && pwd )"
|
||||
|
||||
# Make sure that environment variables are set properly
|
||||
export PATH="${CURA_BUILD_ENV_PATH}/bin:${PATH}"
|
||||
export PKG_CONFIG_PATH="${CURA_BUILD_ENV_PATH}/lib/pkgconfig:${PKG_CONFIG_PATH}"
|
||||
export LD_LIBRARY_PATH="${CURA_BUILD_ENV_PATH}/lib:${LD_LIBRARY_PATH}"
|
||||
|
||||
cd "${PROJECT_DIR}"
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Clone Uranium and set PYTHONPATH first
|
||||
#
|
||||
|
||||
# Check the branch to use for Uranium.
|
||||
# It tries the following branch names and uses the first one that's available.
|
||||
# - GITHUB_HEAD_REF: the branch name of a PR. If it's not a PR, it will be empty.
|
||||
# - GITHUB_BASE_REF: the branch a PR is based on. If it's not a PR, it will be empty.
|
||||
# - GITHUB_REF: the branch name if it's a branch on the repository;
|
||||
# refs/pull/123/merge if it's a pull_request.
|
||||
# - master: the master branch. It should always exist.
|
||||
|
||||
# For debugging.
|
||||
echo "GITHUB_REF: ${GITHUB_REF}"
|
||||
echo "GITHUB_HEAD_REF: ${GITHUB_HEAD_REF}"
|
||||
echo "GITHUB_BASE_REF: ${GITHUB_BASE_REF}"
|
||||
|
||||
GIT_REF_NAME_LIST=( "${GITHUB_HEAD_REF}" "${GITHUB_BASE_REF}" "${GITHUB_REF}" "master" )
|
||||
for git_ref_name in "${GIT_REF_NAME_LIST[@]}"
|
||||
do
|
||||
if [ -z "${git_ref_name}" ]; then
|
||||
continue
|
||||
fi
|
||||
git_ref_name="$(basename "${git_ref_name}")"
|
||||
# Skip refs/pull/1234/merge as pull requests use it as GITHUB_REF
|
||||
if [[ "${git_ref_name}" == "merge" ]]; then
|
||||
echo "Skip [${git_ref_name}]"
|
||||
continue
|
||||
fi
|
||||
URANIUM_BRANCH="${git_ref_name}"
|
||||
output="$(git ls-remote --heads https://github.com/Ultimaker/Uranium.git "${URANIUM_BRANCH}")"
|
||||
if [ -n "${output}" ]; then
|
||||
echo "Found Uranium branch [${URANIUM_BRANCH}]."
|
||||
break
|
||||
else
|
||||
echo "Could not find Uranium branch [${URANIUM_BRANCH}], try next."
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Using Uranium branch ${URANIUM_BRANCH} ..."
|
||||
git clone --depth=1 -b "${URANIUM_BRANCH}" https://github.com/Ultimaker/Uranium.git "${PROJECT_DIR}"/Uranium
|
||||
export PYTHONPATH="${PROJECT_DIR}/Uranium:.:${PYTHONPATH}"
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_PREFIX_PATH="${CURA_BUILD_ENV_PATH}" \
|
||||
-DURANIUM_DIR="${PROJECT_DIR}/Uranium" \
|
||||
-DBUILD_TESTS=ON \
|
||||
-DPRINT_PLUGIN_LIST=OFF \
|
||||
-DGENERATE_TRANSLATIONS=OFF \
|
||||
..
|
||||
make
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
cd build
|
||||
ctest -j4 --output-on-failure -T Test
|
81
docs/Report.md
Normal file
|
@ -0,0 +1,81 @@
|
|||
|
||||
# Reporting Issues
|
||||
|
||||
Please attach the following information in case <br>
|
||||
you want to report crashing or similar issues.
|
||||
|
||||
<br>
|
||||
|
||||
## DxDiag
|
||||
|
||||
### ![Badge Windows]
|
||||
|
||||
The log as produced by **dxdiag**.
|
||||
|
||||
<kbd> start </kbd> » <kbd> run </kbd> » <kbd> dxdiag </kbd> » <kbd> save output </kbd>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
## Cura GUI Log
|
||||
|
||||
If the Cura user interface still starts, you can also <br>
|
||||
reach these directories from the application menu:
|
||||
|
||||
<kbd> Help </kbd> » <kbd> Show settings folder </kbd>
|
||||
|
||||
<br>
|
||||
|
||||
### ![Badge Windows]
|
||||
|
||||
```
|
||||
%APPDATA%\cura\<Cura Version>\cura.log
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
C:\Users\<your username>\AppData\Roaming\cura\<Cura Version>\cura.log
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
### ![Badge Linux]
|
||||
|
||||
```
|
||||
~/.local/share/cura/<Cura Version>/cura.log
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
### ![Badge MacOS]
|
||||
|
||||
```
|
||||
~/Library/Application Support/cura/<Cura Version>/cura.log
|
||||
```
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
## Alternative
|
||||
|
||||
An alternative is to install the **[ExtensiveSupportLogging]** <br>
|
||||
plugin this creates a zip folder of the relevant log files.
|
||||
|
||||
If you're experiencing performance issues, we might ask <br>
|
||||
you to connect the CPU profiler in this plugin and attach <br>
|
||||
the collected data to your support ticket.
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<!----------------------------------------------------------------------------->
|
||||
|
||||
[ExtensiveSupportLogging]: https://marketplace.ultimaker.com/app/cura/plugins/UltimakerPackages/ExtensiveSupportLogging
|
||||
|
||||
|
||||
<!---------------------------------[ Badges ]---------------------------------->
|
||||
|
||||
[Badge Windows]: https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logoColor=white&logo=Windows
|
||||
[Badge Linux]: https://img.shields.io/badge/Linux-00A95C?style=for-the-badge&logoColor=white&logo=Linux
|
||||
[Badge MacOS]: https://img.shields.io/badge/MacOS-403C3D?style=for-the-badge&logoColor=white&logo=MacOS
|
|
@ -2,32 +2,77 @@ Setting Properties
|
|||
====
|
||||
Each setting in Cura has a number of properties. It's not just a key and a value. This page lists the properties that a setting can define.
|
||||
|
||||
* `key` (string): The identifier by which the setting is referenced. This is not a human-readable name, but just a reference string, such as `layer_height_0`. Typically these are named with the most significant category first, in order to sort them better, such as `material_print_temperature`. This is not actually a real property but just an identifier; it can't be changed.
|
||||
* `value` (optional): The current value of the setting. This can be a function, an arbitrary Python expression that depends on the values of other settings. If it's not present, the `default_value` is used.
|
||||
* `default_value`: A default value for the setting if `value` is undefined. This property is required however. It can't be a Python expression, but it can be any JSON type. This is made separate so that CuraEngine can read it out as well for its debugging mode via the command line, without needing a complete Python interpreter.
|
||||
* `label` (string): The human-readable name for the setting. This label is translated.
|
||||
* `description` (string): A longer description of what the setting does when you change it. This description is translated as well.
|
||||
* `type` (string): The type of value that this setting contains. Allowed types are: `bool`, `str`, `float`, `int`, `enum`, `category`, `[int]`, `vec3`, `polygon` and `polygons`.
|
||||
* `unit` (optional string): A unit that is displayed at the right-hand side of the text field where the user enters the setting value.
|
||||
* `resolve` (optional string): A Python expression that resolves disagreements for global settings if multiple per-extruder profiles define different values for a setting. Typically this takes the values for the setting from all stacks and computes one final value for it that will be used for the global setting. For instance, the `resolve` function for the build plate temperature is `max(extruderValues('material_bed_temperature')`, meaning that it will use the hottest bed temperature of all materials of the extruders in use.
|
||||
* `limit_to_extruder` (optional): A Python expression that indicates which extruder a setting will be obtained from. This is used for settings that may be extruder-specific but the extruder is not necessarily the current extruder. For instance, support settings need to be evaluated for the support extruder. Infill settings need to be evaluated for the infill extruder if the infill extruder is changed.
|
||||
* `enabled` (optional string or boolean): Whether the setting can currently be made visible for the user. This can be a simple true/false, or a Python expression that depends on other settings. Typically used for settings that don't apply when another setting is disabled, such as to hide the support settings if support is disabled.
|
||||
* `minimum_value` (optional): The lowest acceptable value for this setting. If it's any lower, Cura will not allow the user to slice. By convention this is used to prevent setting values that are technically or physically impossible, such as a layer height of 0mm. This property only applies to numerical settings.
|
||||
* `maximum_value` (optional): The highest acceptable value for this setting. If it's any higher, Cura will not allow the user to slice. By convention this is used to prevent setting values that are technically or physically impossible, such as a support overhang angle of more than 90 degrees. This property only applies to numerical settings.
|
||||
* `minimum_value_warning` (optional): The threshold under which a warning is displayed to the user. By convention this is used to indicate that it will probably not print very nicely with such a low setting value. This property only applies to numerical settings.
|
||||
* `maximum_value_warning` (optional): The threshold above which a warning is displayed to the user. By convention this is used to indicate that it will probably not print very nicely with such a high setting value. This property only applies to numerical settings.
|
||||
* `settable_globally` (optional boolean): Whether the setting can be changed globally. For some mesh-type settings such as `support_mesh` this doesn't make sense, so those can't be changed globally. They are not displayed in the main settings list then.
|
||||
* `settable_per_meshgroup` (optional boolean): Whether a setting can be changed per group of meshes. Currently unused in Cura.
|
||||
* `settable_per_extruder` (optional boolean): Whether a setting can be changed per extruder. Some settings, like the build plate temperature, can't be adjusted separately for each extruder. An icon is shown in the interface to indicate this. If the user changes these settings they are stored in the global stack.
|
||||
* `settable_per_mesh` (optional boolean): Whether a setting can be changed per mesh. The settings that can be changed per mesh are shown in the list of available settings in the per-object settings tool.
|
||||
* `children` (optional list): A list of child settings. These are displayed with an indentation. If all child settings are overridden by the user, the parent setting gets greyed out to indicate that the parent setting has no effect any more. This is not strictly always the case though, because that would depend on the inheritance functions in the `value`.
|
||||
* `icon` (optional string): A path to an icon to be displayed. Only applies to setting categories.
|
||||
* `allow_empty` (optional bool): Whether the setting is allowed to be empty. If it's not, this will be treated as a setting error and Cura will not allow the user to slice. Only applies to string-type settings.
|
||||
* `warning_description` (optional string): A warning message to display when the setting has a warning value. This is currently unused by Cura.
|
||||
* `error_description` (optional string): An error message to display when the setting has an error value. This is currently unused by Cura.
|
||||
* `options` (dictionary): A list of values that the user can choose from. The keys of this dictionary are keys that CuraEngine identifies the option with. The values are human-readable strings and will be translated. Only applies to (and only required for) enum-type settings.
|
||||
* `comments` (optional string): Comments to other programmers about the setting. This is not used by Cura.
|
||||
* `is_uuid` (optional boolean): Whether or not this setting indicates a UUID-4. If it is, the setting will indicate an error if it's not in the correct format. Only applies to string-type settings.
|
||||
* `regex_blacklist_pattern` (optional string): A regular expression, where if the setting value matches with this regular expression, it gets an error state. Only applies to string-type settings.
|
||||
* `error_value` (optional): If the setting value is equal to this value, it will show a setting error. This is used to display errors for non-numerical settings such as checkboxes.
|
||||
* `warning_value` (optional): If the setting value is equal to this value, it will show a setting warning. This is used to display warnings for non-numerical settings such as checkboxes.
|
||||
* `key` (string): __The identifier by which the setting is referenced.__
|
||||
* This is not a human-readable name, but just a reference string, such as `layer_height_0`.
|
||||
* This is not actually a real property but just an identifier; it can't be changed.
|
||||
* Typically these are named with the most significant category first, in order to sort them better, such as `material_print_temperature`.
|
||||
* `value` (optional): __The current value of the setting.__
|
||||
* This can be a function (an arbitrary Python expression) that depends on the values of other settings.
|
||||
* If it's not present, the `default_value` is used.
|
||||
* `default_value`: __A default value for the setting if `value` is undefined.__
|
||||
* This property is required.
|
||||
* It can't be a Python expression, but it can be any JSON type.
|
||||
* This is made separate so that CuraEngine can read it out for its debugging mode via the command line, without needing a complete Python interpreter.
|
||||
* `label` (string): __The human-readable name for the setting.__
|
||||
* This label is translated.
|
||||
* `description` (string): __A longer description of what the setting does when you change it.__
|
||||
* This description is translated.
|
||||
* `type` (string): __The type of value that this setting contains.__
|
||||
* Allowed types are: `bool`, `str`, `float`, `int`, `enum`, `category`, `[int]`, `vec3`, `polygon` and `polygons`.
|
||||
* `unit` (optional string): __A unit that is displayed at the right-hand side of the text field where the user enters the setting value.__
|
||||
* `resolve` (optional string): __A Python expression that resolves disagreements for global settings if multiple per-extruder profiles define different values for a setting.__
|
||||
* Typically this takes the values for the setting from all stacks and computes one final value for it that will be used for the global setting. For instance, the `resolve` function for the build plate temperature is `max(extruderValues('material_bed_temperature')`, meaning that it will use the hottest bed temperature of all materials of the extruders in use.
|
||||
* `limit_to_extruder` (optional): __A Python expression that indicates which extruder a setting will be obtained from.__
|
||||
* This is used for settings that may be extruder-specific but the extruder is not necessarily the current extruder. For instance, support settings need to be evaluated for the support extruder. Infill settings need to be evaluated for the infill extruder if the infill extruder is changed.
|
||||
* `enabled` (optional string or boolean): __Whether the setting can currently be made visible for the user.__
|
||||
* This can be a simple true/false, or a Python expression that depends on other settings.
|
||||
* Typically used for settings that don't apply when another setting is disabled, such as to hide the support settings if support is disabled.
|
||||
* `minimum_value` (optional): __The lowest acceptable value for this setting.__
|
||||
* If it's any lower, Cura will not allow the user to slice.
|
||||
* This property only applies to numerical settings.
|
||||
* By convention this is used to prevent setting values that are technically or physically impossible, such as a layer height of 0mm.
|
||||
* `maximum_value` (optional): __The highest acceptable value for this setting.__
|
||||
* If it's any higher, Cura will not allow the user to slice.
|
||||
* This property only applies to numerical settings.
|
||||
* By convention this is used to prevent setting values that are technically or physically impossible, such as a support overhang angle of more than 90 degrees.
|
||||
* `minimum_value_warning` (optional): __The threshold under which a warning is displayed to the user.__
|
||||
* This property only applies to numerical settings.
|
||||
* By convention this is used to indicate that it will probably not print very nicely with such a low setting value.
|
||||
* `maximum_value_warning` (optional): __The threshold above which a warning is displayed to the user.__
|
||||
* This property only applies to numerical settings.
|
||||
* By convention this is used to indicate that it will probably not print very nicely with such a high setting value.
|
||||
* `settable_globally` (optional boolean): __Whether the setting can be changed globally.__
|
||||
* For some mesh-type settings such as `support_mesh` this doesn't make sense, so those can't be changed globally. They are not displayed in the main settings list then.
|
||||
* `settable_per_meshgroup` (optional boolean): __Whether a setting can be changed per group of meshes.__
|
||||
* *This is currently unused by Cura.*
|
||||
* `settable_per_extruder` (optional boolean): __Whether a setting can be changed per extruder.__
|
||||
* Some settings, like the build plate temperature, can't be adjusted separately for each extruder. An icon is shown in the interface to indicate this.
|
||||
* If the user changes these settings they are stored in the global stack.
|
||||
* `settable_per_mesh` (optional boolean): __Whether a setting can be changed per mesh.__
|
||||
* The settings that can be changed per mesh are shown in the list of available settings in the per-object settings tool.
|
||||
* `children` (optional list): __A list of child settings.__
|
||||
* These are displayed with an indentation. If all child settings are overridden by the user, the parent setting gets greyed out to indicate that the parent setting has no effect any more. This is not strictly always the case though, because that would depend on the inheritance functions in the `value`.
|
||||
* `icon` (optional string): __A path to an icon to be displayed.__
|
||||
* Only applies to setting categories.
|
||||
* `allow_empty` (optional bool): __Whether the setting is allowed to be empty.__
|
||||
* If it's not, this will be treated as a setting error and Cura will not allow the user to slice.
|
||||
* Only applies to string-type settings.
|
||||
* `warning_description` (optional string): __A warning message to display when the setting has a warning value.__
|
||||
* *This is currently unused by Cura.*
|
||||
* `error_description` (optional string): __An error message to display when the setting has an error value.__
|
||||
* *This is currently unused by Cura.*
|
||||
* `options` (dictionary): __A list of values that the user can choose from.__
|
||||
* The keys of this dictionary are keys that CuraEngine identifies the option with.
|
||||
* The values are human-readable strings and will be translated.
|
||||
* Only applies to (and only required for) enum-type settings.
|
||||
* `comments` (optional string): __Comments to other programmers about the setting.__
|
||||
* *This is currently unused by Cura.*
|
||||
* `is_uuid` (optional boolean): __Whether or not this setting indicates a UUID-4.__
|
||||
* If it is, the setting will indicate an error if it's not in the correct format.
|
||||
* Only applies to string-type settings.
|
||||
* `regex_blacklist_pattern` (optional string): __A regular expression, where if the setting value matches with this regular expression, it gets an error state.__
|
||||
* Only applies to string-type settings.
|
||||
* `error_value` (optional): __If the setting value is equal to this value, it will show a setting error.__
|
||||
* This is used to display errors for non-numerical settings such as checkboxes.
|
||||
* `warning_value` (optional): __If the setting value is equal to this value, it will show a setting warning.__
|
||||
* This is used to display warnings for non-numerical settings such as checkboxes.
|
||||
|
|
|
@ -1,21 +1,33 @@
|
|||
Repositories
|
||||
====
|
||||
Cura uses a number of repositories where parts of our source code are separated, in order to get a cleaner architecture. Those repositories are:
|
||||
* [Cura](https://github.com/Ultimaker/Cura), the main repository for the front-end of Cura. This contains all of the business logic for the front-end, including the specific types of profiles that are available, the concept of 3D printers and materials, specific tools for handling 3D printed models, pretty much all of the GUI, as well as Ultimaker services such as the Marketplace and accounts.
|
||||
* The Cura repository is built on [Uranium](https://github.com/Ultimaker/Uranium), a framework for desktop applications that handle 3D models and have a separate back-end. This provides Cura with a basic GUI framework ([Qt](https://www.qt.io/)), a 3D scene, a rendering system, a plug-in system and a system for stacked profiles that change settings.
|
||||
* In order to slice, Cura starts [CuraEngine](https://github.com/Ultimaker/CuraEngine) in the background. This does the actual process that converts 3D models into a toolpath for the printer.
|
||||
* Communication to CuraEngine goes via [libArcus](https://github.com/Ultimaker/libArcus), a small library that wraps around [Protobuf](https://developers.google.com/protocol-buffers/) in order to make it run over a local socket.
|
||||
* Cura's build scripts are in [cura-build](https://github.com/Ultimaker/cura-build) and build scripts for building dependencies are in [cura-build-environment](https://github.com/Ultimaker/cura-build-environment).
|
||||
* [Cura](https://github.com/Ultimaker/Cura) is the main repository for the front-end of Cura. This contains:
|
||||
- all of the business logic for the front-end, including the specific types of profiles that are available
|
||||
- the concept of 3D printers and materials
|
||||
- specific tools for handling 3D printed models
|
||||
- pretty much all of the GUI
|
||||
- Ultimaker services such as the Marketplace and accounts.
|
||||
* [Uranium](https://github.com/Ultimaker/Uranium) is the underlying framework the Cura repository is built on. [Uranium](https://github.com/Ultimaker/Uranium) is a framework for desktop applications that handle 3D models. It has a separate back-end. This provides Cura with:
|
||||
- a basic GUI framework ([Qt](https://www.qt.io/))
|
||||
- a 3D scene, a rendering system
|
||||
- a plug-in system
|
||||
- a system for stacked profiles that change settings.
|
||||
* [CuraEngine](https://github.com/Ultimaker/CuraEngine) is the slicer used by Cura in the background. This does the actual process that converts 3D models into a toolpath for the printer.
|
||||
* [libArcus](https://github.com/Ultimaker/libArcus) handles the communication to CuraEngine. [libArcus](https://github.com/Ultimaker/libArcus) is a small library that wraps around [Protobuf](https://developers.google.com/protocol-buffers/) in order to make it run over a local socket.
|
||||
* [cura-build](https://github.com/Ultimaker/cura-build): Cura's build scripts.
|
||||
* [cura-build-environment](https://github.com/Ultimaker/cura-build-environment) build scripts for building dependencies.
|
||||
|
||||
There are also a number of repositories under our control that are not integral parts of Cura's architecture, but more like separated side-gigs:
|
||||
* Loading and writing 3MF files is done through [libSavitar](https://github.com/Ultimaker/libSavitar).
|
||||
* Loading and writing UFP files is done through [libCharon](https://github.com/Ultimaker/libCharon).
|
||||
* To make the build system a bit simpler, some parts are pre-compiled in [cura-binary-data](https://github.com/Ultimaker/cura-binary-data). This holds things like the machine-readable translation files and the Marlin builds for firmware updates, which would require considerable tooling to build automatically.
|
||||
* There are automated GUI tests in [Cura-squish-tests](https://github.com/Ultimaker/Cura-squish-tests).
|
||||
* Material profiles are stored in [fdm_materials](https://github.com/Ultimaker/fdm_materials). This is separated out and combined in our build process, so that the firmware for Ultimaker's printers can use the same set of profiles too.
|
||||
* [libSavitar](https://github.com/Ultimaker/libSavitar) is used for loading and writing 3MF files.
|
||||
* [libCharon](https://github.com/Ultimaker/libCharon) is used for loading and writing UFP files.
|
||||
* [cura-binary-data](https://github.com/Ultimaker/cura-binary-data) pre-compiled parts to make the build system a bit simpler. This holds things which would require considerable tooling to build automatically like:
|
||||
- the machine-readable translation files
|
||||
- the Marlin builds for firmware updates
|
||||
* [Cura-squish-tests](https://github.com/Ultimaker/Cura-squish-tests): automated GUI tests.
|
||||
* [fdm_materials](https://github.com/Ultimaker/fdm_materials) stores Material profiles. This is separated out and combined in our build process, so that the firmware for Ultimaker's printers can use the same set of profiles too.
|
||||
|
||||
Interplay
|
||||
----
|
||||
At a very high level, Cura's repositories interconnect as follows:
|
||||
|
||||

|
||||

|
||||
|
|
54
docs/resources/deps.dot
Normal file
|
@ -0,0 +1,54 @@
|
|||
digraph {
|
||||
"cpython/3.10.4@ultimaker/testing" -> "zlib/1.2.12"
|
||||
"cpython/3.10.4@ultimaker/testing" -> "openssl/1.1.1l"
|
||||
"cpython/3.10.4@ultimaker/testing" -> "expat/2.4.1"
|
||||
"cpython/3.10.4@ultimaker/testing" -> "libffi/3.2.1"
|
||||
"cpython/3.10.4@ultimaker/testing" -> "mpdecimal/2.5.0@ultimaker/testing"
|
||||
"cpython/3.10.4@ultimaker/testing" -> "libuuid/1.0.3"
|
||||
"cpython/3.10.4@ultimaker/testing" -> "libxcrypt/4.4.25"
|
||||
"cpython/3.10.4@ultimaker/testing" -> "bzip2/1.0.8"
|
||||
"cpython/3.10.4@ultimaker/testing" -> "gdbm/1.19"
|
||||
"cpython/3.10.4@ultimaker/testing" -> "sqlite3/3.36.0"
|
||||
"cpython/3.10.4@ultimaker/testing" -> "tk/8.6.10"
|
||||
"cpython/3.10.4@ultimaker/testing" -> "ncurses/6.2"
|
||||
"cpython/3.10.4@ultimaker/testing" -> "xz_utils/5.2.5"
|
||||
"pynest2d/5.1.0-beta+3@ultimaker/stable" -> "libnest2d/5.1.0-beta+3@ultimaker/stable"
|
||||
"pynest2d/5.1.0-beta+3@ultimaker/stable" -> "cpython/3.10.4@ultimaker/testing"
|
||||
"freetype/2.12.1" -> "libpng/1.6.37"
|
||||
"freetype/2.12.1" -> "zlib/1.2.12"
|
||||
"freetype/2.12.1" -> "bzip2/1.0.8"
|
||||
"freetype/2.12.1" -> "brotli/1.0.9"
|
||||
"savitar/5.1.0-beta+3@ultimaker/stable" -> "pugixml/1.12.1"
|
||||
"savitar/5.1.0-beta+3@ultimaker/stable" -> "cpython/3.10.4@ultimaker/testing"
|
||||
"arcus/5.1.0-beta+3@ultimaker/stable" -> "protobuf/3.17.1"
|
||||
"arcus/5.1.0-beta+3@ultimaker/stable" -> "cpython/3.10.4@ultimaker/testing"
|
||||
"arcus/5.1.0-beta+3@ultimaker/stable" -> "zlib/1.2.12"
|
||||
"libpng/1.6.37" -> "zlib/1.2.12"
|
||||
"curaengine/5.1.0-beta+3@ultimaker/stable" -> "clipper/6.4.2"
|
||||
"curaengine/5.1.0-beta+3@ultimaker/stable" -> "boost/1.78.0"
|
||||
"curaengine/5.1.0-beta+3@ultimaker/stable" -> "rapidjson/1.1.0"
|
||||
"curaengine/5.1.0-beta+3@ultimaker/stable" -> "stb/20200203"
|
||||
"curaengine/5.1.0-beta+3@ultimaker/stable" -> "protobuf/3.17.1"
|
||||
"curaengine/5.1.0-beta+3@ultimaker/stable" -> "arcus/5.1.0-beta+3@ultimaker/stable"
|
||||
"tcl/8.6.10" -> "zlib/1.2.12"
|
||||
"uranium/5.1.0-beta+3@ultimaker/stable" -> "arcus/5.1.0-beta+3@ultimaker/stable"
|
||||
"uranium/5.1.0-beta+3@ultimaker/stable" -> "cpython/3.10.4@ultimaker/testing"
|
||||
"libnest2d/5.1.0-beta+3@ultimaker/stable" -> "boost/1.78.0"
|
||||
"libnest2d/5.1.0-beta+3@ultimaker/stable" -> "clipper/6.4.2"
|
||||
"libnest2d/5.1.0-beta+3@ultimaker/stable" -> "nlopt/2.7.0"
|
||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "arcus/5.1.0-beta+3@ultimaker/stable"
|
||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "curaengine/5.1.0-beta+3@ultimaker/stable"
|
||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "savitar/5.1.0-beta+3@ultimaker/stable"
|
||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "pynest2d/5.1.0-beta+3@ultimaker/stable"
|
||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "uranium/5.1.0-beta+3@ultimaker/stable"
|
||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "fdm_materials/5.1.0-beta+3@ultimaker/stable"
|
||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "cura_binary_data/5.1.0-beta+3@ultimaker/stable"
|
||||
"conanfile.py (cura/5.1.0-beta+3@ultimaker/testing)" -> "cpython/3.10.4@ultimaker/testing"
|
||||
"fontconfig/2.13.93" -> "freetype/2.12.1"
|
||||
"fontconfig/2.13.93" -> "expat/2.4.1"
|
||||
"fontconfig/2.13.93" -> "libuuid/1.0.3"
|
||||
"tk/8.6.10" -> "tcl/8.6.10"
|
||||
"tk/8.6.10" -> "fontconfig/2.13.93"
|
||||
"tk/8.6.10" -> "xorg/system"
|
||||
"protobuf/3.17.1" -> "zlib/1.2.12"
|
||||
}
|
BIN
icons/cura.icns
20
packaging/AppImage/AppRun
Normal file
|
@ -0,0 +1,20 @@
|
|||
#!/bin/sh
|
||||
|
||||
scriptdir=$(dirname $0)
|
||||
|
||||
export PYTHONPATH="$scriptdir/lib/python3.10"
|
||||
export LD_LIBRARY_PATH=$scriptdir
|
||||
export QT_PLUGIN_PATH="$scriptdir/qt/plugins"
|
||||
export QML2_IMPORT_PATH="$scriptdir/qt/qml"
|
||||
export QT_QPA_FONTDIR=/usr/share/fonts
|
||||
export QT_QPA_PLATFORMTHEME=xdgdesktopportal
|
||||
export QT_XKB_CONFIG_ROOT=/usr/share/X11/xkb
|
||||
|
||||
# Use the openssl.cnf packaged in the AppImage
|
||||
export OPENSSL_CONF="$scriptdir/openssl.cnf"
|
||||
|
||||
# If this variable is set on Zorin OS 16 Cura would crash
|
||||
# unset `QT_STYLE_OVERRIDE` as a precaution
|
||||
unset QT_STYLE_OVERRIDE
|
||||
|
||||
$scriptdir/Ultimaker-Cura "$@"
|
76
packaging/AppImage/create_appimage.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import argparse # Command line arguments parsing and help.
|
||||
from jinja2 import Template
|
||||
import os # Finding installation directory.
|
||||
import os.path # Finding files.
|
||||
import shutil # Copying files.
|
||||
import stat # For setting file permissions.
|
||||
import subprocess # For calling system commands.
|
||||
|
||||
def build_appimage(dist_path, version, appimage_filename):
|
||||
"""
|
||||
Creates an AppImage file from the build artefacts created so far.
|
||||
"""
|
||||
copy_metadata_files(dist_path, version)
|
||||
|
||||
try:
|
||||
os.remove(os.path.join(dist_path, appimage_filename)) # Ensure any old file is removed, if it exists.
|
||||
except FileNotFoundError:
|
||||
pass # If it didn't exist, that's even better.
|
||||
|
||||
generate_appimage(dist_path, appimage_filename)
|
||||
|
||||
sign_appimage(dist_path, appimage_filename)
|
||||
|
||||
def copy_metadata_files(dist_path, version):
|
||||
"""
|
||||
Copy metadata files for the metadata of the AppImage.
|
||||
"""
|
||||
copied_files = {
|
||||
os.path.join("..", "icons", "cura-icon_256x256.png"): "cura-icon.png",
|
||||
"cura.appdata.xml": "cura.appdata.xml",
|
||||
"AppRun": "AppRun"
|
||||
}
|
||||
|
||||
packaging_dir = os.path.dirname(__file__)
|
||||
for source, dest in copied_files.items():
|
||||
print("Copying", os.path.join(packaging_dir, source), "to", os.path.join(dist_path, dest))
|
||||
shutil.copyfile(os.path.join(packaging_dir, source), os.path.join(dist_path, dest))
|
||||
|
||||
# Ensure that AppRun has the proper permissions: 755 (user reads, writes and executes, group reads and executes, world reads and executes).
|
||||
print("Changing permissions for AppRun")
|
||||
os.chmod(os.path.join(dist_path, "AppRun"), stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
|
||||
|
||||
# Provision the Desktop file with the correct version number.
|
||||
template_path = os.path.join(packaging_dir, "cura.desktop.jinja")
|
||||
desktop_path = os.path.join(dist_path, "cura.desktop")
|
||||
print("Provisioning desktop file from", template_path, "to", desktop_path)
|
||||
with open(template_path, "r") as f:
|
||||
desktop_file = Template(f.read())
|
||||
with open(desktop_path, "w") as f:
|
||||
f.write(desktop_file.render(cura_version = version))
|
||||
|
||||
def generate_appimage(dist_path, appimage_filename):
|
||||
appimage_path = os.path.join(dist_path, "..", appimage_filename)
|
||||
appimagetool = os.getenv("APPIMAGETOOL_LOCATION", "appimagetool")
|
||||
command = [appimagetool, "--appimage-extract-and-run", f"{dist_path}/", appimage_path]
|
||||
result = subprocess.call(command)
|
||||
if result != 0:
|
||||
raise RuntimeError(f"The AppImageTool command returned non-zero: {result}")
|
||||
|
||||
def sign_appimage(dist_path, appimage_filename):
|
||||
appimage_path = os.path.join(dist_path, "..", appimage_filename)
|
||||
command = ["gpg", "--yes", "--armor", "--detach-sig", appimage_path]
|
||||
result = subprocess.call(command)
|
||||
if result != 0:
|
||||
raise RuntimeError(f"The GPG command returned non-zero: {result}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = "Create AppImages of Cura.")
|
||||
parser.add_argument("dist_path", type=str, help="Path to where PyInstaller installed the distribution of Cura.")
|
||||
parser.add_argument("version", type=str, help="Full version number of Cura (e.g. '5.1.0-beta')")
|
||||
parser.add_argument("filename", type = str, help = "Filename of the AppImage (e.g. 'Ultimaker-Cura-5.1.0-beta-Linux-X64.AppImage')")
|
||||
args = parser.parse_args()
|
||||
build_appimage(args.dist_path, args.version, args.filename)
|
18
packaging/AppImage/cura.appdata.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>com.ultimaker.cura</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>LGPL-3.0</project_license>
|
||||
<name>Ultimaker Cura</name>
|
||||
<summary>Slicer to prepare your 3D printing projects</summary>
|
||||
<description>
|
||||
<p>Ultimaker Cura is a slicer, an application that prepares your model for 3D printing. Optimized, expert-tested profiles for 3D printers and materials mean you can start printing reliably in no time. And with industry-standard software integration, you can streamline your workflow for maximum efficiency.</p>
|
||||
</description>
|
||||
<url type="homepage">https://ultimaker.com/en/software/ultimaker-cura</url>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Print preparation screen</caption>
|
||||
<image>https://raw.githubusercontent.com/Ultimaker/Cura/master/screenshot.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
</component>
|
|
@ -1,19 +1,15 @@
|
|||
[Desktop Entry]
|
||||
Name=Ultimaker Cura
|
||||
Name[de]=Ultimaker Cura
|
||||
Name[nl]=Ultimaker Cura
|
||||
GenericName=3D Printing Software
|
||||
GenericName[de]=3D-Druck-Software
|
||||
GenericName[nl]=3D-printsoftware
|
||||
GenericName[nl]=3D-Print Software
|
||||
Comment=Cura converts 3D models into paths for a 3D printer. It prepares your print for maximum accuracy, minimum printing time and good reliability with many extra features that make your print come out great.
|
||||
Comment[de]=Cura wandelt 3D-Modelle in Pfade für einen 3D-Drucker um. Es bereitet Ihren Druck für maximale Genauigkeit, minimale Druckzeit und guter Zuverlässigkeit mit vielen zusätzlichen Funktionen vor, damit Ihr Druck großartig wird.
|
||||
Comment[nl]=Cura converteert 3D-modellen naar paden voor een 3D printer. Het bereidt je print voor om zeer precies, snel en betrouwbaar te kunnen printen, met veel extra functionaliteit om je print er goed uit te laten komen.
|
||||
Exec=@CMAKE_INSTALL_FULL_BINDIR@/cura %F
|
||||
TryExec=@CMAKE_INSTALL_FULL_BINDIR@/cura
|
||||
Exec=Ultimaker-Cura %F
|
||||
Icon=cura-icon
|
||||
Terminal=false
|
||||
Type=Application
|
||||
MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;text/x-gcode;application/x-amf;application/x-ply;application/x-ctm;model/vnd.collada+xml;model/gltf-binary;model/gltf+json;model/vnd.collada+xml+zip;
|
||||
Categories=Graphics;
|
||||
Keywords=3D;Printing;Slicer;
|
||||
StartupWMClass=cura.real
|
||||
Keywords=3D;Printing;
|
||||
X-AppImage-Version={{ cura_version }}
|
194
packaging/NSIS/Ultimaker-Cura.nsi.jinja
Normal file
|
@ -0,0 +1,194 @@
|
|||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura's build system is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
!define APP_NAME "{{ app_name }}"
|
||||
!define COMP_NAME "{{ company }}"
|
||||
!define WEB_SITE "{{ web_site }}"
|
||||
!define VERSION "{{ version }}"
|
||||
!define VIVERSION "{{ version_major }}.{{ version_minor }}.{{ version_patch }}.0"
|
||||
!define COPYRIGHT "Copyright (c) {{ year }} {{ company }}"
|
||||
!define DESCRIPTION "Application"
|
||||
!define LICENSE_TXT "{{ cura_license_file }}"
|
||||
!define INSTALLER_NAME "{{ destination }}"
|
||||
!define MAIN_APP_EXE "{{ main_app }}"
|
||||
!define INSTALL_TYPE "SetShellVarContext all"
|
||||
!define REG_ROOT "HKLM"
|
||||
!define REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\${APP_NAME}-${VERSION}"
|
||||
!define UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}-${VERSION}"
|
||||
|
||||
!define REG_START_MENU "Start Menu Folder"
|
||||
|
||||
;Require administrator access
|
||||
RequestExecutionLevel admin
|
||||
|
||||
var SM_Folder
|
||||
|
||||
######################################################################
|
||||
|
||||
VIProductVersion "${VIVERSION}"
|
||||
VIAddVersionKey "ProductName" "{{ app_name }}"
|
||||
VIAddVersionKey "CompanyName" "${COMP_NAME}"
|
||||
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
|
||||
VIAddVersionKey "FileDescription" "${DESCRIPTION}"
|
||||
VIAddVersionKey "FileVersion" "${VIVERSION}"
|
||||
|
||||
######################################################################
|
||||
|
||||
SetCompressor {{ compression_method }}
|
||||
Name "${APP_NAME}"
|
||||
Caption "${APP_NAME}"
|
||||
OutFile "${INSTALLER_NAME}"
|
||||
BrandingText "${APP_NAME}"
|
||||
InstallDir "$PROGRAMFILES64\${APP_NAME}"
|
||||
|
||||
######################################################################
|
||||
|
||||
!include "MUI2.nsh"
|
||||
!include fileassoc.nsh
|
||||
|
||||
!define MUI_ABORTWARNING
|
||||
!define MUI_UNABORTWARNING
|
||||
|
||||
!define MUI_ICON "{{ cura_icon }}"
|
||||
|
||||
!define MUI_WELCOMEFINISHPAGE_BITMAP "{{ cura_banner_img }}"
|
||||
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "{{ cura_banner_img }}"
|
||||
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
|
||||
!ifdef LICENSE_TXT
|
||||
!insertmacro MUI_PAGE_LICENSE "${LICENSE_TXT}"
|
||||
!endif
|
||||
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
|
||||
!ifdef REG_START_MENU
|
||||
!define MUI_STARTMENUPAGE_NODISABLE
|
||||
!define MUI_STARTMENUPAGE_DEFAULTFOLDER "Ultimaker Cura"
|
||||
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "${REG_ROOT}"
|
||||
!define MUI_STARTMENUPAGE_REGISTRY_KEY "${UNINSTALL_PATH}"
|
||||
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "${REG_START_MENU}"
|
||||
!insertmacro MUI_PAGE_STARTMENU Application $SM_Folder
|
||||
!endif
|
||||
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
||||
# Set up explorer to run Cura instead of directly, so it's not executed elevated (with all negative consequences that brings for an unelevated user).
|
||||
!define MUI_FINISHPAGE_RUN "$WINDIR\explorer.exe"
|
||||
!define MUI_FINISHPAGE_RUN_PARAMETERS "$INSTDIR\${MAIN_APP_EXE}"
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
|
||||
!insertmacro MUI_UNPAGE_FINISH
|
||||
|
||||
!insertmacro MUI_LANGUAGE "English"
|
||||
|
||||
######################################################################
|
||||
|
||||
Section -MainProgram
|
||||
${INSTALL_TYPE}
|
||||
SetOverwrite ifnewer
|
||||
{% for out_path, files in mapped_out_paths.items() %}SetOutPath "{{ out_path }}"{% for file in files %}
|
||||
File "{{ file[0] }}"{% endfor %}
|
||||
{% endfor %}SectionEnd
|
||||
|
||||
######################################################################
|
||||
|
||||
Section -Extension_Reg
|
||||
!insertmacro APP_ASSOCIATE "stl" "Cura.model" "Standard Tessellation Language (STL) files" "$INSTDIR\${MAIN_APP_EXE},0" "Open with {{ app_name }}" "$INSTDIR\${MAIN_APP_EXE} $\"%1$\""
|
||||
!insertmacro APP_ASSOCIATE "3mf" "Cura.project" "3D Manufacturing Format (3MF) files" "$INSTDIR\${MAIN_APP_EXE},0" "Open with {{ app_name }}" "$INSTDIR\${MAIN_APP_EXE} $\"%1$\""
|
||||
SectionEnd
|
||||
|
||||
Section -Icons_Reg
|
||||
SetOutPath "$INSTDIR"
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
!ifdef REG_START_MENU
|
||||
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
|
||||
CreateDirectory "$SMPROGRAMS\$SM_Folder"
|
||||
CreateShortCut "$SMPROGRAMS\$SM_Folder\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}"
|
||||
CreateShortCut "$SMPROGRAMS\$SM_Folder\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe"
|
||||
|
||||
!ifdef WEB_SITE
|
||||
WriteIniStr "$INSTDIR\Ultimaker Cura website.url" "InternetShortcut" "URL" "${WEB_SITE}"
|
||||
CreateShortCut "$SMPROGRAMS\$SM_Folder\Ultimaker Cura website.lnk" "$INSTDIR\Ultimaker Cura website.url"
|
||||
!endif
|
||||
!insertmacro MUI_STARTMENU_WRITE_END
|
||||
!endif
|
||||
|
||||
!ifndef REG_START_MENU
|
||||
CreateDirectory "$SMPROGRAMS\{{ app_name }}"
|
||||
CreateShortCut "$SMPROGRAMS\{{ app_name }}\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}"
|
||||
CreateShortCut "$SMPROGRAMS\{{ app_name }}\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe"
|
||||
|
||||
!ifdef WEB_SITE
|
||||
WriteIniStr "$INSTDIR\Ultimaker Cura website.url" "InternetShortcut" "URL" "${WEB_SITE}"
|
||||
CreateShortCut "$SMPROGRAMS\{{ app_name }}\Ultimaker Cura website.lnk" "$INSTDIR\Ultimaker Cura website.url"
|
||||
!endif
|
||||
!endif
|
||||
|
||||
WriteRegStr ${REG_ROOT} "${REG_APP_PATH}" "" "$INSTDIR\${MAIN_APP_EXE}"
|
||||
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayName" "${APP_NAME}"
|
||||
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "UninstallString" "$INSTDIR\uninstall.exe"
|
||||
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayIcon" "$INSTDIR\${MAIN_APP_EXE}"
|
||||
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayVersion" "${VERSION}"
|
||||
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "Publisher" "${COMP_NAME}"
|
||||
|
||||
!ifdef WEB_SITE
|
||||
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "URLInfoAbout" "${WEB_SITE}"
|
||||
!endif
|
||||
SectionEnd
|
||||
|
||||
######################################################################
|
||||
|
||||
Section Uninstall
|
||||
${INSTALL_TYPE}{% for files in mapped_out_paths.values() %}{% for file in files %}
|
||||
Delete "{{ file[1] }}"{% endfor %}{% endfor %}{% for rem_dir in rmdir_paths %}
|
||||
RmDir "{{ rem_dir }}"{% endfor %}
|
||||
|
||||
# FIXME: dirty solution, but for some reason these directories aren't removed
|
||||
RmDir "$INSTDIR\share\cura\resources\scripts"
|
||||
RmDir "$INSTDIR\share\cura\resources"
|
||||
RmDir "$INSTDIR\share\cura"
|
||||
RmDir "$INSTDIR\share\uranium\resources\scripts"
|
||||
RmDir "$INSTDIR\share\uranium\resources"
|
||||
RmDir "$INSTDIR\share\uranium"
|
||||
RmDir "$INSTDIR\share"
|
||||
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
!ifdef WEB_SITE
|
||||
Delete "$INSTDIR\${APP_NAME} website.url"
|
||||
!endif
|
||||
|
||||
RmDir /r /REBOOTOK "$INSTDIR"
|
||||
|
||||
!ifdef REG_START_MENU
|
||||
!insertmacro MUI_STARTMENU_GETFOLDER "Application" $SM_Folder
|
||||
Delete "$SMPROGRAMS\$SM_Folder\${APP_NAME}.lnk"
|
||||
Delete "$SMPROGRAMS\$SM_Folder\Uninstall ${APP_NAME}.lnk"
|
||||
!ifdef WEB_SITE
|
||||
Delete "$SMPROGRAMS\$SM_Folder\Ultimaker Cura website.lnk"
|
||||
!endif
|
||||
RmDir "$SMPROGRAMS\$SM_Folder"
|
||||
!endif
|
||||
|
||||
!ifndef REG_START_MENU
|
||||
Delete "$SMPROGRAMS\{{ app_name }}\${APP_NAME}.lnk"
|
||||
Delete "$SMPROGRAMS\{{ app_name }}\Uninstall ${APP_NAME}.lnk"
|
||||
!ifdef WEB_SITE
|
||||
Delete "$SMPROGRAMS\{{ app_name }}\Ultimaker Cura website.lnk"
|
||||
!endif
|
||||
RmDir "$SMPROGRAMS\{{ app_name }}"
|
||||
!endif
|
||||
|
||||
!insertmacro APP_UNASSOCIATE "stl" "Cura.model"
|
||||
!insertmacro APP_UNASSOCIATE "3mf" "Cura.project"
|
||||
|
||||
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
|
||||
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
|
||||
SectionEnd
|
||||
|
||||
######################################################################
|
80
packaging/NSIS/create_windows_installer.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import os
|
||||
import argparse # Command line arguments parsing and help.
|
||||
import subprocess
|
||||
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from jinja2 import Template
|
||||
|
||||
|
||||
def generate_nsi(source_path: str, dist_path: str, filename: str):
|
||||
dist_loc = Path(os.getcwd(), dist_path)
|
||||
source_loc = Path(os.getcwd(), source_path)
|
||||
instdir = Path("$INSTDIR")
|
||||
dist_paths = [p.relative_to(dist_loc.joinpath("Ultimaker-Cura")) for p in sorted(dist_loc.joinpath("Ultimaker-Cura").rglob("*")) if p.is_file()]
|
||||
mapped_out_paths = {}
|
||||
for dist_path in dist_paths:
|
||||
if "__pycache__" not in dist_path.parts:
|
||||
out_path = instdir.joinpath(dist_path).parent
|
||||
if out_path not in mapped_out_paths:
|
||||
mapped_out_paths[out_path] = [(dist_loc.joinpath("Ultimaker-Cura", dist_path), instdir.joinpath(dist_path))]
|
||||
else:
|
||||
mapped_out_paths[out_path].append((dist_loc.joinpath("Ultimaker-Cura", dist_path), instdir.joinpath(dist_path)))
|
||||
|
||||
rmdir_paths = set()
|
||||
for rmdir_f in mapped_out_paths.values():
|
||||
for _, rmdir_p in rmdir_f:
|
||||
for rmdir in rmdir_p.parents:
|
||||
rmdir_paths.add(rmdir)
|
||||
|
||||
rmdir_paths = sorted(list(rmdir_paths), reverse = True)[:-2] # Removes the `.` and `..` from the list
|
||||
|
||||
jinja_template_path = Path(source_loc.joinpath("packaging", "NSIS", "Ultimaker-Cura.nsi.jinja"))
|
||||
with open(jinja_template_path, "r") as f:
|
||||
template = Template(f.read())
|
||||
|
||||
|
||||
nsis_content = template.render(
|
||||
app_name = f"Ultimaker Cura {os.getenv('CURA_VERSION_FULL')}",
|
||||
main_app = "Ultimaker-Cura.exe",
|
||||
version = os.getenv('CURA_VERSION_FULL'),
|
||||
version_major = os.environ.get("CURA_VERSION_MAJOR"),
|
||||
version_minor = os.environ.get("CURA_VERSION_MINOR"),
|
||||
version_patch = os.environ.get("CURA_VERSION_PATCH"),
|
||||
company = "Ultimaker B.V.",
|
||||
web_site = "https://ultimaker.com",
|
||||
year = datetime.now().year,
|
||||
cura_license_file = str(source_loc.joinpath("packaging", "cura_license.txt")),
|
||||
compression_method = "LZMA", # ZLIB, BZIP2 or LZMA
|
||||
cura_banner_img = str(source_loc.joinpath("packaging", "NSIS", "cura_banner_nsis.bmp")),
|
||||
cura_icon = str(source_loc.joinpath("packaging", "icons", "Cura.ico")),
|
||||
mapped_out_paths = mapped_out_paths,
|
||||
rmdir_paths = rmdir_paths,
|
||||
destination = filename
|
||||
)
|
||||
|
||||
with open(dist_loc.joinpath("Ultimaker-Cura.nsi"), "w") as f:
|
||||
f.write(nsis_content)
|
||||
|
||||
shutil.copy(source_loc.joinpath("packaging", "NSIS", "fileassoc.nsh"), dist_loc.joinpath("fileassoc.nsh"))
|
||||
|
||||
|
||||
def build(dist_path: str):
|
||||
dist_loc = Path(os.getcwd(), dist_path)
|
||||
command = ["makensis", "/V2", "/P4", str(dist_loc.joinpath("Ultimaker-Cura.nsi"))]
|
||||
subprocess.run(command)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = "Create Windows exe installer of Cura.")
|
||||
parser.add_argument("source_path", type=str, help="Path to Conan install Cura folder.")
|
||||
parser.add_argument("dist_path", type=str, help="Path to Pyinstaller dist folder")
|
||||
parser.add_argument("filename", type = str, help = "Filename of the exe (e.g. 'Ultimaker-Cura-5.1.0-beta-Windows-X64.exe')")
|
||||
args = parser.parse_args()
|
||||
generate_nsi(args.source_path, args.dist_path, args.filename)
|
||||
build(args.dist_path)
|
BIN
packaging/NSIS/cura_banner_nsis.bmp
Normal file
After Width: | Height: | Size: 201 KiB |
134
packaging/NSIS/fileassoc.nsh
Normal file
|
@ -0,0 +1,134 @@
|
|||
; fileassoc.nsh
|
||||
; File association helper macros
|
||||
; Written by Saivert
|
||||
;
|
||||
; Improved by Nikku<https://github.com/nikku>.
|
||||
;
|
||||
; Features automatic backup system and UPDATEFILEASSOC macro for
|
||||
; shell change notification.
|
||||
;
|
||||
; |> How to use <|
|
||||
; To associate a file with an application so you can double-click it in explorer, use
|
||||
; the APP_ASSOCIATE macro like this:
|
||||
;
|
||||
; Example:
|
||||
; !insertmacro APP_ASSOCIATE "txt" "myapp.textfile" "Description of txt files" \
|
||||
; "$INSTDIR\myapp.exe,0" "Open with myapp" "$INSTDIR\myapp.exe $\"%1$\""
|
||||
;
|
||||
; Never insert the APP_ASSOCIATE macro multiple times, it is only ment
|
||||
; to associate an application with a single file and using the
|
||||
; the "open" verb as default. To add more verbs (actions) to a file
|
||||
; use the APP_ASSOCIATE_ADDVERB macro.
|
||||
;
|
||||
; Example:
|
||||
; !insertmacro APP_ASSOCIATE_ADDVERB "myapp.textfile" "edit" "Edit with myapp" \
|
||||
; "$INSTDIR\myapp.exe /edit $\"%1$\""
|
||||
;
|
||||
; To have access to more options when registering the file association use the
|
||||
; APP_ASSOCIATE_EX macro. Here you can specify the verb and what verb is to be the
|
||||
; standard action (default verb).
|
||||
;
|
||||
; Note, that this script takes into account user versus global installs.
|
||||
; To properly work you must initialize the SHELL_CONTEXT variable via SetShellVarContext.
|
||||
;
|
||||
; And finally: To remove the association from the registry use the APP_UNASSOCIATE
|
||||
; macro. Here is another example just to wrap it up:
|
||||
; !insertmacro APP_UNASSOCIATE "txt" "myapp.textfile"
|
||||
;
|
||||
; |> Note <|
|
||||
; When defining your file class string always use the short form of your application title
|
||||
; then a period (dot) and the type of file. This keeps the file class sort of unique.
|
||||
; Examples:
|
||||
; Winamp.Playlist
|
||||
; NSIS.Script
|
||||
; Photoshop.JPEGFile
|
||||
;
|
||||
; |> Tech info <|
|
||||
; The registry key layout for a global file association is:
|
||||
;
|
||||
; HKEY_LOCAL_MACHINE\Software\Classes
|
||||
; <".ext"> = <applicationID>
|
||||
; <applicationID> = <"description">
|
||||
; shell
|
||||
; <verb> = <"menu-item text">
|
||||
; command = <"command string">
|
||||
;
|
||||
;
|
||||
; The registry key layout for a per-user file association is:
|
||||
;
|
||||
; HKEY_CURRENT_USER\Software\Classes
|
||||
; <".ext"> = <applicationID>
|
||||
; <applicationID> = <"description">
|
||||
; shell
|
||||
; <verb> = <"menu-item text">
|
||||
; command = <"command string">
|
||||
;
|
||||
|
||||
!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND
|
||||
; Backup the previously associated file class
|
||||
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" ""
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0"
|
||||
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}"
|
||||
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open"
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}`
|
||||
!macroend
|
||||
|
||||
!macro APP_ASSOCIATE_EX EXT FILECLASS DESCRIPTION ICON VERB DEFAULTVERB SHELLNEW COMMANDTEXT COMMAND
|
||||
; Backup the previously associated file class
|
||||
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" ""
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0"
|
||||
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}"
|
||||
StrCmp "${SHELLNEW}" "0" +2
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}\ShellNew" "NullFile" ""
|
||||
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" `${DEFAULTVERB}`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\${VERB}" "" `${COMMANDTEXT}`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\${VERB}\command" "" `${COMMAND}`
|
||||
!macroend
|
||||
|
||||
!macro APP_ASSOCIATE_ADDVERB FILECLASS VERB COMMANDTEXT COMMAND
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\${VERB}" "" `${COMMANDTEXT}`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\${VERB}\command" "" `${COMMAND}`
|
||||
!macroend
|
||||
|
||||
!macro APP_ASSOCIATE_REMOVEVERB FILECLASS VERB
|
||||
DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}\shell\${VERB}`
|
||||
!macroend
|
||||
|
||||
|
||||
!macro APP_UNASSOCIATE EXT FILECLASS
|
||||
; Backup the previously associated file class
|
||||
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0"
|
||||
|
||||
DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}`
|
||||
!macroend
|
||||
|
||||
!macro APP_ASSOCIATE_GETFILECLASS OUTPUT EXT
|
||||
ReadRegStr ${OUTPUT} SHELL_CONTEXT "Software\Classes\.${EXT}" ""
|
||||
!macroend
|
||||
|
||||
|
||||
; !defines for use with SHChangeNotify
|
||||
!ifdef SHCNE_ASSOCCHANGED
|
||||
!undef SHCNE_ASSOCCHANGED
|
||||
!endif
|
||||
!define SHCNE_ASSOCCHANGED 0x08000000
|
||||
!ifdef SHCNF_FLUSH
|
||||
!undef SHCNF_FLUSH
|
||||
!endif
|
||||
!define SHCNF_FLUSH 0x1000
|
||||
|
||||
!macro UPDATEFILEASSOC
|
||||
; Using the system.dll plugin to call the SHChangeNotify Win32 API function so we
|
||||
; can update the shell.
|
||||
System::Call "shell32::SHChangeNotify(i,i,i,i) (${SHCNE_ASSOCCHANGED}, ${SHCNF_FLUSH}, 0, 0)"
|
||||
!macroend
|
165
packaging/cura_license.txt
Normal file
|
@ -0,0 +1,165 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
12
packaging/dmg/cura.entitlements
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
BIN
packaging/dmg/cura_background_dmg.png
Normal file
After Width: | Height: | Size: 56 KiB |
67
packaging/dmg/dmg_sign_noterize.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
import os
|
||||
import argparse # Command line arguments parsing and help.
|
||||
import subprocess
|
||||
|
||||
ULTIMAKER_CURA_DOMAIN = os.environ.get("ULTIMAKER_CURA_DOMAIN", "nl.ultimaker.cura")
|
||||
|
||||
|
||||
def build_dmg(source_path: str, dist_path: str, filename: str) -> None:
|
||||
create_dmg_executable = os.environ.get("CREATE_DMG_EXECUTABLE", "create-dmg")
|
||||
|
||||
arguments = [create_dmg_executable,
|
||||
"--window-pos", "640", "360",
|
||||
"--window-size", "690", "503",
|
||||
"--app-drop-link", "520", "272",
|
||||
"--volicon", f"{source_path}/packaging/icons/VolumeIcons_Cura.icns",
|
||||
"--icon-size", "90",
|
||||
"--icon", "Ultimaker-Cura.app", "169", "272",
|
||||
"--eula", f"{source_path}/packaging/cura_license.txt",
|
||||
"--background", f"{source_path}/packaging/dmg/cura_background_dmg.png",
|
||||
f"{dist_path}/{filename}",
|
||||
f"{dist_path}/Ultimaker-Cura.app"]
|
||||
|
||||
subprocess.run(arguments)
|
||||
|
||||
|
||||
def sign(dist_path: str, filename: str) -> None:
|
||||
codesign_executable = os.environ.get("CODESIGN", "codesign")
|
||||
codesign_identity = os.environ.get("CODESIGN_IDENTITY")
|
||||
|
||||
arguments = [codesign_executable,
|
||||
"-s", codesign_identity,
|
||||
"--timestamp",
|
||||
"-i", f"{ULTIMAKER_CURA_DOMAIN}.dmg", # TODO: check if this really should have the extra dmg. We seem to be doing this also in the old Rundeck scripts
|
||||
f"{dist_path}/{filename}"]
|
||||
|
||||
subprocess.run(arguments)
|
||||
|
||||
|
||||
def notarize(dist_path: str, filename: str) -> None:
|
||||
notarize_user = os.environ.get("MAC_NOTARIZE_USER")
|
||||
notarize_password = os.environ.get("MAC_NOTARIZE_PASS")
|
||||
altool_executable = os.environ.get("ALTOOL_EXECUTABLE", "altool")
|
||||
|
||||
arguments = [
|
||||
"xcrun", altool_executable,
|
||||
"--notarize-app",
|
||||
"--primary-bundle-id", ULTIMAKER_CURA_DOMAIN,
|
||||
"--username", notarize_user,
|
||||
"--password", notarize_password,
|
||||
"--file", f"{dist_path}/{filename}"
|
||||
]
|
||||
|
||||
subprocess.run(arguments)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = "Create dmg of Cura.")
|
||||
parser.add_argument("source_path", type=str, help="Path to Conan install Cura folder.")
|
||||
parser.add_argument("dist_path", type=str, help="Path to Pyinstaller dist folder")
|
||||
parser.add_argument("filename", type = str, help = "Filename of the dmg (e.g. 'Ultimaker-Cura-5.1.0-beta-Linux-X64.dmg')")
|
||||
args = parser.parse_args()
|
||||
build_dmg(args.source_path, args.dist_path, args.filename)
|
||||
sign(args.dist_path, args.filename)
|
||||
|
||||
notarize_dmg = bool(os.environ.get("NOTARIZE_DMG", "TRUE"))
|
||||
if notarize_dmg:
|
||||
notarize(args.dist_path, args.filename)
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |