mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-10-04 01:56:33 +08:00
Merge branch 'Ultimaker:main' into main
This commit is contained in:
commit
0627875c52
2
.github/ISSUE_TEMPLATE/featurerequest.yaml
vendored
2
.github/ISSUE_TEMPLATE/featurerequest.yaml
vendored
@ -1,6 +1,6 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea for this project.
|
||||
labels: "Type: New Feature"
|
||||
labels: ["Type: New Feature", "Status: Triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
79
.github/workflows/conan-package-create.yml
vendored
79
.github/workflows/conan-package-create.yml
vendored
@ -3,10 +3,23 @@ name: Create and Upload Conan package
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
project_name:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
recipe_id_full:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
build_id:
|
||||
required: true
|
||||
type: number
|
||||
|
||||
build_info:
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
recipe_id_latest:
|
||||
required: false
|
||||
type: string
|
||||
@ -37,11 +50,6 @@ on:
|
||||
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 }}
|
||||
@ -53,7 +61,6 @@ env:
|
||||
|
||||
jobs:
|
||||
conan-package-create:
|
||||
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
|
||||
runs-on: ${{ inputs.runs_on }}
|
||||
|
||||
steps:
|
||||
@ -85,7 +92,7 @@ jobs:
|
||||
path: |
|
||||
$HOME/.conan/data
|
||||
$HOME/.conan/conan_download_cache
|
||||
key: conan-${{ runner.os }}-${{ runner.arch }}-create-cache
|
||||
key: conan-${{ inputs.runs_on }}-${{ runner.arch }}-create-cache
|
||||
|
||||
- name: Cache Conan local repository packages (Powershell)
|
||||
uses: actions/cache@v3
|
||||
@ -95,7 +102,7 @@ jobs:
|
||||
C:\Users\runneradmin\.conan\data
|
||||
C:\.conan
|
||||
C:\Users\runneradmin\.conan\conan_download_cache
|
||||
key: conan-${{ runner.os }}-${{ runner.arch }}-create-cache
|
||||
key: conan-${{ inputs.runs_on }}-${{ runner.arch }}-create-cache
|
||||
|
||||
- name: Install MacOS system requirements
|
||||
if: ${{ runner.os == 'Macos' }}
|
||||
@ -107,7 +114,7 @@ jobs:
|
||||
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
|
||||
sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config flex bison -y
|
||||
|
||||
- name: Install GCC-12 on ubuntu-22.04
|
||||
if: ${{ startsWith(inputs.runs_on, 'ubuntu-22.04') }}
|
||||
@ -116,6 +123,12 @@ jobs:
|
||||
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
|
||||
|
||||
@ -127,28 +140,48 @@ jobs:
|
||||
if: ${{ inputs.conan_config_branch == '' }}
|
||||
run: conan config install https://github.com/Ultimaker/conan-config.git
|
||||
|
||||
- name: Create the lock file
|
||||
if: ${{ inputs.build_info }}
|
||||
run: |
|
||||
conan_build_info --v2 start ${{ inputs.project_name }} ${{ github.run_number }}000${{ inputs.build_id }}
|
||||
conan lock create --reference ${{ inputs.recipe_id_full }} --lockfile-out=conan.lock
|
||||
|
||||
- name: Create the Packages using lockfile
|
||||
if: ${{ inputs.build_info }}
|
||||
run: conan install ${{ inputs.recipe_id_full }} --build=missing --update --lockfile=conan.lock --lockfile-out=conan.lock
|
||||
|
||||
- name: Create the Packages
|
||||
if: ${{ !inputs.create_from_source }}
|
||||
if: ${{ ! inputs.build_info }}
|
||||
run: conan install ${{ inputs.recipe_id_full }} --build=missing --update
|
||||
|
||||
- name: Create the Packages (from source)
|
||||
if: ${{ inputs.create_from_source }}
|
||||
run: conan create . ${{ inputs.recipe_id_full }} --build=missing --update
|
||||
|
||||
- name: Remove the latest alias
|
||||
if: ${{ inputs.create_from_source && inputs.recipe_id_latest != '' && runner.os == 'Linux' }}
|
||||
run: |
|
||||
conan remove ${{ inputs.recipe_id_latest }} -r cura -f || true
|
||||
conan remove ${{ inputs.recipe_id_latest }} -r cura-ce -f || true
|
||||
|
||||
- name: Create the latest alias
|
||||
if: ${{ inputs.create_from_source && inputs.recipe_id_latest != '' && always() }}
|
||||
run: conan alias ${{ inputs.recipe_id_latest }} ${{ inputs.recipe_id_full }}
|
||||
- name: Create the build info
|
||||
if: ${{ inputs.build_info }}
|
||||
run: conan_build_info --v2 create buildinfo.json --lockfile conan.lock --user ${{ secrets.CONAN_USER }} --password ${{ secrets.CONAN_PASS }}
|
||||
|
||||
- name: Upload the Package(s)
|
||||
if: always()
|
||||
run: conan upload "*" -r cura --all -c
|
||||
|
||||
- name: Upload the build info
|
||||
if: ${{ inputs.build_info }}
|
||||
run: |
|
||||
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: Upload the Package(s) community
|
||||
if: ${{ always() && inputs.conan_upload_community == true }}
|
||||
run: conan upload "*" -r cura-ce -c
|
||||
|
||||
- name: Upload the log and build artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: log-${{ inputs.runs_on }}-${{ runner.arch }}
|
||||
path: |
|
||||
buildinfo.json
|
||||
conan.lock
|
||||
conanbuildinfo.txt
|
||||
conaninfo.txt
|
||||
graph_info.json
|
||||
build/**
|
||||
retention-days: 1
|
||||
|
17
.github/workflows/conan-package.yml
vendored
17
.github/workflows/conan-package.yml
vendored
@ -44,16 +44,24 @@ on:
|
||||
- '[1-9].[0-9]'
|
||||
- '[1-9].[0-9][0-9]'
|
||||
tags:
|
||||
- '[1-9].[0-9].[0-9]+'
|
||||
- '[1-9].[0-9][0-9].[0-9]+'
|
||||
- '[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:
|
||||
@ -65,12 +73,17 @@ jobs:
|
||||
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: ${{ needs.conan-recipe-version.outputs.project_name }}
|
||||
recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }}
|
||||
build_id: 1
|
||||
runs_on: 'ubuntu-20.04'
|
||||
python_version: '3.10.x'
|
||||
conan_logging_level: 'info'
|
||||
|
121
.github/workflows/conan-recipe-version.yml
vendored
121
.github/workflows/conan-recipe-version.yml
vendored
@ -29,14 +29,18 @@ on:
|
||||
description: "is current branch a release branch?"
|
||||
value: ${{ jobs.get-semver.outputs.release_branch }}
|
||||
|
||||
recipe_user:
|
||||
user:
|
||||
description: "The conan user"
|
||||
value: ${{ jobs.get-semver.outputs.user }}
|
||||
|
||||
recipe_channel:
|
||||
channel:
|
||||
description: "The conan channel"
|
||||
value: ${{ jobs.get-semver.outputs.channel }}
|
||||
|
||||
project_name:
|
||||
description: "The conan projectname"
|
||||
value: ${{ inputs.project_name }}
|
||||
|
||||
jobs:
|
||||
get-semver:
|
||||
|
||||
@ -63,8 +67,7 @@ jobs:
|
||||
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 }}
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
- name: Setup Python and pip
|
||||
uses: actions/setup-python@v4
|
||||
@ -82,6 +85,7 @@ jobs:
|
||||
name: Get Conan broadcast data
|
||||
run: |
|
||||
import subprocess
|
||||
import os
|
||||
from conans import tools
|
||||
from conans.errors import ConanException
|
||||
from git import Repo
|
||||
@ -93,93 +97,96 @@ jobs:
|
||||
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("${{ github.ref_name }}")
|
||||
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 "${{ github.ref_name }}" == f"{branch_version.major}.{branch_version.minor}":
|
||||
if ref_name == f"{branch_version.major}.{branch_version.minor}":
|
||||
channel = 'stable'
|
||||
is_release_branch = True
|
||||
elif "${{ github.ref_name }}" in ("main", "master"):
|
||||
elif ref_name in ("main", "master"):
|
||||
channel = 'testing'
|
||||
else:
|
||||
channel = repo.active_branch.name.split("_")[0].replace("-", "_").lower()
|
||||
channel = "_".join(repo.active_branch.name.replace("-", "_").split("_")[:2]).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():
|
||||
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 = 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 no_commits == 0:
|
||||
# This is a release
|
||||
actual_version = f"{latest_branch_version.major}.{latest_branch_version.minor}.{latest_branch_version.patch}"
|
||||
if channel == "stable":
|
||||
user = "_"
|
||||
channel = "_"
|
||||
else:
|
||||
if latest_branch_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 += ".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}"
|
||||
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}"
|
||||
# FIXME: for when we create a new release branch
|
||||
if is_release_branch:
|
||||
if latest_branch_version.prerelease == "":
|
||||
bump_up_minor = int(latest_branch_version.minor) + 1
|
||||
actual_version = f"{latest_branch_version.major}.{bump_up_minor}.{latest_branch_version.patch}-alpha+{buildmetadata}{channel_metadata}"
|
||||
# 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:
|
||||
actual_version = f"{latest_branch_version.major}.{latest_branch_version.minor}.{latest_branch_version.patch}-{latest_branch_version.prerelease.lower()}+{buildmetadata}{channel_metadata}"
|
||||
else:
|
||||
# FIXME: for external PR's
|
||||
actual_version = f"5.2.0-alpha+{buildmetadata}pr_{issue_number}"
|
||||
# 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)
|
||||
# %% Set the environment output
|
||||
output_env = os.environ["GITHUB_OUTPUT"]
|
||||
content = ""
|
||||
if os.path.exists(output_env):
|
||||
with open(output_env, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
with open(output_env, "w") as f:
|
||||
f.write(content)
|
||||
f.writelines(f"name={project_name}\n")
|
||||
f.writelines(f"version={actual_version}\n")
|
||||
f.writelines(f"channel={channel}\n")
|
||||
f.writelines(f"recipe_id_full={project_name}/{actual_version}@{user}/{channel}\n")
|
||||
f.writelines(f"recipe_id_latest={project_name}/latest@{user}/{channel}\n")
|
||||
f.writelines(f"semver_full={actual_version}\n")
|
||||
f.writelines(f"is_release_branch={str(is_release_branch).lower()}\n")
|
||||
|
||||
print("::group::Conan Recipe Information")
|
||||
print(f"name = {project_name}")
|
||||
|
112
.github/workflows/cura-all-installers.yml
vendored
Normal file
112
.github/workflows/cura-all-installers.yml
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
name: Cura All Installers
|
||||
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
|
||||
type: string
|
||||
conan_args:
|
||||
description: 'Conan args: eq.: --require-override'
|
||||
default: ''
|
||||
required: false
|
||||
type: string
|
||||
conan_config:
|
||||
description: 'Conan config branch to use'
|
||||
default: ''
|
||||
required: false
|
||||
type: string
|
||||
enterprise:
|
||||
description: 'Build Cura as an Enterprise edition'
|
||||
default: false
|
||||
required: true
|
||||
type: boolean
|
||||
staging:
|
||||
description: 'Use staging API'
|
||||
default: false
|
||||
required: true
|
||||
type: boolean
|
||||
installer:
|
||||
description: 'Create the installer'
|
||||
default: true
|
||||
required: true
|
||||
type: boolean
|
||||
build_windows:
|
||||
description: 'Build for Windows'
|
||||
default: true
|
||||
required: true
|
||||
type: boolean
|
||||
build_linux:
|
||||
description: 'Build for Linux'
|
||||
default: true
|
||||
required: true
|
||||
type: boolean
|
||||
build_macos:
|
||||
description: 'Build for MacOs'
|
||||
default: true
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
# Run the nightly at 3:25 UTC on working days
|
||||
schedule:
|
||||
- cron: '25 3 * * 1-5'
|
||||
|
||||
jobs:
|
||||
windows-installer-create:
|
||||
if: ${{ inputs.build_windows }}
|
||||
uses: ./.github/workflows/cura-installer.yml
|
||||
with:
|
||||
platform: 'windows-2022'
|
||||
os_name: 'win64'
|
||||
cura_conan_version: ${{ inputs.cura_conan_version }}
|
||||
conan_args: ${{ inputs.conan_args }}
|
||||
conan_config: ${{ inputs.conan_config }}
|
||||
enterprise: ${{ inputs.enterprise }}
|
||||
staging: ${{ inputs.staging }}
|
||||
installer: ${{ inputs.installer }}
|
||||
secrets: inherit
|
||||
|
||||
linux-installer-create:
|
||||
if: ${{ inputs.build_linux }}
|
||||
uses: ./.github/workflows/cura-installer.yml
|
||||
with:
|
||||
platform: 'ubuntu-20.04'
|
||||
os_name: 'linux'
|
||||
cura_conan_version: ${{ inputs.cura_conan_version }}
|
||||
conan_args: ${{ inputs.conan_args }}
|
||||
conan_config: ${{ inputs.conan_config }}
|
||||
enterprise: ${{ inputs.enterprise }}
|
||||
staging: ${{ inputs.staging }}
|
||||
installer: ${{ inputs.installer }}
|
||||
secrets: inherit
|
||||
|
||||
linux-modern-installer-create:
|
||||
if: ${{ inputs.build_linux }}
|
||||
uses: ./.github/workflows/cura-installer.yml
|
||||
with:
|
||||
platform: 'ubuntu-22.04'
|
||||
os_name: 'linux-modern'
|
||||
cura_conan_version: ${{ inputs.cura_conan_version }}
|
||||
conan_args: ${{ inputs.conan_args }}
|
||||
conan_config: ${{ inputs.conan_config }}
|
||||
enterprise: ${{ inputs.enterprise }}
|
||||
staging: ${{ inputs.staging }}
|
||||
installer: ${{ inputs.installer }}
|
||||
secrets: inherit
|
||||
|
||||
macos-installer-create:
|
||||
if: ${{ inputs.build_macos }}
|
||||
uses: ./.github/workflows/cura-installer.yml
|
||||
with:
|
||||
platform: 'macos-11'
|
||||
os_name: 'mac'
|
||||
cura_conan_version: ${{ inputs.cura_conan_version }}
|
||||
conan_args: ${{ inputs.conan_args }}
|
||||
conan_config: ${{ inputs.conan_config }}
|
||||
enterprise: ${{ inputs.enterprise }}
|
||||
staging: ${{ inputs.staging }}
|
||||
installer: ${{ inputs.installer }}
|
||||
secrets: inherit
|
72
.github/workflows/cura-installer.yml
vendored
72
.github/workflows/cura-installer.yml
vendored
@ -1,41 +1,50 @@
|
||||
name: Cura Installer
|
||||
run-name: ${{ inputs.cura_conan_version }} for ${{ inputs.platform }} by @${{ github.actor }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
platform:
|
||||
description: 'Selected Installer OS'
|
||||
default: 'ubuntu-20.04'
|
||||
required: true
|
||||
type: string
|
||||
os_name:
|
||||
description: 'OS Friendly Name'
|
||||
default: 'linux'
|
||||
required: true
|
||||
type: string
|
||||
cura_conan_version:
|
||||
description: 'Cura Conan Version'
|
||||
default: 'cura/latest@ultimaker/testing'
|
||||
required: true
|
||||
type: string
|
||||
conan_args:
|
||||
description: 'Conan args: eq.: --require-override'
|
||||
default: ''
|
||||
required: false
|
||||
type: string
|
||||
conan_config:
|
||||
description: 'Conan config branch to use'
|
||||
default: ''
|
||||
required: false
|
||||
type: string
|
||||
enterprise:
|
||||
description: 'Build Cura as an Enterprise edition'
|
||||
required: true
|
||||
default: false
|
||||
required: true
|
||||
type: boolean
|
||||
staging:
|
||||
description: 'Use staging API'
|
||||
required: true
|
||||
default: false
|
||||
required: true
|
||||
type: boolean
|
||||
installer:
|
||||
description: 'Create the installer'
|
||||
default: true
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
# Run the nightly at 3:25 UTC on working days
|
||||
#FIXME: Provide the same default values as the workflow dispatch
|
||||
schedule:
|
||||
- cron: '25 3 * * 1-5'
|
||||
|
||||
env:
|
||||
CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }}
|
||||
CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }}
|
||||
@ -52,19 +61,13 @@ env:
|
||||
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' }
|
||||
runs-on: ${{ inputs.platform }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -123,12 +126,18 @@ jobs:
|
||||
echo "APPIMAGETOOL_LOCATION=$GITHUB_WORKSPACE/appimagetool" >> $GITHUB_ENV
|
||||
|
||||
- name: Install GCC-12 on ubuntu-22.04
|
||||
if: ${{ matrix.os == '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
|
||||
|
||||
@ -156,8 +165,13 @@ jobs:
|
||||
if: ${{ inputs.conan_config_branch == '' }}
|
||||
run: conan config install https://github.com/Ultimaker/conan-config.git
|
||||
|
||||
- name: Create the Packages
|
||||
run: conan install ${{ inputs.cura_conan_version }} ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=${{ inputs.enterprise }} -o cura:staging=${{ inputs.staging }} --json "cura_inst/conan_install_info.json"
|
||||
- name: 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' }}
|
||||
@ -198,38 +212,38 @@ jobs:
|
||||
cp openssl/lib/*.lib ./cura_inst/Lib/
|
||||
|
||||
- name: Create the Cura dist
|
||||
run: pyinstaller ./cura_inst/Ultimaker-Cura.spec
|
||||
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/"
|
||||
run: tar -zcf "./UltiMaker-Cura-$CURA_VERSION_FULL-${{ inputs.os_name }}.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"
|
||||
run: Compress-Archive -Path ".\UltiMaker-Cura" -DestinationPath ".\UltiMaker-Cura-$Env:CURA_VERSION_FULL-${{ inputs.os_name }}.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"
|
||||
python ..\cura_inst\packaging\NSIS\create_windows_installer.py ../cura_inst . "UltiMaker-Cura-$Env:CURA_VERSION_FULL-${{ inputs.os_name }}.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"
|
||||
run: python ../cura_inst/packaging/AppImage/create_appimage.py ./UltiMaker-Cura $CURA_VERSION_FULL "UltiMaker-Cura-$CURA_VERSION_FULL-${{ inputs.os_name }}.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"
|
||||
run: python ../cura_inst/packaging/dmg/dmg_sign_noterize.py ../cura_inst . "UltiMaker-Cura-$CURA_VERSION_FULL-${{ inputs.os_name }}.dmg"
|
||||
working-directory: dist
|
||||
|
||||
- name: Upload the artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Ultimaker-Cura-${{ env.CURA_VERSION_FULL }}-${{ matrix.os_id }}
|
||||
name: UltiMaker-Cura-${{ env.CURA_VERSION_FULL }}-${{ inputs.os_name }}
|
||||
path: |
|
||||
dist/*.tar.gz
|
||||
dist/*.zip
|
||||
|
36
.github/workflows/notify_on_print_profile_change.yml
vendored
Normal file
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 }}
|
45
.github/workflows/printer-linter-format.yml
vendored
Normal file
45
.github/workflows/printer-linter-format.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: printer-linter-format
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- '[1-9].[0-9]'
|
||||
- '[1-9].[0-9][0-9]'
|
||||
path:
|
||||
- 'resources/**'
|
||||
|
||||
jobs:
|
||||
printer-linter-format:
|
||||
name: Printer linter auto format
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python and pip
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.11.x
|
||||
cache: 'pip'
|
||||
cache-dependency-path: .github/workflows/requirements-printer-linter.txt
|
||||
|
||||
- uses: technote-space/get-diff-action@v6
|
||||
with:
|
||||
PATTERNS: |
|
||||
resources/+(definitions|extruders)/*.def.json
|
||||
resources/+(intent|quality|variants)/**/*.inst.cfg
|
||||
|
||||
- name: Install Python requirements for runner
|
||||
if: env.GIT_DIFF && !env.MATCHED_FILES
|
||||
run: pip install -r .github/workflows/requirements-printer-linter.txt
|
||||
|
||||
# - name: Format file
|
||||
# if: env.GIT_DIFF && !env.MATCHED_FILES
|
||||
# run: python printer-linter/src/terminal.py --format ${{ env.GIT_DIFF_FILTERED }}
|
||||
|
||||
- uses: stefanzweifel/git-auto-commit-action@v4
|
||||
if: env.GIT_DIFF && !env.MATCHED_FILES
|
||||
with:
|
||||
commit_message: "Applied printer-linter format"
|
59
.github/workflows/printer-linter-pr-diagnose.yml
vendored
Normal file
59
.github/workflows/printer-linter-pr-diagnose.yml
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
name: printer-linter-pr-diagnose
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
path:
|
||||
- 'resources/**'
|
||||
|
||||
jobs:
|
||||
printer-linter-diagnose:
|
||||
name: Printer linter PR diagnose
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Setup Python and pip
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.11.x
|
||||
cache: 'pip'
|
||||
cache-dependency-path: .github/workflows/requirements-printer-linter.txt
|
||||
|
||||
- uses: technote-space/get-diff-action@v6
|
||||
with:
|
||||
PATTERNS: |
|
||||
resources/+(extruders|definitions)/*.def.json
|
||||
resources/+(intent|quality|variants)/**/*.inst.cfg
|
||||
|
||||
- name: Install Python requirements for runner
|
||||
if: env.GIT_DIFF && !env.MATCHED_FILES
|
||||
run: pip install -r .github/workflows/requirements-printer-linter.txt
|
||||
|
||||
- name: Create results directory
|
||||
run: mkdir printer-linter-result
|
||||
|
||||
- name: Diagnose file(s)
|
||||
if: env.GIT_DIFF && !env.MATCHED_FILES
|
||||
run: python printer-linter/src/terminal.py --diagnose --report printer-linter-result/fixes.yml ${{ env.GIT_DIFF_FILTERED }}
|
||||
|
||||
- name: Save PR metadata
|
||||
run: |
|
||||
echo ${{ github.event.number }} > printer-linter-result/pr-id.txt
|
||||
echo ${{ github.event.pull_request.head.repo.full_name }} > printer-linter-result/pr-head-repo.txt
|
||||
echo ${{ github.event.pull_request.head.ref }} > printer-linter-result/pr-head-ref.txt
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: printer-linter-result
|
||||
path: printer-linter-result/
|
||||
|
||||
- name: Run clang-tidy-pr-comments action
|
||||
uses: platisd/clang-tidy-pr-comments@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
clang_tidy_fixes: result.yml
|
||||
request_changes: true
|
80
.github/workflows/printer-linter-pr-post.yml
vendored
Normal file
80
.github/workflows/printer-linter-pr-post.yml
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
name: printer-linter-pr-post
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [ "printer-linter-pr-diagnose" ]
|
||||
types: [ completed ]
|
||||
|
||||
jobs:
|
||||
clang-tidy-results:
|
||||
# Trigger the job only if the previous (insecure) workflow completed successfully
|
||||
if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download analysis results
|
||||
uses: actions/github-script@v3.1.0
|
||||
with:
|
||||
script: |
|
||||
let artifacts = await github.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{github.event.workflow_run.id }},
|
||||
});
|
||||
let matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "printer-linter-result"
|
||||
})[0];
|
||||
let download = await github.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: "zip",
|
||||
});
|
||||
let fs = require("fs");
|
||||
fs.writeFileSync("${{github.workspace}}/printer-linter-result.zip", Buffer.from(download.data));
|
||||
|
||||
- name: Set environment variables
|
||||
run: |
|
||||
mkdir printer-linter-result
|
||||
unzip printer-linter-result.zip -d printer-linter-result
|
||||
echo "pr_id=$(cat printer-linter-result/pr-id.txt)" >> $GITHUB_ENV
|
||||
echo "pr_head_repo=$(cat printer-linter-result/pr-head-repo.txt)" >> $GITHUB_ENV
|
||||
echo "pr_head_ref=$(cat printer-linter-result/pr-head-ref.txt)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ env.pr_head_repo }}
|
||||
ref: ${{ env.pr_head_ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Redownload analysis results
|
||||
uses: actions/github-script@v3.1.0
|
||||
with:
|
||||
script: |
|
||||
let artifacts = await github.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{github.event.workflow_run.id }},
|
||||
});
|
||||
let matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "printer-linter-result"
|
||||
})[0];
|
||||
let download = await github.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: "zip",
|
||||
});
|
||||
let fs = require("fs");
|
||||
fs.writeFileSync("${{github.workspace}}/printer-linter-result.zip", Buffer.from(download.data));
|
||||
|
||||
- name: Extract analysis results
|
||||
run: |
|
||||
mkdir printer-linter-result
|
||||
unzip printer-linter-result.zip -d printer-linter-result
|
||||
|
||||
- name: Run clang-tidy-pr-comments action
|
||||
uses: platisd/clang-tidy-pr-comments@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
clang_tidy_fixes: printer-linter-result/fixes.yml
|
||||
pull_request_id: ${{ env.pr_id }}
|
1
.github/workflows/requirements-printer-linter.txt
vendored
Normal file
1
.github/workflows/requirements-printer-linter.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
pyyaml
|
18
.github/workflows/unit-test.yml
vendored
18
.github/workflows/unit-test.yml
vendored
@ -60,6 +60,9 @@ env:
|
||||
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
|
||||
@ -103,7 +106,15 @@ jobs:
|
||||
|
||||
- name: Install Linux system requirements
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev -y
|
||||
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
|
||||
@ -133,6 +144,11 @@ jobs:
|
||||
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()
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -99,3 +99,4 @@ conanbuildinfo.txt
|
||||
graph_info.json
|
||||
Ultimaker-Cura.spec
|
||||
.run/
|
||||
/printer-linter/src/printerlinter.egg-info/
|
||||
|
15
.printer-linter
Normal file
15
.printer-linter
Normal file
@ -0,0 +1,15 @@
|
||||
checks:
|
||||
diagnostic-mesh-file-extension: true
|
||||
diagnostic-mesh-file-size: true
|
||||
diagnostic-definition-redundant-override: true
|
||||
fixes:
|
||||
diagnostic-definition-redundant-override: true
|
||||
format:
|
||||
format-definition-bracket-newline: true
|
||||
format-definition-paired-coordinate-array: true
|
||||
format-definition-sort-keys: true
|
||||
format-definition-indent: 4
|
||||
format-definition-single-value-single-line: true # Format dicts and lists with a single item on one line "dict": { "value": 10 }
|
||||
format-profile-space-around-delimiters: true
|
||||
format-profile-sort-keys: true
|
||||
diagnostic-mesh-file-size: 1200000
|
14
CITATION.cff
14
CITATION.cff
@ -14,17 +14,17 @@ authors:
|
||||
contact:
|
||||
- email: info@ultimaker.com
|
||||
name: "Ultimaker B.V."
|
||||
url: 'https://ultimaker.com/software/ultimaker-cura'
|
||||
repository-code: 'https://github.com/Ultimaker/Cura'
|
||||
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'
|
||||
version: 5.2.1
|
||||
date-released: "2022-10-19"
|
||||
keywords:
|
||||
- Ultimaker
|
||||
- Cura
|
||||
- Slicer
|
||||
- Uranium
|
||||
- Arachne
|
||||
- 3DPrinting
|
||||
- Slicer
|
||||
...
|
||||
- 3D Printing
|
||||
- Additive Manufacturing
|
||||
|
@ -1,6 +1,8 @@
|
||||
# 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)
|
||||
@ -13,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()
|
||||
@ -67,23 +30,3 @@ if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "")
|
||||
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()
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
CuraAppName = "{{ cura_app_name }}"
|
||||
|
@ -1,55 +0,0 @@
|
||||
mode: ContinuousDelivery
|
||||
next-version: 5.1
|
||||
branches:
|
||||
main:
|
||||
regex: ^main$
|
||||
mode: ContinuousDelivery
|
||||
tag: alpha
|
||||
increment: None
|
||||
prevent-increment-of-merged-branch-version: true
|
||||
track-merge-target: false
|
||||
source-branches: [ ]
|
||||
tracks-release-branches: false
|
||||
is-release-branch: false
|
||||
is-mainline: true
|
||||
pre-release-weight: 55000
|
||||
develop:
|
||||
regex: ^CURA-.*$
|
||||
mode: ContinuousDelivery
|
||||
tag: alpha
|
||||
increment: None
|
||||
prevent-increment-of-merged-branch-version: false
|
||||
track-merge-target: true
|
||||
source-branches: [ 'main' ]
|
||||
tracks-release-branches: true
|
||||
is-release-branch: false
|
||||
is-mainline: false
|
||||
pre-release-weight: 0
|
||||
release:
|
||||
regex: ^[\d]+\.[\d]+$
|
||||
mode: ContinuousDelivery
|
||||
tag: beta
|
||||
increment: None
|
||||
prevent-increment-of-merged-branch-version: true
|
||||
track-merge-target: false
|
||||
source-branches: [ 'main' ]
|
||||
tracks-release-branches: false
|
||||
is-release-branch: true
|
||||
is-mainline: false
|
||||
pre-release-weight: 30000
|
||||
pull-request-main:
|
||||
regex: ^(pull|pull\-requests|pr)[/-]
|
||||
mode: ContinuousDelivery
|
||||
tag: alpha+
|
||||
increment: Inherit
|
||||
prevent-increment-of-merged-branch-version: true
|
||||
tag-number-pattern: '[/-](?<number>\d+)[-/]'
|
||||
track-merge-target: true
|
||||
source-branches: [ 'main' ]
|
||||
tracks-release-branches: false
|
||||
is-release-branch: false
|
||||
is-mainline: false
|
||||
pre-release-weight: 30000
|
||||
ignore:
|
||||
sha: [ ]
|
||||
merge-message-formats: { }
|
74
Jenkinsfile
vendored
74
Jenkinsfile
vendored
@ -1,74 +0,0 @@
|
||||
parallel_nodes(['linux && cura', 'windows && cura'])
|
||||
{
|
||||
timeout(time: 2, unit: "HOURS")
|
||||
{
|
||||
|
||||
// Prepare building
|
||||
stage('Prepare')
|
||||
{
|
||||
// Ensure we start with a clean build directory.
|
||||
step([$class: 'WsCleanup'])
|
||||
|
||||
// Checkout whatever sources are linked to this pipeline.
|
||||
checkout scm
|
||||
}
|
||||
|
||||
// If any error occurs during building, we want to catch it and continue with the "finale" stage.
|
||||
catchError
|
||||
{
|
||||
// Building and testing should happen in a subdirectory.
|
||||
dir('build')
|
||||
{
|
||||
// Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
|
||||
stage('Build')
|
||||
{
|
||||
def branch = env.BRANCH_NAME
|
||||
if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}"))
|
||||
{
|
||||
branch = "master"
|
||||
}
|
||||
|
||||
// Ensure CMake is setup. Note that since this is Python code we do not really "build" it.
|
||||
def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
|
||||
cmake("..", "-DCMAKE_PREFIX_PATH=\"${env.CURA_ENVIRONMENT_PATH}/${branch}\" -DCMAKE_BUILD_TYPE=Release -DURANIUM_DIR=\"${uranium_dir}\"")
|
||||
}
|
||||
|
||||
// Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
|
||||
stage('Unit Test')
|
||||
{
|
||||
if (isUnix())
|
||||
{
|
||||
// For Linux
|
||||
try {
|
||||
sh 'make CTEST_OUTPUT_ON_FAILURE=TRUE test'
|
||||
} catch(e)
|
||||
{
|
||||
currentBuild.result = "UNSTABLE"
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For Windows
|
||||
try
|
||||
{
|
||||
// This also does code style checks.
|
||||
bat 'ctest -V'
|
||||
} catch(e)
|
||||
{
|
||||
currentBuild.result = "UNSTABLE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform any post-build actions like notification and publishing of unit tests.
|
||||
stage('Finalize')
|
||||
{
|
||||
// Publish the test results to Jenkins.
|
||||
junit allowEmptyResults: true, testResults: 'build/junit*.xml'
|
||||
|
||||
notify_build_result(env.CURA_EMAIL_RECIPIENTS, '#cura-dev', ['master', '2.'])
|
||||
}
|
||||
}
|
||||
}
|
@ -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,34 +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/main/cura-logo.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>
|
||||
<content_rating type="oars-1.1" />
|
||||
</component>
|
@ -1,19 +0,0 @@
|
||||
[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
|
||||
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
|
||||
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
|
431
conandata.yml
431
conandata.yml
@ -1,5 +1,5 @@
|
||||
---
|
||||
# Usage: defaults to None
|
||||
# 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)
|
||||
#
|
||||
@ -10,7 +10,7 @@
|
||||
# requirements (use the <dep_name>/(latest)@ultimaker/testing)
|
||||
#
|
||||
# Subject to change in the future!
|
||||
"None":
|
||||
"5.3.0-alpha":
|
||||
requirements:
|
||||
- "pyarcus/(latest)@ultimaker/testing"
|
||||
- "curaengine/(latest)@ultimaker/testing"
|
||||
@ -25,6 +25,431 @@
|
||||
- "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
|
||||
cura_private_data_plugins:
|
||||
package: "cura_private_data"
|
||||
src: "plugins"
|
||||
dst: "share/cura/plugins"
|
||||
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:
|
||||
@ -117,7 +542,7 @@
|
||||
Linux: "./icons/cura-128.png"
|
||||
"5.2.0-alpha":
|
||||
requirements:
|
||||
- "pyarcus/(latest)@ultimaker/testing"
|
||||
- "pyarcus/5.2@ultimaker/testing"
|
||||
- "curaengine/(latest)@ultimaker/testing"
|
||||
- "pysavitar/(latest)@ultimaker/testing"
|
||||
- "pynest2d/(latest)@ultimaker/testing"
|
||||
|
108
conanfile.py
108
conanfile.py
@ -3,30 +3,31 @@ from pathlib import Path
|
||||
|
||||
from jinja2 import Template
|
||||
|
||||
from conans import tools
|
||||
from conan import ConanFile
|
||||
from conan.tools import files
|
||||
from conan.tools.files import copy, rmdir, save, mkdir
|
||||
from conan.tools.microsoft import unix_path
|
||||
from conan.tools.env import VirtualRunEnv, Environment
|
||||
from conan.errors import ConanInvalidConfiguration
|
||||
from conan.tools.scm import Version
|
||||
from conan.errors import ConanInvalidConfiguration, ConanException
|
||||
|
||||
required_conan_version = ">=1.48.0"
|
||||
required_conan_version = ">=1.52.0"
|
||||
|
||||
|
||||
class CuraConan(ConanFile):
|
||||
name = "cura"
|
||||
license = "LGPL-3.0"
|
||||
author = "Ultimaker B.V."
|
||||
author = "UltiMaker"
|
||||
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"
|
||||
exports = "LICENSE*", "UltiMaker-Cura.spec.jinja", "CuraVersion.py.jinja"
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
no_copy_source = True # We won't build so no need to copy sources to the build folder
|
||||
|
||||
# FIXME: Remove specific branch once merged to main
|
||||
# Extending the conanfile with the UMBaseConanfile https://github.com/Ultimaker/conan-ultimaker-index/tree/CURA-9177_Fix_CI_CD/recipes/umbase
|
||||
python_requires = "umbase/0.1.5@ultimaker/testing"
|
||||
python_requires = "umbase/[>=0.1.7]@ultimaker/stable"
|
||||
python_requires_extend = "umbase.UMBaseConanfile"
|
||||
|
||||
options = {
|
||||
@ -43,7 +44,7 @@ class CuraConan(ConanFile):
|
||||
"staging": "False",
|
||||
"devtools": False,
|
||||
"cloud_api_version": "1",
|
||||
"display_name": "Ultimaker Cura",
|
||||
"display_name": "UltiMaker Cura",
|
||||
"cura_debug_mode": False, # Not yet implemented
|
||||
"internal": False,
|
||||
}
|
||||
@ -135,7 +136,7 @@ class CuraConan(ConanFile):
|
||||
def _site_packages(self):
|
||||
if self.settings.os == "Windows":
|
||||
return self._base_dir.joinpath("Lib", "site-packages")
|
||||
py_version = tools.Version(self.deps_cpp_info["cpython"].version)
|
||||
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
|
||||
@ -149,10 +150,13 @@ class CuraConan(ConanFile):
|
||||
with open(Path(__file__).parent.joinpath("CuraVersion.py.jinja"), "r") as f:
|
||||
cura_version_py = Template(f.read())
|
||||
|
||||
cura_version = self.version
|
||||
if self.options.internal:
|
||||
version = tools.Version(self.version)
|
||||
cura_version = f"{version.major}.{version.minor}.{version.patch}-{version.prerelease.replace('+', '+internal_')}"
|
||||
# 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(
|
||||
@ -199,30 +203,36 @@ class CuraConan(ConanFile):
|
||||
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]"):
|
||||
|
||||
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"]))
|
||||
|
||||
for _, dependency in self.dependencies.items():
|
||||
# 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")])
|
||||
binaries.extend([(f"{p}", ".") for p in Path(bin_paths).glob("**/*.dylib")])
|
||||
binaries.extend([(f"{p}", ".") for p in Path(bin_paths).glob("**/*.so")])
|
||||
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("**/*.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:
|
||||
with open(Path(__file__).parent.joinpath("UltiMaker-Cura.spec.jinja"), "r") as f:
|
||||
pyinstaller = Template(f.read())
|
||||
|
||||
cura_version = tools.Version(self.version) if self.version else tools.Version("0.0.0")
|
||||
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:
|
||||
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,
|
||||
@ -239,10 +249,14 @@ class CuraConan(ConanFile):
|
||||
strip = False, # This should be possible on Linux and MacOS but, it can also cause issues on some distributions. Safest is to disable it for now
|
||||
target_arch = "'x86_64'" if self.settings.os == "Macos" else "None", # FIXME: Make this dependent on the settings.arch_target
|
||||
macos = self.settings.os == "Macos",
|
||||
version = f"'{self.version}'",
|
||||
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
|
||||
@ -250,7 +264,8 @@ class CuraConan(ConanFile):
|
||||
self.options["cpython"].shared = True
|
||||
|
||||
def validate(self):
|
||||
if self.version and tools.Version(self.version) <= tools.Version("4"):
|
||||
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):
|
||||
@ -260,6 +275,12 @@ class CuraConan(ConanFile):
|
||||
for req in self._um_data()["internal_requirements"]:
|
||||
self.requires(req)
|
||||
|
||||
def build_requirements(self):
|
||||
if self.options.devtools:
|
||||
if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type = str):
|
||||
# FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement
|
||||
self.tool_requires("gettext/0.21", force_host_context=True)
|
||||
|
||||
def layout(self):
|
||||
self.folders.source = "."
|
||||
self.folders.build = "venv"
|
||||
@ -270,7 +291,14 @@ class CuraConan(ConanFile):
|
||||
self.cpp.package.resdirs = ["resources", "plugins", "packaging", "pip_requirements"] # pip_requirements should be the last item in the list
|
||||
|
||||
def build(self):
|
||||
pass
|
||||
if self.options.devtools:
|
||||
if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type = str):
|
||||
# FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement
|
||||
cpp_info = self.dependencies["gettext"].cpp_info
|
||||
for po_file in self.source_path.joinpath("resources", "i18n").glob("**/*.po"):
|
||||
mo_file = self.build_path.joinpath(po_file.with_suffix('.mo').relative_to(self.source_path))
|
||||
mkdir(self, str(unix_path(self, mo_file.parent)))
|
||||
self.run(f"{cpp_info.bindirs[0]}/msgfmt {po_file} -o {mo_file} -f", env="conanbuild", ignore_errors=True)
|
||||
|
||||
def generate(self):
|
||||
cura_run_envvars = self._cura_run_env.vars(self, scope = "run")
|
||||
@ -289,11 +317,21 @@ class CuraConan(ConanFile):
|
||||
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")
|
||||
|
||||
# Update the po files
|
||||
if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type = str):
|
||||
# FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement
|
||||
cpp_info = self.dependencies["gettext"].cpp_info
|
||||
for po_file in self.source_path.joinpath("resources", "i18n").glob("**/*.po"):
|
||||
pot_file = self.source_path.joinpath("resources", "i18n", po_file.with_suffix('.pot').name)
|
||||
mkdir(self, str(unix_path(self, pot_file.parent)))
|
||||
self.run(f"{cpp_info.bindirs[0]}/msgmerge --no-wrap --no-fuzzy-matching -width=140 -o {po_file} {po_file} {pot_file}",
|
||||
env = "conanbuild", ignore_errors = True)
|
||||
|
||||
def imports(self):
|
||||
self.copy("CuraEngine.exe", root_package = "curaengine", src = "@bindirs", dst = "", keep_path = False)
|
||||
self.copy("CuraEngine", root_package = "curaengine", src = "@bindirs", dst = "", keep_path = False)
|
||||
|
||||
files.rmdir(self, "resources/materials")
|
||||
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)
|
||||
|
||||
@ -343,6 +381,8 @@ class CuraConan(ConanFile):
|
||||
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)
|
||||
self.copy_deps("*", root_package = "cura_private_data", src = self.deps_cpp_info["cura_private_data"].resdirs[1],
|
||||
dst = self._share_dir.joinpath("cura", "plugins"), keep_path = True)
|
||||
|
||||
# Copy resources of Uranium (keep folder structure)
|
||||
self.copy_deps("*", root_package = "uranium", src = self.deps_cpp_info["uranium"].resdirs[0],
|
||||
@ -377,7 +417,8 @@ class CuraConan(ConanFile):
|
||||
self.copy("*.txt", src = self.cpp_info.resdirs[-1], dst = self._base_dir.joinpath("pip_requirements"))
|
||||
|
||||
# Generate the GitHub Action version info Environment
|
||||
cura_version = tools.Version(self.version)
|
||||
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
|
||||
@ -392,7 +433,7 @@ echo "CURA_VERSION_FULL={{ cura_version_full }}" >> ${{ env_prefix }}GITHUB_ENV
|
||||
env_prefix = env_prefix)
|
||||
|
||||
ext = ".sh" if self.settings.os != "Windows" else ".ps1"
|
||||
files.save(self, self._script_dir.joinpath(f"activate_github_actions_version_env{ext}"), activate_github_actions_version_env)
|
||||
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"))
|
||||
|
||||
@ -403,12 +444,13 @@ echo "CURA_VERSION_FULL={{ cura_version_full }}" >> ${{ env_prefix }}GITHUB_ENV
|
||||
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])
|
||||
copy(self, "cura_app.py", src = self.source_path, dst = self.package_path.joinpath(self.cpp.package.bindirs[0]))
|
||||
copy(self, "*", src = self.source_path.joinpath("cura"), dst = self.package_path.joinpath(self.cpp.package.libdirs[0]))
|
||||
copy(self, "*", src = self.source_path.joinpath("resources"), dst = self.package_path.joinpath(self.cpp.package.resdirs[0]), excludes="*.po")
|
||||
copy(self, "*", src = self.build_path.joinpath("resources"), dst = self.package_path.joinpath(self.cpp.package.resdirs[0]))
|
||||
copy(self, "*", src = self.source_path.joinpath("plugins"), dst = self.package_path.joinpath(self.cpp.package.resdirs[1]))
|
||||
copy(self, "requirement*.txt", src = self.source_path, dst = self.package_path.joinpath(self.cpp.package.resdirs[-1]))
|
||||
copy(self, "*", src = self.source_path.joinpath("packaging"), dst = self.package_path.joinpath(self.cpp.package.resdirs[2]))
|
||||
|
||||
def package_info(self):
|
||||
self.user_info.pip_requirements = "requirements.txt"
|
||||
|
@ -16,4 +16,6 @@ Making pull requests
|
||||
--------------------
|
||||
If you want to propose a change to Cura's source code, please create a pull request in the appropriate repository (being [Cura](https://github.com/Ultimaker/Cura), [Uranium](https://github.com/Ultimaker/Uranium), [CuraEngine](https://github.com/Ultimaker/CuraEngine), [fdm_materials](https://github.com/Ultimaker/fdm_materials), [libArcus](https://github.com/Ultimaker/libArcus), [cura-build](https://github.com/Ultimaker/cura-build), [cura-build-environment](https://github.com/Ultimaker/cura-build-environment), [libSavitar](https://github.com/Ultimaker/libSavitar), [libCharon](https://github.com/Ultimaker/libCharon) or [cura-binary-data](https://github.com/Ultimaker/cura-binary-data)) and if your change requires changes on multiple of these repositories, please link them together so that we know to merge them together.
|
||||
|
||||
The style guide for code contributions to Cura and other Ultimaker projects can be found [here](https://github.com/Ultimaker/Meta/blob/master/general/generic_code_conventions.md).
|
||||
|
||||
Some of these repositories will have automated tests running when you create a pull request, indicated by green check marks or red crosses in the Github web page. If you see a red cross, that means that a test has failed. If the test doesn't fail on the Master branch but does fail on your branch, that indicates that you've probably made a mistake and you need to do that. Click on the cross for more details, or run the test locally by running `cmake . && ctest --verbose`.
|
@ -1,11 +1,11 @@
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
# ---------
|
||||
# General constants used in Cura
|
||||
# ---------
|
||||
DEFAULT_CURA_APP_NAME = "cura"
|
||||
DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura"
|
||||
DEFAULT_CURA_DISPLAY_NAME = "UltiMaker Cura"
|
||||
DEFAULT_CURA_VERSION = "dev"
|
||||
DEFAULT_CURA_BUILD_TYPE = ""
|
||||
DEFAULT_CURA_DEBUG_MODE = False
|
||||
@ -14,7 +14,7 @@ DEFAULT_CURA_LATEST_URL = "https://software.ultimaker.com/latest.json"
|
||||
# Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for
|
||||
# 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.1.0"
|
||||
CuraSDKVersion = "8.2.0"
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraLatestURL
|
||||
|
@ -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)
|
||||
|
@ -810,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])
|
||||
@ -840,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.
|
||||
@ -856,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)
|
||||
@ -1076,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.
|
||||
@ -1086,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"),
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import platform
|
||||
import traceback
|
||||
@ -110,7 +110,7 @@ class CrashHandler:
|
||||
layout = QVBoxLayout(dialog)
|
||||
|
||||
label = QLabel()
|
||||
label.setText(catalog.i18nc("@label crash message", """<p><b>Oops, Ultimaker Cura has encountered something that doesn't seem right.</p></b>
|
||||
label.setText(catalog.i18nc("@label crash message", """<p><b>Oops, UltiMaker Cura has encountered something that doesn't seem right.</p></b>
|
||||
<p>We encountered an unrecoverable error during start up. It was possibly caused by some incorrect configuration files. We suggest to backup and reset your configuration.</p>
|
||||
<p>Backups can be found in the configuration folder.</p>
|
||||
<p>Please send us this Crash Report to fix the problem.</p>
|
||||
@ -119,7 +119,7 @@ class CrashHandler:
|
||||
layout.addWidget(label)
|
||||
|
||||
# "send report" check box and show details
|
||||
self._send_report_checkbox = QCheckBox(catalog.i18nc("@action:button", "Send crash report to Ultimaker"), dialog)
|
||||
self._send_report_checkbox = QCheckBox(catalog.i18nc("@action:button", "Send crash report to UltiMaker"), dialog)
|
||||
self._send_report_checkbox.setChecked(True)
|
||||
|
||||
show_details_button = QPushButton(catalog.i18nc("@action:button", "Show detailed crash report"), dialog)
|
||||
|
@ -115,6 +115,7 @@ 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
|
||||
@ -1191,6 +1192,7 @@ class CuraApplication(QtApplication):
|
||||
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")
|
||||
@ -1445,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)
|
||||
|
@ -24,9 +24,12 @@ class LayerPolygon:
|
||||
PrimeTowerType = 11
|
||||
__number_of_types = 12
|
||||
|
||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
|
||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType,
|
||||
numpy.arange(__number_of_types) == MoveCombingType),
|
||||
numpy.arange(__number_of_types) == MoveRetractionType)
|
||||
|
||||
def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray, line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None:
|
||||
def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray,
|
||||
line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None:
|
||||
"""LayerPolygon, used in ProcessSlicedLayersJob
|
||||
|
||||
:param extruder: The position of the extruder
|
||||
@ -39,10 +42,12 @@ class LayerPolygon:
|
||||
|
||||
self._extruder = extruder
|
||||
self._types = line_types
|
||||
for i in range(len(self._types)):
|
||||
if self._types[i] >= self.__number_of_types: # Got faulty line data from the engine.
|
||||
Logger.log("w", "Found an unknown line type: %s", i)
|
||||
self._types[i] = self.NoneType
|
||||
unknown_types = numpy.where(self._types >= self.__number_of_types, self._types, None)
|
||||
if unknown_types.any():
|
||||
# Got faulty line data from the engine.
|
||||
for idx in unknown_types:
|
||||
Logger.warning(f"Found an unknown line type at: {idx}")
|
||||
self._types[idx] = self.NoneType
|
||||
self._data = data
|
||||
self._line_widths = line_widths
|
||||
self._line_thicknesses = line_thicknesses
|
||||
@ -63,9 +68,11 @@ class LayerPolygon:
|
||||
self._color_map = LayerPolygon.getColorMap()
|
||||
self._colors = self._color_map[self._types] # type: numpy.ndarray
|
||||
|
||||
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
|
||||
# When type is used as index returns true if type == LayerPolygon.InfillType
|
||||
# or type == LayerPolygon.SkinType
|
||||
# or type == LayerPolygon.SupportInfillType
|
||||
# Should be generated in better way, not hardcoded.
|
||||
self._is_infill_or_skin_type_map = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype = bool)
|
||||
self._is_infill_or_skin_type_map = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype=bool)
|
||||
|
||||
self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
|
||||
self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
|
||||
@ -80,12 +87,14 @@ class LayerPolygon:
|
||||
# Only if the type of line segment changes do we need to add an extra vertex to change colors
|
||||
self._build_cache_needed_points[1:, 0][:, numpy.newaxis] = self._types[1:] != self._types[:-1]
|
||||
# Mark points as unneeded if they are of types we don't want in the line mesh according to the calculated mask
|
||||
numpy.logical_and(self._build_cache_needed_points, self._build_cache_line_mesh_mask, self._build_cache_needed_points )
|
||||
numpy.logical_and(self._build_cache_needed_points, self._build_cache_line_mesh_mask, self._build_cache_needed_points)
|
||||
|
||||
self._vertex_begin = 0
|
||||
self._vertex_end = cast(int, numpy.sum(self._build_cache_needed_points))
|
||||
|
||||
def build(self, vertex_offset: int, index_offset: int, vertices: numpy.ndarray, colors: numpy.ndarray, line_dimensions: numpy.ndarray, feedrates: numpy.ndarray, extruders: numpy.ndarray, line_types: numpy.ndarray, indices: numpy.ndarray) -> None:
|
||||
def build(self, vertex_offset: int, index_offset: int, vertices: numpy.ndarray,
|
||||
colors: numpy.ndarray, line_dimensions: numpy.ndarray, feedrates: numpy.ndarray,
|
||||
extruders: numpy.ndarray, line_types: numpy.ndarray, indices: numpy.ndarray) -> None:
|
||||
"""Set all the arrays provided by the function caller, representing the LayerPolygon
|
||||
|
||||
The arrays are either by vertex or by indices.
|
||||
@ -111,10 +120,11 @@ class LayerPolygon:
|
||||
line_mesh_mask = self._build_cache_line_mesh_mask
|
||||
needed_points_list = self._build_cache_needed_points
|
||||
|
||||
# Index to the points we need to represent the line mesh. This is constructed by generating simple
|
||||
# start and end points for each line. For line segment n these are points n and n+1. Row n reads [n n+1]
|
||||
# Then then the indices for the points we don't need are thrown away based on the pre-calculated list.
|
||||
index_list = ( numpy.arange(len(self._types)).reshape((-1, 1)) + numpy.array([[0, 1]]) ).reshape((-1, 1))[needed_points_list.reshape((-1, 1))]
|
||||
# Index to the points we need to represent the line mesh.
|
||||
# This is constructed by generating simple start and end points for each line.
|
||||
# For line segment n, these are points n and n+1. Row n reads [n n+1]
|
||||
# Then the indices for the points we don't need are thrown away based on the pre-calculated list.
|
||||
index_list = (numpy.arange(len(self._types)).reshape((-1, 1)) + numpy.array([[0, 1]])).reshape((-1, 1))[needed_points_list.reshape((-1, 1))]
|
||||
|
||||
# The relative values of begin and end indices have already been set in buildCache, so we only need to offset them to the parents offset.
|
||||
self._vertex_begin += vertex_offset
|
||||
@ -138,11 +148,12 @@ class LayerPolygon:
|
||||
# Convert type per vertex to type per line
|
||||
line_types[self._vertex_begin:self._vertex_end] = numpy.tile(self._types, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
|
||||
|
||||
# The relative values of begin and end indices have already been set in buildCache, so we only need to offset them to the parents offset.
|
||||
# The relative values of begin and end indices have already been set in buildCache,
|
||||
# so we only need to offset them to the parents offset.
|
||||
self._index_begin += index_offset
|
||||
self._index_end += index_offset
|
||||
|
||||
indices[self._index_begin:self._index_end, :] = numpy.arange(self._index_end-self._index_begin, dtype = numpy.int32).reshape((-1, 1))
|
||||
indices[self._index_begin:self._index_end, :] = numpy.arange(self._index_end-self._index_begin, dtype=numpy.int32).reshape((-1, 1))
|
||||
# When the line type changes the index needs to be increased by 2.
|
||||
indices[self._index_begin:self._index_end, :] += numpy.cumsum(needed_points_list[line_mesh_mask.ravel(), 0], dtype = numpy.int32).reshape((-1, 1))
|
||||
# Each line segment goes from it's starting point p to p+1, offset by the vertex index.
|
||||
@ -214,13 +225,12 @@ class LayerPolygon:
|
||||
"""
|
||||
|
||||
normals = numpy.copy(self._data)
|
||||
normals[:, 1] = 0.0 # We are only interested in 2D normals
|
||||
normals[:, 1] = 0.0 # We are only interested in 2D normals
|
||||
|
||||
# Calculate the edges between points.
|
||||
# The call to numpy.roll shifts the entire array by one so that
|
||||
# we end up subtracting each next point from the current, wrapping
|
||||
# around. This gives us the edges from the next point to the current
|
||||
# point.
|
||||
# The call to numpy.roll shifts the entire array by one
|
||||
# so that we end up subtracting each next point from the current, wrapping around.
|
||||
# This gives us the edges from the next point to the current point.
|
||||
normals = numpy.diff(normals, 1, 0)
|
||||
|
||||
# Calculate the length of each edge using standard Pythagoras
|
||||
@ -245,17 +255,17 @@ class LayerPolygon:
|
||||
if cls.__color_map is None:
|
||||
theme = cast(Theme, QtApplication.getInstance().getTheme())
|
||||
cls.__color_map = numpy.array([
|
||||
theme.getColor("layerview_none").getRgbF(), # NoneType
|
||||
theme.getColor("layerview_inset_0").getRgbF(), # Inset0Type
|
||||
theme.getColor("layerview_inset_x").getRgbF(), # InsetXType
|
||||
theme.getColor("layerview_skin").getRgbF(), # SkinType
|
||||
theme.getColor("layerview_support").getRgbF(), # SupportType
|
||||
theme.getColor("layerview_skirt").getRgbF(), # SkirtType
|
||||
theme.getColor("layerview_infill").getRgbF(), # InfillType
|
||||
theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType
|
||||
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
|
||||
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
|
||||
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
|
||||
theme.getColor("layerview_none").getRgbF(), # NoneType
|
||||
theme.getColor("layerview_inset_0").getRgbF(), # Inset0Type
|
||||
theme.getColor("layerview_inset_x").getRgbF(), # InsetXType
|
||||
theme.getColor("layerview_skin").getRgbF(), # SkinType
|
||||
theme.getColor("layerview_support").getRgbF(), # SupportType
|
||||
theme.getColor("layerview_skirt").getRgbF(), # SkirtType
|
||||
theme.getColor("layerview_infill").getRgbF(), # InfillType
|
||||
theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType
|
||||
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
|
||||
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
|
||||
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
|
||||
theme.getColor("layerview_prime_tower").getRgbF() # PrimeTowerType
|
||||
])
|
||||
|
||||
|
@ -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
|
||||
@ -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,7 +43,7 @@ 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()
|
||||
|
||||
self._setCheckTimer()
|
||||
|
||||
@ -160,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:
|
||||
@ -212,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")
|
||||
|
81
cura/Machines/Models/CompatibleMachineModel.py
Normal file
81
cura/Machines/Models/CompatibleMachineModel.py
Normal file
@ -0,0 +1,81 @@
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from PyQt6.QtCore import Qt, QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
|
||||
class CompatibleMachineModel(ListModel):
|
||||
NameRole = Qt.ItemDataRole.UserRole + 1
|
||||
UniqueIdRole = Qt.ItemDataRole.UserRole + 2
|
||||
ExtrudersRole = Qt.ItemDataRole.UserRole + 3
|
||||
|
||||
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self._catalog = i18nCatalog("cura")
|
||||
|
||||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.UniqueIdRole, "unique_id")
|
||||
self.addRoleName(self.ExtrudersRole, "extruders")
|
||||
|
||||
self._update()
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||
machine_manager.globalContainerChanged.connect(self._update)
|
||||
machine_manager.outputDevicesChanged.connect(self._update)
|
||||
|
||||
@pyqtSlot()
|
||||
def forceUpdate(self):
|
||||
self._update()
|
||||
|
||||
def _update(self) -> None:
|
||||
self.clear()
|
||||
|
||||
def _makeMaterial(brand, name, color):
|
||||
if name.lower() in ["", "empty"]:
|
||||
return {"brand": "", "name": "(empty)", "hexcolor": "#ffffff"}
|
||||
else:
|
||||
return {"brand": brand, "name": name, "hexcolor": color}
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||
|
||||
# Loop over the output-devices, not the stacks; need all applicable configurations, not just the current loaded one.
|
||||
for output_device in machine_manager.printerOutputDevices:
|
||||
for printer in output_device.printers:
|
||||
extruder_configs = dict()
|
||||
|
||||
# initialize & add current active material:
|
||||
for extruder in printer.extruders:
|
||||
materials = [_makeMaterial(
|
||||
extruder.activeMaterial.brand, extruder.activeMaterial.name, extruder.activeMaterial.color)]
|
||||
extruder_configs[extruder.getPosition()] = {
|
||||
"position": extruder.getPosition(),
|
||||
"core": extruder.hotendID,
|
||||
"materials": materials
|
||||
}
|
||||
|
||||
# add currently inactive, but possible materials:
|
||||
for configuration in printer.availableConfigurations:
|
||||
for extruder in configuration.extruderConfigurations:
|
||||
if not extruder.position in extruder_configs:
|
||||
Logger.log("w", f"No active extruder for position {extruder.position}.")
|
||||
continue
|
||||
|
||||
entry = _makeMaterial(extruder.material.brand, extruder.material.name, extruder.material.color)
|
||||
if entry not in extruder_configs[extruder.position]["materials"]:
|
||||
extruder_configs[extruder.position]["materials"].append(entry)
|
||||
|
||||
if any([len(extruder["materials"]) > 0 for extruder in extruder_configs.values()]):
|
||||
self.appendItem({
|
||||
"name": printer.name,
|
||||
"unique_id": printer.name, # <- Can assume the cloud doesn't have duplicate names?
|
||||
"extruders": list(extruder_configs.values())
|
||||
})
|
@ -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,6 +148,10 @@ 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
|
||||
|
@ -5,13 +5,15 @@
|
||||
# online cloud connected printers are represented within this ListModel. Additional information such as the number of
|
||||
# connected printers for each printer type is gathered.
|
||||
|
||||
from PyQt6.QtCore import Qt, QTimer, pyqtSlot, pyqtProperty, pyqtSignal
|
||||
from typing import Optional, List, cast
|
||||
|
||||
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
|
||||
@ -26,11 +28,13 @@ class MachineListModel(ListModel):
|
||||
MachineCountRole = Qt.ItemDataRole.UserRole + 6
|
||||
IsAbstractMachineRole = Qt.ItemDataRole.UserRole + 7
|
||||
ComponentTypeRole = Qt.ItemDataRole.UserRole + 8
|
||||
IsNetworkedMachineRole = Qt.ItemDataRole.UserRole + 9
|
||||
|
||||
def __init__(self, parent=None) -> None:
|
||||
def __init__(self, parent: Optional[QObject] = None, machines_filter: List[GlobalStack] = None, listenToChanges: bool = True) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self._show_cloud_printers = False
|
||||
self._machines_filter = machines_filter
|
||||
|
||||
self._catalog = i18nCatalog("cura")
|
||||
|
||||
@ -42,17 +46,18 @@ class MachineListModel(ListModel):
|
||||
self.addRoleName(self.MachineCountRole, "machineCount")
|
||||
self.addRoleName(self.IsAbstractMachineRole, "isAbstractMachine")
|
||||
self.addRoleName(self.ComponentTypeRole, "componentType")
|
||||
self.addRoleName(self.IsNetworkedMachineRole, "isNetworked")
|
||||
|
||||
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()
|
||||
if listenToChanges:
|
||||
CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
|
||||
CuraContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
|
||||
CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
|
||||
self._updateDelayed()
|
||||
|
||||
showCloudPrintersChanged = pyqtSignal(bool)
|
||||
|
||||
@ -66,7 +71,7 @@ class MachineListModel(ListModel):
|
||||
self._updateDelayed()
|
||||
self.showCloudPrintersChanged.emit(show_cloud_printers)
|
||||
|
||||
def _onContainerChanged(self, container) -> None:
|
||||
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
|
||||
@ -76,67 +81,79 @@ class MachineListModel(ListModel):
|
||||
def _updateDelayed(self) -> None:
|
||||
self._change_timer.start()
|
||||
|
||||
def _getMachineStacks(self) -> List[ContainerStack]:
|
||||
return CuraContainerRegistry.getInstance().findContainerStacks(type = "machine")
|
||||
|
||||
def _getAbstractMachineStacks(self) -> List[ContainerStack]:
|
||||
return CuraContainerRegistry.getInstance().findContainerStacks(is_abstract_machine = "True")
|
||||
|
||||
def set_machines_filter(self, machines_filter: Optional[List[GlobalStack]]) -> None:
|
||||
self._machines_filter = machines_filter
|
||||
self._update()
|
||||
|
||||
def _update(self) -> None:
|
||||
self.clear()
|
||||
|
||||
other_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type="machine")
|
||||
from cura.CuraApplication import CuraApplication
|
||||
machines_manager = CuraApplication.getInstance().getMachineManager()
|
||||
|
||||
other_machine_stacks = self._getMachineStacks()
|
||||
other_machine_stacks.sort(key = lambda machine: machine.getName().upper())
|
||||
|
||||
abstract_machine_stacks = self._getAbstractMachineStacks()
|
||||
abstract_machine_stacks.sort(key = lambda machine: machine.getName().upper(), reverse = True)
|
||||
|
||||
if self._machines_filter is not None:
|
||||
filter_ids = [machine_filter.id for machine_filter in self._machines_filter]
|
||||
other_machine_stacks = [machine for machine in other_machine_stacks if machine.id in filter_ids]
|
||||
abstract_machine_stacks = [machine for machine in abstract_machine_stacks if machine.id in filter_ids]
|
||||
|
||||
abstract_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(is_abstract_machine = "True")
|
||||
abstract_machine_stacks.sort(key = lambda machine: machine.getName(), reverse = True)
|
||||
for abstract_machine in abstract_machine_stacks:
|
||||
definition_id = abstract_machine.definition.getId()
|
||||
from cura.CuraApplication import CuraApplication
|
||||
machines_manager = CuraApplication.getInstance().getMachineManager()
|
||||
online_machine_stacks = machines_manager.getMachinesWithDefinition(definition_id, online_only = True)
|
||||
|
||||
# Create a list item for abstract machine
|
||||
self.addItem(abstract_machine, len(online_machine_stacks))
|
||||
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)
|
||||
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
|
||||
})
|
||||
self.appendItem({
|
||||
"componentType": "HIDE_BUTTON" if self._show_cloud_printers else "SHOW_BUTTON",
|
||||
"isOnline": True,
|
||||
"isAbstractMachine": False,
|
||||
"machineCount": 0,
|
||||
"catergory": "connected",
|
||||
})
|
||||
|
||||
for stack in other_machine_stacks:
|
||||
self.addItem(stack)
|
||||
self.addItem(stack, False)
|
||||
|
||||
def addItem(self, container_stack: ContainerStack, machine_count: int = 0) -> None:
|
||||
def addItem(self, container_stack: ContainerStack, is_online: bool, machine_count: int = 0) -> None:
|
||||
if parseBool(container_stack.getMetaDataEntry("hidden", False)):
|
||||
return
|
||||
|
||||
# This is required because machines loaded from projects have the is_online="True" but no connection type.
|
||||
# We want to display them the same way as unconnected printers in this case.
|
||||
has_connection = False
|
||||
has_connection |= parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False))
|
||||
for connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value]:
|
||||
has_connection |= connection_type in container_stack.configuredConnectionTypes
|
||||
|
||||
self.appendItem({
|
||||
"componentType": "MACHINE",
|
||||
"name": container_stack.getName(),
|
||||
"id": container_stack.getId(),
|
||||
"metadata": container_stack.getMetaData().copy(),
|
||||
"isOnline": parseBool(container_stack.getMetaDataEntry("is_online", False)) and has_connection,
|
||||
"isAbstractMachine": parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False)),
|
||||
"machineCount": machine_count,
|
||||
})
|
||||
"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)),
|
||||
"isNetworked": cast(GlobalStack, container_stack).hasNetworkedConnection() if isinstance(container_stack, GlobalStack) else False,
|
||||
"machineCount": machine_count,
|
||||
"catergory": "connected" if is_online else "other",
|
||||
})
|
||||
|
@ -274,7 +274,7 @@ class AuthorizationService:
|
||||
self._unable_to_get_data_message.show()
|
||||
else:
|
||||
self._unable_to_get_data_message = Message(i18n_catalog.i18nc("@info",
|
||||
"Unable to reach the Ultimaker account server."),
|
||||
"Unable to reach the UltiMaker account server."),
|
||||
title = i18n_catalog.i18nc("@info:title", "Log-in failed"),
|
||||
message_type = Message.MessageType.ERROR)
|
||||
Logger.warning("Unable to get user profile using auth data from preferences.")
|
||||
|
@ -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
|
||||
|
@ -252,7 +252,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||
# 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()
|
||||
|
@ -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:
|
||||
|
48
cura/Settings/ActiveQuality.py
Normal file
48
cura/Settings/ActiveQuality.py
Normal file
@ -0,0 +1,48 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from UM import i18nCatalog
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
@dataclass
|
||||
class ActiveQuality:
|
||||
""" Represents the active intent+profile combination, contains all information needed to display active quality. """
|
||||
intent_category: str = "" # Name of the base intent. For example "visual" or "engineering".
|
||||
intent_name: str = "" # Name of the base intent formatted for display. For Example "Visual" or "Engineering"
|
||||
profile: str = "" # Name of the base profile. For example "Fine" or "Fast"
|
||||
custom_profile: str = "" # Name of the custom profile, this is based on profile. For example "MyCoolCustomProfile"
|
||||
layer_height: float = None # Layer height of quality in mm. For example 0.4
|
||||
is_experimental: bool = False # If the quality experimental.
|
||||
|
||||
def getMainStringParts(self) -> List[str]:
|
||||
string_parts = []
|
||||
|
||||
if self.custom_profile is not None:
|
||||
string_parts.append(self.custom_profile)
|
||||
else:
|
||||
string_parts.append(self.profile)
|
||||
if self.intent_category is not "default":
|
||||
string_parts.append(self.intent_name)
|
||||
|
||||
return string_parts
|
||||
|
||||
def getTailStringParts(self) -> List[str]:
|
||||
string_parts = []
|
||||
|
||||
if self.custom_profile is not None:
|
||||
string_parts.append(self.profile)
|
||||
if self.intent_category is not "default":
|
||||
string_parts.append(self.intent_name)
|
||||
|
||||
if self.layer_height:
|
||||
string_parts.append(f"{self.layer_height}mm")
|
||||
|
||||
if self.is_experimental:
|
||||
string_parts.append(catalog.i18nc("@label", "Experimental"))
|
||||
|
||||
return string_parts
|
||||
|
||||
def getStringParts(self) -> List[str]:
|
||||
return self.getMainStringParts() + self.getTailStringParts()
|
@ -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
|
||||
|
@ -1,6 +1,8 @@
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import copy
|
||||
|
||||
from typing import Optional, cast
|
||||
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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):
|
||||
|
@ -226,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):
|
||||
@ -347,6 +345,12 @@ class GlobalStack(CuraContainerStack):
|
||||
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(
|
||||
name = "application/x-cura-globalstack",
|
||||
|
@ -40,6 +40,7 @@ from cura.Settings.cura_empty_instance_containers import (empty_definition_chang
|
||||
empty_material_container, empty_quality_container,
|
||||
empty_quality_changes_container, empty_intent_container)
|
||||
from cura.UltimakerCloud.UltimakerCloudConstants import META_UM_LINKED_TO_ACCOUNT
|
||||
from .ActiveQuality import ActiveQuality
|
||||
|
||||
from .CuraStackBuilder import CuraStackBuilder
|
||||
|
||||
@ -99,7 +100,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()
|
||||
@ -112,7 +113,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)
|
||||
|
||||
@ -904,6 +905,7 @@ class MachineManager(QObject):
|
||||
|
||||
if self._global_container_stack is None \
|
||||
or self._global_container_stack.getProperty(setting_key, "value") == new_value \
|
||||
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
|
||||
|
||||
@ -1630,33 +1632,31 @@ class MachineManager(QObject):
|
||||
# Examples:
|
||||
# - "my_profile - Fine" (only based on a default quality, no intent involved)
|
||||
# - "my_profile - Engineering - Fine" (based on an intent)
|
||||
@pyqtProperty("QVariantMap", notify = activeQualityDisplayNameChanged)
|
||||
def activeQualityDisplayNameMap(self) -> Dict[str, str]:
|
||||
@pyqtProperty("QList<QString>", notify = activeQualityDisplayNameChanged)
|
||||
def activeQualityDisplayNameStringParts(self) -> List[str]:
|
||||
return self.activeQualityDisplayNameMap().getStringParts()
|
||||
|
||||
@pyqtProperty("QList<QString>", notify = activeQualityDisplayNameChanged)
|
||||
def activeQualityDisplayNameMainStringParts(self) -> List[str]:
|
||||
return self.activeQualityDisplayNameMap().getMainStringParts()
|
||||
|
||||
@pyqtProperty("QList<QString>", notify = activeQualityDisplayNameChanged)
|
||||
def activeQualityDisplayNameTailStringParts(self) -> List[str]:
|
||||
return self.activeQualityDisplayNameMap().getTailStringParts()
|
||||
|
||||
def activeQualityDisplayNameMap(self) -> ActiveQuality:
|
||||
global_stack = self._application.getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return {"main": "",
|
||||
"suffix": ""}
|
||||
return ActiveQuality()
|
||||
|
||||
display_name = global_stack.quality.getName()
|
||||
|
||||
intent_category = self.activeIntentCategory
|
||||
if intent_category != "default":
|
||||
intent_display_name = IntentCategoryModel.translation(intent_category,
|
||||
"name",
|
||||
intent_category.title())
|
||||
display_name = "{intent_name} - {the_rest}".format(intent_name = intent_display_name,
|
||||
the_rest = display_name)
|
||||
|
||||
main_part = display_name
|
||||
suffix_part = ""
|
||||
|
||||
# Not a custom quality
|
||||
if global_stack.qualityChanges != empty_quality_changes_container:
|
||||
main_part = self.activeQualityOrQualityChangesName
|
||||
suffix_part = display_name
|
||||
|
||||
return {"main": main_part,
|
||||
"suffix": suffix_part}
|
||||
return ActiveQuality(
|
||||
profile = global_stack.quality.getName(),
|
||||
intent_category = self.activeIntentCategory,
|
||||
intent_name = IntentCategoryModel.translation(self.activeIntentCategory, "name", self.activeIntentCategory.title()),
|
||||
custom_profile = self.activeQualityOrQualityChangesName if global_stack.qualityChanges is not empty_quality_changes_container else None,
|
||||
layer_height = self.activeQualityLayerHeight if self.isActiveQualitySupported else None,
|
||||
is_experimental = self.isActiveQualityExperimental and self.isActiveQualitySupported
|
||||
)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setIntentByCategory(self, intent_category: str) -> None:
|
||||
@ -1775,7 +1775,9 @@ class MachineManager(QObject):
|
||||
@pyqtProperty(bool, notify = activeQualityGroupChanged)
|
||||
def hasNotSupportedQuality(self) -> bool:
|
||||
global_container_stack = self._application.getGlobalContainerStack()
|
||||
return (not global_container_stack is None) and global_container_stack.quality == empty_quality_container and global_container_stack.qualityChanges == empty_quality_changes_container
|
||||
return global_container_stack is not None\
|
||||
and global_container_stack.quality == empty_quality_container \
|
||||
and global_container_stack.qualityChanges == empty_quality_changes_container
|
||||
|
||||
@pyqtProperty(bool, notify = activeQualityGroupChanged)
|
||||
def isActiveQualityCustom(self) -> bool:
|
||||
|
@ -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.
|
||||
"""
|
||||
|
||||
@ -186,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
|
||||
@ -368,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
|
||||
@ -392,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.
|
||||
"""
|
||||
@ -446,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)
|
||||
|
@ -13,8 +13,8 @@ export QT_XKB_CONFIG_ROOT=/usr/share/X11/xkb
|
||||
# Use the openssl.cnf packaged in the AppImage
|
||||
export OPENSSL_CONF="$scriptdir/openssl.cnf"
|
||||
|
||||
$scriptdir/Ultimaker-Cura "$@"
|
||||
|
||||
# If this variable is set on Zorin OS 16 Cura would crash
|
||||
# unset `QT_STYLE_OVERRIDE` as a precaution
|
||||
unset QT_STYLE_OVERRIDE
|
||||
|
||||
$scriptdir/UltiMaker-Cura "$@"
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# 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.
|
||||
@ -71,6 +72,6 @@ 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')")
|
||||
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)
|
||||
|
@ -3,16 +3,16 @@
|
||||
<id>com.ultimaker.cura</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>LGPL-3.0</project_license>
|
||||
<name>Ultimaker Cura</name>
|
||||
<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>
|
||||
<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>
|
||||
<image>https://raw.githubusercontent.com/Ultimaker/Cura/main/cura-logo.PNG</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
</component>
|
||||
|
@ -1,11 +1,11 @@
|
||||
[Desktop Entry]
|
||||
Name=Ultimaker Cura
|
||||
Name[de]=Ultimaker Cura
|
||||
Name=UltiMaker Cura
|
||||
Name[de]=UltiMaker Cura
|
||||
GenericName=3D Printing Software
|
||||
GenericName[de]=3D-Druck-Software
|
||||
GenericName[nl]=3D-Print Software
|
||||
Comment=Cura converts 3D models into paths for a 3D printer. It prepares your print for maximum accuracy, minimum printing time and good reliability with many extra features that make your print come out great.
|
||||
Exec=Ultimaker-Cura %F
|
||||
Exec=UltiMaker-Cura %F
|
||||
Icon=cura-icon
|
||||
Terminal=false
|
||||
Type=Application
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# 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 }}"
|
||||
@ -12,9 +12,9 @@
|
||||
!define INSTALLER_NAME "{{ destination }}"
|
||||
!define MAIN_APP_EXE "{{ main_app }}"
|
||||
!define INSTALL_TYPE "SetShellVarContext all"
|
||||
!define REG_ROOT "HKCR"
|
||||
!define REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\${APP_NAME}"
|
||||
!define UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}"
|
||||
!define REG_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"
|
||||
|
||||
@ -64,7 +64,7 @@ InstallDir "$PROGRAMFILES64\${APP_NAME}"
|
||||
|
||||
!ifdef REG_START_MENU
|
||||
!define MUI_STARTMENUPAGE_NODISABLE
|
||||
!define MUI_STARTMENUPAGE_DEFAULTFOLDER "Ultimaker Cura"
|
||||
!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}"
|
||||
@ -113,8 +113,8 @@ 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"
|
||||
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
|
||||
@ -125,8 +125,8 @@ CreateShortCut "$SMPROGRAMS\{{ app_name }}\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP
|
||||
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"
|
||||
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
|
||||
|
||||
@ -163,14 +163,14 @@ Delete "$INSTDIR\uninstall.exe"
|
||||
Delete "$INSTDIR\${APP_NAME} website.url"
|
||||
!endif
|
||||
|
||||
RmDir "$INSTDIR"
|
||||
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"
|
||||
Delete "$SMPROGRAMS\$SM_Folder\UltiMaker Cura website.lnk"
|
||||
!endif
|
||||
RmDir "$SMPROGRAMS\$SM_Folder"
|
||||
!endif
|
||||
@ -179,7 +179,7 @@ RmDir "$SMPROGRAMS\$SM_Folder"
|
||||
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"
|
||||
Delete "$SMPROGRAMS\{{ app_name }}\UltiMaker Cura website.lnk"
|
||||
!endif
|
||||
RmDir "$SMPROGRAMS\{{ app_name }}"
|
||||
!endif
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
|
||||
import os
|
||||
import argparse # Command line arguments parsing and help.
|
||||
import subprocess
|
||||
@ -16,15 +18,15 @@ 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()]
|
||||
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))]
|
||||
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)))
|
||||
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():
|
||||
@ -40,13 +42,13 @@ def generate_nsi(source_path: str, dist_path: str, filename: str):
|
||||
|
||||
|
||||
nsis_content = template.render(
|
||||
app_name = f"Ultimaker Cura {os.getenv('CURA_VERSION_FULL')}",
|
||||
main_app = "Ultimaker-Cura.exe",
|
||||
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.",
|
||||
company = "UltiMaker",
|
||||
web_site = "https://ultimaker.com",
|
||||
year = datetime.now().year,
|
||||
cura_license_file = str(source_loc.joinpath("packaging", "cura_license.txt")),
|
||||
@ -58,7 +60,7 @@ def generate_nsi(source_path: str, dist_path: str, filename: str):
|
||||
destination = filename
|
||||
)
|
||||
|
||||
with open(dist_loc.joinpath("Ultimaker-Cura.nsi"), "w") as f:
|
||||
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"))
|
||||
@ -66,7 +68,7 @@ def generate_nsi(source_path: str, dist_path: str, filename: str):
|
||||
|
||||
def build(dist_path: str):
|
||||
dist_loc = Path(os.getcwd(), dist_path)
|
||||
command = ["makensis", "/V2", "/P4", str(dist_loc.joinpath("Ultimaker-Cura.nsi"))]
|
||||
command = ["makensis", "/V2", "/P4", str(dist_loc.joinpath("UltiMaker-Cura.nsi"))]
|
||||
subprocess.run(command)
|
||||
|
||||
|
||||
@ -74,7 +76,7 @@ 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')")
|
||||
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)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 604 KiB |
Binary file not shown.
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 381 KiB |
@ -1,3 +1,7 @@
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
|
||||
import os
|
||||
import argparse # Command line arguments parsing and help.
|
||||
import subprocess
|
||||
@ -14,11 +18,11 @@ def build_dmg(source_path: str, dist_path: str, filename: str) -> None:
|
||||
"--app-drop-link", "520", "272",
|
||||
"--volicon", f"{source_path}/packaging/icons/VolumeIcons_Cura.icns",
|
||||
"--icon-size", "90",
|
||||
"--icon", "Ultimaker-Cura.app", "169", "272",
|
||||
"--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"]
|
||||
f"{dist_path}/UltiMaker-Cura.app"]
|
||||
|
||||
subprocess.run(arguments)
|
||||
|
||||
@ -57,7 +61,7 @@ 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')")
|
||||
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)
|
||||
|
@ -9,6 +9,7 @@ from typing import cast, Dict, List, Optional, Tuple, Any, Set
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from UM.Util import parseBool
|
||||
from UM.Workspace.WorkspaceReader import WorkspaceReader
|
||||
from UM.Application import Application
|
||||
|
||||
@ -53,6 +54,7 @@ _ignored_machine_network_metadata = {
|
||||
"connection_type",
|
||||
"capabilities",
|
||||
"octoprint_api_key",
|
||||
"is_abstract_machine"
|
||||
} # type: Set[str]
|
||||
|
||||
|
||||
@ -598,7 +600,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
self._dialog.setNumUserSettings(num_user_settings)
|
||||
self._dialog.setActiveMode(active_mode)
|
||||
self._dialog.setUpdatableMachines(updatable_machines)
|
||||
self._dialog.setMachineName(machine_name)
|
||||
self._dialog.setMaterialLabels(material_labels)
|
||||
self._dialog.setMachineType(machine_type)
|
||||
self._dialog.setExtruders(extruders)
|
||||
@ -607,6 +608,36 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
self._dialog.setMissingPackagesMetadata(missing_package_metadata)
|
||||
self._dialog.show()
|
||||
|
||||
# Choosing the initially selected printer in MachineSelector
|
||||
is_networked_machine = False
|
||||
is_abstract_machine = False
|
||||
if global_stack and isinstance(global_stack, GlobalStack):
|
||||
# The machine included in the project file exists locally already, no need to change selected printers.
|
||||
is_networked_machine = global_stack.hasNetworkedConnection()
|
||||
is_abstract_machine = parseBool(existing_global_stack.getMetaDataEntry("is_abstract_machine", False))
|
||||
self._dialog.setMachineToOverride(global_stack.getId())
|
||||
self._dialog.setResolveStrategy("machine", "override")
|
||||
elif self._dialog.updatableMachinesModel.count > 0:
|
||||
# The machine included in the project file does not exist. There is another machine of the same type.
|
||||
# This will always default to an abstract machine first.
|
||||
machine = self._dialog.updatableMachinesModel.getItem(0)
|
||||
machine_name = machine["name"]
|
||||
is_networked_machine = machine["isNetworked"]
|
||||
is_abstract_machine = machine["isAbstractMachine"]
|
||||
self._dialog.setMachineToOverride(machine["id"])
|
||||
self._dialog.setResolveStrategy("machine", "override")
|
||||
else:
|
||||
# The machine included in the project file does not exist. There are no other printers of the same type. Default to "Create New".
|
||||
machine_name = i18n_catalog.i18nc("@button", "Create new")
|
||||
is_networked_machine = False
|
||||
is_abstract_machine = False
|
||||
self._dialog.setMachineToOverride(None)
|
||||
self._dialog.setResolveStrategy("machine", "new")
|
||||
|
||||
self._dialog.setIsNetworkedMachine(is_networked_machine)
|
||||
self._dialog.setIsAbstractMachine(is_abstract_machine)
|
||||
self._dialog.setMachineName(machine_name)
|
||||
|
||||
# Block until the dialog is closed.
|
||||
self._dialog.waitForClose()
|
||||
|
||||
@ -664,10 +695,22 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
|
||||
cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
|
||||
|
||||
# Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its
|
||||
# Create a shadow copy of the preferences (We don't want all of the preferences, but we do want to re-use its
|
||||
# parsing code.
|
||||
temp_preferences = Preferences()
|
||||
serialized = archive.open("Cura/preferences.cfg").read().decode("utf-8")
|
||||
try:
|
||||
serialized = archive.open("Cura/preferences.cfg").read().decode("utf-8")
|
||||
except KeyError as e:
|
||||
# If there is no preferences file, it's not a workspace, so notify user of failure.
|
||||
Logger.log("w", "File %s is not a valid workspace.", file_name)
|
||||
message = Message(i18n_catalog.i18nc("@info:error Don't translate the XML tags <filename> or <message>!",
|
||||
"Project file <filename>{0}</filename> is corrupt: <message>{1}</message>.",
|
||||
file_name, str(e)),
|
||||
title=i18n_catalog.i18nc("@info:title", "Can't Open Project File"),
|
||||
message_type=Message.MessageType.ERROR)
|
||||
message.show()
|
||||
self.setWorkspaceName("")
|
||||
return [], {}
|
||||
temp_preferences.deserialize(serialized)
|
||||
|
||||
# Copy a number of settings from the temp preferences to the global
|
||||
@ -689,7 +732,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
application.expandedCategoriesChanged.emit() # Notify the GUI of the change
|
||||
|
||||
# If there are no machines of the same type, create a new machine.
|
||||
if self._resolve_strategies["machine"] != "override" or self._dialog.updatableMachinesModel.count <= 1:
|
||||
if self._resolve_strategies["machine"] != "override" or self._dialog.updatableMachinesModel.count == 0:
|
||||
# We need to create a new machine
|
||||
machine_name = self._container_registry.uniqueName(self._machine_info.name)
|
||||
|
||||
|
@ -1,43 +0,0 @@
|
||||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
create_new_list_item = {
|
||||
"id": "new",
|
||||
"name": "Create new",
|
||||
"displayName": "Create new",
|
||||
"type": "default_option" # to make sure we are not mixing the "Create new" option with a printer with id "new"
|
||||
} # type: Dict[str, str]
|
||||
|
||||
|
||||
class UpdatableMachinesModel(ListModel):
|
||||
"""Model that holds cura packages.
|
||||
|
||||
By setting the filter property the instances held by this model can be changed.
|
||||
"""
|
||||
|
||||
def __init__(self, parent = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 1, "id")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 2, "name")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 3, "displayName")
|
||||
self.addRoleName(Qt.ItemDataRole.UserRole + 4, "type") # Either "default_option" or "machine"
|
||||
|
||||
def update(self, machines: List[GlobalStack]) -> None:
|
||||
items = [create_new_list_item] # type: List[Dict[str, str]]
|
||||
|
||||
for machine in sorted(machines, key = lambda printer: printer.name):
|
||||
items.append({
|
||||
"id": machine.id,
|
||||
"name": machine.name,
|
||||
"displayName": "Update " + machine.name,
|
||||
"type": "machine"
|
||||
})
|
||||
self.setItems(items)
|
@ -5,6 +5,7 @@ from PyQt6.QtCore import pyqtSignal, QObject, pyqtProperty, QCoreApplication, QU
|
||||
from PyQt6.QtGui import QDesktopServices
|
||||
from typing import List, Optional, Dict, cast
|
||||
|
||||
from cura.Machines.Models.MachineListModel import MachineListModel
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from UM.Application import Application
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
@ -14,8 +15,6 @@ from UM.Message import Message
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
from .UpdatableMachinesModel import UpdatableMachinesModel
|
||||
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
@ -63,10 +62,12 @@ class WorkspaceDialog(QObject):
|
||||
self._extruders = []
|
||||
self._objects_on_plate = False
|
||||
self._is_printer_group = False
|
||||
self._updatable_machines_model = UpdatableMachinesModel(self)
|
||||
self._updatable_machines_model = MachineListModel(self, listenToChanges=False)
|
||||
self._missing_package_metadata: List[Dict[str, str]] = []
|
||||
self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry()
|
||||
self._install_missing_package_dialog: Optional[QObject] = None
|
||||
self._is_abstract_machine = False
|
||||
self._is_networked_machine = False
|
||||
|
||||
machineConflictChanged = pyqtSignal()
|
||||
qualityChangesConflictChanged = pyqtSignal()
|
||||
@ -80,6 +81,8 @@ class WorkspaceDialog(QObject):
|
||||
intentNameChanged = pyqtSignal()
|
||||
machineNameChanged = pyqtSignal()
|
||||
updatableMachinesChanged = pyqtSignal()
|
||||
isAbstractMachineChanged = pyqtSignal()
|
||||
isNetworkedChanged = pyqtSignal()
|
||||
materialLabelsChanged = pyqtSignal()
|
||||
objectsOnPlateChanged = pyqtSignal()
|
||||
numUserSettingsChanged = pyqtSignal()
|
||||
@ -161,13 +164,31 @@ class WorkspaceDialog(QObject):
|
||||
self.machineNameChanged.emit()
|
||||
|
||||
@pyqtProperty(QObject, notify = updatableMachinesChanged)
|
||||
def updatableMachinesModel(self) -> UpdatableMachinesModel:
|
||||
return cast(UpdatableMachinesModel, self._updatable_machines_model)
|
||||
def updatableMachinesModel(self) -> MachineListModel:
|
||||
return cast(MachineListModel, self._updatable_machines_model)
|
||||
|
||||
def setUpdatableMachines(self, updatable_machines: List[GlobalStack]) -> None:
|
||||
self._updatable_machines_model.update(updatable_machines)
|
||||
self._updatable_machines_model.set_machines_filter(updatable_machines)
|
||||
self.updatableMachinesChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, notify = isAbstractMachineChanged)
|
||||
def isAbstractMachine(self) -> bool:
|
||||
return self._is_abstract_machine
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def setIsAbstractMachine(self, is_abstract_machine: bool) -> None:
|
||||
self._is_abstract_machine = is_abstract_machine
|
||||
self.isAbstractMachineChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, notify = isNetworkedChanged)
|
||||
def isNetworked(self) -> bool:
|
||||
return self._is_networked_machine
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def setIsNetworkedMachine(self, is_networked_machine: bool) -> None:
|
||||
self._is_networked_machine = is_networked_machine
|
||||
self.isNetworkedChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify=qualityTypeChanged)
|
||||
def qualityType(self) -> str:
|
||||
return self._quality_type
|
||||
|
@ -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.
|
||||
|
||||
import QtQuick 2.10
|
||||
@ -11,47 +11,48 @@ import Cura 1.1 as Cura
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
id: base
|
||||
id: workspaceDialog
|
||||
title: catalog.i18nc("@title:window", "Open Project")
|
||||
|
||||
minimumWidth: UM.Theme.getSize("popup_dialog").width
|
||||
minimumHeight: UM.Theme.getSize("popup_dialog").height
|
||||
width: minimumWidth
|
||||
backgroundColor: UM.Theme.getColor("main_background")
|
||||
margin: UM.Theme.getSize("default_margin").width
|
||||
property int comboboxHeight: UM.Theme.getSize("default_margin").height
|
||||
minimumWidth: UM.Theme.getSize("modal_window_minimum").width
|
||||
minimumHeight: UM.Theme.getSize("modal_window_minimum").height
|
||||
|
||||
onClosing: manager.notifyClosed()
|
||||
onVisibleChanged:
|
||||
backgroundColor: UM.Theme.getColor("detail_background")
|
||||
|
||||
headerComponent: Rectangle
|
||||
{
|
||||
if (visible)
|
||||
height: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height
|
||||
color: UM.Theme.getColor("main_background")
|
||||
|
||||
UM.Label
|
||||
{
|
||||
machineResolveComboBox.currentIndex = 0
|
||||
qualityChangesResolveComboBox.currentIndex = 0
|
||||
materialResolveComboBox.currentIndex = 0
|
||||
id: titleLabel
|
||||
text: catalog.i18nc("@action:title", "Summary - Cura Project")
|
||||
font: UM.Theme.getFont("large")
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
}
|
||||
|
||||
Flickable
|
||||
Rectangle
|
||||
{
|
||||
clip: true
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentHeight: dialogSummaryItem.height
|
||||
ScrollBar.vertical: UM.ScrollBar { id: verticalScrollBar }
|
||||
anchors.fill: parent
|
||||
UM.I18nCatalog { id: catalog; name: "cura" }
|
||||
color: UM.Theme.getColor("main_background")
|
||||
|
||||
Item
|
||||
Flickable
|
||||
{
|
||||
id: dialogSummaryItem
|
||||
width: verticalScrollBar.visible ? parent.width - verticalScrollBar.width - UM.Theme.getSize("default_margin").width : parent.width
|
||||
height: childrenRect.height
|
||||
anchors.margins: 10 * screenScaleFactor
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
clip: true
|
||||
|
||||
contentHeight: contentColumn.height
|
||||
ScrollBar.vertical: UM.ScrollBar { id: scrollbar }
|
||||
|
||||
ListModel
|
||||
{
|
||||
@ -68,373 +69,245 @@ UM.Dialog
|
||||
|
||||
Column
|
||||
{
|
||||
width: parent.width
|
||||
id: contentColumn
|
||||
width: parent.width - scrollbar.width - UM.Theme.getSize("default_margin").width
|
||||
height: childrenRect.height
|
||||
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
leftPadding: UM.Theme.getSize("default_margin").width
|
||||
rightPadding: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Column
|
||||
WorkspaceSection
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
UM.Label
|
||||
id: printerSection
|
||||
title: catalog.i18nc("@action:label", "Printer settings")
|
||||
iconSource: UM.Theme.getIcon("Printer")
|
||||
content: Column
|
||||
{
|
||||
id: titleLabel
|
||||
text: catalog.i18nc("@action:title", "Summary - Cura Project")
|
||||
font: UM.Theme.getFont("large")
|
||||
}
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
leftPadding: UM.Theme.getSize("medium_button_icon").width + UM.Theme.getSize("default_margin").width
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: separator
|
||||
color: UM.Theme.getColor("text")
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
UM.TooltipArea
|
||||
{
|
||||
id: machineResolveStrategyTooltip
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: (parent.width / 3) | 0
|
||||
height: visible ? comboboxHeight : 0
|
||||
visible: base.visible && machineResolveComboBox.model.count > 1
|
||||
text: catalog.i18nc("@info:tooltip", "How should the conflict in the machine be resolved?")
|
||||
Cura.ComboBox
|
||||
WorkspaceRow
|
||||
{
|
||||
id: machineResolveComboBox
|
||||
model: manager.updatableMachinesModel
|
||||
visible: machineResolveStrategyTooltip.visible
|
||||
textRole: "displayName"
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("button").height
|
||||
onCurrentIndexChanged:
|
||||
leftLabelText: catalog.i18nc("@action:label", "Type")
|
||||
rightLabelText: manager.machineType
|
||||
}
|
||||
|
||||
WorkspaceRow
|
||||
{
|
||||
leftLabelText: catalog.i18nc("@action:label", manager.isPrinterGroup ? "Printer Group" : "Printer Name")
|
||||
rightLabelText: manager.machineName == catalog.i18nc("@button", "Create new") ? "" : manager.machineName
|
||||
}
|
||||
}
|
||||
|
||||
comboboxTitle: catalog.i18nc("@action:label", "Open With")
|
||||
comboboxTooltipText: catalog.i18nc("@info:tooltip", "Printer settings will be updated to match the settings saved with the project.")
|
||||
comboboxVisible: workspaceDialog.visible && manager.updatableMachinesModel.count > 1
|
||||
combobox: Cura.MachineSelector
|
||||
{
|
||||
id: machineSelector
|
||||
headerCornerSide: Cura.RoundedRectangle.Direction.All
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
machineListModel: manager.updatableMachinesModel
|
||||
machineName: manager.machineName
|
||||
|
||||
isConnectedCloudPrinter: false
|
||||
isCloudRegistered: false
|
||||
isNetworkPrinter: manager.isNetworked
|
||||
isGroup: manager.isAbstractMachine
|
||||
connectionStatus: ""
|
||||
|
||||
minDropDownWidth: machineSelector.width
|
||||
|
||||
buttons: [
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
if (model.getItem(currentIndex).id == "new"
|
||||
&& model.getItem(currentIndex).type == "default_option")
|
||||
id: createNewPrinter
|
||||
text: catalog.i18nc("@button", "Create new")
|
||||
fixedWidthMode: true
|
||||
width: parent.width - leftPadding * 1.5
|
||||
onClicked:
|
||||
{
|
||||
toggleContent()
|
||||
manager.setResolveStrategy("machine", "new")
|
||||
}
|
||||
else
|
||||
{
|
||||
manager.setResolveStrategy("machine", "override")
|
||||
manager.setMachineToOverride(model.getItem(currentIndex).id)
|
||||
machineSelector.machineName = catalog.i18nc("@button", "Create new")
|
||||
manager.setIsAbstractMachine(false)
|
||||
manager.setIsNetworkedMachine(false)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
onVisibleChanged:
|
||||
{
|
||||
if (!visible) {return}
|
||||
|
||||
currentIndex = 0
|
||||
// If the project printer exists in Cura, set it as the default dropdown menu option.
|
||||
// No need to check object 0, which is the "Create new" option
|
||||
for (var i = 1; i < model.count; i++)
|
||||
{
|
||||
if (model.getItem(i).name == manager.machineName)
|
||||
{
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
// The project printer does not exist in Cura. If there is at least one printer of the same
|
||||
// type, select the first one, else set the index to "Create new"
|
||||
if (currentIndex == 0 && model.count > 1)
|
||||
{
|
||||
currentIndex = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
UM.Label
|
||||
onSelectPrinter: function(machine)
|
||||
{
|
||||
id: printer_settings_label
|
||||
text: catalog.i18nc("@action:label", "Printer settings")
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Type")
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
UM.Label
|
||||
{
|
||||
text: manager.machineType
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", manager.isPrinterGroup ? "Printer Group" : "Printer Name")
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
UM.Label
|
||||
{
|
||||
text: manager.machineName
|
||||
width: (parent.width / 3) | 0
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
toggleContent();
|
||||
machineSelector.machineName = machine.name
|
||||
manager.setResolveStrategy("machine", "override")
|
||||
manager.setMachineToOverride(machine.id)
|
||||
manager.setIsAbstractMachine(machine.isAbstractMachine)
|
||||
manager.setIsNetworkedMachine(machine.isNetworked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
WorkspaceSection
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
UM.TooltipArea
|
||||
id: profileSection
|
||||
title: catalog.i18nc("@action:label", "Profile settings")
|
||||
iconSource: UM.Theme.getIcon("Sliders")
|
||||
content: Column
|
||||
{
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
width: (parent.width / 3) | 0
|
||||
height: visible ? comboboxHeight : 0
|
||||
id: profileSettingsValuesTable
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
leftPadding: UM.Theme.getSize("medium_button_icon").width + UM.Theme.getSize("default_margin").width
|
||||
|
||||
WorkspaceRow
|
||||
{
|
||||
leftLabelText: catalog.i18nc("@action:label", "Name")
|
||||
rightLabelText: manager.qualityName
|
||||
}
|
||||
|
||||
WorkspaceRow
|
||||
{
|
||||
leftLabelText: catalog.i18nc("@action:label", "Intent")
|
||||
rightLabelText: manager.intentName
|
||||
}
|
||||
|
||||
WorkspaceRow
|
||||
{
|
||||
leftLabelText: catalog.i18nc("@action:label", "Not in profile")
|
||||
rightLabelText: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.numUserSettings).arg(manager.numUserSettings)
|
||||
visible: manager.numUserSettings != 0
|
||||
}
|
||||
|
||||
WorkspaceRow
|
||||
{
|
||||
leftLabelText: catalog.i18nc("@action:label", "Derivative from")
|
||||
rightLabelText: catalog.i18ncp("@action:label", "%1, %2 override", "%1, %2 overrides", manager.numSettingsOverridenByQualityChanges).arg(manager.qualityType).arg(manager.numSettingsOverridenByQualityChanges)
|
||||
visible: manager.numSettingsOverridenByQualityChanges != 0
|
||||
}
|
||||
}
|
||||
|
||||
comboboxVisible: manager.qualityChangesConflict
|
||||
combobox: Cura.ComboBox
|
||||
{
|
||||
id: qualityChangesResolveComboBox
|
||||
model: resolveStrategiesModel
|
||||
textRole: "label"
|
||||
visible: manager.qualityChangesConflict
|
||||
text: catalog.i18nc("@info:tooltip", "How should the conflict in the profile be resolved?")
|
||||
Cura.ComboBox
|
||||
{
|
||||
model: resolveStrategiesModel
|
||||
textRole: "label"
|
||||
id: qualityChangesResolveComboBox
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("button").height
|
||||
onActivated:
|
||||
{
|
||||
manager.setResolveStrategy("quality_changes", resolveStrategiesModel.get(index).key)
|
||||
}
|
||||
}
|
||||
}
|
||||
contentLeftPadding: UM.Theme.getSize("default_margin").width + UM.Theme.getSize("narrow_margin").width
|
||||
textFont: UM.Theme.getFont("medium")
|
||||
|
||||
Column
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
UM.Label
|
||||
background: Cura.RoundedRectangle
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Profile settings")
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
color: qualityChangesResolveComboBox.hovered ? UM.Theme.getColor("expandable_hover") : UM.Theme.getColor("action_button")
|
||||
cornerSide: Cura.RoundedRectangle.Direction.All
|
||||
radius: UM.Theme.getSize("default_radius").width
|
||||
}
|
||||
|
||||
Row
|
||||
// This is a hack. This will trigger onCurrentIndexChanged and set the index when this component in loaded
|
||||
currentIndex:
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Name")
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
UM.Label
|
||||
{
|
||||
text: manager.qualityName
|
||||
width: (parent.width / 3) | 0
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
currentIndex = 0
|
||||
}
|
||||
|
||||
Row
|
||||
onCurrentIndexChanged:
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Intent")
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
UM.Label
|
||||
{
|
||||
text: manager.intentName
|
||||
width: (parent.width / 3) | 0
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Not in profile")
|
||||
visible: manager.numUserSettings != 0
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.numUserSettings).arg(manager.numUserSettings)
|
||||
visible: manager.numUserSettings != 0
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Derivative from")
|
||||
visible: manager.numSettingsOverridenByQualityChanges != 0
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18ncp("@action:label", "%1, %2 override", "%1, %2 overrides", manager.numSettingsOverridenByQualityChanges).arg(manager.qualityType).arg(manager.numSettingsOverridenByQualityChanges)
|
||||
width: (parent.width / 3) | 0
|
||||
visible: manager.numSettingsOverridenByQualityChanges != 0
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
manager.setResolveStrategy("quality_changes", resolveStrategiesModel.get(currentIndex).key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
WorkspaceSection
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
UM.TooltipArea
|
||||
id: materialSection
|
||||
title: catalog.i18nc("@action:label", "Material settings")
|
||||
iconSource: UM.Theme.getIcon("Spool")
|
||||
content: Column
|
||||
{
|
||||
id: materialResolveTooltip
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
width: (parent.width / 3) | 0
|
||||
height: visible ? comboboxHeight : 0
|
||||
visible: manager.materialConflict
|
||||
text: catalog.i18nc("@info:tooltip", "How should the conflict in the material be resolved?")
|
||||
Cura.ComboBox
|
||||
{
|
||||
model: resolveStrategiesModel
|
||||
textRole: "label"
|
||||
id: materialResolveComboBox
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("button").height
|
||||
onActivated:
|
||||
{
|
||||
manager.setResolveStrategy("material", resolveStrategiesModel.get(index).key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
Row
|
||||
{
|
||||
height: childrenRect.height
|
||||
width: parent.width
|
||||
spacing: UM.Theme.getSize("narrow_margin").width
|
||||
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Material settings")
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
}
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
leftPadding: UM.Theme.getSize("medium_button_icon").width + UM.Theme.getSize("default_margin").width
|
||||
|
||||
Repeater
|
||||
{
|
||||
model: manager.materialLabels
|
||||
delegate: Row
|
||||
delegate: WorkspaceRow
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Name")
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
UM.Label
|
||||
{
|
||||
text: modelData
|
||||
width: (parent.width / 3) | 0
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
leftLabelText: catalog.i18nc("@action:label", "Name")
|
||||
rightLabelText: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
comboboxVisible: manager.materialConflict
|
||||
|
||||
combobox: Cura.ComboBox
|
||||
{
|
||||
id: materialResolveComboBox
|
||||
model: resolveStrategiesModel
|
||||
textRole: "label"
|
||||
visible: manager.materialConflict
|
||||
contentLeftPadding: UM.Theme.getSize("default_margin").width + UM.Theme.getSize("narrow_margin").width
|
||||
textFont: UM.Theme.getFont("medium")
|
||||
|
||||
background: Cura.RoundedRectangle
|
||||
{
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
color: materialResolveComboBox.hovered ? UM.Theme.getColor("expandable_hover") : UM.Theme.getColor("action_button")
|
||||
cornerSide: Cura.RoundedRectangle.Direction.All
|
||||
radius: UM.Theme.getSize("default_radius").width
|
||||
}
|
||||
|
||||
// This is a hack. This will trigger onCurrentIndexChanged and set the index when this component in loaded
|
||||
currentIndex:
|
||||
{
|
||||
currentIndex = 0
|
||||
}
|
||||
|
||||
onCurrentIndexChanged:
|
||||
{
|
||||
manager.setResolveStrategy("material", resolveStrategiesModel.get(currentIndex).key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column
|
||||
WorkspaceSection
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
id: visibilitySection
|
||||
title: catalog.i18nc("@action:label", "Setting visibility")
|
||||
iconSource: UM.Theme.getIcon("Eye")
|
||||
content: Column
|
||||
{
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
leftPadding: UM.Theme.getSize("medium_button_icon").width + UM.Theme.getSize("default_margin").width
|
||||
bottomPadding: UM.Theme.getSize("narrow_margin").height
|
||||
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Setting visibility")
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
}
|
||||
Row
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
UM.Label
|
||||
WorkspaceRow
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Mode")
|
||||
width: (parent.width / 3) | 0
|
||||
leftLabelText: catalog.i18nc("@action:label", "Mode")
|
||||
rightLabelText: manager.activeMode
|
||||
}
|
||||
UM.Label
|
||||
|
||||
WorkspaceRow
|
||||
{
|
||||
text: manager.activeMode
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
}
|
||||
Row
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
visible: manager.hasVisibleSettingsField
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Visible settings:")
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "%1 out of %2" ).arg(manager.numVisibleSettings).arg(manager.totalNumberOfSettings)
|
||||
width: (parent.width / 3) | 0
|
||||
leftLabelText: catalog.i18nc("@action:label", "%1 out of %2" ).arg(manager.numVisibleSettings).arg(manager.totalNumberOfSettings)
|
||||
rightLabelText: manager.activeMode
|
||||
visible: manager.hasVisibleSettingsField
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
id: clearBuildPlateWarning
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
visible: manager.hasObjectsOnPlate
|
||||
|
||||
UM.ColorImage
|
||||
{
|
||||
width: warningLabel.height
|
||||
@ -459,14 +332,18 @@ UM.Dialog
|
||||
color: warning ? UM.Theme.getColor("warning") : "transparent"
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
height: childrenRect.height + 2 * base.margin
|
||||
height: childrenRect.height + (warning ? 2 * workspaceDialog.margin : workspaceDialog.margin)
|
||||
|
||||
Column
|
||||
{
|
||||
height: childrenRect.height
|
||||
spacing: base.margin
|
||||
spacing: workspaceDialog.margin
|
||||
|
||||
anchors.leftMargin: workspaceDialog.margin
|
||||
anchors.rightMargin: workspaceDialog.margin
|
||||
anchors.bottomMargin: workspaceDialog.margin
|
||||
anchors.topMargin: warning ? workspaceDialog.margin : 0
|
||||
|
||||
anchors.margins: base.margin
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
@ -476,7 +353,7 @@ UM.Dialog
|
||||
id: warningRow
|
||||
height: childrenRect.height
|
||||
visible: warning
|
||||
spacing: base.margin
|
||||
spacing: workspaceDialog.margin
|
||||
UM.ColorImage
|
||||
{
|
||||
width: UM.Theme.getSize("extruder_icon").width
|
||||
@ -500,7 +377,7 @@ UM.Dialog
|
||||
}
|
||||
}
|
||||
|
||||
buttonSpacing: UM.Theme.getSize("default_margin").width
|
||||
buttonSpacing: UM.Theme.getSize("wide_margin").width
|
||||
|
||||
rightButtons: [
|
||||
Cura.TertiaryButton
|
||||
@ -532,6 +409,19 @@ UM.Dialog
|
||||
}
|
||||
]
|
||||
|
||||
onClosing: manager.notifyClosed()
|
||||
onRejected: manager.onCancelButtonClicked()
|
||||
onAccepted: manager.onOkButtonClicked()
|
||||
onVisibleChanged:
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
// Force relead the comboboxes
|
||||
// Since this dialog is only created once the first time you open it, these comboxes need to be reloaded
|
||||
// each time it is shown after the first time so that the indexes will update correctly.
|
||||
materialSection.reloadValues()
|
||||
profileSection.reloadValues()
|
||||
printerSection.reloadValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
34
plugins/3MFReader/WorkspaceRow.qml
Normal file
34
plugins/3MFReader/WorkspaceRow.qml
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import UM 1.5 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
Row
|
||||
{
|
||||
property alias leftLabelText: leftLabel.text
|
||||
property alias rightLabelText: rightLabel.text
|
||||
|
||||
width: parent.width
|
||||
height: visible ? childrenRect.height : 0
|
||||
|
||||
UM.Label
|
||||
{
|
||||
id: leftLabel
|
||||
text: catalog.i18nc("@action:label", "Type")
|
||||
width: Math.round(parent.width / 4)
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
UM.Label
|
||||
{
|
||||
id: rightLabel
|
||||
text: manager.machineType
|
||||
width: Math.round(parent.width / 3)
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
128
plugins/3MFReader/WorkspaceSection.qml
Normal file
128
plugins/3MFReader/WorkspaceSection.qml
Normal file
@ -0,0 +1,128 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
|
||||
import UM 1.5 as UM
|
||||
|
||||
|
||||
Item
|
||||
{
|
||||
property alias title: sectionTitle.text
|
||||
property alias iconSource: sectionTitleIcon.source
|
||||
property Component content: Item { visible: false }
|
||||
|
||||
property alias comboboxTitle: comboboxLabel.text
|
||||
property Component combobox: Item { visible: false }
|
||||
property string comboboxTooltipText: ""
|
||||
property bool comboboxVisible: false
|
||||
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Row
|
||||
{
|
||||
id: sectionTitleRow
|
||||
anchors.top: parent.top
|
||||
bottomPadding: UM.Theme.getSize("default_margin").height
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
|
||||
UM.ColorImage
|
||||
{
|
||||
id: sectionTitleIcon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
source: ""
|
||||
height: UM.Theme.getSize("medium_button_icon").height
|
||||
color: UM.Theme.getColor("text")
|
||||
width: height
|
||||
}
|
||||
UM.Label
|
||||
{
|
||||
id: sectionTitle
|
||||
text: ""
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: comboboxTooltip
|
||||
width: Math.round(parent.width / 2.5)
|
||||
height: visible ? UM.Theme.getSize("default_margin").height : 0
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
visible: comboboxVisible
|
||||
|
||||
UM.Label
|
||||
{
|
||||
id: comboboxLabel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
visible: comboboxVisible && text != ""
|
||||
text: ""
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
}
|
||||
|
||||
Loader
|
||||
{
|
||||
id: comboboxLoader
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("button").height
|
||||
anchors.top: comboboxLabel.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
sourceComponent: combobox
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
id: helpIconMouseArea
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: comboboxLabel.verticalCenter
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
hoverEnabled: true
|
||||
|
||||
UM.ColorImage
|
||||
{
|
||||
width: UM.Theme.getSize("section_icon").width
|
||||
height: width
|
||||
|
||||
visible: comboboxTooltipText != ""
|
||||
source: UM.Theme.getIcon("Help")
|
||||
color: UM.Theme.getColor("text")
|
||||
|
||||
UM.ToolTip
|
||||
{
|
||||
text: comboboxTooltipText
|
||||
visible: helpIconMouseArea.containsMouse
|
||||
targetPoint: Qt.point(parent.x + Math.round(parent.width / 2), parent.y)
|
||||
x: 0
|
||||
y: parent.y + parent.height + UM.Theme.getSize("default_margin").height
|
||||
width: UM.Theme.getSize("tooltip").width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loader
|
||||
{
|
||||
width: parent.width
|
||||
height: content.height
|
||||
anchors.top: sectionTitleRow.bottom
|
||||
sourceComponent: content
|
||||
}
|
||||
|
||||
function reloadValues()
|
||||
{
|
||||
comboboxLoader.sourceComponent = null
|
||||
comboboxLoader.sourceComponent = combobox
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2021-2022 Ultimaker B.V.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import argparse #To run the engine in debug mode if the front-end is in debug mode.
|
||||
@ -166,7 +166,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||
self._slicing_error_message.addAction(
|
||||
action_id = "report_bug",
|
||||
name = catalog.i18nc("@message:button", "Report a bug"),
|
||||
description = catalog.i18nc("@message:description", "Report a bug on Ultimaker Cura's issue tracker."),
|
||||
description = catalog.i18nc("@message:description", "Report a bug on UltiMaker Cura's issue tracker."),
|
||||
icon = "[no_icon]"
|
||||
)
|
||||
self._slicing_error_message.actionTriggered.connect(self._reportBackendError)
|
||||
|
@ -487,6 +487,10 @@ class StartSliceJob(Job):
|
||||
settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], initial_extruder_nr)
|
||||
settings["machine_end_gcode"] = self._expandGcodeTokens(settings["machine_end_gcode"], initial_extruder_nr)
|
||||
|
||||
# Manually add 'nozzle offsetting', since that is a metadata-entry instead for some reason.
|
||||
# NOTE: This probably needs to be an actual setting at some point.
|
||||
settings["nozzle_offsetting_for_disallowed_areas"] = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("nozzle_offsetting_for_disallowed_areas", True)
|
||||
|
||||
# Add all sub-messages for each individual setting.
|
||||
for key, value in settings.items():
|
||||
setting_message = self._slice_message.getMessage("global_settings").addRepeatedMessage("settings")
|
||||
|
@ -1,9 +1,8 @@
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import re # For escaping characters in the settings.
|
||||
import json
|
||||
import copy
|
||||
|
||||
from UM.Mesh.MeshWriter import MeshWriter
|
||||
from UM.Logger import Logger
|
||||
@ -12,6 +11,8 @@ from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from cura.Settings.CuraStackBuilder import CuraStackBuilder
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
@ -96,25 +97,6 @@ class GCodeWriter(MeshWriter):
|
||||
self.setInformation(catalog.i18nc("@warning:status", "Please prepare G-code before exporting."))
|
||||
return False
|
||||
|
||||
def _createFlattenedContainerInstance(self, instance_container1, instance_container2):
|
||||
"""Create a new container with container 2 as base and container 1 written over it."""
|
||||
|
||||
flat_container = InstanceContainer(instance_container2.getName())
|
||||
|
||||
# The metadata includes id, name and definition
|
||||
flat_container.setMetaData(copy.deepcopy(instance_container2.getMetaData()))
|
||||
|
||||
if instance_container1.getDefinition():
|
||||
flat_container.setDefinition(instance_container1.getDefinition().getId())
|
||||
|
||||
for key in instance_container2.getAllKeys():
|
||||
flat_container.setProperty(key, "value", instance_container2.getProperty(key, "value"))
|
||||
|
||||
for key in instance_container1.getAllKeys():
|
||||
flat_container.setProperty(key, "value", instance_container1.getProperty(key, "value"))
|
||||
|
||||
return flat_container
|
||||
|
||||
def _serialiseSettings(self, stack):
|
||||
"""Serialises a container stack to prepare it for writing at the end of the g-code.
|
||||
|
||||
@ -145,22 +127,22 @@ class GCodeWriter(MeshWriter):
|
||||
container_with_profile.setDefinition(machine_definition_id_for_quality)
|
||||
container_with_profile.setMetaDataEntry("setting_version", stack.quality.getMetaDataEntry("setting_version"))
|
||||
|
||||
flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile)
|
||||
merged_global_instance_container = InstanceContainer.createMergedInstanceContainer(stack.userChanges, container_with_profile)
|
||||
# If the quality changes is not set, we need to set type manually
|
||||
if flat_global_container.getMetaDataEntry("type", None) is None:
|
||||
flat_global_container.setMetaDataEntry("type", "quality_changes")
|
||||
if merged_global_instance_container.getMetaDataEntry("type", None) is None:
|
||||
merged_global_instance_container.setMetaDataEntry("type", "quality_changes")
|
||||
|
||||
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
|
||||
if flat_global_container.getMetaDataEntry("quality_type", None) is None:
|
||||
flat_global_container.setMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
|
||||
if merged_global_instance_container.getMetaDataEntry("quality_type", None) is None:
|
||||
merged_global_instance_container.setMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
|
||||
|
||||
# Get the machine definition ID for quality profiles
|
||||
flat_global_container.setMetaDataEntry("definition", machine_definition_id_for_quality)
|
||||
merged_global_instance_container.setMetaDataEntry("definition", machine_definition_id_for_quality)
|
||||
|
||||
serialized = flat_global_container.serialize()
|
||||
serialized = merged_global_instance_container.serialize()
|
||||
data = {"global_quality": serialized}
|
||||
|
||||
all_setting_keys = flat_global_container.getAllKeys()
|
||||
all_setting_keys = merged_global_instance_container.getAllKeys()
|
||||
for extruder in stack.extruderList:
|
||||
extruder_quality = extruder.qualityChanges
|
||||
if extruder_quality.getId() == "empty_quality_changes":
|
||||
@ -174,7 +156,7 @@ class GCodeWriter(MeshWriter):
|
||||
extruder_quality.setDefinition(machine_definition_id_for_quality)
|
||||
extruder_quality.setMetaDataEntry("setting_version", stack.quality.getMetaDataEntry("setting_version"))
|
||||
|
||||
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality)
|
||||
flat_extruder_quality = InstanceContainer.createMergedInstanceContainer(extruder.userChanges, extruder_quality)
|
||||
# If the quality changes is not set, we need to set type manually
|
||||
if flat_extruder_quality.getMetaDataEntry("type", None) is None:
|
||||
flat_extruder_quality.setMetaDataEntry("type", "quality_changes")
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import json
|
||||
from typing import List, Dict, Any, Set
|
||||
@ -142,7 +142,7 @@ class CloudPackageChecker(QObject):
|
||||
sync_message = Message(self._i18n_catalog.i18nc(
|
||||
"@info:generic",
|
||||
"Do you want to sync material and software packages with your account?"),
|
||||
title = self._i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", ))
|
||||
title = self._i18n_catalog.i18nc("@info:title", "Changes detected from your UltiMaker account", ))
|
||||
sync_message.addAction("sync",
|
||||
name = self._i18n_catalog.i18nc("@action:button", "Sync"),
|
||||
icon = "",
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import tempfile
|
||||
from typing import Dict, List, Any
|
||||
@ -92,7 +92,7 @@ class DownloadPresenter:
|
||||
lifetime = 0,
|
||||
use_inactivity_timer = False,
|
||||
progress = 0.0,
|
||||
title = i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account"))
|
||||
title = i18n_catalog.i18nc("@info:title", "Changes detected from your UltiMaker account"))
|
||||
|
||||
def _onFinished(self, package_id: str, reply: QNetworkReply) -> None:
|
||||
self._progress[package_id]["received"] = self._progress[package_id]["total"]
|
||||
|
@ -44,7 +44,7 @@ class LocalPackageList(PackageList):
|
||||
|
||||
def _sortSectionsOnUpdate(self) -> None:
|
||||
section_order = dict(zip([i for k, v in self.PACKAGE_CATEGORIES.items() for i in self.PACKAGE_CATEGORIES[k].values()], ["a", "b", "c", "d"]))
|
||||
self.sort(lambda model: (section_order[model.sectionTitle], model.canUpdate, model.displayName.lower()), key = "package")
|
||||
self.sort(lambda model: (section_order[model.sectionTitle], not model.canUpdate, model.displayName.lower()), key = "package")
|
||||
|
||||
def _removePackageModel(self, package_id: str) -> None:
|
||||
"""
|
||||
|
@ -92,7 +92,7 @@ class PackageModel(QObject):
|
||||
"display_name": display_name,
|
||||
"package_version": package_version,
|
||||
"package_type": package_type,
|
||||
"description": "The material package associated with the Cura project could not be found on the Ultimaker marketplace. Use the partial material profile definition stored in the Cura project file at your own risk."
|
||||
"description": catalog.i18nc("@label:label Ultimaker Marketplace is a brand name, don't translate", "The material package associated with the Cura project could not be found on the Ultimaker Marketplace. Use the partial material profile definition stored in the Cura project file at your own risk.")
|
||||
}
|
||||
package_model = cls(package_data)
|
||||
package_model.setIsMissingPackageInformation(True)
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2021 Ultimaker B.V.
|
||||
// Copyright (c) 2022 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
@ -12,7 +13,7 @@ Packages
|
||||
|
||||
bannerVisible: UM.Preferences.getValue("cura/market_place_show_manage_packages_banner");
|
||||
bannerIcon: UM.Theme.getIcon("ArrowDoubleCircleRight")
|
||||
bannerText: catalog.i18nc("@text", "Manage your Ultimaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.")
|
||||
bannerText: catalog.i18nc("@text", "Manage your UltiMaker Cura plugins and material profiles here. Make sure to keep your plugins up to date and backup your setup regularly.")
|
||||
bannerReadMoreUrl: "https://support.ultimaker.com/hc/en-us/articles/4411125921426/?utm_source=cura&utm_medium=software&utm_campaign=marketplace-learn-manage"
|
||||
onRemoveBanner: function() {
|
||||
UM.Preferences.setValue("cura/market_place_show_manage_packages_banner", false);
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Ultimaker B.V.
|
||||
// Copyright (c) 2022 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import UM 1.4 as UM
|
||||
@ -9,7 +9,7 @@ Packages
|
||||
|
||||
bannerVisible: UM.Preferences.getValue("cura/market_place_show_material_banner")
|
||||
bannerIcon: UM.Theme.getIcon("Spool")
|
||||
bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your Ultimaker 3D printers.")
|
||||
bannerText: catalog.i18nc("@text", "Select and install material profiles optimised for your UltiMaker 3D printers.")
|
||||
bannerReadMoreUrl: "https://support.ultimaker.com/hc/en-us/articles/360011968360/?utm_source=cura&utm_medium=software&utm_campaign=marketplace-learn-materials"
|
||||
onRemoveBanner: function() {
|
||||
UM.Preferences.setValue("cura/market_place_show_material_banner", false);
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Ultimaker B.V.
|
||||
// Copyright (c) 2022 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import UM 1.4 as UM
|
||||
@ -9,7 +9,7 @@ Packages
|
||||
|
||||
bannerVisible: UM.Preferences.getValue("cura/market_place_show_plugin_banner")
|
||||
bannerIcon: UM.Theme.getIcon("Shop")
|
||||
bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your Ultimaker Cura experience with plugins contributed by our amazing community of users.")
|
||||
bannerText: catalog.i18nc("@text", "Streamline your workflow and customize your UltiMaker Cura experience with plugins contributed by our amazing community of users.")
|
||||
bannerReadMoreUrl: "https://support.ultimaker.com/hc/en-us/articles/360011968360/?utm_source=cura&utm_medium=software&utm_campaign=marketplace-learn-plugins"
|
||||
onRemoveBanner: function() {
|
||||
UM.Preferences.setValue("cura/market_place_show_plugin_banner", false)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Ultimaker B.V.
|
||||
// Copyright (c) 2022 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.15
|
||||
@ -18,9 +18,9 @@ Control
|
||||
{
|
||||
switch(packageData.packageType)
|
||||
{
|
||||
case "plugin": return catalog.i18nc("@info", "Ultimaker Verified Plug-in");
|
||||
case "material": return catalog.i18nc("@info", "Ultimaker Certified Material");
|
||||
default: return catalog.i18nc("@info", "Ultimaker Verified Package");
|
||||
case "plugin": return catalog.i18nc("@info", "UltiMaker Verified Plug-in");
|
||||
case "material": return catalog.i18nc("@info", "UltiMaker Certified Material");
|
||||
default: return catalog.i18nc("@info", "UltiMaker Verified Package");
|
||||
}
|
||||
}
|
||||
visible: parent.hovered
|
||||
|
@ -19,5 +19,7 @@ Item
|
||||
width: UM.Theme.getSize("machine_selector_widget").width
|
||||
height: parent.height
|
||||
anchors.centerIn: parent
|
||||
|
||||
machineListModel: Cura.MachineListModel {}
|
||||
}
|
||||
}
|
@ -150,6 +150,7 @@ Item
|
||||
width: parent.width / 2 - UM.Theme.getSize("default_margin").width
|
||||
height: UM.Theme.getSize("setting_control").height
|
||||
textRole: "text"
|
||||
forceHighlight: base.hovered
|
||||
|
||||
model: ListModel
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ class PauseAtHeight(Script):
|
||||
"description": "Whether to pause at a certain height or at a certain layer.",
|
||||
"type": "enum",
|
||||
"options": {"height": "Height", "layer_no": "Layer Number"},
|
||||
"default_value": "height"
|
||||
"default_value": "layer_no"
|
||||
},
|
||||
"pause_height":
|
||||
{
|
||||
@ -58,16 +58,25 @@ class PauseAtHeight(Script):
|
||||
"default_value": "marlin",
|
||||
"value": "\\\"griffin\\\" if machine_gcode_flavor==\\\"Griffin\\\" else \\\"reprap\\\" if machine_gcode_flavor==\\\"RepRap (RepRap)\\\" else \\\"repetier\\\" if machine_gcode_flavor==\\\"Repetier\\\" else \\\"bq\\\" if \\\"BQ\\\" in machine_name or \\\"Flying Bear Ghost 4S\\\" in machine_name else \\\"marlin\\\""
|
||||
},
|
||||
"hold_steppers_on":
|
||||
{
|
||||
"label": "Keep motors engaged",
|
||||
"description": "Keep the steppers engaged to allow change of filament without moving the head. Applying too much force will move the head/bed anyway",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"enabled": "pause_method != \\\"griffin\\\""
|
||||
},
|
||||
"disarm_timeout":
|
||||
{
|
||||
"label": "Disarm timeout",
|
||||
"description": "After this time steppers are going to disarm (meaning that they can easily lose their positions). Set this to 0 if you don't want to set any duration.",
|
||||
"description": "After this time steppers are going to disarm (meaning that they can easily lose their positions). Set this to 0 if you don't want to set any duration and disarm immediately.",
|
||||
"type": "int",
|
||||
"value": "0",
|
||||
"minimum_value": "0",
|
||||
"minimum_value_warning": "0",
|
||||
"maximum_value_warning": "1800",
|
||||
"unit": "s"
|
||||
"unit": "s",
|
||||
"enabled": "not hold_steppers_on"
|
||||
},
|
||||
"head_park_enabled":
|
||||
{
|
||||
@ -192,6 +201,22 @@ class PauseAtHeight(Script):
|
||||
"default_value": "RepRap (Marlin/Sprinter)",
|
||||
"enabled": false
|
||||
},
|
||||
"beep_at_pause":
|
||||
{
|
||||
"label": "Beep at pause",
|
||||
"description": "Make a beep when pausing",
|
||||
"type": "bool",
|
||||
"default_value": true
|
||||
},
|
||||
"beep_length":
|
||||
{
|
||||
"label": "Beep length",
|
||||
"description": "How much should the beep last",
|
||||
"type": "int",
|
||||
"default_value": "1000",
|
||||
"unit": "ms",
|
||||
"enabled": "beep_at_pause"
|
||||
},
|
||||
"custom_gcode_before_pause":
|
||||
{
|
||||
"label": "G-code Before Pause",
|
||||
@ -242,6 +267,7 @@ class PauseAtHeight(Script):
|
||||
pause_at = self.getSettingValueByKey("pause_at")
|
||||
pause_height = self.getSettingValueByKey("pause_height")
|
||||
pause_layer = self.getSettingValueByKey("pause_layer")
|
||||
hold_steppers_on = self.getSettingValueByKey("hold_steppers_on")
|
||||
disarm_timeout = self.getSettingValueByKey("disarm_timeout")
|
||||
retraction_amount = self.getSettingValueByKey("retraction_amount")
|
||||
retraction_speed = self.getSettingValueByKey("retraction_speed")
|
||||
@ -260,6 +286,8 @@ class PauseAtHeight(Script):
|
||||
display_text = self.getSettingValueByKey("display_text")
|
||||
gcode_before = self.getSettingValueByKey("custom_gcode_before_pause")
|
||||
gcode_after = self.getSettingValueByKey("custom_gcode_after_pause")
|
||||
beep_at_pause = self.getSettingValueByKey("beep_at_pause")
|
||||
beep_length = self.getSettingValueByKey("beep_length")
|
||||
|
||||
pause_method = self.getSettingValueByKey("pause_method")
|
||||
pause_command = {
|
||||
@ -437,19 +465,26 @@ class PauseAtHeight(Script):
|
||||
prepend_gcode += "M117 " + display_text + "\n"
|
||||
|
||||
# Set the disarm timeout
|
||||
if disarm_timeout > 0:
|
||||
prepend_gcode += self.putValue(M = 18, S = disarm_timeout) + " ; Set the disarm timeout\n"
|
||||
if hold_steppers_on:
|
||||
prepend_gcode += self.putValue(M = 84, S = 3600) + " ; Keep steppers engaged for 1h\n"
|
||||
elif disarm_timeout > 0:
|
||||
prepend_gcode += self.putValue(M = 84, S = disarm_timeout) + " ; Set the disarm timeout\n"
|
||||
|
||||
# Beep at pause
|
||||
if beep_at_pause:
|
||||
prepend_gcode += self.putValue(M = 300, S = 440, P = beep_length) + " ; Beep\n"
|
||||
|
||||
|
||||
# Set a custom GCODE section before pause
|
||||
if gcode_before:
|
||||
prepend_gcode += gcode_before + "\n"
|
||||
prepend_gcode += gcode_before.replace(";","\n") + "\n"
|
||||
|
||||
# Wait till the user continues printing
|
||||
prepend_gcode += pause_command + " ; Do the actual pause\n"
|
||||
|
||||
# Set a custom GCODE section before pause
|
||||
# Set a custom GCODE section after pause
|
||||
if gcode_after:
|
||||
prepend_gcode += gcode_after + "\n"
|
||||
prepend_gcode += gcode_after.replace(";","\n") + "\n"
|
||||
|
||||
if pause_method == "repetier":
|
||||
#Push the filament back,
|
||||
|
@ -55,6 +55,50 @@ Item
|
||||
Layout.preferredWidth: parent.machineSelectorWidth
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
machineManager: Cura.MachineManager
|
||||
onSelectPrinter: function(machine)
|
||||
{
|
||||
toggleContent();
|
||||
Cura.MachineManager.setActiveMachine(machine.id);
|
||||
}
|
||||
|
||||
machineListModel: Cura.MachineListModel {}
|
||||
|
||||
buttons: [
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
id: addPrinterButton
|
||||
leftPadding: UM.Theme.getSize("default_margin").width
|
||||
rightPadding: UM.Theme.getSize("default_margin").width
|
||||
text: catalog.i18nc("@button", "Add printer")
|
||||
// The maximum width of the button is half of the total space, minus the padding of the parent, the left
|
||||
// padding of the component and half the spacing because of the space between buttons.
|
||||
fixedWidthMode: true
|
||||
width: Math.round(parent.width / 2 - leftPadding * 1.5)
|
||||
onClicked:
|
||||
{
|
||||
machineSelection.toggleContent()
|
||||
Cura.Actions.addMachine.trigger()
|
||||
}
|
||||
},
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
id: managePrinterButton
|
||||
leftPadding: UM.Theme.getSize("default_margin").width
|
||||
rightPadding: UM.Theme.getSize("default_margin").width
|
||||
text: catalog.i18nc("@button", "Manage printers")
|
||||
fixedWidthMode: true
|
||||
// The maximum width of the button is half of the total space, minus the padding of the parent, the right
|
||||
// padding of the component and half the spacing because of the space between buttons.
|
||||
width: Math.round(parent.width / 2 - rightPadding * 1.5)
|
||||
onClicked:
|
||||
{
|
||||
machineSelection.toggleContent()
|
||||
Cura.Actions.configureMachines.trigger()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Cura.ConfigurationMenu
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Copyright (c) 2022 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
@ -70,7 +70,7 @@ Window
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
text: catalog.i18nc("@text:window", "Ultimaker Cura collects anonymous data in order to improve the print quality and user experience. Below is an example of all the data that is shared:")
|
||||
text: catalog.i18nc("@text:window", "UltiMaker Cura collects anonymous data in order to improve the print quality and user experience. Below is an example of all the data that is shared:")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ class UFPReader(MeshReader):
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-ufp",
|
||||
comment = "Ultimaker Format Package",
|
||||
comment = "UltiMaker Format Package",
|
||||
suffixes = ["ufp"]
|
||||
)
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
#Copyright (c) 2019 Ultimaker B.V.
|
||||
#Cura is released under the terms of the LGPLv3 or higher.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import sys
|
||||
|
||||
@ -19,7 +19,7 @@ def getMetaData():
|
||||
{
|
||||
"mime_type": "application/x-ufp",
|
||||
"extension": "ufp",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Ultimaker Format Package")
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "UltiMaker Format Package")
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import json
|
||||
from dataclasses import asdict
|
||||
from typing import cast, List, Dict
|
||||
|
||||
from Charon.VirtualFile import VirtualFile # To open UFP files.
|
||||
@ -10,19 +11,25 @@ from io import StringIO # For converting g-code to bytes.
|
||||
|
||||
from PyQt6.QtCore import QBuffer
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Mesh.MeshWriter import MeshWriter # The writer we need to implement.
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
||||
from UM.PluginRegistry import PluginRegistry # To get the g-code writer.
|
||||
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Settings.CuraStackBuilder import CuraStackBuilder
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
METADATA_OBJECTS_PATH = "metadata/objects"
|
||||
SLICE_METADATA_PATH = "Cura/slicemetadata.json"
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
@ -34,7 +41,7 @@ class UFPWriter(MeshWriter):
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-ufp",
|
||||
comment = "Ultimaker Format Package",
|
||||
comment = "UltiMaker Format Package",
|
||||
suffixes = ["ufp"]
|
||||
)
|
||||
)
|
||||
@ -67,7 +74,21 @@ class UFPWriter(MeshWriter):
|
||||
try:
|
||||
gcode = archive.getStream("/3D/model.gcode")
|
||||
gcode.write(gcode_textio.getvalue().encode("UTF-8"))
|
||||
archive.addRelation(virtual_path = "/3D/model.gcode", relation_type = "http://schemas.ultimaker.org/package/2018/relationships/gcode")
|
||||
archive.addRelation(virtual_path = "/3D/model.gcode",
|
||||
relation_type = "http://schemas.ultimaker.org/package/2018/relationships/gcode")
|
||||
except EnvironmentError as e:
|
||||
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
||||
self.setInformation(error_msg)
|
||||
Logger.error(error_msg)
|
||||
return False
|
||||
|
||||
# Write settings
|
||||
try:
|
||||
archive.addContentType(extension="json", mime_type="application/json")
|
||||
setting_textio = StringIO()
|
||||
json.dump(self._getSliceMetadata(), setting_textio, separators=(", ", ": "), indent=4)
|
||||
steam = archive.getStream(SLICE_METADATA_PATH)
|
||||
steam.write(setting_textio.getvalue().encode("UTF-8"))
|
||||
except EnvironmentError as e:
|
||||
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
||||
self.setInformation(error_msg)
|
||||
@ -190,3 +211,61 @@ class UFPWriter(MeshWriter):
|
||||
return [{"name": item.getName()}
|
||||
for item in DepthFirstIterator(node)
|
||||
if item.getMeshData() is not None and not item.callDecoration("isNonPrintingMesh")]
|
||||
|
||||
def _getSliceMetadata(self) -> Dict[str, Dict[str, Dict[str, str]]]:
|
||||
"""Get all changed settings and all settings. For each extruder and the global stack"""
|
||||
print_information = CuraApplication.getInstance().getPrintInformation()
|
||||
machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||
settings = {
|
||||
"material": {
|
||||
"length": print_information.materialLengths,
|
||||
"weight": print_information.materialWeights,
|
||||
"cost": print_information.materialCosts,
|
||||
},
|
||||
"global": {
|
||||
"changes": {},
|
||||
"all_settings": {},
|
||||
},
|
||||
"quality": asdict(machine_manager.activeQualityDisplayNameMap()),
|
||||
}
|
||||
|
||||
global_stack = cast(GlobalStack, Application.getInstance().getGlobalContainerStack())
|
||||
|
||||
# Add global user or quality changes
|
||||
global_flattened_changes = InstanceContainer.createMergedInstanceContainer(global_stack.userChanges, global_stack.qualityChanges)
|
||||
for setting in global_flattened_changes.getAllKeys():
|
||||
value = global_flattened_changes.getProperty(setting, "value")
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(global_flattened_changes)
|
||||
settings["global"]["changes"][setting] = value
|
||||
|
||||
# Get global all settings values without user or quality changes
|
||||
for setting in global_stack.getAllKeys():
|
||||
value = global_stack.getProperty(setting, "value")
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(global_stack)
|
||||
settings["global"]["all_settings"][setting] = value
|
||||
|
||||
for i, extruder in enumerate(global_stack.extruderList):
|
||||
# Add extruder fields to settings dictionary
|
||||
settings[f"extruder_{i}"] = {
|
||||
"changes": {},
|
||||
"all_settings": {},
|
||||
}
|
||||
|
||||
# Add extruder user or quality changes
|
||||
extruder_flattened_changes = InstanceContainer.createMergedInstanceContainer(extruder.userChanges, extruder.qualityChanges)
|
||||
for setting in extruder_flattened_changes.getAllKeys():
|
||||
value = extruder_flattened_changes.getProperty(setting, "value")
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(extruder_flattened_changes)
|
||||
settings[f"extruder_{i}"]["changes"][setting] = value
|
||||
|
||||
# Get extruder all settings values without user or quality changes
|
||||
for setting in extruder.getAllKeys():
|
||||
value = extruder.getProperty(setting, "value")
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(extruder)
|
||||
settings[f"extruder_{i}"]["all_settings"][setting] = value
|
||||
|
||||
return settings
|
||||
|
@ -1,5 +1,5 @@
|
||||
#Copyright (c) 2018 Ultimaker B.V.
|
||||
#Cura is released under the terms of the LGPLv3 or higher.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import sys
|
||||
|
||||
@ -25,7 +25,7 @@ def getMetaData():
|
||||
"mime_type": "application/x-ufp",
|
||||
"mode": MeshWriter.OutputMode.BinaryMode,
|
||||
"extension": "ufp",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Ultimaker Format Package")
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "UltiMaker Format Package")
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -172,7 +172,14 @@ Item
|
||||
{
|
||||
id: printerConfiguration
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
buildplate: catalog.i18nc("@label", "Glass")
|
||||
buildplate: {
|
||||
switch (printJob.assignedPrinter.buildplate) {
|
||||
case "glass":
|
||||
return catalog.i18nc("@label", "Glass");
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
configurations: base.printJob ? base.printJob.configuration.extruderConfigurations : null
|
||||
height: Math.round(72 * screenScaleFactor) // TODO: Theme!
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Copyright (c) 2022 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.3
|
||||
@ -175,7 +175,14 @@ Item
|
||||
{
|
||||
id: printerConfiguration
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
buildplate: printer ? catalog.i18nc("@label", "Glass") : null // 'Glass' as a default
|
||||
buildplate: {
|
||||
switch (printer.buildplate) {
|
||||
case "glass":
|
||||
return catalog.i18nc("@label", "Glass");
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
configurations:
|
||||
{
|
||||
var configs = []
|
||||
@ -277,7 +284,7 @@ Item
|
||||
MonitorInfoBlurb
|
||||
{
|
||||
id: cameraDisabledInfo
|
||||
text: catalog.i18nc("@info", "Webcam feeds for cloud printers cannot be viewed from Ultimaker Cura." +
|
||||
text: catalog.i18nc("@info", "Webcam feeds for cloud printers cannot be viewed from UltiMaker Cura." +
|
||||
" Click \"Manage printer\" to visit Ultimaker Digital Factory and view this webcam.")
|
||||
target: cameraButton
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// Copyright (c) 2022 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.15
|
||||
@ -127,7 +127,7 @@ Component
|
||||
id: sendToFactoryButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: catalog.i18nc("@button", "View printers in Digital Factory")
|
||||
onClicked: Qt.openUrlExternally("https://digitalfactory.ultimaker.com/app/print-jobs?utm_source=cura&utm_medium=software&utm_campaign=monitor-view-cloud-printer-type")
|
||||
onClicked: Qt.openUrlExternally("https://digitalfactory.ultimaker.com/app/welcome?utm_source=cura&utm_medium=software&utm_campaign=monitor-view-cloud-printer-type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,19 @@
|
||||
from time import time
|
||||
from typing import List
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
from PyQt6.QtCore import QObject
|
||||
from PyQt6.QtCore import QObject, pyqtSlot
|
||||
from PyQt6.QtNetwork import QNetworkReply
|
||||
|
||||
from UM import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
from UM.FileHandler.FileHandler import FileHandler
|
||||
from UM.Resources import Resources
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState
|
||||
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
|
||||
from .CloudApiClient import CloudApiClient
|
||||
from ..Models.Http.CloudClusterResponse import CloudClusterResponse
|
||||
from ..Models.Http.CloudClusterWithConfigResponse import CloudClusterWithConfigResponse
|
||||
from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice
|
||||
|
||||
@ -19,7 +23,7 @@ I18N_CATALOG = i18nCatalog("cura")
|
||||
class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
||||
API_CHECK_INTERVAL = 10.0 # seconds
|
||||
|
||||
def __init__(self, api_client: CloudApiClient, printer_type: str, parent: QObject = None) -> None:
|
||||
def __init__(self, api_client: CloudApiClient, printer_type: str, request_write_callback: Callable, refresh_callback: Callable, parent: QObject = None) -> None:
|
||||
|
||||
self._api = api_client
|
||||
properties = {b"printer_type": printer_type.encode()}
|
||||
@ -31,6 +35,11 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self._on_print_dialog: Optional[QObject] = None
|
||||
self._nodes: List[SceneNode] = None
|
||||
self._request_write_callback = request_write_callback
|
||||
self._refresh_callback = refresh_callback
|
||||
|
||||
self._setInterfaceElements()
|
||||
|
||||
def connect(self) -> None:
|
||||
@ -41,7 +50,6 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
||||
Logger.log("i", "Attempting to connect AbstractCloudOutputDevice %s", self.key)
|
||||
super().connect()
|
||||
|
||||
#CuraApplication.getInstance().getBackend().backendStateChange.connect(self._onBackendStateChange)
|
||||
self._update()
|
||||
|
||||
def disconnect(self) -> None:
|
||||
@ -84,4 +92,31 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
||||
self._updatePrinters(all_configurations)
|
||||
|
||||
def _onError(self, reply: QNetworkReply, error: QNetworkReply.NetworkError) -> None:
|
||||
pass
|
||||
Logger.log("w", f"Failed to get clusters by machine type: {str(error)}.")
|
||||
|
||||
@pyqtSlot(str)
|
||||
def printerSelected(self, unique_id: str):
|
||||
self._request_write_callback(unique_id, self._nodes)
|
||||
if self._on_print_dialog:
|
||||
self._on_print_dialog.close()
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh(self):
|
||||
self._refresh_callback()
|
||||
self._update()
|
||||
|
||||
def _openChoosePrinterDialog(self) -> None:
|
||||
if self._on_print_dialog is None:
|
||||
qml_path = Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Dialogs", "ChoosePrinterDialog.qml")
|
||||
self._on_print_dialog = CuraApplication.getInstance().createQmlComponent(qml_path, {})
|
||||
if self._on_print_dialog is None: # Failed to load QML file.
|
||||
return
|
||||
self._on_print_dialog.setProperty("manager", self)
|
||||
self._on_print_dialog.show()
|
||||
|
||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs) -> None:
|
||||
if not nodes or len(nodes) < 1:
|
||||
Logger.log("w", "Nothing to print.")
|
||||
return
|
||||
self._nodes = nodes
|
||||
self._openChoosePrinterDialog()
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from time import time
|
||||
import os
|
||||
@ -10,7 +10,6 @@ from PyQt6.QtGui import QDesktopServices
|
||||
from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest # Parse errors specific to print job uploading.
|
||||
|
||||
from UM import i18nCatalog
|
||||
from UM.Backend.Backend import BackendState
|
||||
from UM.FileHandler.FileHandler import FileHandler
|
||||
from UM.Logger import Logger
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
@ -18,6 +17,8 @@ from UM.Version import Version
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState
|
||||
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
|
||||
from cura.Scene.GCodeListDecorator import GCodeListDecorator
|
||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||
|
||||
from .CloudApiClient import CloudApiClient
|
||||
from ..ExportFileJob import ExportFileJob
|
||||
@ -110,6 +111,9 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
||||
self._pre_upload_print_job = None # type: Optional[CloudPrintJobResponse]
|
||||
self._uploaded_print_job = None # type: Optional[CloudPrintJobResponse]
|
||||
|
||||
CuraApplication.getInstance().getBackend().backendDone.connect(self._resetPrintJob)
|
||||
CuraApplication.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged)
|
||||
|
||||
def connect(self) -> None:
|
||||
"""Connects this device."""
|
||||
|
||||
@ -117,8 +121,6 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
||||
return
|
||||
Logger.log("i", "Attempting to connect to cluster %s", self.key)
|
||||
super().connect()
|
||||
|
||||
CuraApplication.getInstance().getBackend().backendStateChange.connect(self._onBackendStateChange)
|
||||
self._update()
|
||||
|
||||
def disconnect(self) -> None:
|
||||
@ -128,11 +130,14 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
||||
return
|
||||
super().disconnect()
|
||||
Logger.log("i", "Disconnected from cluster %s", self.key)
|
||||
CuraApplication.getInstance().getBackend().backendStateChange.disconnect(self._onBackendStateChange)
|
||||
|
||||
def _onBackendStateChange(self, _: BackendState) -> None:
|
||||
"""Resets the print job that was uploaded to force a new upload, runs whenever the user re-slices."""
|
||||
def _onSceneChanged(self, node: SceneNode):
|
||||
# This will reset the print job if a ufp file is loaded. This forces a new upload when printing via cloud from ufp.
|
||||
if node.getDecorator(GCodeListDecorator) or node.getDecorator(SliceableObjectDecorator):
|
||||
self._resetPrintJob()
|
||||
|
||||
def _resetPrintJob(self) -> None:
|
||||
"""Resets the print job that was uploaded to force a new upload, runs whenever slice finishes."""
|
||||
self._tool_path = None
|
||||
self._pre_upload_print_job = None
|
||||
self._uploaded_print_job = None
|
||||
@ -406,3 +411,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
||||
|
||||
root_url_prefix = "-staging" if self._account.is_staging else ""
|
||||
return f"https://digitalfactory{root_url_prefix}.ultimaker.com/app/jobs/{self.clusterData.cluster_id}"
|
||||
|
||||
def __del__(self):
|
||||
CuraApplication.getInstance().getBackend().backendDone.disconnect(self._resetPrintJob)
|
||||
CuraApplication.getInstance().getController().getScene().sceneChanged.disconnect(self._onSceneChanged)
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from typing import Dict, List, Optional, Set
|
||||
@ -172,6 +172,13 @@ class CloudOutputDeviceManager:
|
||||
self._syncing = False
|
||||
self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR)
|
||||
|
||||
def _requestWrite(self, unique_id: str, nodes: List["SceneNode"]):
|
||||
for remote in self._remote_clusters.values():
|
||||
if unique_id == remote.name: # No other id-type would match. Assume cloud doesn't have duplicate names.
|
||||
remote.requestWrite(nodes)
|
||||
return
|
||||
Logger.log("e", f"Failed writing to specific cloud printer: {unique_id} not in remote clusters.")
|
||||
|
||||
def _createMachineStacksForDiscoveredClusters(self, discovered_clusters: List[CloudClusterResponse]) -> None:
|
||||
"""**Synchronously** create machines for discovered devices
|
||||
|
||||
@ -193,7 +200,7 @@ class CloudOutputDeviceManager:
|
||||
output_device = CloudOutputDevice(self._api, cluster_data)
|
||||
|
||||
if cluster_data.printer_type not in self._abstract_clusters:
|
||||
self._abstract_clusters[cluster_data.printer_type] = AbstractCloudOutputDevice(self._api, cluster_data.printer_type)
|
||||
self._abstract_clusters[cluster_data.printer_type] = AbstractCloudOutputDevice(self._api, cluster_data.printer_type, self._requestWrite, self.refreshConnections)
|
||||
# Ensure that the abstract machine is added (either because it was never added, or it somehow got
|
||||
# removed)
|
||||
_abstract_machine = CuraStackBuilder.createAbstractMachine(cluster_data.printer_type)
|
||||
|
@ -1,5 +1,6 @@
|
||||
# Copyright (c) 2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM import i18nCatalog
|
||||
from UM.Message import Message
|
||||
from cura.CuraApplication import CuraApplication
|
||||
@ -31,7 +32,7 @@ class RemovedPrintersMessage(Message):
|
||||
super().__init__(title=self.i18n_catalog.i18ncp("info:status",
|
||||
"A cloud connection is not available for a printer",
|
||||
"A cloud connection is not available for some printers",
|
||||
len(self.removed_devices)),
|
||||
len(self._removed_devices)),
|
||||
message_type=Message.MessageType.WARNING,
|
||||
text = message_text)
|
||||
|
||||
|
@ -429,7 +429,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
print_job.updateTimeElapsed(elapsed_time)
|
||||
estimated_time = self._print_estimated_time
|
||||
if progress > .1:
|
||||
estimated_time = self._print_estimated_time * (1 - progress) + elapsed_time
|
||||
estimated_time = int(self._print_estimated_time * (1 - progress) + elapsed_time)
|
||||
print_job.updateTimeTotal(estimated_time)
|
||||
|
||||
self._gcode_position += 1
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Copyright (c) 2022 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
@ -27,7 +27,7 @@ Cura.MachineAction
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Please select any upgrades made to this Ultimaker Original")
|
||||
text: catalog.i18nc("@label","Please select any upgrades made to this UltiMaker Original")
|
||||
font: UM.Theme.getFont("medium")
|
||||
}
|
||||
|
||||
|
@ -1125,21 +1125,28 @@ class XmlMaterialProfile(InstanceContainer):
|
||||
id_list = list(id_list)
|
||||
return id_list
|
||||
|
||||
__product_to_id_map: Optional[Dict[str, List[str]]] = None
|
||||
|
||||
@classmethod
|
||||
def getProductIdMap(cls) -> Dict[str, List[str]]:
|
||||
"""Gets a mapping from product names in the XML files to their definition IDs.
|
||||
|
||||
This loads the mapping from a file.
|
||||
"""
|
||||
if cls.__product_to_id_map is not None:
|
||||
return cls.__product_to_id_map
|
||||
|
||||
plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath("XmlMaterialProfile"))
|
||||
product_to_id_file = os.path.join(plugin_path, "product_to_id.json")
|
||||
with open(product_to_id_file, encoding = "utf-8") as f:
|
||||
product_to_id_map = json.load(f)
|
||||
product_to_id_map = {key: [value] for key, value in product_to_id_map.items()}
|
||||
contents = ""
|
||||
for line in f:
|
||||
contents += line if "#" not in line else "".join([line.replace("#", str(n)) for n in range(1, 12)])
|
||||
cls.__product_to_id_map = json.loads(contents)
|
||||
cls.__product_to_id_map = {key: [value] for key, value in cls.__product_to_id_map.items()}
|
||||
#This also loads "Ultimaker S5" -> "ultimaker_s5" even though that is not strictly necessary with the default to change spaces into underscores.
|
||||
#However it is not always loaded with that default; this mapping is also used in serialize() without that default.
|
||||
return product_to_id_map
|
||||
return cls.__product_to_id_map
|
||||
|
||||
@staticmethod
|
||||
def _parseCompatibleValue(value: str):
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user