Merge branch 'Ultimaker:main' into main

This commit is contained in:
Mehmet Sutaş 2022-11-29 23:54:47 +03:00 committed by GitHub
commit c04b96d721
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
615 changed files with 194860 additions and 220568 deletions

View File

@ -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:

View File

@ -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 }}
@ -84,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
@ -94,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' }}
@ -106,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') }}
@ -115,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
@ -126,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

View File

@ -81,7 +81,9 @@ jobs:
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'

View File

@ -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,12 +97,13 @@ 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 = "_"
@ -108,13 +113,13 @@ jobs:
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}"
@ -165,38 +170,30 @@ jobs:
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}"
else:
# FIXME: for external PR's
actual_version = f"5.3.0-alpha+{buildmetadata}pr_{issue_number}"
if is_tag and "${{ github.ref_name }}" == "5.2.0-beta":
actual_version = "5.2.0-beta"
is_release_branch = True
user = "_"
channel = "_"
# %% 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()
# %% 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)
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}")
print(f"version = {actual_version}")
print(f"user = {user}")
print(f"channel = {channel}")
print(f"= {project_name}/{actual_version}@{user}/{channel}")
print(f"recipe_id_full = {project_name}/{actual_version}@{user}/{channel}")
print(f"recipe_id_latest = {project_name}/latest@{user}/{channel}")
print(f"semver_full = {actual_version}")
print(f"is_release_branch = {str(is_release_branch).lower()}")

View 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

View File

@ -1,42 +1,50 @@
name: Cura Installer
run-name: ${{ inputs.cura_conan_version }} by @${{ github.actor }}
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'
required: true
default: true
required: true
type: boolean
# Run the nightly at 3:25 UTC on working days
#FIXME: Provide the same default values as the workflow dispatch
schedule:
- cron: '25 3 * * 1-5'
env:
CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }}
CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }}
@ -59,16 +67,7 @@ env:
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
@ -127,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
@ -207,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

View File

@ -19,6 +19,7 @@ on:
- 'resources/intent/ultimaker**'
- 'resources/quality/ultimaker**'
- 'resources/variants/ultimaker**'
permissions: {}
jobs:
slackNotification:
name: Slack Notification

View 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"

View 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

View 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 }}

View File

@ -0,0 +1 @@
pyyaml

View File

@ -107,17 +107,14 @@ jobs:
- name: Install Linux system requirements
if: ${{ runner.os == 'Linux' }}
run: |
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
sudo apt update
sudo apt upgrade
sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config -y
- name: Install GCC-12 on ubuntu-22.04
if: ${{ startsWith(inputs.runs_on, 'ubuntu-22.04') }}
- name: Use GCC-10 on ubuntu-20.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
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

1
.gitignore vendored
View File

@ -99,3 +99,4 @@ conanbuildinfo.txt
graph_info.json
Ultimaker-Cura.spec
.run/
/printer-linter/src/printerlinter.egg-info/

15
.printer-linter Normal file
View 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

View File

@ -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

View File

@ -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 }}"

View File

@ -10,6 +10,221 @@
# requirements (use the <dep_name>/(latest)@ultimaker/testing)
#
# Subject to change in the future!
"5.3.0-alpha":
requirements:
- "pyarcus/(latest)@ultimaker/testing"
- "curaengine/(latest)@ultimaker/testing"
- "pysavitar/(latest)@ultimaker/testing"
- "pynest2d/(latest)@ultimaker/testing"
- "uranium/(latest)@ultimaker/testing"
- "fdm_materials/(latest)@ultimaker/testing"
- "cura_binary_data/(latest)@ultimaker/testing"
- "cpython/3.10.4"
internal_requirements:
- "fdm_materials_private/(latest)@ultimaker/testing"
- "cura_private_data/(latest)@ultimaker/testing"
runinfo:
entrypoint: "cura_app.py"
pyinstaller:
datas:
cura_plugins:
package: "cura"
src: "plugins"
dst: "share/cura/plugins"
cura_resources:
package: "cura"
src: "resources"
dst: "share/cura/resources"
cura_private_data:
package: "cura_private_data"
src: "resources"
dst: "share/cura/resources"
internal: true
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"

View File

@ -4,23 +4,24 @@ from pathlib import Path
from jinja2 import Template
from conan import ConanFile
from conan.tools.files import copy, rmdir, save
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.tools.scm import Version
from conan.errors import ConanInvalidConfiguration, ConanException
required_conan_version = ">=1.50.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
@ -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,
}
@ -225,13 +226,13 @@ class CuraConan(ConanFile):
# 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())
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,
@ -274,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"
@ -284,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")
@ -303,6 +317,16 @@ 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)
@ -357,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],
@ -418,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"

View File

@ -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

View File

@ -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"),

View File

@ -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)

View File

@ -5,7 +5,7 @@
# online cloud connected printers are represented within this ListModel. Additional information such as the number of
# connected printers for each printer type is gathered.
from typing import Optional
from typing import Optional, List, cast
from PyQt6.QtCore import Qt, QTimer, QObject, pyqtSlot, pyqtProperty, pyqtSignal
@ -14,7 +14,6 @@ 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
@ -29,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: Optional[QObject] = 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")
@ -45,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)
@ -79,17 +81,33 @@ 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()
from cura.CuraApplication import CuraApplication
machines_manager = CuraApplication.getInstance().getMachineManager()
other_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type="machine")
other_machine_stacks = self._getMachineStacks()
other_machine_stacks.sort(key = lambda machine: machine.getName().upper())
abstract_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(is_abstract_machine = "True")
abstract_machine_stacks.sort(key = lambda machine: machine.getName().upper())
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]
for abstract_machine in abstract_machine_stacks:
definition_id = abstract_machine.definition.getId()
online_machine_stacks = machines_manager.getMachinesWithDefinition(definition_id, online_only = True)
@ -113,18 +131,13 @@ class MachineListModel(ListModel):
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, False)
@ -134,11 +147,13 @@ class MachineListModel(ListModel):
return
self.appendItem({
"componentType": "MACHINE",
"name": container_stack.getName(),
"id": container_stack.getId(),
"metadata": container_stack.getMetaData().copy(),
"isOnline": is_online,
"isAbstractMachine": parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False)),
"machineCount": machine_count,
})
"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",
})

View File

@ -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.")

View File

@ -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()

View 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()

View File

@ -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")

View File

@ -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):

View File

@ -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):

View File

@ -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
@ -1631,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:
@ -1776,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:

View File

@ -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"),
})

View File

@ -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",

View File

@ -17,4 +17,4 @@ export OPENSSL_CONF="$scriptdir/openssl.cnf"
# unset `QT_STYLE_OVERRIDE` as a precaution
unset QT_STYLE_OVERRIDE
$scriptdir/Ultimaker-Cura "$@"
$scriptdir/UltiMaker-Cura "$@"

View File

@ -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)

View File

@ -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>

View File

@ -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

View File

@ -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 }}"
@ -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
@ -170,7 +170,7 @@ RmDir /r /REBOOTOK "$INSTDIR"
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

View File

@ -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

View File

@ -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)

View File

@ -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
@ -599,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)
@ -608,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()
@ -670,7 +700,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
temp_preferences = Preferences()
try:
serialized = archive.open("Cura/preferences.cfg").read().decode("utf-8")
except KeyError:
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>!",
@ -702,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)

View File

@ -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)

View File

@ -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

View File

@ -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()
}
}
}

View 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
}
}

View 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
}
}

View File

@ -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)

View File

@ -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")

View File

@ -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 = "",

View File

@ -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"]

View File

@ -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:
"""

View File

@ -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)

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -19,5 +19,7 @@ Item
width: UM.Theme.getSize("machine_selector_widget").width
height: parent.height
anchors.centerIn: parent
machineListModel: Cura.MachineListModel {}
}
}

View File

@ -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
{

View File

@ -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,

View File

@ -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

View File

@ -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
}

View File

@ -21,7 +21,7 @@ class UFPReader(MeshReader):
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-ufp",
comment = "Ultimaker Format Package",
comment = "UltiMaker Format Package",
suffixes = ["ufp"]
)
)

View File

@ -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")
}
]
}

View File

@ -1,6 +1,7 @@
# 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.
@ -40,7 +41,7 @@ class UFPWriter(MeshWriter):
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-ufp",
comment = "Ultimaker Format Package",
comment = "UltiMaker Format Package",
suffixes = ["ufp"]
)
)
@ -225,8 +226,7 @@ class UFPWriter(MeshWriter):
"changes": {},
"all_settings": {},
},
"intent": machine_manager.activeIntentCategory,
"quality": machine_manager.activeQualityOrQualityChangesName,
"quality": asdict(machine_manager.activeQualityDisplayNameMap()),
}
global_stack = cast(GlobalStack, Application.getInstance().getGlobalContainerStack())

View File

@ -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")
}
]
}

View File

@ -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
@ -284,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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")
}

View File

@ -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):

View File

@ -1,14 +1,11 @@
{
"Ultimaker 2": "ultimaker2",
"Ultimaker 2 Extended": "ultimaker2_extended",
"Ultimaker 2 Extended+": "ultimaker2_extended_plus",
"Ultimaker 2 Go": "ultimaker2_go",
"Ultimaker 2+": "ultimaker2_plus",
"Ultimaker 2+ Connect": "ultimaker2_plus_connect",
"Ultimaker 3": "ultimaker3",
"Ultimaker 3 Extended": "ultimaker3_extended",
"Ultimaker S3": "ultimaker_s3",
"Ultimaker S5": "ultimaker_s5",
"Ultimaker #": "ultimaker#",
"Ultimaker # Extended": "ultimaker#_extended",
"Ultimaker # Extended+": "ultimaker#_extended_plus",
"Ultimaker # Go": "ultimaker#_go",
"Ultimaker #+": "ultimaker#_plus",
"Ultimaker #+ Connect": "ultimaker#_plus_connect",
"Ultimaker S#": "ultimaker_s#",
"Ultimaker Original": "ultimaker_original",
"Ultimaker Original+": "ultimaker_original_plus",
"Ultimaker Original Dual Extrusion": "ultimaker_original_dual",

33
printer-linter/README.md Normal file
View File

@ -0,0 +1,33 @@
# Printer Linter
Printer linter is a python package that does linting on Cura definitions files.
Running this on your definition files will get them ready for a pull request.
## Running Locally
From the Cura root folder.
```python3 printer-linter/src/terminal.py "flashforge_dreamer_nx.def.json" "flashforge_base.def.json" --fix --format```
## Developing
### Printer Linter Rules
Inside ```.printer-linter``` you can find a list of rules. These are seperated into roughly three categories.
1. Checks
1. These rules are about checking if a file meets some requirements that can't be fixed by replacing its content.
2. An example of a check is ```diagnostic-mesh-file-extension``` this checks if a mesh file extension is acceptable.
2. Format
1. These rules are purely about how a file is structured, not content.
2. An example of a format rule is ```format-definition-bracket-newline``` This rule says that when assigning a dict value the bracket should go on a new line.
3. Fixes
1. These are about the content of the file.
2. An example of a fix is ```diagnostic-definition-redundant-override``` This removes settings that have already been defined by a parent definition
### Linters
Linters find issues within a file. There are separate linters for each type of file. The linter that is used is decided by the create function in factory.py. All linters implement the abstract class Linter.
A Linter class returns an iterator of Diagnostics, each diagnostic is an issue with the file. The diagnostics can also contain suggested fixes.
### Formatters
Formatters load a file reformat it and write it to disk. There are separate formatters for each file type. All formatters implement the abstract class Formatter.
Formatters should format based on the Format rules in .printer-linter

View File

@ -0,0 +1,17 @@
[project]
name = "printerlinter"
description = "Cura UltiMaker printer linting tool"
version = "0.1.0"
authors = [
{ name = "UltiMaker", email = "cura@ultimaker.com" }
]
dependencies = [
"pyyaml"
]
[project.scripts]
printer-linter = "terminal:main"
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

10
printer-linter/setup.cfg Normal file
View File

@ -0,0 +1,10 @@
[metadata]
name = printerlinter
[options]
package_dir=
=src
packages=find:
[options.packages.find]
where=src

6
printer-linter/setup.py Normal file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env python
from setuptools import setup
if __name__ == "__main__":
setup()

View File

@ -0,0 +1,4 @@
from .diagnostic import Diagnostic
from .factory import getLinter
__all__ = ["Diagnostic", "getLinter"]

View File

@ -0,0 +1,34 @@
from pathlib import Path
from typing import Optional, List, Dict, Any
from .replacement import Replacement
class Diagnostic:
def __init__(self, file: Path, diagnostic_name: str, message: str, level: str, offset: int, replacements: Optional[List[Replacement]] = None) -> None:
""" A diagnosis of an issue in "file" at "offset" in that file. May include suggested replacements.
@param file: The path to the file this diagnostic is for.
@param diagnostic_name: The name of the diagnostic rule that spawned this result. A list can be found in .printer-linter.
@param message: A message explaining the issue with this file.
@param level: How important this diagnostic is, ranges from Warning -> Error.
@param offset: The offset in file where the issue is.
@param replacements: A list of Replacement that contain replacement text.
"""
self.file = file
self.diagnostic_name = diagnostic_name
self.message = message
self.offset = offset
self.level = level
self.replacements = replacements
def toDict(self) -> Dict[str, Any]:
return {"DiagnosticName": self.diagnostic_name,
"DiagnosticMessage": {
"Message": self.message,
"FilePath": self.file.as_posix(),
"FileOffset": self.offset,
"Replacements": [] if self.replacements is None else [r.toDict() for r in self.replacements],
},
"Level": self.level
}

View File

@ -0,0 +1,26 @@
from pathlib import Path
from typing import Optional
from .linters.profile import Profile
from .linters.defintion import Definition
from .linters.linter import Linter
from .linters.meshes import Meshes
def getLinter(file: Path, settings: dict) -> Optional[Linter]:
""" Returns a Linter depending on the file format """
if not file.exists():
return None
if ".inst" in file.suffixes and ".cfg" in file.suffixes:
return Profile(file, settings)
if ".def" in file.suffixes and ".json" in file.suffixes:
if file.stem in ("fdmprinter.def", "fdmextruder.def"):
return None
return Definition(file, settings)
if file.parent.stem == "meshes":
return Meshes(file, settings)
return None

View File

@ -0,0 +1,4 @@
from .def_json_formatter import DefJsonFormatter
from .inst_cfg_formatter import InstCfgFormatter
__all__ = ["DefJsonFormatter", "InstCfgFormatter"]

View File

@ -0,0 +1,84 @@
import json
import re
from collections import OrderedDict
from pathlib import Path
from typing import Dict
from .formatter import FileFormatter
# Dictionary items with matching keys will be sorted as if they were the value
# Example: "version" will be sorted as if it was "0"
TOP_LEVEL_SORT_PRIORITY = {
"version": "0",
"name": "1",
"inherits": "3",
}
METADATA_SORT_PRIORITY = {
"visible": "0",
"author": "1",
"manufacturer": "2",
"file_formats": "3",
"platform": "4",
}
class DefJsonFormatter(FileFormatter):
def formatFile(self, file: Path):
""" Format .def.json files according to the rules in settings.
You can assume that you will be running regex on standard formatted json files, because we load the json first and then
dump it to a string. This means you only have to write regex that works on the output of json.dump()
"""
definition = json.loads(file.read_text(), object_pairs_hook=OrderedDict)
if self._settings["format"].get("format-definition-sort-keys", True):
definition = self.order_keys(definition)
content = json.dumps(definition, indent=self._settings["format"].get("format-definition-indent", 4))
if self._settings["format"].get("format-definition-bracket-newline", True):
newline = re.compile(r"(\B\s+)(\"[\w\"]+)(\:\s\{)")
content = newline.sub(r"\1\2:\1{", content)
if self._settings["format"].get("format-definition-single-value-single-line", True):
single_value_dict = re.compile(r"(:)(\s*\n?.*\{\s+)(\".*)(\d*\s*\})(.*\n,?)")
content = single_value_dict.sub(r"\1 { \3 }\5", content)
single_value_list = re.compile(r"(:)(\s*\n?.*\[\s+)(\".*)(\d*\s*\])(.*\n,?)")
content = single_value_list.sub(r"\1 [ \3 ]\5", content)
if self._settings["format"].get("format-definition-paired-coordinate-array", True):
paired_coordinates = re.compile(r"(\s*\[)\s*([-\d\.]+),\s*([-\d\.]+)[\s]*(\])")
content = paired_coordinates.sub(r"\1\2, \3\4", content)
file.write_text(content)
def order_keys(self, json_content: OrderedDict) -> OrderedDict:
""" Orders json keys lexicographically """
# First order all keys (Recursive) lexicographically
json_content_text = json.dumps(json_content, sort_keys=True)
json_content = json.loads(json_content_text, object_pairs_hook=OrderedDict)
# Do a custom ordered sort on the top level items in the json. This is so that keys like "version" appear at the top.
json_content = self.custom_sort_keys(json_content, TOP_LEVEL_SORT_PRIORITY)
# Do a custom ordered sort on collections that are one level deep into the json
if "metadata" in json_content.keys():
json_content["metadata"] = self.custom_sort_keys(json_content["metadata"], METADATA_SORT_PRIORITY)
return json_content
def custom_sort_keys(self, ordered_dictionary: OrderedDict, sort_priority: Dict[str, str]) -> OrderedDict:
""" Orders keys in dictionary lexicographically, except for keys with matching strings in sort_priority.
Keys in ordered_dictionary that match keys in sort_priority will sort based on the value in sort_priority.
@param ordered_dictionary: A dictionary that will have it's top level keys sorted
@param sort_priority: A mapping from string keys to alternative strings to be used instead when sorting.
@return: A dictionary sorted by it's top level keys
"""
return OrderedDict(sorted(ordered_dictionary.items(), key=lambda x: sort_priority[x[0]] if str(x[0]) in sort_priority.keys() else str(x[0])))

View File

@ -0,0 +1,16 @@
from abc import ABC, abstractmethod
from pathlib import Path
class FileFormatter(ABC):
def __init__(self, settings: dict) -> None:
""" Yields Diagnostics for file, these are issues with the file such as bad text format or too large file size.
@param file: A file to generate diagnostics for
@param settings: A list of settings containing rules for creating diagnostics
"""
self._settings = settings
@abstractmethod
def formatFile(self, file: Path) -> None:
pass

View File

@ -0,0 +1,21 @@
import configparser
import json
import re
from collections import OrderedDict
from pathlib import Path
from .formatter import FileFormatter
class InstCfgFormatter(FileFormatter):
def formatFile(self, file: Path):
""" Format .inst.cfg files according to the rules in settings """
config = configparser.ConfigParser()
config.read(file)
if self._settings["format"].get("format-profile-sort-keys", True):
for section in config._sections:
config._sections[section] = OrderedDict(sorted(config._sections[section].items(), key=lambda t: t[0]))
config._sections = OrderedDict(sorted(config._sections.items(), key=lambda t: t[0]))
with open(file, "w") as f:
config.write(f, space_around_delimiters=self._settings["format"].get("format-profile-space-around-delimiters", True))

View File

@ -0,0 +1,6 @@
from .profile import Profile
from .meshes import Meshes
from .linter import Linter
from .defintion import Definition
__all__ = ["Profile", "Meshes", "Linter", "Definition"]

View File

@ -0,0 +1,122 @@
import json
import re
from pathlib import Path
from typing import Iterator
from ..diagnostic import Diagnostic
from .linter import Linter
from ..replacement import Replacement
class Definition(Linter):
""" Finds issues in definition files, such as overriding default parameters """
def __init__(self, file: Path, settings: dict) -> None:
super().__init__(file, settings)
self._definitions = {}
self._loadDefinitionFiles(file)
self._content = self._file.read_text()
self._loadBasePrinterSettings()
@property
def base_def(self):
if "fdmextruder" in self._definitions:
return "fdmextruder"
return "fdmprinter"
def check(self) -> Iterator[Diagnostic]:
if self._settings["checks"].get("diagnostic-definition-redundant-override", False):
for check in self.checkRedefineOverride():
yield check
# Add other which will yield Diagnostic's
# TODO: A check to determine if the user set value is with the min and max value defined in the parent and doesn't trigger a warning
# TODO: A check if the key exist in the first place
# TODO: Check if the model platform exist
yield
def checkRedefineOverride(self) -> Iterator[Diagnostic]:
""" Checks if definition file overrides its parents settings with the same value. """
definition_name = list(self._definitions.keys())[0]
definition = self._definitions[definition_name]
if "overrides" in definition and definition_name not in ("fdmprinter", "fdmextruder"):
for key, value_dict in definition["overrides"].items():
is_redefined, value, parent = self._isDefinedInParent(key, value_dict, definition['inherits'])
if is_redefined:
redefined = re.compile(r'.*(\"' + key + r'\"[\s\S]*?\{)[\s\S]*?(\}[,\"]?)')
found = redefined.search(self._content)
yield Diagnostic(
file = self._file,
diagnostic_name = "diagnostic-definition-redundant-override",
message = f"Overriding {key} with the same value ({value}) as defined in parent definition: {definition['inherits']}",
level = "Warning",
offset = found.span(0)[0],
replacements = [Replacement(
file = self._file,
offset = found.span(1)[0],
length = found.span(2)[1] - found.span(1)[0],
replacement_text = "")]
)
def _loadDefinitionFiles(self, definition_file) -> None:
""" Loads definition file contents into self._definitions. Also load parent definition if it exists. """
definition_name = Path(definition_file.stem).stem
if not definition_file.exists() or definition_name in self._definitions:
return
# Load definition file into dictionary
self._definitions[definition_name] = json.loads(definition_file.read_text())
# Load parent definition if it exists
if "inherits" in self._definitions[definition_name]:
if self._definitions[definition_name]['inherits'] in ("fdmextruder", "fdmprinter"):
parent_file = definition_file.parent.parent.joinpath("definitions", f"{self._definitions[definition_name]['inherits']}.def.json")
else:
parent_file = definition_file.parent.joinpath(f"{self._definitions[definition_name]['inherits']}.def.json")
self._loadDefinitionFiles(parent_file)
def _isDefinedInParent(self, key, value_dict, inherits_from):
if "overrides" not in self._definitions[inherits_from]:
return self._isDefinedInParent(key, value_dict, self._definitions[inherits_from]["inherits"])
parent = self._definitions[inherits_from]["overrides"]
if key not in self._definitions[self.base_def]["overrides"]:
is_number = False
else:
is_number = self._definitions[self.base_def]["overrides"][key]["type"] in ("float", "int")
for value in value_dict.values():
if key in parent:
check_values = [cv for cv in [parent[key].get("default_value", None), parent[key].get("value", None)] if cv is not None]
for check_value in check_values:
if is_number:
try:
v = str(float(value))
except:
v = value
try:
cv = str(float(check_value))
except:
cv = check_value
else:
v = value
cv = check_value
if v == cv:
return True, value, parent
if "inherits" in parent:
return self._isDefinedInParent(key, value_dict, parent["inherits"])
return False, None, None
def _loadBasePrinterSettings(self):
""" TODO @Jelle please explain why this """
settings = {}
for k, v in self._definitions[self.base_def]["settings"].items():
self._getSetting(k, v, settings)
self._definitions[self.base_def] = {"overrides": settings}
def _getSetting(self, name, setting, settings) -> None:
if "children" in setting:
for childname, child in setting["children"].items():
self._getSetting(childname, child, settings)
settings |= {name: setting}

View File

@ -0,0 +1,20 @@
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Iterator
from ..diagnostic import Diagnostic
class Linter(ABC):
def __init__(self, file: Path, settings: dict) -> None:
""" Yields Diagnostics for file, these are issues with the file such as bad text format or too large file size.
@param file: A file to generate diagnostics for
@param settings: A list of settings containing rules for creating diagnostics
"""
self._settings = settings
self._file = file
@abstractmethod
def check(self) -> Iterator[Diagnostic]:
pass

View File

@ -0,0 +1,47 @@
from pathlib import Path
from typing import Iterator
from ..diagnostic import Diagnostic
from .linter import Linter
class Meshes(Linter):
def __init__(self, file: Path, settings: dict) -> None:
""" Finds issues in model files, such as incorrect file format or too large size """
super().__init__(file, settings)
self._max_file_size = self._settings.get("diagnostic-mesh-file-size", 1e6)
def check(self) -> Iterator[Diagnostic]:
if self._settings["checks"].get("diagnostic-mesh-file-extension", False):
for check in self.checkFileFormat():
yield check
if self._settings["checks"].get("diagnostic-mesh-file-size", False):
for check in self.checkFileSize():
yield check
yield
def checkFileFormat(self) -> Iterator[Diagnostic]:
""" Check if mesh is in supported format """
if self._file.suffix.lower() not in (".3mf", ".obj", ".stl"):
yield Diagnostic(
file = self._file,
diagnostic_name = "diagnostic-mesh-file-extension",
message = f"Extension {self._file.suffix} not supported, use 3mf, obj or stl",
level = "Error",
offset = 1
)
yield
def checkFileSize(self) -> Iterator[Diagnostic]:
""" Check if file is within size limits for Cura """
if self._file.stat().st_size > self._max_file_size:
yield Diagnostic(
file = self._file,
diagnostic_name = "diagnostic-mesh-file-size",
message = f"Mesh file with a size {self._file.stat().st_size} is bigger then allowed maximum of {self._max_file_size}",
level = "Error",
offset = 1
)
yield

View File

@ -0,0 +1,9 @@
from typing import Iterator
from ..diagnostic import Diagnostic
from .linter import Linter
class Profile(Linter):
def check(self) -> Iterator[Diagnostic]:
yield

View File

@ -0,0 +1,21 @@
from pathlib import Path
class Replacement:
def __init__(self, file: Path, offset: int, length: int, replacement_text: str):
""" Replacement text for file between offset and offset+length.
@param file: File to replace text in
@param offset: Offset in file to start text replace
@param length: Length of text that will be replaced. offset -> offset+length is the section of text to replace.
@param replacement_text: Text to insert of offset in file.
"""
self.file = file
self.offset = offset
self.length = length
self.replacement_text = replacement_text
def toDict(self) -> dict:
return {"FilePath": self.file.as_posix(),
"Offset": self.offset,
"Length": self.length,
"ReplacementText": self.replacement_text}

View File

@ -0,0 +1,117 @@
from argparse import ArgumentParser
from os import getcwd
from pathlib import Path
from typing import List
import yaml
from printerlinter import factory
from printerlinter.diagnostic import Diagnostic
from printerlinter.formatters.def_json_formatter import DefJsonFormatter
from printerlinter.formatters.inst_cfg_formatter import InstCfgFormatter
def main() -> None:
parser = ArgumentParser(
description="UltiMaker Cura printer linting, static analysis and formatting of Cura printer definitions and other resources")
parser.add_argument("--setting", required=False, type=Path, help="Path to the `.printer-linter` setting file")
parser.add_argument("--report", required=False, type=Path, help="Path where the diagnostic report should be stored")
parser.add_argument("--format", action="store_true", help="Format the files")
parser.add_argument("--diagnose", action="store_true", help="Diagnose the files")
parser.add_argument("--fix", action="store_true", help="Attempt to apply the suggested fixes on the files")
parser.add_argument("Files", metavar="F", type=Path, nargs="+", help="Files or directories to format")
args = parser.parse_args()
files = extractFilePaths(args.Files)
setting_path = args.setting
to_format = args.format
to_fix = args.fix
to_diagnose = args.diagnose
report = args.report
if not setting_path:
setting_path = Path(getcwd(), ".printer-linter")
if not setting_path.exists():
print(f"Can't find the settings: {setting_path}")
return
with open(setting_path, "r") as f:
settings = yaml.load(f, yaml.FullLoader)
full_body_check = {"Diagnostics": []}
if to_fix or to_diagnose:
for file in files:
diagnostics = diagnoseIssuesWithFile(file, settings)
full_body_check["Diagnostics"].extend([d.toDict() for d in diagnostics])
results = yaml.dump(full_body_check, default_flow_style=False, indent=4, width=240)
if report:
report.write_text(results)
else:
print(results)
if to_fix:
for file in files:
if f"{file.as_posix()}" in full_body_check:
applyFixesToFile(file, settings, full_body_check)
if to_format:
for file in files:
applyFormattingToFile(file, settings)
def diagnoseIssuesWithFile(file: Path, settings: dict) -> List[Diagnostic]:
""" For file, runs all diagnostic checks in settings and returns a list of diagnostics """
linter = factory.getLinter(file, settings)
if not linter:
return []
return list(filter(lambda d: d is not None, linter.check()))
def applyFixesToFile(file, settings, full_body_check) -> None:
if not file.exists():
return
ext = ".".join(file.name.split(".")[-2:])
if ext == "def.json":
issues = full_body_check[f"{file.as_posix()}"]
for issue in issues:
if issue["diagnostic"] == "diagnostic-definition-redundant-override" and settings["fixes"].get(
"diagnostic-definition-redundant-override", True):
pass
def applyFormattingToFile(file: Path, settings) -> None:
if not file.exists():
return
ext = ".".join(file.name.split(".")[-2:])
if ext == "def.json":
formatter = DefJsonFormatter(settings)
formatter.formatFile(file)
if ext == "inst.cfg":
formatter = InstCfgFormatter(settings)
formatter.formatFile(file)
def extractFilePaths(paths: List[Path]) -> List[Path]:
""" Takes list of files and directories, returns the files as well as all files within directories as a List """
file_paths = []
for path in paths:
if path.is_dir():
file_paths.extend(path.rglob("**/*"))
else:
file_paths.append(path)
return file_paths
if __name__ == "__main__":
main()

View File

@ -1,5 +1,6 @@
pytest
pyinstaller
pyinstaller-hooks-contrib
pyyaml
sip==6.5.1
jinja2

View File

@ -1 +1 @@
git+https://github.com/ultimaker/libcharon@master#egg=charon
git+https://github.com/ultimaker/libcharon@master/s-line#egg=charon

View File

@ -10,7 +10,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -27,7 +27,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -61,7 +61,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -78,7 +78,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -95,7 +95,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -112,7 +112,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -129,7 +129,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -146,7 +146,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -163,7 +163,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -180,7 +180,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -197,7 +197,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -214,7 +214,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -248,7 +248,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -265,7 +265,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -282,7 +282,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -316,7 +316,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -333,7 +333,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -350,7 +350,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -367,7 +367,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -384,7 +384,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -401,7 +401,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -418,7 +418,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -435,7 +435,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -452,7 +452,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -469,7 +469,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -486,7 +486,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -503,7 +503,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -520,7 +520,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -537,7 +537,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -554,7 +554,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -571,7 +571,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -588,7 +588,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -605,7 +605,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -622,7 +622,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -639,7 +639,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -656,7 +656,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -673,7 +673,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -690,7 +690,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -707,7 +707,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -724,7 +724,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -741,7 +741,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -758,7 +758,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -775,7 +775,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -792,7 +792,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -809,7 +809,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -826,7 +826,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -843,7 +843,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -860,7 +860,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -877,7 +877,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -894,7 +894,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -911,7 +911,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -928,7 +928,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -945,7 +945,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -962,7 +962,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -979,7 +979,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -997,7 +997,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -1031,7 +1031,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -1048,7 +1048,7 @@
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
@ -1609,7 +1609,7 @@
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
@ -1628,7 +1628,7 @@
"website": "https://ultimaker.com/products/materials/breakaway",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
@ -1647,7 +1647,7 @@
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
@ -1666,7 +1666,7 @@
"website": "https://ultimaker.com/products/materials/cpe",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
@ -1685,7 +1685,7 @@
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
@ -1704,7 +1704,7 @@
"website": "https://ultimaker.com/products/materials/pc",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
@ -1723,7 +1723,7 @@
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
@ -1742,7 +1742,7 @@
"website": "https://ultimaker.com/products/materials/pp",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
@ -1761,7 +1761,7 @@
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
@ -1780,7 +1780,7 @@
"website": "https://ultimaker.com/products/materials/tpu-95a",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
@ -1799,7 +1799,7 @@
"website": "https://ultimaker.com/products/materials/tough-pla",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",

View File

@ -29,7 +29,7 @@
"machine_center_is_zero": { "default_value": false },
"gantry_height": { "value": "0" },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": { "default_value": ";Profil Homepage: https://github.com/NilsRo/Cura_Anycubic_MegaS_Profile\n\n;Slicer Information - (Support for OctoPrint Slicer Estimator)\n;Slicer info:material_guid;{material_guid}\n;Slicer info:material_id;{material_id}\n;Slicer info:material_brand;{material_brand}\n;Slicer info:material_name;{material_name}\n;Slicer info:filament_cost;{filament_cost}\n;Slicer info:material_bed_temperature;{material_bed_temperature}\n;Slicer info:material_bed_temperature_layer_0;{material_bed_temperature_layer_0}\n;Slicer info:material_print_temperature;{material_print_temperature}\n;Slicer info:material_print_temperature_layer_0;{material_print_temperature_layer_0}\n;Slicer info:material_flow;{material_flow}\n;Slicer info:layer_height;{layer_height}\n;Slicer info:machine_nozzle_size;{machine_nozzle_size}\n;Slicer info:wall_thickness;{wall_thickness}\n;Slicer info:speed_print;{speed_print}\n;Slicer info:speed_topbottom;{speed_topbottom}\n;Slicer info:travel_speed;{travel_speed}\n;Slicer info:support;{support}\n;Slicer info:retraction_speed;{retraction_speed}\n;Slicer info:retraction_amount;{retraction_amount}\n;Slicer info:layer_height;{layer_height}\n;Slicer info:infill_pattern;{infill_pattern}\n;Slicer info:infill_sparse_density;{infill_sparse_density}\n;Slicer info:cool_fan_enabled;{cool_fan_enabled}\n;Slicer info:cool_fan_speed;{cool_fan_speed}\n;Slicer info:sliced_at;{day} {date} {time}\nG21 ; metric values \nG90 ; absolute positioning \nM82 ; set extruder to absolute mode \nM107 ; start with the fan off \nM140 S{material_bed_temperature_layer_0} ; Start heating the bed \nG4 S60 ; wait 1 minute \nM104 S{material_print_temperature_layer_0} ; start heating the hot end \nM190 S{material_bed_temperature_layer_0} ; wait for bed \nM109 S{material_print_temperature_layer_0} ; wait for hotend \nM300 S1000 P500 ; BEEP heating done \nG28 X0 Y10 Z0 ; move X/Y to min endstops \nM420 S1 ; Enable leveling \nM420 Z2.0 ; Set leveling fading height to 2 mm \nG0 Z0.15 ; lift nozzle a bit \nG92 E0 ; zero the extruded length \nG1 X50 E20 F500 ; Extrude 20mm of filament in a 5cm line. \nG92 E0 ; zero the extruded length again \nG1 E-2 F500 ; Retract a little \nG1 X50 F500 ; wipe away from the filament line\nG1 X100 F9000 ; Quickly wipe away from the filament line" },
"machine_start_gcode": { "default_value": ";Profil Homepage: https://github.com/NilsRo/Cura_Anycubic_MegaS_Profile\n\n;Slicer Information - (Support for OctoPrint Slicer Estimator)\n;Slicer info:material_guid;{material_guid}\n;Slicer info:material_id;{material_id}\n;Slicer info:material_brand;{material_brand}\n;Slicer info:material_name;{material_name}\n;Slicer info:filament_cost;{filament_cost}\n;Slicer info:material_bed_temperature;{material_bed_temperature}\n;Slicer info:material_bed_temperature_layer_0;{material_bed_temperature_layer_0}\n;Slicer info:material_print_temperature;{material_print_temperature}\n;Slicer info:material_print_temperature_layer_0;{material_print_temperature_layer_0}\n;Slicer info:material_flow;{material_flow}\n;Slicer info:layer_height;{layer_height}\n;Slicer info:machine_nozzle_size;{machine_nozzle_size}\n;Slicer info:wall_thickness;{wall_thickness}\n;Slicer info:speed_print;{speed_print}\n;Slicer info:speed_topbottom;{speed_topbottom}\n;Slicer info:travel_speed;{travel_speed}\n;Slicer info:support;{support}\n;Slicer info:retraction_speed;{retraction_speed}\n;Slicer info:retraction_amount;{retraction_amount}\n;Slicer info:layer_height;{layer_height}\n;Slicer info:infill_pattern;{infill_pattern}\n;Slicer info:infill_sparse_density;{infill_sparse_density}\n;Slicer info:cool_fan_enabled;{cool_fan_enabled}\n;Slicer info:cool_fan_speed;{cool_fan_speed}\n;Slicer info:sliced_at;{day} {date} {time}\nG21 ; metric values \nG90 ; absolute positioning \nM82 ; set extruder to absolute mode \nM900 K0 ; disable lin. adv. if not set in GCODE\nM107 ; start with the fan off \nM140 S{material_bed_temperature_layer_0} ; Start heating the bed \nG4 S60 ; wait 1 minute \nM104 S{material_print_temperature_layer_0} ; start heating the hot end \nM190 S{material_bed_temperature_layer_0} ; wait for bed \nM109 S{material_print_temperature_layer_0} ; wait for hotend \nM300 S1000 P500 ; BEEP heating done \nG28 X0 Y10 Z0 ; move X/Y to min endstops \nM420 S1 ; Enable leveling \nM420 Z2.0 ; Set leveling fading height to 2 mm \nG0 Z0.15 ; lift nozzle a bit \nG92 E0 ; zero the extruded length \nG1 X50 E20 F500 ; Extrude 20mm of filament in a 5cm line. \nG92 E0 ; zero the extruded length again \nG1 E-2 F500 ; Retract a little \nG1 X50 F500 ; wipe away from the filament line\nG1 X100 F9000 ; Quickly wipe away from the filament line" },
"machine_end_gcode": { "default_value": "M104 S0 ; Extruder off \nM140 S0 ; Heatbed off \nM107 ; Fan off \nG91 ; relative positioning \nG1 E-5 F300 ; retract a little \nG1 Z+10 E-5 ; X-20 Y-20 F{travel_xy_speed} ; lift print head \nG28 X0 Y0 ; homing \nG1 Y180 F2000 ; reset feedrate \nM84 ; disable stepper motors \nG90 ; absolute positioning \nM300 S440 P200 ; Make Print Completed Tones \nM300 S660 P250 ; beep \nM300 S880 P300 ; beep" },
"machine_max_acceleration_x": { "value": 3000 },

View File

@ -0,0 +1,27 @@
{
"version": 2,
"name": "UMO+ DXU",
"inherits": "dxu",
"overrides": {
"machine_disallowed_areas": {
"default_value": [
[[100, -102.5], [ 110, -102.5], [ 110, -62.5], [100, -62.5]]
]
},
"machine_width": { "default_value": 220 },
"machine_depth": { "default_value": 205 },
"machine_height": { "default_value": 200 },
"machine_nozzle_heat_up_speed": {
"default_value": 1.95
},
"machine_nozzle_cool_down_speed": {
"default_value": 0.8
},
"machine_start_gcode" : {
"value": "\"\" if machine_gcode_flavor == \"UltiGCode\" else \"; Script based on an original created by tjjfvi (https://github.com/tjjfvi)\\n; An up-to-date version of the tjjfvi's original script can be found\\n; here: https://csi.t6.fyi/\\n; Note - This script will only work in Cura V4.2 and above!\\n; --- Global Settings\\n; layer_height = {layer_height}\\n; smooth_spiralized_contours = {smooth_spiralized_contours}\\n; magic_mesh_surface_mode = {magic_mesh_surface_mode}\\n; machine_extruder_count = {machine_extruder_count}\\n; --- Single Extruder Settings\\n; speed_z_hop = {speed_z_hop}\\n; retraction_amount = {retraction_amount}\\n; retraction_hop = {retraction_hop}\\n; retraction_hop_enabled = {retraction_hop_enabled}\\n; retraction_enable = {retraction_enable}\\n; retraction_speed = {retraction_speed}\\n; retraction_retract_speed = {retraction_retract_speed}\\n; retraction_prime_speed = {retraction_prime_speed}\\n; speed_travel = {speed_travel}\\n\\nM355 S1 P25;turn on case light\\n\\n;material_bed_temperature={material_bed_temperature} material_print_temperature={material_print_temperature} material_print_temperature_layer_0={material_print_temperature_layer_0}\\nM190 S{material_bed_temperature_layer_0}\\nG21 ;metric values\\nG90 ;absolute positioning\\nM82 ;set extruder to absolute mode\\nM107 ;start with the fan off\\nM200 D0 T{initial_extruder_nr} ;reset filament diameter\\nG28 ;home all\\nT{initial_extruder_nr} ;switch to the first nozzle used for print\\nM104 T{initial_extruder_nr} S{material_standby_temperature, initial_extruder_nr}\\nG0 X25 Y20 F7200 ;change Y20 to Y0 ansonl\\nG0 Z20 F2400\\nM109 T{initial_extruder_nr} S{material_print_temperature_layer_0, initial_extruder_nr}\\nG0 X210 Y0 F7200\\nG92 E-12.0 ; increase purge 6.5 to 12\\nG1 E0 F200 ;purge nozzle ;change F45 to F200 like ultimaker code ansonl\\nG1 E-6.5 F1500\\nG1 E0 F1500\\nG1 Y50 F9000 ;add quick movement to Y50 like ultimaker code ansonl\\nM400 ;finish all moves\\nT{initial_extruder_nr}\\n;end of startup sequence\\n\\nM355 S1 P50;turn on case light\""
},
"machine_end_gcode" : {
"value": "\"\" if machine_gcode_flavor == \"UltiGCode\" else \";end code from UM3\\nG91 ;Relative movement\\nG0 F15000 X8.0 Y8.0 Z3.5 E-4.5 ;Wiping+material retraction ;increase bed lower 0.5>5.0 and add Y movement\\nG0 F10000 Z1.5 E4.5 ;Compensation for the retraction\\nG90 ;Disable relative movement\\n\\nG90 ;absolute positioning\\nM104 S0 T0 ;extruder heater off\\nM104 S0 T1\\nM140 S0 ;turn off bed\\nT0 ; move to the first head\\nM107 ;fan off\\nM355 S0 ;turn off case light\""
}
}
}

View File

@ -0,0 +1,33 @@
{
"version": 2,
"name": "UMO+ DXU Dual",
"inherits": "dxu_dual",
"overrides": {
"machine_disallowed_areas": {
"default_value": [
[[100, -102.5], [ 110, -102.5], [ 110, -62.5], [100, -62.5]]
]
},
"machine_width": { "default_value": 220 },
"machine_depth": { "default_value": 205 },
"machine_height": { "default_value": 200 },
"machine_nozzle_heat_up_speed": {
"default_value": 1.95
},
"machine_nozzle_cool_down_speed": {
"default_value": 0.8
},
"machine_nozzle_size": {
"default_value": 0.4
},
"material_diameter": {
"default_value": 1.75
},
"machine_start_gcode" : {
"value": "\"\" if machine_gcode_flavor == \"UltiGCode\" else \"; Script based on an original created by tjjfvi (https://github.com/tjjfvi)\\n; An up-to-date version of the tjjfvi's original script can be found\\n; here: https://csi.t6.fyi/\\n; Note - This script will only work in Cura V4.2 and above!\\n; --- Global Settings\\n; layer_height = {layer_height}\\n; smooth_spiralized_contours = {smooth_spiralized_contours}\\n; magic_mesh_surface_mode = {magic_mesh_surface_mode}\\n; machine_extruder_count = {machine_extruder_count}\\n; --- Single Extruder Settings\\n; speed_z_hop = {speed_z_hop}\\n; retraction_amount = {retraction_amount}\\n; retraction_hop = {retraction_hop}\\n; retraction_hop_enabled = {retraction_hop_enabled}\\n; retraction_enable = {retraction_enable}\\n; retraction_speed = {retraction_speed}\\n; retraction_retract_speed = {retraction_retract_speed}\\n; retraction_prime_speed = {retraction_prime_speed}\\n; speed_travel = {speed_travel}\\n; --- Multi-Extruder Settings\\n; speed_z_hop_0 = {speed_z_hop, 0}\\n; speed_z_hop_1 = {speed_z_hop, 1}\\n; retraction_amount_0 = {retraction_amount, 0}\\n; retraction_amount_1 = {retraction_amount, 1}\\n; retraction_hop_0 = {retraction_hop, 0}\\n; retraction_hop_1 = {retraction_hop, 1}\\n; retraction_hop_enabled_0 = {retraction_hop_enabled, 0}\\n; retraction_hop_enabled_1 = {retraction_hop_enabled, 1}\\n; retraction_prime_speed_0 = {retraction_prime_speed, 0}\\n; retraction_prime_speed_1 = {retraction_prime_speed, 1}\\n; retraction_retract_speed_0 = {retraction_retract_speed, 0}\\n; retraction_retract_speed_1 = {retraction_retract_speed, 1}\\n; retraction_speed_0 = {retraction_speed, 0}\\n; retraction_speed_1 = {retraction_speed, 1}\\n; retraction_enable_0 = {retraction_enable, 0}\\n; retraction_enable_1 = {retraction_enable, 1}\\n; speed_travel_0 = {speed_travel, 0}\\n; speed_travel_1 = {speed_travel, 1}\\n\\nM355 S1 P25;turn on case light\\n\\n;material_bed_temperature={material_bed_temperature} material_print_temperature={material_print_temperature} material_print_temperature_layer_0={material_print_temperature_layer_0}\\nM190 S{material_bed_temperature_layer_0}\\nM104 T0 S{material_standby_temperature, 0}\\nM104 T0 S{material_print_temperature_layer_0, 0}\\nG21 ;metric values\\nG90 ;absolute positioning\\nM82 ;set extruder to absolute mode\\nM107 ;start with the fan off\\nM200 D0 T0 ;reset filament diameter\\nM200 D0 T1\\nG28 ;home all\\nT1 ; move to the nozzle 2\\nG0 Z20 F2400 ;move the platform to 30mm\\nM109 T1 S{material_print_temperature_layer_0, 1}\\nG0 X210 Y0 F7200 ;change Y20 to Y0 ansonl\\nG92 E0\\nG92 E-12.0 ;prime distance ;increase purge 6.5 to 12\\nG1 E0 F200 ;purge nozzle ;change F45 to F200 like ultimaker code ansonl\\nG1 E-6.5 F1500 ; retract\\nT0 ; move to the nozzle 1\\nM104 T1 S{material_standby_temperature, 1}\\nG0 Z20 F2400\\nM109 T0 S{material_print_temperature_layer_0, 0}\\nG0 X210 Y0 F7200 ;change Y20 to Y0 ansonl\\nG92 E0\\nG92 E-12.0\\nG1 E0 F200 ;purge nozzle\\nG1 E-6.5 F1500\\nM104 T0 S{material_standby_temperature, 0}\\nT{initial_extruder_nr} ;switch to the first nozzle used for print\\nM109 T{initial_extruder_nr} S{material_print_temperature_layer_0, initial_extruder_nr}\\nM400 ;finish all moves\\nG1 E0 F1500\\nG92 E0\\nG1 Y100 F9000 ;add quick movement to Y50 like ultimaker code ansonl\\n;end of startup sequence\\n\\nM355 S1 P50;turn on case light\""
},
"machine_end_gcode" : {
"value": "\"\" if machine_gcode_flavor == \"UltiGCode\" else \";end code from UM3\\nG91 ;Relative movement\\nG0 F15000 X8.0 Y8.0 Z3.5 E-4.5 ;Wiping+material retraction ;increase bed lower 0.5>5.0 and add Y movement\\nG0 F10000 Z1.5 E4.5 ;Compensation for the retraction\\nG90 ;Disable relative movement\\n\\nG90 ;absolute positioning\\nM104 S0 T0 ;extruder heater off\\nM104 S0 T1\\nM140 S0 ;turn off bed\\nT0 ; move to the first head\\nM107 ;fan off\\nM355 S0 ;turn off case light\""
}
}
}

View File

@ -4015,7 +4015,7 @@
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
"maximum_value_warning": "70",
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true,
"children":
{
@ -4032,7 +4032,7 @@
"maximum_value_warning": "70",
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
"value": "retraction_speed",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true
},
"retraction_prime_speed":
@ -4048,7 +4048,7 @@
"maximum_value_warning": "70",
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
"value": "retraction_speed",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true
}
}
@ -4063,7 +4063,7 @@
"minimum_value_warning": "-0.0001",
"maximum_value_warning": "5.0",
"enabled": "retraction_enable",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true
},
"retraction_min_travel":
@ -4077,7 +4077,7 @@
"minimum_value": "0",
"minimum_value_warning": "line_width * 1.5",
"maximum_value_warning": "10",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true
},
"retraction_count_max":
@ -4232,7 +4232,7 @@
"type": "bool",
"default_value": false,
"enabled": "retraction_enable and retraction_hop_enabled and travel_avoid_other_parts",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true
},
"retraction_hop": {
@ -4244,7 +4244,7 @@
"minimum_value_warning": "0",
"maximum_value_warning": "10",
"enabled": "retraction_enable and retraction_hop_enabled",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true
},
"retraction_hop_after_extruder_switch": {
@ -4435,6 +4435,7 @@
"description": "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing.",
"type": "bool",
"default_value": false,
"resolve": "any(extruderValues('support_enable'))",
"settable_per_mesh": true,
"settable_per_extruder": false
},
@ -4681,6 +4682,54 @@
"settable_per_mesh": false,
"settable_per_extruder": true
},
"support_interface_wall_count":
{
"label": "Support Interface Wall Line Count",
"description": "The number of walls with which to surround support interface. Adding a wall can make support print more reliably and can support overhangs better, but increases print time and material used.",
"default_value": 0,
"minimum_value": "0",
"minimum_value_warning": "0",
"maximum_value_warning": "0 if (support_skip_some_zags and support_interface_pattern == 'zigzag') else 3",
"maximum_value": "999999",
"type": "int",
"value": "1 if (support_interface_pattern == 'zigzag') else 0",
"enabled": "support_interface_enable or support_meshes_present",
"limit_to_extruder": "support_interface_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true,
"children": {
"support_roof_wall_count": {
"label": "Support Roof Wall Line Count",
"description": "The number of walls with which to surround support interface roof. Adding a wall can make support print more reliably and can support overhangs better, but increases print time and material used.",
"default_value": 0,
"minimum_value": "0",
"minimum_value_warning": "0",
"maximum_value_warning": "0 if (support_skip_some_zags and support_interface_pattern == 'zigzag') else 3",
"maximum_value": "999999",
"type": "int",
"value": "support_interface_wall_count",
"enabled": "support_interface_enable or support_meshes_present",
"limit_to_extruder": "support_interface_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"support_bottom_wall_count": {
"label": "Support Bottom Wall Line Count",
"description": "The number of walls with which to surround support interface floor. Adding a wall can make support print more reliably and can support overhangs better, but increases print time and material used.",
"default_value": 0,
"minimum_value": "0",
"minimum_value_warning": "0",
"maximum_value_warning": "0 if (support_skip_some_zags and support_interface_pattern == 'zigzag') else 3",
"maximum_value": "999999",
"type": "int",
"value": "support_interface_wall_count",
"enabled": "support_interface_enable or support_meshes_present",
"limit_to_extruder": "support_interface_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
}
}
},
"zig_zaggify_support":
{
"label": "Connect Support Lines",
@ -4784,7 +4833,6 @@
"default_value": 8.0,
"minimum_value": "0.0",
"maximum_value_warning": "50.0",
"maximum_value": "0.5 * min(machine_width, machine_depth)",
"enabled": "(support_enable or support_meshes_present) and support_brim_enable",
"settable_per_mesh": false,
"settable_per_extruder": true,
@ -4799,7 +4847,6 @@
"default_value": 20,
"minimum_value": "0",
"maximum_value_warning": "50 / skirt_brim_line_width",
"maximum_value": "0.5 * min(machine_width, machine_depth) / skirt_brim_line_width",
"value": "math.ceil(support_brim_width / (skirt_brim_line_width * initial_layer_line_width_factor / 100.0))",
"enabled": "(support_enable or support_meshes_present) and support_brim_enable",
"settable_per_mesh": false,
@ -5558,10 +5605,11 @@
{
"label": "Skirt/Brim Extruder",
"description": "The extruder train to use for printing the skirt or brim. This is used in multi-extrusion.",
"type": "extruder",
"type": "optional_extruder",
"default_value": "0",
"value": "adhesion_extruder_nr",
"enabled": "extruders_enabled_count > 1 and (resolveOrValue('adhesion_type') == 'skirt' or resolveOrValue('adhesion_type') == 'brim' or resolveOrValue('prime_tower_brim_enable'))",
"resolve": "'-1' if '-1' in extruderValues('skirt_brim_extruder_nr') else adhesion_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@ -5613,9 +5661,9 @@
"maximum_value_warning": "10",
"maximum_value": "0.5 * min(machine_width, machine_depth) / skirt_brim_line_width",
"enabled": "resolveOrValue('adhesion_type') == 'skirt'",
"limit_to_extruder": "skirt_brim_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "skirt_brim_extruder_nr"
"settable_per_extruder": true
},
"skirt_gap":
{
@ -5627,9 +5675,9 @@
"minimum_value_warning": "max(extruderValues('machine_nozzle_size'))",
"maximum_value_warning": "10",
"enabled": "resolveOrValue('adhesion_type') == 'skirt'",
"limit_to_extruder": "skirt_brim_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "skirt_brim_extruder_nr"
"settable_per_extruder": true
},
"skirt_brim_minimal_length":
{
@ -5642,6 +5690,7 @@
"minimum_value_warning": "25",
"maximum_value_warning": "2500",
"enabled": "resolveOrValue('adhesion_type') == 'skirt' or resolveOrValue('adhesion_type') == 'brim' or resolveOrValue('prime_tower_brim_enable')",
"limit_to_extruder": "skirt_brim_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
},
@ -5654,11 +5703,10 @@
"default_value": 8.0,
"minimum_value": "0.0",
"maximum_value_warning": "50.0",
"maximum_value": "0.5 * min(machine_width, machine_depth)",
"enabled": "resolveOrValue('adhesion_type') == 'brim' or resolveOrValue('prime_tower_brim_enable')",
"limit_to_extruder": "skirt_brim_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "skirt_brim_extruder_nr",
"children":
{
"brim_line_count":
@ -5669,12 +5717,11 @@
"default_value": 20,
"minimum_value": "0",
"maximum_value_warning": "50 / skirt_brim_line_width",
"maximum_value": "0.5 * min(machine_width, machine_depth) / skirt_brim_line_width",
"value": "math.ceil(brim_width / (skirt_brim_line_width * initial_layer_line_width_factor / 100.0))",
"enabled": "resolveOrValue('adhesion_type') == 'brim' or resolveOrValue('prime_tower_brim_enable')",
"limit_to_extruder": "skirt_brim_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "skirt_brim_extruder_nr"
"settable_per_extruder": true
}
}
},
@ -5688,9 +5735,9 @@
"minimum_value": "0",
"maximum_value_warning": "skirt_brim_line_width",
"enabled": "resolveOrValue('adhesion_type') == 'brim'",
"limit_to_extruder": "skirt_brim_extruder_nr",
"settable_per_mesh": true,
"settable_per_extruder": true,
"limit_to_extruder": "skirt_brim_extruder_nr"
"settable_per_extruder": true
},
"brim_replaces_support":
{
@ -5699,9 +5746,9 @@
"type": "bool",
"default_value": true,
"enabled": "resolveOrValue('adhesion_type') == 'brim' and (support_enable or support_meshes_present)",
"limit_to_extruder": "skirt_brim_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "support_infill_extruder_nr"
"settable_per_extruder": true
},
"brim_outside_only":
{
@ -5710,9 +5757,22 @@
"type": "bool",
"default_value": true,
"enabled": "resolveOrValue('adhesion_type') == 'brim'",
"limit_to_extruder": "skirt_brim_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "skirt_brim_extruder_nr"
"settable_per_extruder": true
},
"brim_inside_margin":
{
"label": "Brim Inside Avoid Margin",
"description": "If brim is only on outside then parts fully enclosed inside another part will get a brim which might overlap with the internal holes of the outer part. This setting controls how far to stay away from those internal holes. Set to a high value to prevent any brim from being generated for parts enclosed within the holes of other parts.",
"unit": "mm",
"type": "float",
"default_value": 5,
"minimum_value": "0",
"enabled": "resolveOrValue('adhesion_type') == 'brim' and any(extruderValues('brim_outside_only'))",
"limit_to_extruder": "skirt_brim_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"raft_margin":
{
@ -6334,6 +6394,81 @@
"settable_per_mesh": false,
"settable_per_extruder": false
},
"interlocking_enable":
{
"label": "Generate Interlocking Structure",
"description": "Whether to generate a structure at the interface between two models of a different material in order to improve the adhesion. The structure consists of cells of horizontal beams with alternating direction which connect to each other over the Z direction in order to interlock with the beams of the other material.",
"type": "bool",
"enabled": "extruders_enabled_count > 1",
"default_value": false,
"resolve": "(extruders_enabled_count > 1) and any(extruderValues('interlocking_enable'))",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"interlocking_beam_width":
{
"label": "Interlocking Beam Width",
"description": "The width of the beams of this material in the interlocking structure.",
"type": "float",
"unit": "mm",
"enabled": "extruders_enabled_count > 1 and resolveOrValue('interlocking_enable')",
"default_value": 0.8,
"value": "2 * wall_line_width_0",
"minimum_value": "0.001",
"maximum_value": "min(0.5 * machine_width, 0.5 * machine_depth)",
"maximum_value_warning": "max(extruderValues('wall_line_width_0')) * 6",
"settable_per_mesh": true,
"settable_per_extruder": true
},
"interlocking_orientation":
{
"label": "Interlocking Structure Orientation",
"description": "The direction of the beams of the interlocking structure in the XY plane as a rotation about the Z axis.",
"unit": "°",
"type": "float",
"enabled": "extruders_enabled_count > 1 and resolveOrValue('interlocking_enable')",
"default_value": 22.5,
"resolve": "min(extruderValues('interlocking_orientation'))",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"interlocking_beam_layer_count":
{
"label": "Interlocking Beam Layer Count",
"description": "The height of the beams of the interlocking structure as measured in number of layers. Less layers is stronger, but more prone to manufacturing defects.",
"type": "int",
"enabled": "extruders_enabled_count > 1 and resolveOrValue('interlocking_enable')",
"default_value": 2,
"minimum_value": "1",
"resolve": "max(extruderValues('interlocking_beam_layer_count'))",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"interlocking_depth":
{
"label": "Interlocking Depth",
"description": "The number of cells along the depth of the interface between two models where an interlocking structure is to be generated. Less cells is better, but too little cells can cause the not to be connected properly, which reduces the adhesion performance of the interlocking structure.",
"type": "int",
"enabled": "extruders_enabled_count > 1 and resolveOrValue('interlocking_enable')",
"default_value": 2,
"minimum_value": "1",
"resolve": "max(extruderValues('interlocking_depth'))",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"interlocking_boundary_avoidance":
{
"label": "Interlocking Boundary Avoidance",
"description": "The distance close to the boundary of the print where not to generate an interlocking structure as measued in number of cells times 2. If set to a value lower than the Inerlocking Depth then the interlocking structure can become visible on the outside of the print near the interfaces where two models meet.",
"type": "int",
"enabled": "extruders_enabled_count > 1 and resolveOrValue('interlocking_enable')",
"default_value": 3,
"minimum_value": "0",
"resolve": "max(extruderValues('interlocking_boundary_avoidance'))",
"minimum_value_warning": "resolveOrValue('interlocking_depth')",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"switch_extruder_retraction_amount":
{
"label": "Nozzle Switch Retraction Distance",
@ -7849,7 +7984,7 @@
"minimum_value_warning": "-0.0001",
"maximum_value_warning": "10.0",
"enabled": "wipe_retraction_enable and clean_between_layers",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true,
"settable_per_meshgroup": false
},
@ -7864,7 +7999,7 @@
"minimum_value_warning": "-0.0001",
"maximum_value_warning": "10.0",
"enabled": "wipe_retraction_enable and clean_between_layers",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true
},
"wipe_retraction_speed":
@ -7897,7 +8032,7 @@
"maximum_value_warning": "70",
"enabled": "wipe_retraction_enable and clean_between_layers",
"value": "wipe_retraction_speed",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true
},
"wipe_retraction_prime_speed":
@ -7913,7 +8048,7 @@
"maximum_value_warning": "70",
"enabled": "wipe_retraction_enable and clean_between_layers",
"value": "wipe_retraction_speed",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true
}
}
@ -7927,7 +8062,7 @@
"default_value": 0,
"minimum_value": "0",
"enabled": "clean_between_layers",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true,
"settable_per_meshgroup": false
},
@ -7952,7 +8087,7 @@
"default_value": 1,
"value": "retraction_hop",
"enabled": "wipe_hop_enable and clean_between_layers",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true,
"settable_per_meshgroup": false
},
@ -7967,7 +8102,7 @@
"minimum_value": "0",
"minimum_value_warning": "1",
"enabled": "wipe_hop_enable and clean_between_layers",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true,
"settable_per_meshgroup": false
},
@ -7992,7 +8127,7 @@
"minimum_value": "0",
"default_value": 5,
"enabled": "clean_between_layers",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true,
"settable_per_meshgroup": false
},
@ -8004,7 +8139,7 @@
"type": "float",
"default_value": 20,
"enabled": "clean_between_layers",
"settable_per_mesh": false,
"settable_per_mesh": true,
"settable_per_extruder": true,
"settable_per_meshgroup": false
},

View File

@ -9,289 +9,117 @@
"exclude_materials": [ "generic_hips", "structur3d_dap100silicone" ]
},
"overrides": {
"machine_max_feedrate_e": {
"default_value": 45
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_travel_enabled": { "value": false },
"bottom_layers": { "value": "math.ceil(round(bottom_thickness / resolveOrValue('layer_height'), 4))" },
"bridge_enable_more_layers": { "value": false },
"bridge_fan_speed": { "value": "cool_fan_speed_max" },
"bridge_fan_speed_2": { "value": "cool_fan_speed_min" },
"bridge_fan_speed_3": { "value": "cool_fan_speed_min" },
"bridge_settings_enabled": { "value": true },
"bridge_skin_density": { "value": "80" },
"bridge_skin_density_2": { "value": 100 },
"bridge_skin_density_3": { "value": 100 },
"bridge_skin_material_flow": { "value": "skin_material_flow" },
"bridge_skin_material_flow_2": { "value": "skin_material_flow" },
"bridge_skin_material_flow_3": { "value": "skin_material_flow" },
"bridge_skin_speed": { "value": "speed_topbottom" },
"bridge_skin_speed_2": { "value": "speed_topbottom" },
"bridge_skin_speed_3": { "value": "speed_topbottom" },
"bridge_skin_support_threshold": { "value": 50 },
"bridge_sparse_infill_max_density": { "value": 0 },
"bridge_wall_coast": { "value": 0 },
"bridge_wall_material_flow": { "value": "wall_material_flow" },
"bridge_wall_speed": { "value": "bridge_skin_speed" },
"cool_fan_speed_0": { "value": "cool_fan_speed_min" },
"gradual_support_infill_steps": { "value": "2 if support_interface_enable else 0" },
"gradual_support_infill_step_height": { "value": "4 * layer_height" },
"infill_material_flow": { "value": "(1.95-infill_sparse_density / 100 if infill_sparse_density > 95 else 1) * material_flow" },
"inset_direction": { "value": "'outside_in'" },
"jerk_infill": { "minimum_value_warning": 20 },
"jerk_wall": { "minimum_value_warning": 20 },
"jerk_wall_0": { "minimum_value_warning": 20 },
"jerk_prime_tower": { "minimum_value_warning": 20 },
"jerk_print":
{
"value": "20",
"minimum_value_warning": 20
},
"material_print_temperature": {
"minimum_value": "0"
},
"material_bed_temperature": {
"jerk_print_layer_0": { "value": "max(20, jerk_wall_0)" },
"jerk_roofing": { "minimum_value_warning": 20 },
"jerk_support": { "minimum_value_warning": 20 },
"jerk_support_infill": { "minimum_value_warning": 20 },
"jerk_support_interface": { "minimum_value_warning": 20 },
"jerk_topbottom": { "minimum_value_warning": 20 },
"jerk_travel": { "value": "jerk_print" },
"jerk_travel_enabled": { "value": false },
"layer_height_0": { "value": "max(0.2, layer_height)" },
"line_width": { "value": "machine_nozzle_size" },
"machine_max_feedrate_e": { "default_value": 45 },
"material_bed_temperature":
{
"minimum_value": "0",
"maximum_value_warning": "125"
},
"material_bed_temperature_layer_0":
"material_bed_temperature_layer_0": { "maximum_value_warning": "125" },
"material_print_temperature": { "minimum_value": "0" },
"material_standby_temperature":
{
"maximum_value_warning": "125"
},
"material_standby_temperature": {
"value": "material_print_temperature - 100",
"minimum_value": "0"
},
"extruder_prime_pos_y":
{
"minimum_value": "0",
"maximum_value": "machine_depth"
},
"extruder_prime_pos_x":
{
"minimum_value": "0",
"maximum_value": "machine_width"
},
"meshfix_maximum_deviation": { "value": "machine_nozzle_size / 10" },
"meshfix_maximum_resolution": { "value": "max(speed_wall_0 / 75, 0.5)" },
"minimum_support_area": { "value": "4.0" },
"raft_base_speed": { "value": "raft_speed" },
"raft_base_thickness": { "value": "min(machine_nozzle_size * 0.75, 0.3)" },
"raft_interface_fan_speed": { "value": "(raft_base_fan_speed + raft_surface_fan_speed) / 2" },
"raft_interface_line_width": { "value": "(raft_base_line_width + raft_surface_line_width) / 2" },
"raft_interface_speed": { "value": "(raft_surface_speed + raft_base_speed) / 2" },
"raft_interface_thickness": { "value": "(raft_base_thickness + raft_surface_thickness) / 2" },
"raft_speed": { "value": 15 },
"raft_surface_fan_speed": { "value": "cool_fan_speed_min" },
"raft_surface_speed": { "value": "speed_topbottom" },
"relative_extrusion":
{
"value": false,
"enabled": false
},
"skin_angles": {
"value": "[] if infill_pattern not in ['cross', 'cross_3d'] else [20, 110]"
},
"line_width": {
"value": "machine_nozzle_size"
},
"wall_thickness": {
"value": "wall_line_width_0 + wall_line_width_x"
},
"infill_material_flow": {
"value": "(1.95-infill_sparse_density / 100 if infill_sparse_density > 95 else 1) * material_flow"
},
"inset_direction": {
"value": "'outside_in'"
},
"retraction_combing": {
"value": "'no_outer_surfaces'"
},
"retraction_count_max": {
"value": 25
},
"retraction_extrusion_window": {
"value": 1
},
"roofing_layer_count": {
"value": "1"
},
"roofing_material_flow": {
"value": "material_flow"
},
"skin_material_flow": {
"value": "0.95 * material_flow"
},
"support_interface_material_flow": {
"value": "skin_material_flow"
},
"skin_monotonic" : {
"value": "roofing_layer_count == 0"
},
"speed_equalize_flow_width_factor": {
"value": "110.0"
},
"top_layers": {
"value": "math.ceil(round(top_thickness / resolveOrValue('layer_height'), 4))"
},
"bottom_layers": {
"value": "math.ceil(round(bottom_thickness / resolveOrValue('layer_height'), 4))"
},
"xy_offset": {
"value": "-layer_height * 0.1"
},
"meshfix_maximum_resolution": {
"value": "max(speed_wall_0 / 75, 0.5)"
},
"meshfix_maximum_deviation": {
"value": "machine_nozzle_size / 10"
},
"jerk_travel_enabled": {
"value": false
},
"acceleration_travel_enabled": {
"value": false
},
"acceleration_travel": {
"value": "acceleration_wall"
},
"skin_edge_support_thickness": {
"value": "4 * layer_height if infill_sparse_density < 30 else 0"
},
"bridge_settings_enabled": {
"value": true
},
"bridge_skin_support_threshold": {
"value": 50
},
"bridge_sparse_infill_max_density": {
"value": 0
},
"bridge_wall_coast": {
"value": 0
},
"bridge_wall_speed": {
"value": "bridge_skin_speed"
},
"bridge_wall_material_flow": {
"value": "wall_material_flow"
},
"bridge_skin_speed": {
"value": "speed_topbottom"
},
"bridge_skin_material_flow": {
"value": "skin_material_flow"
},
"bridge_skin_density": {
"value": "80"
},
"bridge_fan_speed": {
"value": "cool_fan_speed_max"
},
"bridge_enable_more_layers": {
"value": false
},
"bridge_skin_speed_2": {
"value": "speed_topbottom"
},
"bridge_skin_material_flow_2": {
"value": "skin_material_flow"
},
"bridge_skin_density_2": {
"value": 100
},
"bridge_fan_speed_2": {
"value": "cool_fan_speed_min"
},
"bridge_skin_speed_3": {
"value": "speed_topbottom"
},
"bridge_skin_material_flow_3": {
"value": "skin_material_flow"
},
"bridge_skin_density_3": {
"value": 100
},
"bridge_fan_speed_3": {
"value": "cool_fan_speed_min"
},
"jerk_print": {
"value": "20",
"minimum_value_warning": 20
},
"jerk_infill": {
"minimum_value_warning": 20
},
"jerk_wall": {
"minimum_value_warning": 20
},
"jerk_wall_0": {
"minimum_value_warning": 20
},
"jerk_roofing": {
"minimum_value_warning": 20
},
"jerk_topbottom": {
"minimum_value_warning": 20
},
"jerk_support": {
"minimum_value_warning": 20
},
"jerk_support_infill": {
"minimum_value_warning": 20
},
"jerk_support_interface": {
"minimum_value_warning": 20
},
"jerk_prime_tower": {
"minimum_value_warning": 20
},
"jerk_layer_0": {
"minimum_value_warning": 20
},
"jerk_print_layer_0": {
"minimum_value_warning": 20
},
"jerk_travel": {
"value": "jerk_print",
"minimum_value_warning": 20
},
"jerk_travel_layer_0": {
"minimum_value_warning": 20
},
"jerk_skirt_brim": {
"minimum_value_warning": 20
},
"support_wall_count": {
"value": "1 if support_structure == 'tree' else 0"
},
"zig_zaggify_support": {
"value": true
},
"support_infill_rate": {
"value": "0 if support_structure == 'tree' else 80 if gradual_support_infill_steps != 0 else 15"
},
"support_line_distance": {
"minimum_value_warning": "0 if support_structure == 'tree' else support_line_width"
},
"support_initial_layer_line_distance": {
"minimum_value_warning": "0 if support_structure == 'tree' else support_line_width"
},
"gradual_support_infill_steps": {
"value": "2 if support_interface_enable else 0"
},
"gradual_support_infill_step_height": {
"value": "4 * layer_height"
},
"raft_base_speed": {
"value": "raft_speed"
},
"raft_base_thickness": {
"value": "min(machine_nozzle_size * 0.75, 0.3)"
},
"raft_interface_fan_speed": {
"value": "(raft_base_fan_speed + raft_surface_fan_speed) / 2"
},
"raft_interface_line_width": {
"value": "(raft_base_line_width + raft_surface_line_width) / 2"
},
"raft_interface_speed": {
"value": "(raft_surface_speed + raft_base_speed) / 2"
},
"raft_interface_thickness": {
"value": "(raft_base_thickness + raft_surface_thickness) / 2"
},
"raft_speed": {
"value": 15
},
"raft_surface_fan_speed": {
"value": "cool_fan_speed"
},
"raft_surface_speed": {
"value": "speed_topbottom"
},
"support_interface_height": {
"value": "2 * layer_height"
},
"support_offset": {
"value": "support_xy_distance if support_interface_enable else 0"
},
"support_xy_distance": {
"value": "1"
},
"support_xy_distance_overhang": {
"value": "0.2"
},
"minimum_support_area": {
"value": "(2 + support_offset)**2"
},
"support_interface_skip_height": {
"value": "layer_height"
},
"support_interface_pattern": {
"value": "'concentric'"
},
"support_interface_offset": {
"value": "support_offset"
},
"support_use_towers": {
"value": false
},
"support_z_distance": {
"value": "0"
}
"retraction_combing": { "value": "'no_outer_surfaces'" },
"retraction_combing_max_distance": { "value": 15 },
"retraction_count_max": { "value": 25 },
"retraction_extrusion_window": { "value": 1 },
"roofing_layer_count": { "value": "1" },
"roofing_material_flow": { "value": "material_flow" },
"skin_angles": { "value": "[] if infill_pattern not in ['cross', 'cross_3d'] else [20, 110]" },
"skin_edge_support_thickness": { "value": "4 * layer_height if infill_sparse_density < 30 else 0" },
"skin_material_flow": { "value": "0.95 * material_flow" },
"skin_material_flow_layer_0": { "value": "0.85 * material_flow_layer_0" },
"skin_monotonic" : { "value": "roofing_layer_count == 0" },
"speed_equalize_flow_width_factor": { "value": "110.0" },
"speed_layer_0": { "value": "min(30, layer_height / layer_height_0 * speed_wall_0)" },
"speed_slowdown_layers": { "value": 1 },
"speed_travel_layer_0": { "value": "speed_travel" },
"support_infill_rate": { "value": "0 if support_structure == 'tree' else 80 if gradual_support_infill_steps != 0 else 15" },
"support_initial_layer_line_distance": { "minimum_value_warning": "0 if support_structure == 'tree' else support_line_width" },
"support_interface_height": { "value": "2 * layer_height" },
"support_interface_material_flow": { "value": "skin_material_flow" },
"support_interface_offset": { "value": "support_offset" },
"support_interface_pattern": { "value": "'concentric'" },
"support_interface_skip_height": { "value": "layer_height" },
"support_line_distance": { "minimum_value_warning": "0 if support_structure == 'tree' else support_line_width" },
"support_offset": { "value": "support_xy_distance if support_interface_enable else 0" },
"support_use_towers": { "value": false },
"support_wall_count": { "value": "1 if support_structure == 'tree' else 0" },
"support_xy_distance": { "value": "1" },
"support_xy_distance_overhang": { "value": "0.2" },
"support_z_distance": { "value": "0" },
"top_layers": { "value": "math.ceil(round(top_thickness / resolveOrValue('layer_height'), 4))" },
"wall_0_material_flow_layer_0": { "value": "1.10 * material_flow_layer_0" },
"wall_thickness": { "value": "wall_line_width_0 + wall_line_width_x" },
"wall_x_material_flow_layer_0": { "value": "0.95 * material_flow_layer_0" },
"xy_offset": { "value": "-layer_height * 0.1" },
"xy_offset_layer_0": { "value": "-wall_line_width_0 / 5 + xy_offset" },
"zig_zaggify_support": { "value": true }
}
}

Some files were not shown because too many files have changed in this diff Show More