Merge branch 'main' into main

This commit is contained in:
HellAholic 2024-08-30 11:18:25 +02:00 committed by GitHub
commit 7db3af745e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
758 changed files with 29951 additions and 7725 deletions

View File

@ -6,9 +6,9 @@ body:
attributes: attributes:
value: | value: |
### ✨Try our improved Cura 5.7✨ ### ✨Try our improved Cura 5.7✨
Before filling out the report below, we want you to try the latest Cura 5.7 Beta. Before filling out the report below, we want you to try the latest Cura 5.7.
This version of Cura has become significantly more reliable and has an updated slicing engine that will automatically send a report to the Cura Team for analysis. This version of Cura has become significantly more reliable and has an updated slicing engine that will automatically send a report to the Cura Team for analysis.
#### [You can find the downloads here](https://github.com/Ultimaker/Cura/releases/tag/5.7.0-beta.1) #### #### [You can find the downloads here](https://github.com/Ultimaker/Cura/releases/latest) ####
If you still encounter a crash you are still welcome to report the issue so we can use your model as a test case, you can find instructions on how to do that below. If you still encounter a crash you are still welcome to report the issue so we can use your model as a test case, you can find instructions on how to do that below.
### Project File ### Project File
@ -35,7 +35,7 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
We work hard on improving our slicing crashes. Our most recent release is 5.6.0. We work hard on improving our slicing crashes. Our most recent release is 5.7.1.
If you are not on the latest version of Cura, [you can download it here](https://github.com/Ultimaker/Cura/releases/latest) If you are not on the latest version of Cura, [you can download it here](https://github.com/Ultimaker/Cura/releases/latest)
- type: input - type: input
attributes: attributes:

View File

@ -0,0 +1,40 @@
name: conan-package-resources
on:
push:
paths:
- '.github/workflows/conan-package-resources.yml'
- 'resources/definitions/**'
- 'resources/extruders/**'
- 'resources/images/**'
- 'resources/intent/**'
- 'resources/meshes/**'
- 'resources/quality/**'
- 'resources/variants/**'
- 'resources/conanfile.py'
branches:
- 'main'
- 'CURA-*'
- 'PP-*'
- 'NP-*'
- '[0-9].[0-9]*'
- '[0-9].[0-9][0-9]*'
env:
CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }}
CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }}
jobs:
conan-recipe-version:
uses: ultimaker/cura-workflows/.github/workflows/conan-recipe-version.yml@main
with:
project_name: cura_resources
conan-package-export:
needs: [ conan-recipe-version ]
uses: ultimaker/cura-workflows/.github/workflows/conan-recipe-export.yml@main
with:
recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }}
recipe_id_latest: ${{ needs.conan-recipe-version.outputs.recipe_id_latest }}
conan_recipe_root: "./resources/"
secrets: inherit

View File

@ -4,12 +4,20 @@ on:
push: push:
paths: paths:
- 'plugins/**' - 'plugins/**'
- 'resources/**'
- 'cura/**' - 'cura/**'
- 'resources/bundled_packages/**'
- 'resources/i18n/**'
- 'resources/qml/**'
- 'resources/setting_visibility/**'
- 'resources/shaders/**'
- 'resources/texts/**'
- 'resources/themes/**'
- 'resources/public_key.pem'
- 'resources/README_resources.txt'
- 'icons/**' - 'icons/**'
- 'tests/**' - 'tests/**'
- 'packaging/**' - 'packaging/**'
- '.github/workflows/conan-*.yml' - '.github/workflows/conan-package.yml'
- '.github/workflows/notify.yml' - '.github/workflows/notify.yml'
- '.github/workflows/requirements-runner.txt' - '.github/workflows/requirements-runner.txt'
- 'requirements*.txt' - 'requirements*.txt'
@ -20,6 +28,7 @@ on:
- 'main' - 'main'
- 'CURA-*' - 'CURA-*'
- 'PP-*' - 'PP-*'
- 'NP-*'
- '[0-9].[0-9]*' - '[0-9].[0-9]*'
- '[0-9].[0-9][0-9]*' - '[0-9].[0-9][0-9]*'

View File

@ -30,6 +30,29 @@ on:
required: true required: true
type: boolean type: boolean
workflow_call:
inputs:
cura_conan_version:
default: 'cura/latest@ultimaker/testing'
required: true
type: string
conan_args:
default: ''
required: false
type: string
enterprise:
default: false
required: true
type: boolean
staging:
default: false
required: true
type: boolean
nightly:
default: false
required: true
type: boolean
schedule: schedule:
# Daily at 4:15 CET (main-branch) and 5:15 CET (release-branch) # Daily at 4:15 CET (main-branch) and 5:15 CET (release-branch)
- cron: '15 3 * * *' - cron: '15 3 * * *'
@ -70,7 +93,7 @@ jobs:
enterprise: ${{ github.event.inputs.enterprise == 'true' }} enterprise: ${{ github.event.inputs.enterprise == 'true' }}
staging: ${{ github.event.inputs.staging == 'true' }} staging: ${{ github.event.inputs.staging == 'true' }}
architecture: X64 architecture: X64
operating_system: ubuntu-22.04 operating_system: self-hosted-Ubuntu22-X64
secrets: inherit secrets: inherit
macos-installer: macos-installer:
@ -109,7 +132,7 @@ jobs:
fetch-depth: 1 fetch-depth: 1
- name: Download the run info - name: Download the run info
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4
with: with:
name: linux-run-info name: linux-run-info
@ -151,13 +174,13 @@ jobs:
f.writelines(f"NIGHTLY_TIME={nightly_creation_time}\n") f.writelines(f"NIGHTLY_TIME={nightly_creation_time}\n")
- name: Download linux installer jobs artifacts - name: Download linux installer jobs artifacts
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4
with: with:
name: ${{ steps.filename.outputs.LINUX }}-AppImage name: ${{ steps.filename.outputs.LINUX }}-AppImage
path: installers path: installers
- name: Download linux installer jobs asc artifacts - name: Download linux installer jobs asc artifacts
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4
with: with:
name: ${{ steps.filename.outputs.LINUX }}-asc name: ${{ steps.filename.outputs.LINUX }}-asc
path: installers path: installers
@ -175,13 +198,13 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download win msi installer jobs artifacts - name: Download win msi installer jobs artifacts
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4
with: with:
name: ${{ steps.filename.outputs.WIN_MSI }}-msi name: ${{ steps.filename.outputs.WIN_MSI }}-msi
path: installers path: installers
- name: Download win exe installer jobs artifacts - name: Download win exe installer jobs artifacts
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4
with: with:
name: ${{ steps.filename.outputs.WIN_EXE }}-exe name: ${{ steps.filename.outputs.WIN_EXE }}-exe
path: installers path: installers
@ -199,13 +222,13 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download MacOS (X64) dmg installer jobs artifacts - name: Download MacOS (X64) dmg installer jobs artifacts
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4
with: with:
name: ${{ steps.filename.outputs.MAC_X64_DMG }}-dmg name: ${{ steps.filename.outputs.MAC_X64_DMG }}-dmg
path: installers path: installers
- name: Download MacOS (X64) pkg installer jobs artifacts - name: Download MacOS (X64) pkg installer jobs artifacts
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4
with: with:
name: ${{ steps.filename.outputs.MAC_X64_PKG }}-pkg name: ${{ steps.filename.outputs.MAC_X64_PKG }}-pkg
path: installers path: installers
@ -223,13 +246,13 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download MacOS (ARM-64) dmg installer jobs artifacts - name: Download MacOS (ARM-64) dmg installer jobs artifacts
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4
with: with:
name: ${{ steps.filename.outputs.MAC_ARM_DMG }}-dmg name: ${{ steps.filename.outputs.MAC_ARM_DMG }}-dmg
path: installers path: installers
- name: Download MacOS (ARM-64) pkg installer jobs artifacts - name: Download MacOS (ARM-64) pkg installer jobs artifacts
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4
with: with:
name: ${{ steps.filename.outputs.MAC_ARM_PKG }}-pkg name: ${{ steps.filename.outputs.MAC_ARM_PKG }}-pkg
path: installers path: installers

View File

@ -34,10 +34,11 @@ on:
operating_system: operating_system:
description: 'OS' description: 'OS'
required: true required: true
default: 'ubuntu-22.04' default: 'self-hosted-Ubuntu22-X64'
type: choice type: choice
options: options:
- ubuntu-22.04 - ubuntu-22.04
- self-hosted-Ubuntu22-X64
jobs: jobs:
linux-installer: linux-installer:

View File

@ -40,7 +40,6 @@ on:
options: options:
- self-hosted-X64 - self-hosted-X64
- self-hosted-ARM64 - self-hosted-ARM64
- macos-11
- macos-12 - macos-12
jobs: jobs:

View File

@ -5,6 +5,9 @@ on:
path: path:
- "resources/**" - "resources/**"
permissions:
contents: read
jobs: jobs:
printer-linter-diagnose: printer-linter-diagnose:
name: Printer linter PR diagnose name: Printer linter PR diagnose
@ -18,6 +21,7 @@ jobs:
- uses: technote-space/get-diff-action@v6 - uses: technote-space/get-diff-action@v6
with: with:
DIFF_FILTER: AMRCD
PATTERNS: | PATTERNS: |
resources/+(extruders|definitions)/*.def.json resources/+(extruders|definitions)/*.def.json
resources/+(intent|quality|variants)/**/*.inst.cfg resources/+(intent|quality|variants)/**/*.inst.cfg
@ -41,11 +45,15 @@ jobs:
if: env.GIT_DIFF && !env.MATCHED_FILES 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 }} run: python printer-linter/src/terminal.py --diagnose --report printer-linter-result/fixes.yml ${{ env.GIT_DIFF_FILTERED }}
- name: Check Deleted Files(s)
if: env.GIT_DIFF
run: python printer-linter/src/terminal.py --deleted --report printer-linter-result/comment.md ${{ env.GIT_DIFF_FILTERED }}
- name: Save PR metadata - name: Save PR metadata
run: | run: |
echo ${{ github.event.number }} > printer-linter-result/pr-id.txt 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.repo.full_name }} > printer-linter-result/pr-head-repo.txt
echo ${{ github.event.pull_request.head.ref }} > printer-linter-result/pr-head-ref.txt echo ${{ github.event.pull_request.head.sha }} > printer-linter-result/pr-head-sha.txt
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:

View File

@ -6,76 +6,106 @@ on:
types: [completed] types: [completed]
jobs: jobs:
clang-tidy-results: printer-linter-result:
# Trigger the job only if the previous (insecure) workflow completed successfully # 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' }} if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
pull-requests: write
steps: steps:
- name: Download analysis results - name: Download analysis results
uses: actions/github-script@v3.1.0 uses: actions/github-script@v7
with: with:
script: | script: |
let artifacts = await github.actions.listWorkflowRunArtifacts({ const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }}, run_id: ${{github.event.workflow_run.id }},
}); });
let matchArtifact = artifacts.data.artifacts.filter((artifact) => { const matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "printer-linter-result" return artifact.name == "printer-linter-result"
})[0]; })[0];
let download = await github.actions.downloadArtifact({ const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
artifact_id: matchArtifact.id, artifact_id: matchArtifact.id,
archive_format: "zip", archive_format: "zip",
}); });
let fs = require("fs"); const 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@v3
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)); fs.writeFileSync("${{ github.workspace }}/printer-linter-result.zip", Buffer.from(download.data));
- name: Extract analysis results - name: Extract analysis results
run: | run: |
mkdir printer-linter-result mkdir printer-linter-result
unzip printer-linter-result.zip -d printer-linter-result unzip -j printer-linter-result.zip -d printer-linter-result
- name: Set PR details environment variables
uses: actions/github-script@v7
with:
script: |
const assert = require("node:assert").strict;
const fs = require("fs");
function exportVar(varName, fileName, regEx) {
const val = fs.readFileSync("${{ github.workspace }}/printer-linter-result/" + fileName, {
encoding: "ascii"
}).trimEnd();
assert.ok(regEx.test(val), "Invalid value format for " + varName);
core.exportVariable(varName, val);
}
exportVar("PR_ID", "pr-id.txt", /^[0-9]+$/);
exportVar("PR_HEAD_REPO", "pr-head-repo.txt", /^[-./0-9A-Z_a-z]+$/);
exportVar("PR_HEAD_SHA", "pr-head-sha.txt", /^[0-9A-Fa-f]+$/);
fs.access("${{ github.workspace }}/printer-linter-result/comment.md", fs.constants.F_OK, (err) => {
if (err) {
core.exportVariable("commentFileExists", "false");
} else {
core.exportVariable("commentFileExists", "true");
}
});
- uses: actions/checkout@v4
with:
repository: ${{ env.PR_HEAD_REPO }}
ref: ${{ env.PR_HEAD_SHA }}
persist-credentials: false
- name: Redownload analysis results
uses: actions/github-script@v7
with:
script: |
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
const matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "printer-linter-result"
})[0];
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: "zip",
});
const 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 -j printer-linter-result.zip -d printer-linter-result
- name: Run PR Comments
if: env.commentFileExists == 'true'
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ env.PR_ID }}
body-path: 'printer-linter-result/comment.md'
- name: Run clang-tidy-pr-comments action - name: Run clang-tidy-pr-comments action
uses: platisd/clang-tidy-pr-comments@bc0bb7da034a8317d54e7fe1e819159002f4cc40 uses: platisd/clang-tidy-pr-comments@v1
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
clang_tidy_fixes: printer-linter-result/fixes.yml clang_tidy_fixes: printer-linter-result/fixes.yml
pull_request_id: ${{ env.pr_id }} pull_request_id: ${{ env.PR_ID }}
request_changes: true request_changes: true

View File

@ -0,0 +1,32 @@
name: Feature Freeze
run-name: Feature freeze Cura ${{ inputs.cura_version }} by @${{ github.actor }}
on:
workflow_dispatch:
inputs:
cura_version:
description: 'Cura version major and minor, e.g. 5.7'
required: true
type: string
jobs:
parse-version:
name: Parse input version string
runs-on: ubuntu-latest
outputs:
package_version: ${{ steps.version_parser.outputs.major }}.${{ steps.version_parser.outputs.minor }}.0-alpha.1
steps:
- name: Parse version string
id: version_parser
uses: booxmedialtd/ws-action-parse-semver@v1.4.7
with:
input_string: ${{ inputs.cura_version }}.0
feature-freeze:
name: Process feature freeze
uses: Ultimaker/Cura-workflows/.github/workflows/cura-set-packages-versions.yml@main
needs: [parse-version]
with:
cura_version: ${{ needs.parse-version.outputs.package_version }}
create_feature_branch: true
secrets: inherit

View File

@ -0,0 +1,187 @@
name: Prepare Release Candidate
run-name: Release Candidate for Cura ${{ inputs.cura_version }} by @${{ github.actor }}
on:
workflow_dispatch:
inputs:
cura_version:
description: 'Cura version number, e.g. 5.7.0, 5.7.2 or 5.8.0-beta.2'
required: true
type: string
jobs:
parse-version:
name: Parse input version string
runs-on: ubuntu-latest
outputs:
version_major: ${{ steps.version_parser.outputs.major }}
version_minor: ${{ steps.version_parser.outputs.minor }}
version_patch: ${{ steps.version_parser.outputs.patch }}
branch_name: ${{ steps.version_parser.outputs.major }}.${{ steps.version_parser.outputs.minor }}
steps:
- name: Parse version string
id: version_parser
uses: booxmedialtd/ws-action-parse-semver@v1.4.7
with:
input_string: ${{ inputs.cura_version }}
freeze-packages-versions:
name: Freeze packges versions
uses: Ultimaker/Cura-workflows/.github/workflows/cura-set-packages-versions.yml@main
needs: [parse-version]
with:
cura_version: ${{ inputs.cura_version }}
create_feature_branch: false
secrets: inherit
find-rc-tag:
name: Find RC tag name
runs-on: ubuntu-latest
needs: [freeze-packages-versions]
outputs:
tag_name: ${{ steps.find-available-tag-name.outputs.tag_name }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-tags: true
fetch-depth: 0
- name: Find available tag name
id: find-available-tag-name
run: |
VERSION=${{ inputs.cura_version }}
RC_INDEX=0
while
RC_INDEX=$((RC_INDEX+1))
TAG_NAME="$VERSION-RC$RC_INDEX"
[[ $(git tag -l "$TAG_NAME") ]]
do true; done
echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT"
create-tags:
name: Create tags
runs-on: ubuntu-latest
needs: [parse-version, find-rc-tag]
outputs:
main_commit: ${{ steps.export-main-commit.outputs.main_commit }}
strategy:
matrix:
repository: [Cura, Uranium, CuraEngine, cura-binary-data, fdm_materials]
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
repository: Ultimaker/${{ matrix.repository }}
ref: ${{ needs.parse-version.outputs.branch_name }}
token: ${{ secrets.CURA_AUTORELEASE_PAT }}
- name: Create RC tag
run: |
git tag ${{ needs.find-rc-tag.outputs.tag_name }}
git push origin tag ${{ needs.find-rc-tag.outputs.tag_name }}
- name: Create or update release tag
run: |
git tag -f ${{ inputs.cura_version }}
git push -f origin tag ${{ inputs.cura_version }}
- name: Export Cura tagged commit
id: export-main-commit
if: ${{ matrix.repository == 'Cura' }}
run: |
echo "main_commit=`git rev-parse HEAD`" >> "$GITHUB_OUTPUT"
create-dependencies-packages:
name: Create conan packages for dependencies
uses: ultimaker/cura-workflows/.github/workflows/conan-package-release.yml@main
needs: [parse-version, freeze-packages-versions]
strategy:
matrix:
repository: [Cura, Uranium, CuraEngine, cura-binary-data, fdm_materials]
include:
- conan_recipe_root: "."
- repository: Cura
conan_recipe_root: "resources"
with:
repository: ${{ matrix.repository }}
ref_name: ${{ needs.parse-version.outputs.branch_name }}
version: ${{ inputs.cura_version }}
conan_release: true
conan_user_channel: ultimaker/stable
conan_internal: false
conan_latest: true
conan_recipe_root: ${{ matrix.conan_recipe_root }}
secrets: inherit
create-cura-package:
name: Create conan package for Cura
uses: ultimaker/cura-workflows/.github/workflows/conan-package-release.yml@main
needs: [parse-version, create-dependencies-packages]
with:
repository: Cura
ref_name: ${{ needs.parse-version.outputs.branch_name }}
version: ${{ inputs.cura_version }}
conan_release: true
conan_user_channel: ultimaker/stable
conan_internal: false
conan_latest: true
secrets: inherit
create-installers:
name: Create installers
uses: ./.github/workflows/installers.yml
needs: [parse-version, create-cura-package]
with:
cura_conan_version: cura/${{ inputs.cura_version }}@/
enterprise: false
staging: false
nightly: false
secrets: inherit
create-release-draft:
name: Create the release draft
runs-on: ubuntu-latest
needs: [create-installers, parse-version, create-tags]
steps:
- name: Checkout Cura repo
uses: actions/checkout@v4
with:
ref: ${{ needs.parse-version.outputs.branch_name }}
- name: Extract changelog
run: python ./scripts/extract_changelog.py --version ${{ needs.parse-version.outputs.version_major }}.${{ needs.parse-version.outputs.version_minor }}.${{ needs.parse-version.outputs.version_patch }} --changelog ./resources/texts/change_log.txt > formatted_changelog.txt
- name: Create release
uses: notpeelz/action-gh-create-release@v5.0.1
with:
target: ${{ needs.create-tags.outputs.main_commit }}
tag: ${{ inputs.cura_version }}
strategy: replace
title: UltiMaker Cura ${{ inputs.cura_version }}
draft: true
body-source: file
body: formatted_changelog.txt
- name: Download artifacts
uses: actions/download-artifact@v4.1.7
with:
path: artifacts
merge-multiple: true
- name: Upload artifacts
working-directory: artifacts
run: |
gh release upload ${{ inputs.cura_version }} UltiMaker-Cura-${{ inputs.cura_version }}-linux-X64.AppImage --clobber
gh release upload ${{ inputs.cura_version }} UltiMaker-Cura-${{ inputs.cura_version }}-linux-X64.AppImage.asc --clobber
gh release upload ${{ inputs.cura_version }} UltiMaker-Cura-${{ inputs.cura_version }}-macos-ARM64.dmg --clobber
gh release upload ${{ inputs.cura_version }} UltiMaker-Cura-${{ inputs.cura_version }}-macos-ARM64.pkg --clobber
gh release upload ${{ inputs.cura_version }} UltiMaker-Cura-${{ inputs.cura_version }}-macos-X64.dmg --clobber
gh release upload ${{ inputs.cura_version }} UltiMaker-Cura-${{ inputs.cura_version }}-macos-X64.pkg --clobber
gh release upload ${{ inputs.cura_version }} UltiMaker-Cura-${{ inputs.cura_version }}-win64-X64.exe --clobber
gh release upload ${{ inputs.cura_version }} UltiMaker-Cura-${{ inputs.cura_version }}-win64-X64.msi --clobber
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -2,7 +2,12 @@ checks:
diagnostic-mesh-file-extension: true diagnostic-mesh-file-extension: true
diagnostic-mesh-file-size: true diagnostic-mesh-file-size: true
diagnostic-definition-redundant-override: true diagnostic-definition-redundant-override: true
diagnostic-definition-experimental-setting: true
diagnostic-resources-macos-app-directory-name: true diagnostic-resources-macos-app-directory-name: true
diagnostic-incorrect-formula: true
diagnostic-resource-file-deleted: true
diagnostic-material-temperature-defined: false
diagnostic-long-profile-names: true
fixes: fixes:
diagnostic-definition-redundant-override: true diagnostic-definition-redundant-override: true
format: format:

View File

@ -1,14 +1,14 @@
version: "5.7.0-beta.0" version: "5.9.0-alpha.0"
requirements: requirements:
- "uranium/(latest)@ultimaker/stable" - "cura_resources/(latest)@ultimaker/testing"
- "curaengine/(latest)@ultimaker/stable" - "uranium/(latest)@ultimaker/testing"
- "cura_binary_data/(latest)@ultimaker/stable" - "curaengine/(latest)@ultimaker/testing"
- "fdm_materials/(latest)@ultimaker/stable" - "cura_binary_data/(latest)@ultimaker/testing"
- "curaengine_plugin_gradual_flow/0.1.0-beta.3" - "fdm_materials/(latest)@ultimaker/testing"
- "dulcificum/latest@ultimaker/testing" - "dulcificum/0.2.1"
- "pysavitar/5.3.0" - "pysavitar/5.3.0"
- "pynest2d/5.3.0" - "pynest2d/5.3.0"
- "curaengine_grpc_definitions/0.2.0" - "native_cad_plugin/2.0.0"
requirements_internal: requirements_internal:
- "fdm_materials/(latest)@internal/testing" - "fdm_materials/(latest)@internal/testing"
- "cura_private_data/(latest)@internal/testing" - "cura_private_data/(latest)@internal/testing"
@ -33,18 +33,22 @@ pyinstaller:
package: "cura" package: "cura"
src: "plugins" src: "plugins"
dst: "share/cura/plugins" dst: "share/cura/plugins"
curaengine_gradual_flow_plugin: native_cad_plugin:
package: "curaengine_plugin_gradual_flow" package: "native_cad_plugin"
src: "res/plugins/CuraEngineGradualFlow" src: "res/plugins/NativeCADplugin"
dst: "share/cura/plugins/CuraEngineGradualFlow" dst: "share/cura/plugins/NativeCADplugin"
curaengine_gradual_flow_plugin_bundled: native_cad_plugin_bundled:
package: "curaengine_plugin_gradual_flow" package: "native_cad_plugin"
src: "res/bundled_packages" src: "res/bundled_packages"
dst: "share/cura/resources/bundled_packages" dst: "share/cura/resources/bundled_packages"
cura_resources: cura_resources:
package: "cura" package: "cura"
src: "resources" src: "resources"
dst: "share/cura/resources" dst: "share/cura/resources"
cura_shared_resources:
package: "cura_resources"
src: "res"
dst: "share/cura/resources"
cura_private_data: cura_private_data:
package: "cura_private_data" package: "cura_private_data"
src: "res" src: "res"
@ -92,11 +96,6 @@ pyinstaller:
src: "bin" src: "bin"
dst: "." dst: "."
binary: "CuraEngine" binary: "CuraEngine"
curaengine_gradual_flow_plugin_service:
package: "curaengine_plugin_gradual_flow"
src: "bin"
dst: "."
binary: "curaengine_plugin_gradual_flow"
hiddenimports: hiddenimports:
- "pySavitar" - "pySavitar"
- "pyArcus" - "pyArcus"

View File

@ -231,6 +231,8 @@ class CuraConan(ConanFile):
else: else:
src_path = os.path.join(self.source_folder, data["src"]) src_path = os.path.join(self.source_folder, data["src"])
else: else:
if data["package"] not in self.deps_cpp_info.deps:
continue
src_path = os.path.join(self.deps_cpp_info[data["package"]].rootpath, data["src"]) src_path = os.path.join(self.deps_cpp_info[data["package"]].rootpath, data["src"])
elif "root" in data: # get the paths relative from the install folder elif "root" in data: # get the paths relative from the install folder
src_path = os.path.join(self.install_folder, data["root"], data["src"]) src_path = os.path.join(self.install_folder, data["root"], data["src"])
@ -327,7 +329,6 @@ class CuraConan(ConanFile):
self.options["cpython"].shared = True self.options["cpython"].shared = True
self.options["boost"].header_only = True self.options["boost"].header_only = True
if self.settings.os == "Linux": if self.settings.os == "Linux":
self.options["curaengine_grpc_definitions"].shared = True
self.options["openssl"].shared = True self.options["openssl"].shared = True
if self.conf.get("user.curaengine:sentry_url", "", check_type=str) != "": if self.conf.get("user.curaengine:sentry_url", "", check_type=str) != "":
self.options["curaengine"].enable_sentry = True self.options["curaengine"].enable_sentry = True
@ -343,6 +344,8 @@ class CuraConan(ConanFile):
for req in self.conan_data["requirements"]: for req in self.conan_data["requirements"]:
if self._internal and "fdm_materials" in req: if self._internal and "fdm_materials" in req:
continue continue
if not self._enterprise and "native_cad_plugin" in req:
continue
self.requires(req) self.requires(req)
if self._internal: if self._internal:
for req in self.conan_data["requirements_internal"]: for req in self.conan_data["requirements_internal"]:
@ -387,11 +390,11 @@ class CuraConan(ConanFile):
copy(self, "CuraEngine", curaengine.bindirs[0], self.source_folder, keep_path = False) copy(self, "CuraEngine", curaengine.bindirs[0], self.source_folder, keep_path = False)
# Copy the external plugins that we want to bundle with Cura # Copy the external plugins that we want to bundle with Cura
rmdir(self,str(self.source_path.joinpath("plugins", "CuraEngineGradualFlow"))) if self._enterprise:
curaengine_plugin_gradual_flow = self.dependencies["curaengine_plugin_gradual_flow"].cpp_info rmdir(self, str(self.source_path.joinpath("plugins", "NativeCADplugin")))
copy(self, "*", curaengine_plugin_gradual_flow.resdirs[0], str(self.source_path.joinpath("plugins", "CuraEngineGradualFlow")), keep_path = True) native_cad_plugin = self.dependencies["native_cad_plugin"].cpp_info
copy(self, "*", curaengine_plugin_gradual_flow.bindirs[0], self.source_folder, keep_path = False) copy(self, "*", native_cad_plugin.resdirs[0], str(self.source_path.joinpath("plugins", "NativeCADplugin")), keep_path = True)
copy(self, "bundled_*.json", curaengine_plugin_gradual_flow.resdirs[1], str(self.source_path.joinpath("resources", "bundled_packages")), keep_path = False) copy(self, "bundled_*.json", native_cad_plugin.resdirs[1], str(self.source_path.joinpath("resources", "bundled_packages")), keep_path = False)
# Copy resources of cura_binary_data # Copy resources of cura_binary_data
cura_binary_data = self.dependencies["cura_binary_data"].cpp_info cura_binary_data = self.dependencies["cura_binary_data"].cpp_info
@ -458,6 +461,12 @@ class CuraConan(ConanFile):
copy(self, "*", os.path.join(self.package_folder, self.cpp_info.resdirs[0]), str(self._share_dir.joinpath("cura", "resources")), keep_path = True) copy(self, "*", os.path.join(self.package_folder, self.cpp_info.resdirs[0]), str(self._share_dir.joinpath("cura", "resources")), keep_path = True)
copy(self, "*", os.path.join(self.package_folder, self.cpp_info.resdirs[1]), str(self._share_dir.joinpath("cura", "plugins")), keep_path = True) copy(self, "*", os.path.join(self.package_folder, self.cpp_info.resdirs[1]), str(self._share_dir.joinpath("cura", "plugins")), keep_path = True)
# Copy the cura_resources resources from the package
rm(self, "conanfile.py", os.path.join(self.package_folder, self.cpp.package.resdirs[0]))
cura_resources = self.dependencies["cura_resources"].cpp_info
for res_dir in cura_resources.resdirs:
copy(self, "*", res_dir, str(self._share_dir.joinpath("cura", "resources", Path(res_dir).name)), keep_path = True)
# Copy resources of Uranium (keep folder structure) # Copy resources of Uranium (keep folder structure)
uranium = self.dependencies["uranium"].cpp_info uranium = self.dependencies["uranium"].cpp_info
copy(self, "*", uranium.resdirs[0], str(self._share_dir.joinpath("uranium", "resources")), keep_path = True) copy(self, "*", uranium.resdirs[0], str(self._share_dir.joinpath("uranium", "resources")), keep_path = True)
@ -502,13 +511,15 @@ echo "CURA_APP_NAME={{ cura_app_name }}" >> ${{ env_prefix }}GITHUB_ENV
copy(self, "requirement*.txt", src = self.source_folder, dst = os.path.join(self.package_folder, self.cpp.package.resdirs[-1])) copy(self, "requirement*.txt", src = self.source_folder, dst = os.path.join(self.package_folder, self.cpp.package.resdirs[-1]))
copy(self, "*", src = os.path.join(self.source_folder, "packaging"), dst = os.path.join(self.package_folder, self.cpp.package.resdirs[2])) copy(self, "*", src = os.path.join(self.source_folder, "packaging"), dst = os.path.join(self.package_folder, self.cpp.package.resdirs[2]))
# Remove the CuraEngineGradualFlow plugin from the package
rmdir(self, os.path.join(self.package_folder, self.cpp.package.resdirs[1], "CuraEngineGradualFlow"))
rm(self, "bundled_*.json", os.path.join(self.package_folder, self.cpp.package.resdirs[0], "bundled_packages"), recursive = False)
# Remove the fdm_materials from the package # Remove the fdm_materials from the package
rmdir(self, os.path.join(self.package_folder, self.cpp.package.resdirs[0], "materials")) rmdir(self, os.path.join(self.package_folder, self.cpp.package.resdirs[0], "materials"))
# Remove the cura_resources resources from the package
rm(self, "conanfile.py", os.path.join(self.package_folder, self.cpp.package.resdirs[0]))
cura_resources = self.dependencies["cura_resources"].cpp_info
for res_dir in cura_resources.resdirs:
rmdir(self, os.path.join(self.package_folder, self.cpp.package.resdirs[0], Path(res_dir).name))
def package_info(self): def package_info(self):
self.user_info.pip_requirements = "requirements.txt" self.user_info.pip_requirements = "requirements.txt"
self.user_info.pip_requirements_git = "requirements-ultimaker.txt" self.user_info.pip_requirements_git = "requirements-ultimaker.txt"

View File

@ -115,15 +115,15 @@ class Account(QObject):
self._update_timer.setSingleShot(True) self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self.sync) self._update_timer.timeout.connect(self.sync)
self._sync_services: Dict[str, int] = {}
"""contains entries "service_name" : SyncState""" """contains entries "service_name" : SyncState"""
self.syncRequested.connect(self._updatePermissions) self._sync_services: Dict[str, int] = {}
def initialize(self) -> None: def initialize(self) -> None:
self._authorization_service.initialize(self._application.getPreferences()) self._authorization_service.initialize(self._application.getPreferences())
self._authorization_service.onAuthStateChanged.connect(self._onLoginStateChanged) self._authorization_service.onAuthStateChanged.connect(self._onLoginStateChanged)
self._authorization_service.onAuthenticationError.connect(self._onLoginStateChanged) self._authorization_service.onAuthenticationError.connect(self._onLoginStateChanged)
self._authorization_service.accessTokenChanged.connect(self._onAccessTokenChanged) self._authorization_service.accessTokenChanged.connect(self._onAccessTokenChanged)
self._authorization_service.accessTokenChanged.connect(self._updatePermissions)
self._authorization_service.loadAuthDataFromPreferences() self._authorization_service.loadAuthDataFromPreferences()
@pyqtProperty(int, notify=syncStateChanged) @pyqtProperty(int, notify=syncStateChanged)
@ -190,6 +190,20 @@ class Account(QObject):
def isLoggedIn(self) -> bool: def isLoggedIn(self) -> bool:
return self._logged_in return self._logged_in
@pyqtSlot()
def stopSyncing(self) -> None:
Logger.debug(f"Stopping sync of cloud printers")
self._setManualSyncEnabled(True)
if self._update_timer.isActive():
self._update_timer.stop()
@pyqtSlot()
def startSyncing(self) -> None:
Logger.debug(f"Starting sync of cloud printers")
self._setManualSyncEnabled(False)
if not self._update_timer.isActive():
self._update_timer.start()
def _onLoginStateChanged(self, logged_in: bool = False, error_message: Optional[str] = None) -> None: def _onLoginStateChanged(self, logged_in: bool = False, error_message: Optional[str] = None) -> None:
if error_message: if error_message:
if self._error_message: if self._error_message:

View File

@ -1,7 +1,13 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING from dataclasses import asdict
from typing import cast, Dict, TYPE_CHECKING
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingFunction import SettingFunction
from cura.Settings.GlobalStack import GlobalStack
if TYPE_CHECKING: if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
@ -47,3 +53,57 @@ class Settings:
""" """
return self.application.getSidebarCustomMenuItems() return self.application.getSidebarCustomMenuItems()
def getSliceMetadata(self) -> Dict[str, Dict[str, Dict[str, str]]]:
"""Get all changed settings and all settings. For each extruder and the global stack"""
print_information = self.application.getPrintInformation()
machine_manager = self.application.getMachineManager()
settings = {
"material": {
"length": print_information.materialLengths,
"weight": print_information.materialWeights,
"cost": print_information.materialCosts,
},
"global": {
"changes": {},
"all_settings": {},
},
"quality": asdict(machine_manager.activeQualityDisplayNameMap()),
}
def _retrieveValue(container: InstanceContainer, setting_: str):
value_ = container.getProperty(setting_, "value")
for _ in range(0, 1024): # Prevent possibly endless loop by not using a limit.
if not isinstance(value_, SettingFunction):
return value_ # Success!
value_ = value_(container)
return 0 # Fallback value after breaking possibly endless loop.
global_stack = cast(GlobalStack, self.application.getGlobalContainerStack())
# Add global user or quality changes
global_flattened_changes = InstanceContainer.createMergedInstanceContainer(global_stack.userChanges, global_stack.qualityChanges)
for setting in global_flattened_changes.getAllKeys():
settings["global"]["changes"][setting] = _retrieveValue(global_flattened_changes, setting)
# Get global all settings values without user or quality changes
for setting in global_stack.getAllKeys():
settings["global"]["all_settings"][setting] = _retrieveValue(global_stack, setting)
for i, extruder in enumerate(global_stack.extruderList):
# Add extruder fields to settings dictionary
settings[f"extruder_{i}"] = {
"changes": {},
"all_settings": {},
}
# Add extruder user or quality changes
extruder_flattened_changes = InstanceContainer.createMergedInstanceContainer(extruder.userChanges, extruder.qualityChanges)
for setting in extruder_flattened_changes.getAllKeys():
settings[f"extruder_{i}"]["changes"][setting] = _retrieveValue(extruder_flattened_changes, setting)
# Get extruder all settings values without user or quality changes
for setting in extruder.getAllKeys():
settings[f"extruder_{i}"]["all_settings"][setting] = _retrieveValue(extruder, setting)
return settings

View File

@ -14,7 +14,7 @@ DEFAULT_CURA_LATEST_URL = "https://software.ultimaker.com/latest.json"
# Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for # Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for
# example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the # example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the
# CuraVersion.py.in template. # CuraVersion.py.in template.
CuraSDKVersion = "8.7.0" CuraSDKVersion = "8.8.0"
try: try:
from cura.CuraVersion import CuraLatestURL from cura.CuraVersion import CuraLatestURL

View File

@ -18,8 +18,8 @@ class BackendPlugin(AdditionalSettingDefinitionsAppender, PluginObject):
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
settings_catalog = i18nCatalog("fdmprinter.def.json") settings_catalog = i18nCatalog("fdmprinter.def.json")
def __init__(self) -> None: def __init__(self, catalog_i18n = settings_catalog) -> None:
super().__init__(self.settings_catalog) super().__init__(catalog_i18n)
self.__port: int = 0 self.__port: int = 0
self._plugin_address: str = "127.0.0.1" self._plugin_address: str = "127.0.0.1"
self._plugin_command: Optional[List[str]] = None self._plugin_command: Optional[List[str]] = None

View File

@ -33,6 +33,7 @@ from UM.Message import Message
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.SetTransformOperation import SetTransformOperation from UM.Operations.SetTransformOperation import SetTransformOperation
from UM.OutputDevice.ProjectOutputDevice import ProjectOutputDevice
from UM.Platform import Platform from UM.Platform import Platform
from UM.PluginError import PluginNotFoundError from UM.PluginError import PluginNotFoundError
from UM.Preferences import Preferences from UM.Preferences import Preferences
@ -1217,6 +1218,8 @@ class CuraApplication(QtApplication):
# Once we're at this point, everything should have been flushed already (past OnExitCallbackManager). # Once we're at this point, everything should have been flushed already (past OnExitCallbackManager).
# It's more difficult to call sys.exit(0): That requires that it happens as the result of a pyqtSignal-emit. # It's more difficult to call sys.exit(0): That requires that it happens as the result of a pyqtSignal-emit.
# (See https://doc.qt.io/qt-6/qcoreapplication.html#quit) # (See https://doc.qt.io/qt-6/qcoreapplication.html#quit)
# WARNING: With this in place you CAN NOT use cProfile. You will need to replace the next line with pass
# for it to work!
os._exit(0) os._exit(0)
return super().event(event) return super().event(event)
@ -1457,7 +1460,11 @@ class CuraApplication(QtApplication):
self._scene_bounding_box = scene_bounding_box self._scene_bounding_box = scene_bounding_box
self.sceneBoundingBoxChanged.emit() self.sceneBoundingBoxChanged.emit()
self._platform_activity = True if count > 0 else False if count > 0:
self._platform_activity = True
else:
ProjectOutputDevice.setLastOutputName(None)
self._platform_activity = False
self.activityChanged.emit() self.activityChanged.emit()
@pyqtSlot() @pyqtSlot()

View File

@ -49,7 +49,7 @@ class MachineErrorChecker(QObject):
self._keys_to_check = set() # type: Set[str] self._keys_to_check = set() # type: Set[str]
self._num_keys_to_check_per_update = 10 self._num_keys_to_check_per_update = 1
def initialize(self) -> None: def initialize(self) -> None:
self._error_check_timer.timeout.connect(self._rescheduleCheck) self._error_check_timer.timeout.connect(self._rescheduleCheck)

View File

@ -1,8 +1,9 @@
# Copyright (c) 2019 Ultimaker B.V. # Copyright (c) 2024 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Dict, List from typing import Dict, List
from UM.Decorators import deprecated
from UM.Logger import Logger from UM.Logger import Logger
from UM.Signal import Signal from UM.Signal import Signal
from UM.Util import parseBool from UM.Util import parseBool
@ -168,13 +169,18 @@ class MachineNode(ContainerNode):
return self.global_qualities.get(self.preferred_quality_type, next(iter(self.global_qualities.values()))) return self.global_qualities.get(self.preferred_quality_type, next(iter(self.global_qualities.values())))
def isExcludedMaterial(self, material: MaterialNode) -> bool: def isExcludedMaterialBaseFile(self, material_base_file: str) -> bool:
"""Returns whether the material should be excluded from the list of materials.""" """Returns whether the material should be excluded from the list of materials."""
for exclude_material in self.exclude_materials: for exclude_material in self.exclude_materials:
if exclude_material in material["id"]: if exclude_material in material_base_file:
return True return True
return False return False
@deprecated("Use isExcludedMaterialBaseFile instead.", since = "5.9.0")
def isExcludedMaterial(self, material: MaterialNode) -> bool:
"""Returns whether the material should be excluded from the list of materials."""
return self.isExcludedMaterialBaseFile(material.base_file)
@UM.FlameProfiler.profile @UM.FlameProfiler.profile
def _loadAll(self) -> None: def _loadAll(self) -> None:
"""(Re)loads all variants under this printer.""" """(Re)loads all variants under this printer."""

View File

@ -21,13 +21,20 @@ class MaterialNode(ContainerNode):
Its subcontainers are quality profiles. Its subcontainers are quality profiles.
""" """
def __init__(self, container_id: str, variant: "VariantNode") -> None: def __init__(self, container_id: str, variant: "VariantNode", *, container: ContainerInterface = None) -> None:
super().__init__(container_id) super().__init__(container_id)
self.variant = variant self.variant = variant
self.qualities = {} # type: Dict[str, QualityNode] # Mapping container IDs to quality profiles. self.qualities = {} # type: Dict[str, QualityNode] # Mapping container IDs to quality profiles.
self.materialChanged = Signal() # Triggered when the material is removed or its metadata is updated. self.materialChanged = Signal() # Triggered when the material is removed or its metadata is updated.
container_registry = ContainerRegistry.getInstance() container_registry = ContainerRegistry.getInstance()
if container is not None:
self.base_file = container.getMetaDataEntry("base_file")
self.material_type = container.getMetaDataEntry("material")
self.brand = container.getMetaDataEntry("brand")
self.guid = container.getMetaDataEntry("GUID")
else:
my_metadata = container_registry.findContainersMetadata(id = container_id)[0] my_metadata = container_registry.findContainersMetadata(id = container_id)[0]
self.base_file = my_metadata["base_file"] self.base_file = my_metadata["base_file"]
self.material_type = my_metadata["material"] self.material_type = my_metadata["material"]

View File

@ -54,10 +54,7 @@ class ActiveIntentQualitiesModel(ListModel):
self._updateDelayed() self._updateDelayed()
def _update(self): def _update(self):
active_extruder_stack = cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeStack self._intent_category = IntentManager.getInstance().currentIntentCategory
if active_extruder_stack:
self._intent_category = active_extruder_stack.intent.getMetaDataEntry("intent_category", "")
new_items: List[Dict[str, Any]] = [] new_items: List[Dict[str, Any]] = []
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if not global_stack: if not global_stack:

View File

@ -24,6 +24,10 @@ intent_translations["quick"] = {
"name": catalog.i18nc("@label", "Draft"), "name": catalog.i18nc("@label", "Draft"),
"description": catalog.i18nc("@text", "The draft profile is designed to print initial prototypes and concept validation with the intent of significant print time reduction.") "description": catalog.i18nc("@text", "The draft profile is designed to print initial prototypes and concept validation with the intent of significant print time reduction.")
} }
intent_translations["annealing"] = {
"name": catalog.i18nc("@label", "Annealing"),
"description": catalog.i18nc("@text", "The annealing profile requires post-processing in an oven after the print is finished. This profile retains the dimensional accuracy of the printed part after annealing and improves strength, stiffness, and thermal resistance.")
}
intent_translations["solid"] = { intent_translations["solid"] = {
"name": catalog.i18nc("@label", "Solid"), "name": catalog.i18nc("@label", "Solid"),
"description": catalog.i18nc("@text", "description": catalog.i18nc("@text",

View File

@ -60,7 +60,7 @@ class VariantNode(ContainerNode):
materials = list(materials_per_base_file.values()) materials = list(materials_per_base_file.values())
# Filter materials based on the exclude_materials property. # Filter materials based on the exclude_materials property.
filtered_materials = [material for material in materials if not self.machine.isExcludedMaterial(material)] filtered_materials = [material for material in materials if not self.machine.isExcludedMaterialBaseFile(material["id"])]
for material in filtered_materials: for material in filtered_materials:
base_file = material["base_file"] base_file = material["base_file"]
@ -127,7 +127,7 @@ class VariantNode(ContainerNode):
material_definition = container.getMetaDataEntry("definition") material_definition = container.getMetaDataEntry("definition")
base_file = container.getMetaDataEntry("base_file") base_file = container.getMetaDataEntry("base_file")
if base_file in self.machine.exclude_materials: if self.machine.isExcludedMaterialBaseFile(base_file):
return # Material is forbidden for this printer. return # Material is forbidden for this printer.
if base_file not in self.materials: # Completely new base file. Always better than not having a file as long as it matches our set-up. if base_file not in self.materials: # Completely new base file. Always better than not having a file as long as it matches our set-up.
if material_definition != "fdmprinter" and material_definition != self.machine.container_id: if material_definition != "fdmprinter" and material_definition != self.machine.container_id:
@ -148,7 +148,7 @@ class VariantNode(ContainerNode):
if "empty_material" in self.materials: if "empty_material" in self.materials:
del self.materials["empty_material"] del self.materials["empty_material"]
self.materials[base_file] = MaterialNode(container.getId(), variant = self) self.materials[base_file] = MaterialNode(container.getId(), variant = self, container = container)
self.materials[base_file].materialChanged.connect(self.materialsChanged) self.materials[base_file].materialChanged.connect(self.materialsChanged)
self.materialsChanged.emit(self.materials[base_file]) self.materialsChanged.emit(self.materials[base_file])

View File

@ -96,7 +96,7 @@ class AuthorizationHelpers:
return return
if token_response.error() != QNetworkReply.NetworkError.NoError: if token_response.error() != QNetworkReply.NetworkError.NoError:
callback(AuthenticationResponse(success = False, err_message = token_data["error_description"])) callback(AuthenticationResponse(success = False, err_message = token_data.get("error_description", "an unknown server error occurred")))
return return
callback(AuthenticationResponse(success = True, callback(AuthenticationResponse(success = True,

View File

@ -0,0 +1,106 @@
# Copyright (c) 2024 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Resources import Resources
import json
from typing import Dict, List, Optional
class FormatMaps:
# A map from the printer-type in their native file-formats to the internal name we use.
PRINTER_TYPE_NAME = {
"fire_e": "ultimaker_method",
"lava_f": "ultimaker_methodx",
"magma_10": "ultimaker_methodxl",
"sketch": "ultimaker_sketch"
}
# A map from the extruder-name in their native file-formats to the internal name we use.
EXTRUDER_NAME_MAP = {
"mk14_hot": "1XA",
"mk14_hot_s": "2XA",
"mk14_c": "1C",
"mk14": "1A",
"mk14_s": "2A",
"mk14_e": "LABS"
}
# A map from the material-name in their native file-formats to some info, including the internal name we use.
MATERIAL_MAP = {
"abs": {"name": "ABS", "guid": "2780b345-577b-4a24-a2c5-12e6aad3e690"},
"abs-cf10": {"name": "ABS-CF", "guid": "495a0ce5-9daf-4a16-b7b2-06856d82394d"},
"abs-wss1": {"name": "ABS-R", "guid": "88c8919c-6a09-471a-b7b6-e801263d862d"},
"asa": {"name": "ASA", "guid": "f79bc612-21eb-482e-ad6c-87d75bdde066"},
"nylon12-cf": {"name": "Nylon 12 CF", "guid": "3c6f2877-71cc-4760-84e6-4b89ab243e3b"},
"nylon": {"name": "Nylon", "guid": "283d439a-3490-4481-920c-c51d8cdecf9c"},
"pc": {"name": "PC", "guid": "62414577-94d1-490d-b1e4-7ef3ec40db02"},
"petg": {"name": "PETG", "guid": "69386c85-5b6c-421a-bec5-aeb1fb33f060"},
"pla": {"name": "PLA", "guid": "abb9c58e-1f56-48d1-bd8f-055fde3a5b56"},
"pva": {"name": "PVA", "guid": "add51ef2-86eb-4c39-afd5-5586564f0715"},
"wss1": {"name": "RapidRinse", "guid": "a140ef8f-4f26-4e73-abe0-cfc29d6d1024"},
"sr30": {"name": "SR-30", "guid": "77873465-83a9-4283-bc44-4e542b8eb3eb"},
"bvoh": {"name": "BVOH", "guid": "923e604c-8432-4b09-96aa-9bbbd42207f4"},
"cpe": {"name": "CPE", "guid": "da1872c1-b991-4795-80ad-bdac0f131726"},
"hips": {"name": "HIPS", "guid": "a468d86a-220c-47eb-99a5-bbb47e514eb0"},
"tpu": {"name": "TPU 95A", "guid": "19baa6a9-94ff-478b-b4a1-8157b74358d2"},
"im-pla": {"name": "Tough", "guid": "de031137-a8ca-4a72-bd1b-17bb964033ad"}
}
__inverse_printer_name: Optional[Dict[str, str]] = None
__inverse_extruder_type: Optional[Dict[str, str]] = None
__inverse_material_map: Optional[Dict[str, str]] = None
__product_to_id_map: Optional[Dict[str, List[str]]] = None
@classmethod
def getInversePrinterNameMap(cls) -> Dict[str, str]:
"""Returns the inverse of the printer name map, that is, from the internal name to the name used in output."""
if cls.__inverse_printer_name is not None:
return cls.__inverse_printer_name
cls.__inverse_printer_name = {}
for key, value in cls.PRINTER_TYPE_NAME.items():
cls.__inverse_printer_name[value] = key
return cls.__inverse_printer_name
@classmethod
def getInverseExtruderTypeMap(cls) -> Dict[str, str]:
"""Returns the inverse of the extruder type map, that is, from the internal name to the name used in output."""
if cls.__inverse_extruder_type is not None:
return cls.__inverse_extruder_type
cls.__inverse_extruder_type = {}
for key, value in cls.EXTRUDER_NAME_MAP.items():
cls.__inverse_extruder_type[value] = key
return cls.__inverse_extruder_type
@classmethod
def getInverseMaterialMap(cls) -> Dict[str, str]:
"""Returns the inverse of the material map, that is, from the internal name to the name used in output.
Note that this drops the extra info saved in the non-inverse material map, use that if you need it.
"""
if cls.__inverse_material_map is not None:
return cls.__inverse_material_map
cls.__inverse_material_map = {}
for key, value in cls.MATERIAL_MAP.items():
cls.__inverse_material_map[value["name"]] = key
return cls.__inverse_material_map
@classmethod
def getProductIdMap(cls) -> Dict[str, List[str]]:
"""Gets a mapping from product names (for example, 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
product_to_id_file = Resources.getPath(Resources.Texts, "product_to_id.json")
with open(product_to_id_file, encoding = "utf-8") as f:
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 cls.__product_to_id_map

View File

@ -1,9 +1,10 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2024 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional from typing import Optional
from PyQt6.QtCore import pyqtProperty, QObject, pyqtSignal from PyQt6.QtCore import pyqtProperty, QObject, pyqtSignal
from cura.PrinterOutput.FormatMaps import FormatMaps
from .MaterialOutputModel import MaterialOutputModel from .MaterialOutputModel import MaterialOutputModel
@ -45,15 +46,8 @@ class ExtruderConfigurationModel(QObject):
@staticmethod @staticmethod
def applyNameMappingHotend(hotendId) -> str: def applyNameMappingHotend(hotendId) -> str:
_EXTRUDER_NAME_MAP = { if hotendId in FormatMaps.EXTRUDER_NAME_MAP:
"mk14_hot":"1XA", return FormatMaps.EXTRUDER_NAME_MAP[hotendId]
"mk14_hot_s":"2XA",
"mk14_c":"1C",
"mk14":"1A",
"mk14_s":"2A"
}
if hotendId in _EXTRUDER_NAME_MAP:
return _EXTRUDER_NAME_MAP[hotendId]
return hotendId return hotendId
@pyqtProperty(str, fset = setHotendID, notify = extruderConfigurationChanged) @pyqtProperty(str, fset = setHotendID, notify = extruderConfigurationChanged)

View File

@ -1,9 +1,10 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2024 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional from typing import Optional
from PyQt6.QtCore import pyqtProperty, QObject from PyQt6.QtCore import pyqtProperty, QObject
from cura.PrinterOutput.FormatMaps import FormatMaps
class MaterialOutputModel(QObject): class MaterialOutputModel(QObject):
@ -23,29 +24,9 @@ class MaterialOutputModel(QObject):
@staticmethod @staticmethod
def getMaterialFromDefinition(guid, type, brand, name): def getMaterialFromDefinition(guid, type, brand, name):
if guid is None and brand != "empty" and type in FormatMaps.MATERIAL_MAP:
_MATERIAL_MAP = { "abs" :{"name" :"ABS" ,"guid": "2780b345-577b-4a24-a2c5-12e6aad3e690"}, name = FormatMaps.MATERIAL_MAP[type]["name"]
"abs-cf10": {"name": "ABS-CF", "guid": "495a0ce5-9daf-4a16-b7b2-06856d82394d"}, guid = FormatMaps.MATERIAL_MAP[type]["guid"]
"abs-wss1" :{"name" :"ABS-R" ,"guid": "88c8919c-6a09-471a-b7b6-e801263d862d"},
"asa" :{"name" :"ASA" ,"guid": "416eead4-0d8e-4f0b-8bfc-a91a519befa5"},
"nylon12-cf":{"name": "Nylon 12 CF" ,"guid": "3c6f2877-71cc-4760-84e6-4b89ab243e3b"},
"nylon" :{"name" :"Nylon" ,"guid": "283d439a-3490-4481-920c-c51d8cdecf9c"},
"pc" :{"name" :"PC" ,"guid": "62414577-94d1-490d-b1e4-7ef3ec40db02"},
"petg" :{"name" :"PETG" ,"guid": "69386c85-5b6c-421a-bec5-aeb1fb33f060"},
"pla" :{"name" :"PLA" ,"guid": "0ff92885-617b-4144-a03c-9989872454bc"},
"pva" :{"name" :"PVA" ,"guid": "a4255da2-cb2a-4042-be49-4a83957a2f9a"},
"wss1" :{"name" :"RapidRinse" ,"guid": "a140ef8f-4f26-4e73-abe0-cfc29d6d1024"},
"sr30" :{"name" :"SR-30" ,"guid": "77873465-83a9-4283-bc44-4e542b8eb3eb"},
"bvoh" :{"name" :"BVOH" ,"guid": "923e604c-8432-4b09-96aa-9bbbd42207f4"},
"cpe" :{"name" :"CPE" ,"guid": "da1872c1-b991-4795-80ad-bdac0f131726"},
"hips" :{"name" :"HIPS" ,"guid": "a468d86a-220c-47eb-99a5-bbb47e514eb0"},
"tpu" :{"name" :"TPU 95A" ,"guid": "19baa6a9-94ff-478b-b4a1-8157b74358d2"}
}
if guid is None and brand != "empty" and type in _MATERIAL_MAP:
name = _MATERIAL_MAP[type]["name"]
guid = _MATERIAL_MAP[type]["guid"]
return name, guid return name, guid

View File

@ -1,4 +1,4 @@
# Copyright (c) 2021 Ultimaker B.V. # Copyright (c) 2024 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from UM.FileHandler.FileHandler import FileHandler #For typing. from UM.FileHandler.FileHandler import FileHandler #For typing.
@ -6,6 +6,7 @@ from UM.Logger import Logger
from UM.Scene.SceneNode import SceneNode #For typing. from UM.Scene.SceneNode import SceneNode #For typing.
from cura.API import Account from cura.API import Account
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.FormatMaps import FormatMaps
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
@ -419,13 +420,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
@staticmethod @staticmethod
def applyPrinterTypeMapping(printer_type): def applyPrinterTypeMapping(printer_type):
_PRINTER_TYPE_NAME = { if printer_type in FormatMaps.PRINTER_TYPE_NAME:
"fire_e": "ultimaker_method", return FormatMaps.PRINTER_TYPE_NAME[printer_type]
"lava_f": "ultimaker_methodx",
"magma_10": "ultimaker_methodxl"
}
if printer_type in _PRINTER_TYPE_NAME:
return _PRINTER_TYPE_NAME[printer_type]
return printer_type return printer_type
@pyqtProperty(str, constant = True) @pyqtProperty(str, constant = True)

View File

@ -83,6 +83,15 @@ class GlobalStack(CuraContainerStack):
""" """
return self.getMetaDataEntry("supports_material_export", False) return self.getMetaDataEntry("supports_material_export", False)
@pyqtProperty("QVariantList", constant = True)
def getOutputFileFormats(self) -> List[str]:
"""
Which output formats the printer supports.
:return: A list of strings with MIME-types.
"""
all_formats_str = self.getMetaDataEntry("file_formats", "")
return all_formats_str.split(";")
@classmethod @classmethod
def getLoadingPriority(cls) -> int: def getLoadingPriority(cls) -> int:
return 2 return 2

View File

@ -145,10 +145,24 @@ class IntentManager(QObject):
@pyqtProperty(str, notify = intentCategoryChanged) @pyqtProperty(str, notify = intentCategoryChanged)
def currentIntentCategory(self) -> str: def currentIntentCategory(self) -> str:
application = cura.CuraApplication.CuraApplication.getInstance() application = cura.CuraApplication.CuraApplication.getInstance()
active_extruder_stack = application.getMachineManager().activeStack global_stack = application.getGlobalContainerStack()
if active_extruder_stack is None:
return "" active_intent = "default"
return active_extruder_stack.intent.getMetaDataEntry("intent_category", "") if global_stack is None:
return active_intent
# Loop over all active extruders and check if they have an intent that isn't default.
# The logic behind this is that support materials (for instance, PVA) don't have intents, but they should be
# combinable with all other intents. So if one extruder has "engineering" as an intent and the other has
# "default" the 'dominant' intent is "engineering"
for extruder_stack in global_stack.extruderList:
if not extruder_stack.isEnabled: # Ignore disabled stacks
continue
extruder_intent = extruder_stack.intent.getMetaDataEntry("intent_category", "")
if extruder_intent != "default":
active_intent = extruder_intent
return active_intent
@pyqtSlot(str, str) @pyqtSlot(str, str)
def selectIntent(self, intent_category: str, quality_type: str) -> None: def selectIntent(self, intent_category: str, quality_type: str) -> None:

View File

@ -847,6 +847,24 @@ class MachineManager(QObject):
return result return result
@pyqtProperty(bool, notify = currentConfigurationChanged)
def variantCoreUsableForFactor4(self) -> bool:
"""The selected core is usable if it is in second extruder of Factor4
"""
result = True
if not self._global_container_stack:
return result
if self.activeMachine.definition.id != "ultimaker_factor4":
return result
for extruder_container in self._global_container_stack.extruderList:
if extruder_container.definition.id.startswith("ultimaker_factor4_extruder_right"):
if extruder_container.material == empty_material_container:
return True
if extruder_container.variant.id.startswith("ultimaker_factor4_bb"):
return False
return True
@pyqtSlot(str, result = str) @pyqtSlot(str, result = str)
def getDefinitionByMachineId(self, machine_id: str) -> Optional[str]: def getDefinitionByMachineId(self, machine_id: str) -> Optional[str]:
"""Get the Definition ID of a machine (specified by ID) """Get the Definition ID of a machine (specified by ID)

View File

@ -1,6 +1,6 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import List, Optional, TYPE_CHECKING from typing import List, Optional, Set, TYPE_CHECKING
from PyQt6.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal from PyQt6.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal
from UM.FlameProfiler import pyqtSlot from UM.FlameProfiler import pyqtSlot
@ -168,37 +168,26 @@ class SettingInheritanceManager(QObject):
def settingsWithInheritanceWarning(self) -> List[str]: def settingsWithInheritanceWarning(self) -> List[str]:
return self._settings_with_inheritance_warning return self._settings_with_inheritance_warning
def _settingIsOverwritingInheritance(self, key: str, stack: ContainerStack = None) -> bool: def _userSettingIsOverwritingInheritance(self, key: str, stack: ContainerStack, all_keys: Set[str] = set()) -> bool:
"""Check if a setting has an inheritance function that is overwritten""" """Check if a setting known as having a User state has an inheritance function that is overwritten"""
has_setting_function = False has_setting_function = False
if not stack:
stack = self._active_container_stack
if not stack: # No active container stack yet!
return False
if self._active_container_stack is None:
return False
all_keys = self._active_container_stack.getAllKeys()
containers = [] # type: List[ContainerInterface] containers = [] # type: List[ContainerInterface]
has_user_state = stack.getProperty(key, "state") == InstanceState.User
"""Check if the setting has a user state. If not, it is never overwritten."""
if not has_user_state:
return False
# If a setting is not enabled, don't label it as overwritten (It's never visible anyway). # If a setting is not enabled, don't label it as overwritten (It's never visible anyway).
if not stack.getProperty(key, "enabled"): if not stack.getProperty(key, "enabled"):
return False return False
user_container = stack.getTop() user_container = stack.getTop()
"""Also check if the top container is not a setting function (this happens if the inheritance is restored).""" # Also check if the top container is not a setting function (this happens if the inheritance is restored).
if user_container and isinstance(user_container.getProperty(key, "value"), SettingFunction): if user_container and isinstance(user_container.getProperty(key, "value"), SettingFunction):
return False return False
if not all_keys:
all_keys = self._active_container_stack.getAllKeys()
## Mash all containers for all the stacks together. ## Mash all containers for all the stacks together.
while stack: while stack:
containers.extend(stack.getContainers()) containers.extend(stack.getContainers())
@ -229,17 +218,35 @@ class SettingInheritanceManager(QObject):
break # There is a setting function somewhere, stop looking deeper. break # There is a setting function somewhere, stop looking deeper.
return has_setting_function and has_non_function_value return has_setting_function and has_non_function_value
def _settingIsOverwritingInheritance(self, key: str, stack: ContainerStack = None) -> bool:
"""Check if a setting has an inheritance function that is overwritten"""
if not stack:
stack = self._active_container_stack
if not stack: # No active container stack yet!
return False
if self._active_container_stack is None:
return False
has_user_state = stack.getProperty(key, "state") == InstanceState.User
if not has_user_state:
return False
return self._userSettingIsOverwritingInheritance(key, stack)
def _update(self) -> None: def _update(self) -> None:
self._settings_with_inheritance_warning = [] # Reset previous data. self._settings_with_inheritance_warning = [] # Reset previous data.
# Make sure that the GlobalStack is not None. sometimes the globalContainerChanged signal gets here late. # Make sure that the GlobalStack is not None. sometimes the globalContainerChanged signal gets here late.
if self._global_container_stack is None: if self._global_container_stack is None or self._active_container_stack is None:
return return
# Check all setting keys that we know of and see if they are overridden. # Check all user setting keys that we know of and see if they are overridden.
for setting_key in self._global_container_stack.getAllKeys(): all_keys = self._active_container_stack.getAllKeys()
override = self._settingIsOverwritingInheritance(setting_key) for setting_key in self._active_container_stack.getAllKeysWithUserState():
if override: if self._userSettingIsOverwritingInheritance(setting_key, self._active_container_stack, all_keys):
self._settings_with_inheritance_warning.append(setting_key) self._settings_with_inheritance_warning.append(setting_key)
# Check all the categories if any of their children have their inheritance overwritten. # Check all the categories if any of their children have their inheritance overwritten.

View File

@ -1,8 +1,9 @@
# Copyright (c) 2024 Ultimaker B.V. # Copyright (c) 2024 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from PyQt6.QtCore import Qt from PyQt6.QtCore import Qt, pyqtSignal
from UM import i18nCatalog
from UM.Logger import Logger from UM.Logger import Logger
from UM.Settings.SettingDefinition import SettingDefinition from UM.Settings.SettingDefinition import SettingDefinition
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
@ -19,9 +20,11 @@ class SpecificSettingsModel(ListModel):
self.addRoleName(self.LabelRole, "label") self.addRoleName(self.LabelRole, "label")
self.addRoleName(self.ValueRole, "value") self.addRoleName(self.ValueRole, "value")
self._i18n_catalog = None self._settings_catalog = i18nCatalog("fdmprinter.def.json")
self._update() self._update()
modelChanged = pyqtSignal()
def addSettingsFromStack(self, stack, category, settings): def addSettingsFromStack(self, stack, category, settings):
for setting, value in settings.items(): for setting, value in settings.items():
@ -30,17 +33,30 @@ class SpecificSettingsModel(ListModel):
setting_type = stack.getProperty(setting, "type") setting_type = stack.getProperty(setting, "type")
if setting_type is not None: if setting_type is not None:
# This is not very good looking, but will do for now # This is not very good looking, but will do for now
value = str(SettingDefinition.settingValueToString(setting_type, value)) + " " + str(unit) value = str(SettingDefinition.settingValueToString(setting_type, value))
if unit:
value += " " + str(unit)
if setting_type == "enum":
options = stack.getProperty(setting, "options")
value_msgctxt = f"{str(setting)} option {str(value)}"
value_msgid = options[stack.getProperty(setting, "value")]
value = self._settings_catalog.i18nc(value_msgctxt, value_msgid)
else: else:
value = str(value) value = str(value)
label_msgctxt = f"{str(setting)} label"
label_msgid = stack.getProperty(setting, "label")
label = self._settings_catalog.i18nc(label_msgctxt, label_msgid)
self.appendItem({ self.appendItem({
"category": category, "category": category,
"label": stack.getProperty(setting, "label"), "label": label,
"value": value "value": value
}) })
self.modelChanged.emit()
def _update(self): def _update(self):
Logger.debug(f"Updating {self.__class__.__name__}") Logger.debug(f"Updating {self.__class__.__name__}")
self.setItems([]) self.setItems([])
self.modelChanged.emit()
return return

View File

@ -17,6 +17,7 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from UM.Scene.GroupDecorator import GroupDecorator from UM.Scene.GroupDecorator import GroupDecorator
from UM.Scene.SceneNode import SceneNode # For typing. from UM.Scene.SceneNode import SceneNode # For typing.
from UM.Scene.SceneNodeSettings import SceneNodeSettings from UM.Scene.SceneNodeSettings import SceneNodeSettings
from UM.Util import parseBool
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.Machines.ContainerTree import ContainerTree from cura.Machines.ContainerTree import ContainerTree
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
@ -182,7 +183,7 @@ class ThreeMFReader(MeshReader):
um_node.printOrder = int(setting_value) um_node.printOrder = int(setting_value)
continue continue
if key =="drop_to_buildplate": if key =="drop_to_buildplate":
um_node.setSetting(SceneNodeSettings.AutoDropDown, eval(setting_value)) um_node.setSetting(SceneNodeSettings.AutoDropDown, parseBool(setting_value))
continue continue
if key in known_setting_keys: if key in known_setting_keys:
setting_container.setProperty(key, "value", setting_value) setting_container.setProperty(key, "value", setting_value)

View File

@ -10,6 +10,8 @@ from typing import cast, Dict, List, Optional, Tuple, Any, Set
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from UM.Math.AxisAlignedBox import AxisAlignedBox
from UM.Math.Vector import Vector
from UM.Util import parseBool from UM.Util import parseBool
from UM.Workspace.WorkspaceReader import WorkspaceReader from UM.Workspace.WorkspaceReader import WorkspaceReader
from UM.Application import Application from UM.Application import Application
@ -936,6 +938,24 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if nodes is None: if nodes is None:
nodes = [] nodes = []
if self._is_ucp:
# We might be on a different printer than the one this project was made on.
# The offset to the printers' center isn't saved; instead, try to just fit everything on the buildplate.
full_extents = None
for node in nodes:
extents = node.getMeshData().getExtents() if node.getMeshData() else None
if extents is not None:
pos = node.getPosition()
node_box = AxisAlignedBox(extents.minimum + pos, extents.maximum + pos)
if full_extents is None:
full_extents = node_box
else:
full_extents = full_extents + node_box
if full_extents and full_extents.isValid():
for node in nodes:
pos = node.getPosition()
node.setPosition(Vector(pos.x - full_extents.center.x, pos.y, pos.z - full_extents.center.z))
base_file_name = os.path.basename(file_name) base_file_name = os.path.basename(file_name)
self.setWorkspaceName(base_file_name) self.setWorkspaceName(base_file_name)
@ -1225,7 +1245,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
node = machine_node.variants.get(machine_node.preferred_variant_name, next(iter(machine_node.variants.values()))) node = machine_node.variants.get(machine_node.preferred_variant_name, next(iter(machine_node.variants.values())))
else: else:
variant_name = extruder_info.variant_info.parser["general"]["name"] variant_name = extruder_info.variant_info.parser["general"]["name"]
node = ContainerTree.getInstance().machines[global_stack.definition.getId()].variants[variant_name] node = ContainerTree.getInstance().machines[global_stack.definition.getId()].variants.get(variant_name, next(iter(machine_node.variants.values())))
extruder_stack.variant = node.container extruder_stack.variant = node.container
def _applyMaterials(self, global_stack, extruder_stack_dict): def _applyMaterials(self, global_stack, extruder_stack_dict):
@ -1240,7 +1260,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
root_material_id = extruder_info.root_material_id root_material_id = extruder_info.root_material_id
root_material_id = self._old_new_materials.get(root_material_id, root_material_id) root_material_id = self._old_new_materials.get(root_material_id, root_material_id)
material_node = machine_node.variants[extruder_stack.variant.getName()].materials[root_material_id] available_materials = machine_node.variants[extruder_stack.variant.getName()].materials
if root_material_id not in available_materials:
continue
material_node = available_materials[root_material_id]
extruder_stack.material = material_node.container extruder_stack.material = material_node.container
def _clearMachineSettings(self, global_stack, extruder_stack_dict): def _clearMachineSettings(self, global_stack, extruder_stack_dict):

View File

@ -77,6 +77,7 @@ class WorkspaceDialog(QObject):
self._is_compatible_machine = False self._is_compatible_machine = False
self._allow_create_machine = True self._allow_create_machine = True
self._exported_settings_model = SpecificSettingsModel() self._exported_settings_model = SpecificSettingsModel()
self._exported_settings_model.modelChanged.connect(self.exportedSettingModelChanged.emit)
self._current_machine_pos_index = 0 self._current_machine_pos_index = 0
self._is_ucp = False self._is_ucp = False
@ -104,6 +105,7 @@ class WorkspaceDialog(QObject):
missingPackagesChanged = pyqtSignal() missingPackagesChanged = pyqtSignal()
isCompatibleMachineChanged = pyqtSignal() isCompatibleMachineChanged = pyqtSignal()
isUcpChanged = pyqtSignal() isUcpChanged = pyqtSignal()
exportedSettingModelChanged = pyqtSignal()
@pyqtProperty(bool, notify = isPrinterGroupChanged) @pyqtProperty(bool, notify = isPrinterGroupChanged)
def isPrinterGroup(self) -> bool: def isPrinterGroup(self) -> bool:
@ -356,10 +358,17 @@ class WorkspaceDialog(QObject):
def allowCreateMachine(self): def allowCreateMachine(self):
return self._allow_create_machine return self._allow_create_machine
@pyqtProperty(QObject) @pyqtProperty(QObject, notify=exportedSettingModelChanged)
def exportedSettingModel(self): def exportedSettingModel(self):
return self._exported_settings_model return self._exported_settings_model
@pyqtProperty("QVariantList", notify=exportedSettingModelChanged)
def exportedSettingModelItems(self):
return self._exported_settings_model.items
@pyqtProperty(int, notify=exportedSettingModelChanged)
def exportedSettingModelRowCount(self):
return self._exported_settings_model.rowCount()
@pyqtSlot() @pyqtSlot()
def closeBackend(self) -> None: def closeBackend(self) -> None:
"""Close the backend: otherwise one could end up with "Slicing...""" """Close the backend: otherwise one could end up with "Slicing..."""

View File

@ -24,31 +24,36 @@ UM.Dialog
{ {
height: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height height: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height
color: UM.Theme.getColor("main_background") color: UM.Theme.getColor("main_background")
ColumnLayout
{
id: headerColumn
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.rightMargin: anchors.leftMargin
RowLayout
{
UM.Label UM.Label
{ {
id: titleLabel id: titleLabel
text: manager.isUcp? catalog.i18nc("@action:title Don't translate 'Universal Cura Project'", "Summary - Open Universal Cura Project (UCP)"): catalog.i18nc("@action:title", "Summary - Cura Project") text: manager.isUcp? catalog.i18nc("@action:title Don't translate 'Universal Cura Project'", "Summary - Open Universal Cura Project (UCP)"): catalog.i18nc("@action:title", "Summary - Cura Project")
font: UM.Theme.getFont("large") 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
} }
Cura.TertiaryButton Cura.TertiaryButton
{ {
id: learnMoreButton id: learnMoreButton
visible: manager.isUcp visible: manager.isUcp
anchors.right: parent.right
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.rightMargin: UM.Theme.getSize("default_margin").height
text: catalog.i18nc("@button", "Learn more") text: catalog.i18nc("@button", "Learn more")
iconSource: UM.Theme.getIcon("LinkExternal") iconSource: UM.Theme.getIcon("LinkExternal")
isIconOnRightSide: true isIconOnRightSide: true
onClicked: Qt.openUrlExternally("https://support.ultimaker.com/s/article/000002979") onClicked: Qt.openUrlExternally("https://support.ultimaker.com/s/article/000002979")
} }
} }
}
}
Rectangle Rectangle
{ {
@ -184,8 +189,9 @@ UM.Dialog
WorkspaceRow WorkspaceRow
{ {
id: numberOfOverrides
leftLabelText: catalog.i18nc("@action:label", "Settings Loaded from UCP file") leftLabelText: catalog.i18nc("@action:label", "Settings Loaded from UCP file")
rightLabelText: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.exportedSettingModel.rowCount()).arg(manager.exportedSettingModel.rowCount()) rightLabelText: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.exportedSettingModelRowCount).arg(manager.exportedSettingModelRowCount)
buttonText: tableViewSpecificSettings.shouldBeVisible ? catalog.i18nc("@action:button", "Hide settings") : catalog.i18nc("@action:button", "Show settings") buttonText: tableViewSpecificSettings.shouldBeVisible ? catalog.i18nc("@action:button", "Hide settings") : catalog.i18nc("@action:button", "Show settings")
onButtonClicked: tableViewSpecificSettings.shouldBeVisible = !tableViewSpecificSettings.shouldBeVisible onButtonClicked: tableViewSpecificSettings.shouldBeVisible = !tableViewSpecificSettings.shouldBeVisible
} }
@ -208,15 +214,18 @@ UM.Dialog
{ {
id: tableModel id: tableModel
headers: ["category", "label", "value"] headers: ["category", "label", "value"]
rows: manager.exportedSettingModel.items rows: manager.exportedSettingModelItems
}
} }
property var modelRows: manager.exportedSettingModel.items Connections
onModelRowsChanged: {
target: manager
function onExportedSettingModelChanged()
{ {
tableModel.clear() tableModel.clear()
tableModel.rows = modelRows tableModel.rows = manager.exportedSettingModelItems
}
}
} }
} }

View File

@ -91,11 +91,11 @@ Item
} }
} }
Loader Loader
{ {
width: parent.width width: parent.width
height: content.height height: content.height
z: -1
anchors.top: sectionTitleRow.bottom anchors.top: sectionTitleRow.bottom
sourceComponent: content sourceComponent: content
} }

View File

@ -6,13 +6,15 @@ from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal
class SettingExport(QObject): class SettingExport(QObject):
def __init__(self, id, name, value, selectable): def __init__(self, id, name, value, value_name, selectable, show):
super().__init__() super().__init__()
self.id = id self.id = id
self._name = name self._name = name
self._value = value self._value = value
self._value_name = value_name
self._selected = selectable self._selected = selectable
self._selectable = selectable self._selectable = selectable
self._show_in_menu = show
@pyqtProperty(str, constant=True) @pyqtProperty(str, constant=True)
def name(self): def name(self):
@ -22,6 +24,10 @@ class SettingExport(QObject):
def value(self): def value(self):
return self._value return self._value
@pyqtProperty(str, constant=True)
def valuename(self):
return str(self._value_name)
selectedChanged = pyqtSignal(bool) selectedChanged = pyqtSignal(bool)
def setSelected(self, selected): def setSelected(self, selected):
@ -36,3 +42,7 @@ class SettingExport(QObject):
@pyqtProperty(bool, constant=True) @pyqtProperty(bool, constant=True)
def selectable(self): def selectable(self):
return self._selectable return self._selectable
@pyqtProperty(bool, constant=True)
def isVisible(self):
return self._show_in_menu

View File

@ -24,7 +24,7 @@ RowLayout
UM.Label UM.Label
{ {
text: modelData.value text: modelData.valuename
} }
UM.HelpIcon UM.HelpIcon

View File

@ -23,6 +23,7 @@ class SettingsExportGroup(QObject):
self._category_details = category_details self._category_details = category_details
self._extruder_index = extruder_index self._extruder_index = extruder_index
self._extruder_color = extruder_color self._extruder_color = extruder_color
self._visible_settings = []
@pyqtProperty(str, constant=True) @pyqtProperty(str, constant=True)
def name(self): def name(self):
@ -32,6 +33,12 @@ class SettingsExportGroup(QObject):
def settings(self): def settings(self):
return self._settings return self._settings
@pyqtProperty(list, constant=True)
def visibleSettings(self):
if self._visible_settings == []:
self._visible_settings = list(filter(lambda item : item.isVisible, self._settings))
return self._visible_settings
@pyqtProperty(int, constant=True) @pyqtProperty(int, constant=True)
def category(self): def category(self):
return self._category return self._category

View File

@ -6,6 +6,7 @@ from typing import Optional, cast, List, Dict, Pattern, Set
from PyQt6.QtCore import QObject, pyqtProperty from PyQt6.QtCore import QObject, pyqtProperty
from UM import i18nCatalog
from UM.Settings.SettingDefinition import SettingDefinition from UM.Settings.SettingDefinition import SettingDefinition
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingFunction import SettingFunction from UM.Settings.SettingFunction import SettingFunction
@ -109,6 +110,7 @@ class SettingsExportModel(QObject):
@staticmethod @staticmethod
def _exportSettings(settings_stack): def _exportSettings(settings_stack):
settings_catalog = i18nCatalog("fdmprinter.def.json")
user_settings_container = settings_stack.userChanges user_settings_container = settings_stack.userChanges
user_keys = user_settings_container.getAllKeys() user_keys = user_settings_container.getAllKeys()
exportable_settings = SettingsExportModel.EXPORTABLE_SETTINGS exportable_settings = SettingsExportModel.EXPORTABLE_SETTINGS
@ -117,11 +119,22 @@ class SettingsExportModel(QObject):
is_exportable = any(key in SettingsExportModel.PER_MODEL_EXPORTABLE_SETTINGS_KEYS for key in user_keys) is_exportable = any(key in SettingsExportModel.PER_MODEL_EXPORTABLE_SETTINGS_KEYS for key in user_keys)
for setting_to_export in user_keys: for setting_to_export in user_keys:
label = settings_stack.getProperty(setting_to_export, "label") show_in_menu = setting_to_export not in SettingsExportModel.PER_MODEL_EXPORTABLE_SETTINGS_KEYS
label_msgtxt = f"{str(setting_to_export)} label"
label_msgid = settings_stack.getProperty(setting_to_export, "label")
label = settings_catalog.i18nc(label_msgtxt, label_msgid)
value = settings_stack.getProperty(setting_to_export, "value") value = settings_stack.getProperty(setting_to_export, "value")
unit = settings_stack.getProperty(setting_to_export, "unit") unit = settings_stack.getProperty(setting_to_export, "unit")
setting_type = settings_stack.getProperty(setting_to_export, "type") setting_type = settings_stack.getProperty(setting_to_export, "type")
value_name = str(SettingDefinition.settingValueToString(setting_type, value))
if unit:
value_name += " " + str(unit)
if setting_type == "enum":
options = settings_stack.getProperty(setting_to_export, "options")
value_msgctxt = f"{str(setting_to_export)} option {str(value)}"
value_msgid = options.get(value, "")
value_name = settings_catalog.i18nc(value_msgctxt, value_msgid)
if setting_type is not None: if setting_type is not None:
value = f"{str(SettingDefinition.settingValueToString(setting_type, value))} {unit}" value = f"{str(SettingDefinition.settingValueToString(setting_type, value))} {unit}"
else: else:
@ -130,6 +143,8 @@ class SettingsExportModel(QObject):
settings_export.append(SettingExport(setting_to_export, settings_export.append(SettingExport(setting_to_export,
label, label,
value, value,
is_exportable or setting_to_export in exportable_settings)) value_name,
is_exportable or setting_to_export in exportable_settings,
show_in_menu))
return settings_export return settings_export

View File

@ -71,8 +71,8 @@ ColumnLayout
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: contentHeight Layout.preferredHeight: contentHeight
spacing: 0 spacing: 0
model: modelData.settings model: modelData.visibleSettings
visible: modelData.settings.length > 0 visible: modelData.visibleSettings.length > 0
delegate: SettingSelection { } delegate: SettingSelection { }
} }
@ -80,8 +80,7 @@ ColumnLayout
UM.Label UM.Label
{ {
UM.I18nCatalog { id: catalog; name: "cura" } UM.I18nCatalog { id: catalog; name: "cura" }
text: catalog.i18nc("@label", "No specific value has been set") text: catalog.i18nc("@label", "No specific value has been set")
visible: modelData.settings.length === 0 visible: modelData.visibleSettings.length === 0
} }
} }

View File

@ -96,7 +96,8 @@ class ThreeMFWriter(MeshWriter):
@staticmethod @staticmethod
def _convertUMNodeToSavitarNode(um_node, def _convertUMNodeToSavitarNode(um_node,
transformation = Matrix(), transformation = Matrix(),
exported_settings: Optional[Dict[str, Set[str]]] = None): exported_settings: Optional[Dict[str, Set[str]]] = None,
center_mesh = False):
"""Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode """Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode
:returns: Uranium Scene node. :returns: Uranium Scene node.
@ -111,20 +112,26 @@ class ThreeMFWriter(MeshWriter):
savitar_node = Savitar.SceneNode() savitar_node = Savitar.SceneNode()
savitar_node.setName(um_node.getName()) savitar_node.setName(um_node.getName())
node_matrix = Matrix()
mesh_data = um_node.getMeshData() mesh_data = um_node.getMeshData()
node_matrix = um_node.getLocalTransformation()
node_matrix.preMultiply(transformation)
if center_mesh:
center_matrix = Matrix()
# compensate for original center position, if object(s) is/are not around its zero position # compensate for original center position, if object(s) is/are not around its zero position
if mesh_data is not None: if mesh_data is not None:
extents = mesh_data.getExtents() extents = mesh_data.getExtents()
if extents is not None: if extents is not None:
# We use a different coordinate space while writing, so flip Z and Y # We use a different coordinate space while writing, so flip Z and Y
center_vector = Vector(extents.center.x, extents.center.z, extents.center.y) center_vector = Vector(-extents.center.x, -extents.center.y, -extents.center.z)
node_matrix.setByTranslation(center_vector) center_matrix.setByTranslation(center_vector)
node_matrix.multiply(um_node.getLocalTransformation()) node_matrix.preMultiply(center_matrix)
matrix_string = ThreeMFWriter._convertMatrixToString(node_matrix.preMultiply(transformation)) matrix_string = ThreeMFWriter._convertMatrixToString(node_matrix)
savitar_node.setTransformation(matrix_string) savitar_node.setTransformation(matrix_string)
if mesh_data is not None: if mesh_data is not None:
savitar_node.getMeshData().setVerticesFromBytes(mesh_data.getVerticesAsByteArray()) savitar_node.getMeshData().setVerticesFromBytes(mesh_data.getVerticesAsByteArray())
indices_array = mesh_data.getIndicesAsByteArray() indices_array = mesh_data.getIndicesAsByteArray()
@ -283,7 +290,8 @@ class ThreeMFWriter(MeshWriter):
for root_child in node.getChildren(): for root_child in node.getChildren():
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(root_child, savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(root_child,
transformation_matrix, transformation_matrix,
exported_model_settings) exported_model_settings,
center_mesh = True)
if savitar_node: if savitar_node:
savitar_scene.addSceneNode(savitar_node) savitar_scene.addSceneNode(savitar_node)
else: else:
@ -442,7 +450,7 @@ class ThreeMFWriter(MeshWriter):
def sceneNodesToString(scene_nodes: [SceneNode]) -> str: def sceneNodesToString(scene_nodes: [SceneNode]) -> str:
savitar_scene = Savitar.Scene() savitar_scene = Savitar.Scene()
for scene_node in scene_nodes: for scene_node in scene_nodes:
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(scene_node) savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(scene_node, center_mesh = True)
savitar_scene.addSceneNode(savitar_node) savitar_scene.addSceneNode(savitar_node)
parser = Savitar.ThreeMFParser() parser = Savitar.ThreeMFParser()
scene_string = parser.sceneToString(savitar_scene) scene_string = parser.sceneToString(savitar_scene)

View File

@ -69,7 +69,7 @@ class UCPDialog(QObject):
device.writeSuccess.connect(lambda: self._onSuccess()) device.writeSuccess.connect(lambda: self._onSuccess())
device.writeFinished.connect(lambda: self._onFinished()) device.writeFinished.connect(lambda: self._onFinished())
file_name = CuraApplication.getInstance().getPrintInformation().baseName file_name = f"UCP_{CuraApplication.getInstance().getPrintInformation().baseName}"
try: try:
device.requestWrite( device.requestWrite(

View File

@ -544,7 +544,7 @@ class CuraEngineBackend(QObject, Backend):
if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder: if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder:
self._error_message = Message(catalog.i18nc("@info:status", self._error_message = Message(catalog.i18nc("@info:status",
"Unable to slice because there are objects associated with disabled Extruder %s.") % job.getMessage(), "Unable to slice because there are objects associated with disabled Extruder %s.") % job.getAssociatedDisabledExtruders(),
title = catalog.i18nc("@info:title", "Unable to slice"), title = catalog.i18nc("@info:title", "Unable to slice"),
message_type = Message.MessageType.WARNING) message_type = Message.MessageType.WARNING)
self._error_message.show() self._error_message.show()

View File

@ -146,6 +146,7 @@ class StartSliceJob(Job):
self._slice_message: Arcus.PythonMessage = slice_message self._slice_message: Arcus.PythonMessage = slice_message
self._is_cancelled: bool = False self._is_cancelled: bool = False
self._build_plate_number: Optional[int] = None self._build_plate_number: Optional[int] = None
self._associated_disabled_extruders: Optional[str] = None
# cache for all setting values from all stacks (global & extruder) for the current machine # cache for all setting values from all stacks (global & extruder) for the current machine
self._all_extruders_settings: Optional[Dict[str, Any]] = None self._all_extruders_settings: Optional[Dict[str, Any]] = None
@ -153,6 +154,9 @@ class StartSliceJob(Job):
def getSliceMessage(self) -> Arcus.PythonMessage: def getSliceMessage(self) -> Arcus.PythonMessage:
return self._slice_message return self._slice_message
def getAssociatedDisabledExtruders(self) -> Optional[str]:
return self._associated_disabled_extruders
def setBuildPlate(self, build_plate_number: int) -> None: def setBuildPlate(self, build_plate_number: int) -> None:
self._build_plate_number = build_plate_number self._build_plate_number = build_plate_number
@ -334,7 +338,7 @@ class StartSliceJob(Job):
if has_model_with_disabled_extruders: if has_model_with_disabled_extruders:
self.setResult(StartJobResult.ObjectsWithDisabledExtruder) self.setResult(StartJobResult.ObjectsWithDisabledExtruder)
associated_disabled_extruders = {p + 1 for p in associated_disabled_extruders} associated_disabled_extruders = {p + 1 for p in associated_disabled_extruders}
self.setMessage(", ".join(map(str, sorted(associated_disabled_extruders)))) self._associated_disabled_extruders = ", ".join(map(str, sorted(associated_disabled_extruders)))
return return
# There are cases when there is nothing to slice. This can happen due to one at a time slicing not being # There are cases when there is nothing to slice. This can happen due to one at a time slicing not being
@ -362,7 +366,12 @@ class StartSliceJob(Job):
for extruder_stack in global_stack.extruderList: for extruder_stack in global_stack.extruderList:
self._buildExtruderMessage(extruder_stack) self._buildExtruderMessage(extruder_stack)
for plugin in CuraApplication.getInstance().getBackendPlugins(): backend_plugins = CuraApplication.getInstance().getBackendPlugins()
# Sort backend plugins by name. Not a very good strategy, but at least it is repeatable. This will be improved later.
backend_plugins = sorted(backend_plugins, key=lambda backend_plugin: backend_plugin.getId())
for plugin in backend_plugins:
if not plugin.usePlugin(): if not plugin.usePlugin():
continue continue
for slot in plugin.getSupportedSlots(): for slot in plugin.getSupportedSlots():
@ -550,9 +559,13 @@ class StartSliceJob(Job):
start_gcode = settings["machine_start_gcode"] start_gcode = settings["machine_start_gcode"]
# Remove all the comments from the start g-code # Remove all the comments from the start g-code
start_gcode = re.sub(r";.+?(\n|$)", "\n", start_gcode) start_gcode = re.sub(r";.+?(\n|$)", "\n", start_gcode)
if settings["material_bed_temp_prepend"]:
bed_temperature_settings = ["material_bed_temperature", "material_bed_temperature_layer_0"] bed_temperature_settings = ["material_bed_temperature", "material_bed_temperature_layer_0"]
pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(bed_temperature_settings) # match {setting} as well as {setting, extruder_nr} pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(bed_temperature_settings) # match {setting} as well as {setting, extruder_nr}
settings["material_bed_temp_prepend"] = re.search(pattern, start_gcode) == None settings["material_bed_temp_prepend"] = re.search(pattern, start_gcode) == None
if settings["material_print_temp_prepend"]:
print_temperature_settings = ["material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature", "print_temperature"] print_temperature_settings = ["material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature", "print_temperature"]
pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(print_temperature_settings) # match {setting} as well as {setting, extruder_nr} pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(print_temperature_settings) # match {setting} as well as {setting, extruder_nr}
settings["material_print_temp_prepend"] = re.search(pattern, start_gcode) is None settings["material_print_temp_prepend"] = re.search(pattern, start_gcode) is None

View File

@ -208,7 +208,14 @@ Item
anchors.rightMargin: UM.Theme.getSize("thin_margin").height anchors.rightMargin: UM.Theme.getSize("thin_margin").height
enabled: UM.Backend.state == UM.Backend.Done enabled: UM.Backend.state == UM.Backend.Done
currentIndex: UM.Backend.state == UM.Backend.Done ? dfFilenameTextfield.text.startsWith("MM")? 1 : 0 : 2
// Pre-select the correct index, depending on the situation (see the model-property below):
// - Don't select any post-slice-file-format when the engine isn't done.
// - Choose either the S-series or the Makerbot-series of printers' format otherwise, depending on the active printer.
// This way, the user can just click 'save' without having to worry about wether or not the format is right.
property int isMakerbotFormat: Cura.MachineManager.activeMachine.getOutputFileFormats.includes("application/x-makerbot") || Cura.MachineManager.activeMachine.getOutputFileFormats.includes("application/x-makerbot-sketch")
property int isBackendDone: UM.Backend.state == UM.Backend.Done
currentIndex: isBackendDone ? (isMakerbotFormat ? 1 : 0) : 2
textRole: "text" textRole: "text"
valueRole: "value" valueRole: "value"

View File

@ -1,9 +1,8 @@
# Copyright (c) 2023 UltiMaker # Copyright (c) 2024 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from io import StringIO, BufferedIOBase from io import StringIO, BufferedIOBase
import json import json
from typing import cast, List, Optional, Dict from typing import cast, List, Optional, Dict, Tuple
from zipfile import BadZipFile, ZipFile, ZIP_DEFLATED from zipfile import BadZipFile, ZipFile, ZIP_DEFLATED
import pyDulcificum as du import pyDulcificum as du
@ -19,6 +18,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.FormatMaps import FormatMaps
from cura.Snapshot import Snapshot from cura.Snapshot import Snapshot
from cura.Utils.Threading import call_on_qt_thread from cura.Utils.Threading import call_on_qt_thread
from cura.CuraVersion import ConanInstalls from cura.CuraVersion import ConanInstalls
@ -39,16 +39,27 @@ class MakerbotWriter(MeshWriter):
suffixes=["makerbot"] suffixes=["makerbot"]
) )
) )
MimeTypeDatabase.addMimeType(
MimeType(
name="application/x-makerbot-sketch",
comment="Makerbot Toolpath Package",
suffixes=["makerbot"]
)
)
_PNG_FORMATS = [ _PNG_FORMAT = [
{"prefix": "isometric_thumbnail", "width": 120, "height": 120}, {"prefix": "isometric_thumbnail", "width": 120, "height": 120},
{"prefix": "isometric_thumbnail", "width": 320, "height": 320}, {"prefix": "isometric_thumbnail", "width": 320, "height": 320},
{"prefix": "isometric_thumbnail", "width": 640, "height": 640}, {"prefix": "isometric_thumbnail", "width": 640, "height": 640},
{"prefix": "thumbnail", "width": 90, "height": 90},
]
_PNG_FORMAT_METHOD = [
{"prefix": "thumbnail", "width": 140, "height": 106}, {"prefix": "thumbnail", "width": 140, "height": 106},
{"prefix": "thumbnail", "width": 212, "height": 300}, {"prefix": "thumbnail", "width": 212, "height": 300},
{"prefix": "thumbnail", "width": 960, "height": 1460}, {"prefix": "thumbnail", "width": 960, "height": 1460},
{"prefix": "thumbnail", "width": 90, "height": 90},
] ]
_META_VERSION = "3.0.0" _META_VERSION = "3.0.0"
# must be called from the main thread because of OpenGL # must be called from the main thread because of OpenGL
@ -74,6 +85,7 @@ class MakerbotWriter(MeshWriter):
return None return None
def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode=MeshWriter.OutputMode.BinaryMode) -> bool: def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode=MeshWriter.OutputMode.BinaryMode) -> bool:
metadata, file_format = self._getMeta(nodes)
if mode != MeshWriter.OutputMode.BinaryMode: if mode != MeshWriter.OutputMode.BinaryMode:
Logger.log("e", "MakerbotWriter does not support text mode.") Logger.log("e", "MakerbotWriter does not support text mode.")
self.setInformation(catalog.i18nc("@error:not supported", "MakerbotWriter does not support text mode.")) self.setInformation(catalog.i18nc("@error:not supported", "MakerbotWriter does not support text mode."))
@ -92,14 +104,20 @@ class MakerbotWriter(MeshWriter):
gcode_text_io = StringIO() gcode_text_io = StringIO()
success = gcode_writer.write(gcode_text_io, None) success = gcode_writer.write(gcode_text_io, None)
filename, filedata = "", ""
# Writing the g-code failed. Then I can also not write the gzipped g-code. # Writing the g-code failed. Then I can also not write the gzipped g-code.
if not success: if not success:
self.setInformation(gcode_writer.getInformation()) self.setInformation(gcode_writer.getInformation())
return False return False
match file_format:
json_toolpaths = du.gcode_2_miracle_jtp(gcode_text_io.getvalue()) case "application/x-makerbot-sketch":
metadata = self._getMeta(nodes) filename, filedata = "print.gcode", gcode_text_io.getvalue()
self._PNG_FORMATS = self._PNG_FORMAT
case "application/x-makerbot":
filename, filedata = "print.jsontoolpath", du.gcode_2_miracle_jtp(gcode_text_io.getvalue())
self._PNG_FORMATS = self._PNG_FORMAT + self._PNG_FORMAT_METHOD
case _:
raise Exception("Unsupported Mime type")
png_files = [] png_files = []
for png_format in self._PNG_FORMATS: for png_format in self._PNG_FORMATS:
@ -116,10 +134,34 @@ class MakerbotWriter(MeshWriter):
try: try:
with ZipFile(stream, "w", compression=ZIP_DEFLATED) as zip_stream: with ZipFile(stream, "w", compression=ZIP_DEFLATED) as zip_stream:
zip_stream.writestr("meta.json", json.dumps(metadata, indent=4)) zip_stream.writestr("meta.json", json.dumps(metadata, indent=4))
zip_stream.writestr("print.jsontoolpath", json_toolpaths) zip_stream.writestr(filename, filedata)
for png_file in png_files: for png_file in png_files:
file, data = png_file["file"], png_file["data"] file, data = png_file["file"], png_file["data"]
zip_stream.writestr(file, data) zip_stream.writestr(file, data)
api = CuraApplication.getInstance().getCuraAPI()
metadata_json = api.interface.settings.getSliceMetadata()
# All the mapping stuff we have to do:
product_to_id_map = FormatMaps.getProductIdMap()
printer_name_map = FormatMaps.getInversePrinterNameMap()
extruder_type_map = FormatMaps.getInverseExtruderTypeMap()
material_map = FormatMaps.getInverseMaterialMap()
for key, value in metadata_json.items():
if "all_settings" in value:
if "machine_name" in value["all_settings"]:
machine_name = value["all_settings"]["machine_name"]
if machine_name in product_to_id_map:
machine_name = product_to_id_map[machine_name][0]
value["all_settings"]["machine_name"] = printer_name_map.get(machine_name, machine_name)
if "machine_nozzle_id" in value["all_settings"]:
extruder_type = value["all_settings"]["machine_nozzle_id"]
value["all_settings"]["machine_nozzle_id"] = extruder_type_map.get(extruder_type, extruder_type)
if "material_type" in value["all_settings"]:
material_type = value["all_settings"]["material_type"]
value["all_settings"]["material_type"] = material_map.get(material_type, material_type)
slice_metadata = json.dumps(metadata_json, separators=(", ", ": "), indent=4)
zip_stream.writestr("slicemetadata.json", slice_metadata)
except (IOError, OSError, BadZipFile) as ex: except (IOError, OSError, BadZipFile) as ex:
Logger.log("e", f"Could not write to (.makerbot) file because: '{ex}'.") Logger.log("e", f"Could not write to (.makerbot) file because: '{ex}'.")
self.setInformation(catalog.i18nc("@error", "MakerbotWriter could not save to the designated path.")) self.setInformation(catalog.i18nc("@error", "MakerbotWriter could not save to the designated path."))
@ -127,7 +169,7 @@ class MakerbotWriter(MeshWriter):
return True return True
def _getMeta(self, root_nodes: List[SceneNode]) -> Dict[str, any]: def _getMeta(self, root_nodes: List[SceneNode]) -> Tuple[Dict[str, any], str]:
application = CuraApplication.getInstance() application = CuraApplication.getInstance()
machine_manager = application.getMachineManager() machine_manager = application.getMachineManager()
global_stack = machine_manager.activeMachine global_stack = machine_manager.activeMachine
@ -143,7 +185,9 @@ class MakerbotWriter(MeshWriter):
nodes.append(node) nodes.append(node)
meta = dict() meta = dict()
# This is a bit of a "hack", the mime type should be passed through with the export writer but
# since this is not the case we get the mime type from the global stack instead
file_format = global_stack.definition.getMetaDataEntry("file_formats")
meta["bot_type"] = global_stack.definition.getMetaDataEntry("reference_machine_id") meta["bot_type"] = global_stack.definition.getMetaDataEntry("reference_machine_id")
bounds: Optional[AxisAlignedBox] = None bounds: Optional[AxisAlignedBox] = None
@ -155,7 +199,8 @@ class MakerbotWriter(MeshWriter):
bounds = node_bounds bounds = node_bounds
else: else:
bounds = bounds + node_bounds bounds = bounds + node_bounds
if file_format == "application/x-makerbot-sketch":
bounds = None
if bounds is not None: if bounds is not None:
meta["bounding_box"] = { meta["bounding_box"] = {
"x_min": bounds.left, "x_min": bounds.left,
@ -196,7 +241,7 @@ class MakerbotWriter(MeshWriter):
meta["extruder_temperature"] = materials_temps[0] meta["extruder_temperature"] = materials_temps[0]
meta["extruder_temperatures"] = materials_temps meta["extruder_temperatures"] = materials_temps
meta["model_counts"] = [{"count": 1, "name": node.getName()} for node in nodes] meta["model_counts"] = [{"count": len(nodes), "name": "instance0"}]
tool_types = [extruder.variant.getMetaDataEntry("reference_extruder_id") for extruder in extruders] tool_types = [extruder.variant.getMetaDataEntry("reference_extruder_id") for extruder in extruders]
meta["tool_type"] = tool_types[0] meta["tool_type"] = tool_types[0]
@ -205,14 +250,13 @@ class MakerbotWriter(MeshWriter):
meta["version"] = MakerbotWriter._META_VERSION meta["version"] = MakerbotWriter._META_VERSION
meta["preferences"] = dict() meta["preferences"] = dict()
for node in nodes: bounds = application.getBuildVolume().getBoundingBox()
bounds = node.getBoundingBox() meta["preferences"]["instance0"] = {
meta["preferences"][str(node.getName())] = {
"machineBounds": [bounds.right, bounds.back, bounds.left, bounds.front] if bounds is not None else None, "machineBounds": [bounds.right, bounds.back, bounds.left, bounds.front] if bounds is not None else None,
"printMode": CuraApplication.getInstance().getIntentManager().currentIntentCategory, "printMode": CuraApplication.getInstance().getIntentManager().currentIntentCategory,
} }
meta["miracle_config"] = {"gaggles": {str(node.getName()): {} for node in nodes}} meta["miracle_config"] = {"gaggles": {"instance0": {}}}
version_info = dict() version_info = dict()
cura_engine_info = ConanInstalls.get("curaengine", {"version": "unknown", "revision": "unknown"}) cura_engine_info = ConanInstalls.get("curaengine", {"version": "unknown", "revision": "unknown"})
@ -245,7 +289,7 @@ class MakerbotWriter(MeshWriter):
# platform_temperature # platform_temperature
# total_commands # total_commands
return meta return meta, file_format
def meterToMillimeter(value: float) -> float: def meterToMillimeter(value: float) -> float:

View File

@ -11,14 +11,23 @@ catalog = i18nCatalog("cura")
def getMetaData(): def getMetaData():
file_extension = "makerbot" file_extension = "makerbot"
return { return {
"mesh_writer": { "mesh_writer":
"output": [{ {
"output": [
{
"extension": file_extension, "extension": file_extension,
"description": catalog.i18nc("@item:inlistbox", "Makerbot Printfile"), "description": catalog.i18nc("@item:inlistbox", "Makerbot Printfile"),
"mime_type": "application/x-makerbot", "mime_type": "application/x-makerbot",
"mode": MakerbotWriter.MakerbotWriter.OutputMode.BinaryMode, "mode": MakerbotWriter.MakerbotWriter.OutputMode.BinaryMode,
}], },
{
"extension": file_extension,
"description": catalog.i18nc("@item:inlistbox", "Makerbot Sketch Printfile"),
"mime_type": "application/x-makerbot-sketch",
"mode": MakerbotWriter.MakerbotWriter.OutputMode.BinaryMode,
} }
]
},
} }

View File

@ -26,27 +26,40 @@ class InsertAtLayerChange(Script):
}, },
"gcode_to_add": "gcode_to_add":
{ {
"label": "G-code to insert.", "label": "G-code to insert",
"description": "G-code to add before or after layer change.", "description": "G-code to add before or after layer change.",
"type": "str", "type": "str",
"default_value": "" "default_value": ""
},
"skip_layers":
{
"label": "Skip layers",
"description": "Number of layers to skip between insertions (0 for every layer).",
"type": "int",
"default_value": 0,
"minimum_value": 0
} }
} }
}""" }"""
def execute(self, data): def execute(self, data):
gcode_to_add = self.getSettingValueByKey("gcode_to_add") + "\n" gcode_to_add = self.getSettingValueByKey("gcode_to_add") + "\n"
skip_layers = self.getSettingValueByKey("skip_layers")
count = 0
for layer in data: for layer in data:
# Check that a layer is being printed # Check that a layer is being printed
lines = layer.split("\n") lines = layer.split("\n")
for line in lines: for line in lines:
if ";LAYER:" in line: if ";LAYER:" in line:
index = data.index(layer) index = data.index(layer)
if count == 0:
if self.getSettingValueByKey("insert_location") == "before": if self.getSettingValueByKey("insert_location") == "before":
layer = gcode_to_add + layer layer = gcode_to_add + layer
else: else:
layer = layer + gcode_to_add layer = layer + gcode_to_add
data[index] = layer data[index] = layer
count = (count + 1) % (skip_layers + 1)
break break
return data return data

View File

@ -153,7 +153,8 @@ class SimulationPass(RenderPass):
# In the current layer, we show just the indicated paths # In the current layer, we show just the indicated paths
if layer == self._layer_view._current_layer_num: if layer == self._layer_view._current_layer_num:
# We look for the position of the head, searching the point of the current path # We look for the position of the head, searching the point of the current path
index = int(self._layer_view.getCurrentPath()) index = int(self._layer_view.getCurrentPath()) if not math.isnan(
self._layer_view.getCurrentPath()) else 0
for polygon in layer_data.getLayer(layer).polygons: for polygon in layer_data.getLayer(layer).polygons:
# The size indicates all values in the two-dimension array, and the second dimension is # The size indicates all values in the two-dimension array, and the second dimension is
# always size 3 because we have 3D points. # always size 3 because we have 3D points.

View File

@ -1,5 +1,6 @@
# Copyright (c) 2021 Ultimaker B.V. # Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import math
import sys import sys
from PyQt6.QtCore import Qt from PyQt6.QtCore import Qt
@ -216,7 +217,8 @@ class SimulationView(CuraView):
Logger.warn( Logger.warn(
f"Binary search error (out of bounds): index {i}: left value {left_value} right value {right_value} and current time is {self._current_time}") f"Binary search error (out of bounds): index {i}: left value {left_value} right value {right_value} and current time is {self._current_time}")
fractional_value = (self._current_time - left_value) / (right_value - left_value) segment_duration = right_value - left_value
fractional_value = 0.0 if segment_duration == 0.0 else (self._current_time - left_value) / segment_duration
self.setPath(i + fractional_value) self.setPath(i + fractional_value)

View File

@ -356,7 +356,10 @@ geometry41core =
EndPrimitive(); EndPrimitive();
} }
if ((u_show_starts == 1) && (v_prev_line_type[0] != 1) && (v_line_type[0] == 1)) { if ((u_show_starts == 1) && (
((v_prev_line_type[0] != 1) && (v_line_type[0] == 1)) ||
((v_prev_line_type[0] != 4) && (v_line_type[0] == 4))
)) {
float w = size_x; float w = size_x;
float h = size_y; float h = size_y;

View File

@ -24,6 +24,7 @@ from UM.Settings.InstanceContainer import InstanceContainer
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
from cura.Utils.Threading import call_on_qt_thread from cura.Utils.Threading import call_on_qt_thread
from cura.API import CuraAPI
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
@ -85,7 +86,8 @@ class UFPWriter(MeshWriter):
try: try:
archive.addContentType(extension="json", mime_type="application/json") archive.addContentType(extension="json", mime_type="application/json")
setting_textio = StringIO() setting_textio = StringIO()
json.dump(self._getSliceMetadata(), setting_textio, separators=(", ", ": "), indent=4) api = CuraApplication.getInstance().getCuraAPI()
json.dump(api.interface.settings.getSliceMetadata(), setting_textio, separators=(", ", ": "), indent=4)
steam = archive.getStream(SLICE_METADATA_PATH) steam = archive.getStream(SLICE_METADATA_PATH)
steam.write(setting_textio.getvalue().encode("UTF-8")) steam.write(setting_textio.getvalue().encode("UTF-8"))
except EnvironmentError as e: except EnvironmentError as e:
@ -210,57 +212,3 @@ class UFPWriter(MeshWriter):
return [{"name": item.getName()} return [{"name": item.getName()}
for item in DepthFirstIterator(node) for item in DepthFirstIterator(node)
if item.getMeshData() is not None and not item.callDecoration("isNonPrintingMesh")] if item.getMeshData() is not None and not item.callDecoration("isNonPrintingMesh")]
def _getSliceMetadata(self) -> Dict[str, Dict[str, Dict[str, str]]]:
"""Get all changed settings and all settings. For each extruder and the global stack"""
print_information = CuraApplication.getInstance().getPrintInformation()
machine_manager = CuraApplication.getInstance().getMachineManager()
settings = {
"material": {
"length": print_information.materialLengths,
"weight": print_information.materialWeights,
"cost": print_information.materialCosts,
},
"global": {
"changes": {},
"all_settings": {},
},
"quality": asdict(machine_manager.activeQualityDisplayNameMap()),
}
def _retrieveValue(container: InstanceContainer, setting_: str):
value_ = container.getProperty(setting_, "value")
for _ in range(0, 1024): # Prevent possibly endless loop by not using a limit.
if not isinstance(value_, SettingFunction):
return value_ # Success!
value_ = value_(container)
return 0 # Fallback value after breaking possibly endless loop.
global_stack = cast(GlobalStack, Application.getInstance().getGlobalContainerStack())
# Add global user or quality changes
global_flattened_changes = InstanceContainer.createMergedInstanceContainer(global_stack.userChanges, global_stack.qualityChanges)
for setting in global_flattened_changes.getAllKeys():
settings["global"]["changes"][setting] = _retrieveValue(global_flattened_changes, setting)
# Get global all settings values without user or quality changes
for setting in global_stack.getAllKeys():
settings["global"]["all_settings"][setting] = _retrieveValue(global_stack, setting)
for i, extruder in enumerate(global_stack.extruderList):
# Add extruder fields to settings dictionary
settings[f"extruder_{i}"] = {
"changes": {},
"all_settings": {},
}
# Add extruder user or quality changes
extruder_flattened_changes = InstanceContainer.createMergedInstanceContainer(extruder.userChanges, extruder.qualityChanges)
for setting in extruder_flattened_changes.getAllKeys():
settings[f"extruder_{i}"]["changes"][setting] = _retrieveValue(extruder_flattened_changes, setting)
# Get extruder all settings values without user or quality changes
for setting in extruder.getAllKeys():
settings[f"extruder_{i}"]["all_settings"][setting] = _retrieveValue(extruder, setting)
return settings

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 KiB

View File

@ -5,6 +5,7 @@ import urllib.parse
from json import JSONDecodeError from json import JSONDecodeError
from time import time from time import time
from typing import Callable, List, Type, TypeVar, Union, Optional, Tuple, Dict, Any, cast from typing import Callable, List, Type, TypeVar, Union, Optional, Tuple, Dict, Any, cast
from pathlib import Path
from PyQt6.QtCore import QUrl from PyQt6.QtCore import QUrl
from PyQt6.QtNetwork import QNetworkRequest, QNetworkReply from PyQt6.QtNetwork import QNetworkRequest, QNetworkReply
@ -38,14 +39,17 @@ class CloudApiClient:
# The cloud URL to use for this remote cluster. # The cloud URL to use for this remote cluster.
ROOT_PATH = UltimakerCloudConstants.CuraCloudAPIRoot ROOT_PATH = UltimakerCloudConstants.CuraCloudAPIRoot
CLUSTER_API_ROOT = "{}/connect/v1".format(ROOT_PATH) CLUSTER_API_ROOT = f"{ROOT_PATH}/connect/v1"
CURA_API_ROOT = "{}/cura/v1".format(ROOT_PATH) CURA_API_ROOT = f"{ROOT_PATH}/cura/v1"
DEFAULT_REQUEST_TIMEOUT = 10 # seconds DEFAULT_REQUEST_TIMEOUT = 30 # seconds
# In order to avoid garbage collection we keep the callbacks in this list. # In order to avoid garbage collection we keep the callbacks in this list.
_anti_gc_callbacks = [] # type: List[Callable[[Any], None]] _anti_gc_callbacks = [] # type: List[Callable[[Any], None]]
# Custom machine definition ID to cloud cluster name mapping
_machine_id_to_name: Dict[str, str] = None
def __init__(self, app: CuraApplication, on_error: Callable[[List[CloudError]], None]) -> None: def __init__(self, app: CuraApplication, on_error: Callable[[List[CloudError]], None]) -> None:
"""Initializes a new cloud API client. """Initializes a new cloud API client.
@ -84,13 +88,9 @@ class CloudApiClient:
# conversion! # conversion!
# API points to "MakerBot Method" for a makerbot printertypes which we already changed to allign with other printer_type # API points to "MakerBot Method" for a makerbot printertypes which we already changed to allign with other printer_type
method_x = { machine_id_to_name = self.getMachineIDMap()
"ultimaker_method":"MakerBot Method", if machine_type in machine_id_to_name:
"ultimaker_methodx":"MakerBot Method X", machine_type = machine_id_to_name[machine_type]
"ultimaker_methodxl":"MakerBot Method XL"
}
if machine_type in method_x:
machine_type = method_x[machine_type]
else: else:
machine_type = machine_type.replace("_plus", "+") machine_type = machine_type.replace("_plus", "+")
machine_type = machine_type.replace("_", " ") machine_type = machine_type.replace("_", " ")
@ -174,6 +174,7 @@ class CloudApiClient:
:param cluster_id: The ID of the cluster. :param cluster_id: The ID of the cluster.
:param cluster_job_id: The ID of the print job within the cluster. :param cluster_job_id: The ID of the print job within the cluster.
:param action: The name of the action to execute. :param action: The name of the action to execute.
:param data: Optional data to send with the POST request
""" """
body = json.dumps({"data": data}).encode() if data else b"" body = json.dumps({"data": data}).encode() if data else b""
@ -216,8 +217,11 @@ class CloudApiClient:
Logger.logException("e", "Could not parse the stardust response: %s", error.toDict()) Logger.logException("e", "Could not parse the stardust response: %s", error.toDict())
return status_code, {"errors": [error.toDict()]} return status_code, {"errors": [error.toDict()]}
def _parseResponse(self, response: Dict[str, Any], on_finished: Union[Callable[[CloudApiClientModel], Any], def _parseResponse(self,
Callable[[List[CloudApiClientModel]], Any]], model_class: Type[CloudApiClientModel]) -> None: response: Dict[str, Any],
on_finished: Union[Callable[[CloudApiClientModel], Any],
Callable[[List[CloudApiClientModel]], Any]],
model_class: Type[CloudApiClientModel]) -> None:
"""Parses the given response and calls the correct callback depending on the result. """Parses the given response and calls the correct callback depending on the result.
:param response: The response from the server, after being converted to a dict. :param response: The response from the server, after being converted to a dict.
@ -276,3 +280,14 @@ class CloudApiClient:
self._anti_gc_callbacks.append(parse) self._anti_gc_callbacks.append(parse)
return parse return parse
@classmethod
def getMachineIDMap(cls) -> Dict[str, str]:
if cls._machine_id_to_name is None:
try:
with open(Path(__file__).parent / "machine_id_to_name.json", "rt") as f:
cls._machine_id_to_name = json.load(f)
except Exception as e:
Logger.logException("e", f"Could not load machine_id_to_name.json: '{e}'")
cls._machine_id_to_name = {}
return cls._machine_id_to_name

View File

@ -331,7 +331,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
return False return False
[printer, *_] = self._printers [printer, *_] = self._printers
return printer.type in ("MakerBot Method X", "MakerBot Method XL") return printer.type in ("MakerBot Method X", "MakerBot Method XL", "MakerBot Sketch")
@pyqtProperty(bool, notify=_cloudClusterPrintersChanged) @pyqtProperty(bool, notify=_cloudClusterPrintersChanged)
def supportsPrintJobActions(self) -> bool: def supportsPrintJobActions(self) -> bool:

View File

@ -0,0 +1,7 @@
{
"ultimaker_method": "MakerBot Method",
"ultimaker_methodx": "MakerBot Method X",
"ultimaker_methodxl": "MakerBot Method XL",
"ultimaker_factor4": "Ultimaker Factor 4",
"ultimaker_sketch": "MakerBot Sketch"
}

View File

@ -152,9 +152,3 @@ class VersionUpgrade22to24(VersionUpgrade):
config.write(output) config.write(output)
return [filename], [output.getvalue()] return [filename], [output.getvalue()]
def getCfgVersion(self, serialised: str) -> int:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
return format_version * 1000000 + setting_version

View File

@ -33,23 +33,6 @@ _renamed_i18n = {
class VersionUpgrade27to30(VersionUpgrade): class VersionUpgrade27to30(VersionUpgrade):
## Gets the version number from a CFG file in Uranium's 2.7 format.
#
# Since the format may change, this is implemented for the 2.7 format only
# and needs to be included in the version upgrade system rather than
# globally in Uranium.
#
# \param serialised The serialised form of a CFG file.
# \return The version number stored in the CFG file.
# \raises ValueError The format of the version number in the file is
# incorrect.
# \raises KeyError The format of the file is incorrect.
def getCfgVersion(self, serialised: str) -> int:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
return format_version * 1000000 + setting_version
## Upgrades a preferences file from version 2.7 to 3.0. ## Upgrades a preferences file from version 2.7 to 3.0.
# #

View File

@ -33,12 +33,6 @@ default_qualities_per_nozzle_and_material = {
class VersionUpgrade460to462(VersionUpgrade): class VersionUpgrade460to462(VersionUpgrade):
def getCfgVersion(self, serialised: str) -> int:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) # Explicitly give an exception when this fails. That means that the file format is not recognised.
setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
return format_version * 1000000 + setting_version
def upgradePreferences(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]: def upgradePreferences(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
""" """

View File

@ -17,6 +17,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.FormatMaps import FormatMaps
from cura.Machines.VariantType import VariantType from cura.Machines.VariantType import VariantType
try: try:
@ -249,7 +250,7 @@ class XmlMaterialProfile(InstanceContainer):
machine_variant_map[definition_id][variant_name] = variant_dict machine_variant_map[definition_id][variant_name] = variant_dict
# Map machine human-readable names to IDs # Map machine human-readable names to IDs
product_id_map = self.getProductIdMap() product_id_map = FormatMaps.getProductIdMap()
for definition_id, container in machine_container_map.items(): for definition_id, container in machine_container_map.items():
definition_id = container.getMetaDataEntry("definition") definition_id = container.getMetaDataEntry("definition")
@ -647,7 +648,7 @@ class XmlMaterialProfile(InstanceContainer):
self._dirty = False self._dirty = False
# Map machine human-readable names to IDs # Map machine human-readable names to IDs
product_id_map = self.getProductIdMap() product_id_map = FormatMaps.getProductIdMap()
machines = data.iterfind("./um:settings/um:machine", self.__namespaces) machines = data.iterfind("./um:settings/um:machine", self.__namespaces)
for machine in machines: for machine in machines:
@ -923,7 +924,7 @@ class XmlMaterialProfile(InstanceContainer):
result_metadata.append(base_metadata) result_metadata.append(base_metadata)
# Map machine human-readable names to IDs # Map machine human-readable names to IDs
product_id_map = cls.getProductIdMap() product_id_map = FormatMaps.getProductIdMap()
for machine in data.iterfind("./um:settings/um:machine", cls.__namespaces): for machine in data.iterfind("./um:settings/um:machine", cls.__namespaces):
machine_compatibility = common_compatibility machine_compatibility = common_compatibility
@ -1083,10 +1084,8 @@ class XmlMaterialProfile(InstanceContainer):
# Skip material properties (eg diameter) or metadata (eg GUID) # Skip material properties (eg diameter) or metadata (eg GUID)
return return
if instance.value is True: if tag_name != "cura:setting" and isinstance(instance.value, bool):
data = "yes" data = "yes" if instance.value else "no"
elif instance.value is False:
data = "no"
else: else:
data = str(instance.value) data = str(instance.value)
@ -1129,29 +1128,6 @@ class XmlMaterialProfile(InstanceContainer):
id_list = list(id_list) id_list = list(id_list)
return 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:
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 cls.__product_to_id_map
@staticmethod @staticmethod
def _parseCompatibleValue(value: str): def _parseCompatibleValue(value: str):
"""Parse the value of the "material compatible" property.""" """Parse the value of the "material compatible" property."""

View File

@ -1,7 +1,7 @@
[project] [project]
name = "printerlinter" name = "printerlinter"
description = "Cura UltiMaker printer linting tool" description = "Cura UltiMaker printer linting tool"
version = "0.1.1" version = "0.1.2"
authors = [ authors = [
{ name = "UltiMaker", email = "cura@ultimaker.com" } { name = "UltiMaker", email = "cura@ultimaker.com" }
] ]

View File

@ -32,3 +32,13 @@ class Diagnostic:
}, },
"Level": self.level "Level": self.level
} }
class GitComment:
def __init__(self, comment: str) -> None:
"""
@param comment: The comment text.
"""
self.comment = comment
def toDict(self) -> Dict[str, Any]:
return self.comment

View File

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

View File

@ -13,8 +13,11 @@ class Definition(Linter):
def __init__(self, file: Path, settings: dict) -> None: def __init__(self, file: Path, settings: dict) -> None:
super().__init__(file, settings) super().__init__(file, settings)
self._definitions = {} self._definitions = {}
self._definition_name = None
self._experimental_settings = []
self._loadDefinitionFiles(file) self._loadDefinitionFiles(file)
self._content = self._file.read_text() self._content = self._file.read_text()
self._loadExperimentalSettings()
self._loadBasePrinterSettings() self._loadBasePrinterSettings()
@property @property
@ -28,6 +31,14 @@ class Definition(Linter):
for check in self.checkRedefineOverride(): for check in self.checkRedefineOverride():
yield check yield check
if self._settings["checks"].get("diagnostic-material-temperature-defined", False):
for check in self.checkMaterialTemperature():
yield check
if self._settings["checks"].get("diagnostic-definition-experimental-setting", False):
for check in self.checkExperimentalSetting():
yield check
# Add other which will yield Diagnostic's # 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 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: A check if the key exist in the first place
@ -37,11 +48,10 @@ class Definition(Linter):
def checkRedefineOverride(self) -> Iterator[Diagnostic]: def checkRedefineOverride(self) -> Iterator[Diagnostic]:
""" Checks if definition file overrides its parents settings with the same value. """ """ Checks if definition file overrides its parents settings with the same value. """
definition_name = list(self._definitions.keys())[0] definition = self._definitions[self._definition_name]
definition = self._definitions[definition_name] if "overrides" in definition and self._definition_name not in ("fdmprinter", "fdmextruder"):
if "overrides" in definition and definition_name not in ("fdmprinter", "fdmextruder"):
for key, value_dict in definition["overrides"].items(): for key, value_dict in definition["overrides"].items():
is_redefined, child_key, child_value, parent = self._isDefinedInParent(key, value_dict, definition['inherits']) is_redefined, child_key, child_value, parent, inherited_by= self._isDefinedInParent(key, value_dict, definition['inherits'])
if is_redefined: if is_redefined:
redefined = re.compile(r'.*(\"' + key + r'\"[\s\:\S]*?)\{[\s\S]*?\},?') redefined = re.compile(r'.*(\"' + key + r'\"[\s\:\S]*?)\{[\s\S]*?\},?')
found = redefined.search(self._content) found = redefined.search(self._content)
@ -59,12 +69,55 @@ class Definition(Linter):
yield Diagnostic( yield Diagnostic(
file = self._file, file = self._file,
diagnostic_name = "diagnostic-definition-redundant-override", diagnostic_name = "diagnostic-definition-redundant-override",
message = f"Overriding {key} with the same value ({child_key}: {child_value}) as defined in parent definition: {definition['inherits']}", message = f"Overriding {key} with the same value ({child_key}: {child_value}) as defined in parent definition: {inherited_by}",
level = "Warning", level = "Warning",
offset = found.span(0)[0], offset = found.span(0)[0],
replacements = replacements replacements = replacements
) )
def checkMaterialTemperature(self) -> Iterator[Diagnostic]:
"""Checks if definition file has material tremperature defined within them"""
definition = self._definitions[self._definition_name]
if "overrides" in definition and self._definition_name not in ("fdmprinter", "fdmextruder"):
for key, value_dict in definition["overrides"].items():
if "temperature" in key and "material" in key:
redefined = re.compile(r'.*(\"' + key + r'\"[\s\:\S]*?)\{[\s\S]*?\},?')
found = redefined.search(self._content)
if len(found.group().splitlines()) > 1:
replacements = []
else:
replacements = [Replacement(
file=self._file,
offset=found.span(1)[0],
length=len(found.group()),
replacement_text="")]
yield Diagnostic(
file=self._file,
diagnostic_name="diagnostic-material-temperature-defined",
message=f"Overriding {key} as it belongs to material temperature catagory and shouldn't be placed in machine definitions",
level="Warning",
offset=found.span(0)[0],
replacements=replacements
)
def checkExperimentalSetting(self) -> Iterator[Diagnostic]:
"""Checks if definition uses experimental settings"""
definition = self._definitions[self._definition_name]
if "overrides" in definition and self._definition_name not in ("fdmprinter", "fdmextruder"):
for setting in definition["overrides"]:
if setting in self._experimental_settings:
redefined = re.compile(setting)
found = redefined.search(self._content)
yield Diagnostic(
file=self._file,
diagnostic_name="diagnostic-definition-experimental-setting",
message=f"Setting {setting} is still experimental and should not be used in default profiles",
level="Warning",
offset=found.span(0)[0]
)
def _loadDefinitionFiles(self, definition_file) -> None: def _loadDefinitionFiles(self, definition_file) -> None:
""" Loads definition file contents into self._definitions. Also load parent definition if it exists. """ """ Loads definition file contents into self._definitions. Also load parent definition if it exists. """
definition_name = Path(definition_file.stem).stem definition_name = Path(definition_file.stem).stem
@ -72,6 +125,9 @@ class Definition(Linter):
if not definition_file.exists() or definition_name in self._definitions: if not definition_file.exists() or definition_name in self._definitions:
return return
if self._definition_name is None:
self._definition_name = definition_name
# Load definition file into dictionary # Load definition file into dictionary
self._definitions[definition_name] = json.loads(definition_file.read_text()) self._definitions[definition_name] = json.loads(definition_file.read_text())
@ -85,7 +141,7 @@ class Definition(Linter):
def _isDefinedInParent(self, key, value_dict, inherits_from): def _isDefinedInParent(self, key, value_dict, inherits_from):
if self._ignore(key, "diagnostic-definition-redundant-override"): if self._ignore(key, "diagnostic-definition-redundant-override"):
return False, None, None, None return False, None, None, None, None
if "overrides" not in self._definitions[inherits_from]: if "overrides" not in self._definitions[inherits_from]:
return self._isDefinedInParent(key, value_dict, self._definitions[inherits_from]["inherits"]) return self._isDefinedInParent(key, value_dict, self._definitions[inherits_from]["inherits"])
@ -114,11 +170,17 @@ class Definition(Linter):
v = child_value v = child_value
cv = check_value cv = check_value
if v == cv: if v == cv:
return True, child_key, child_value, parent return True, child_key, child_value, parent, inherits_from
if "inherits" in parent: if "inherits" in parent:
return self._isDefinedInParent(key, value_dict, parent["inherits"]) return self._isDefinedInParent(key, value_dict, parent["inherits"])
return False, None, None, None return False, None, None, None, None
def _loadExperimentalSettings(self):
try:
self._experimental_settings = self._definitions[self.base_def]["settings"]["experimental"]["children"].keys()
except:
pass
def _loadBasePrinterSettings(self): def _loadBasePrinterSettings(self):
settings = {} settings = {}

View File

@ -1,7 +1,7 @@
from pathlib import Path from pathlib import Path
from typing import Iterator from typing import Iterator
from ..diagnostic import Diagnostic from ..diagnostic import Diagnostic, GitComment
from .linter import Linter from .linter import Linter
@ -11,9 +11,12 @@ class Directory(Linter):
super().__init__(file, settings) super().__init__(file, settings)
def check(self) -> Iterator[Diagnostic]: def check(self) -> Iterator[Diagnostic]:
if self._settings["checks"].get("diagnostic-resources-macos-app-directory-name", False): if self._file.exists() and self._settings["checks"].get("diagnostic-resources-macos-app-directory-name", False):
for check in self.checkForDotInDirName(): for check in self.checkForDotInDirName():
yield check yield check
elif self._settings["checks"].get("diagnostic-resource-file-deleted", False):
for check in self.checkFilesDeleted():
yield check
yield yield
@ -29,3 +32,8 @@ class Directory(Linter):
) )
yield yield
def checkFilesDeleted(self) -> Iterator[GitComment]:
if not self._file.exists():
""" Check if there is a file that is deleted, this causes upgrade scripts to not work properly """
yield GitComment( f'File: **{self._file}** must not be deleted as it is not allowed. It will create issues upgrading Cura' )
yield

View File

@ -0,0 +1,186 @@
import difflib
import json
import os
import re
from configparser import ConfigParser
from pathlib import Path
from typing import Iterator
from ..diagnostic import Diagnostic
from ..replacement import Replacement
from .linter import Linter
FORMULA_NAMES = [
"extruderValue",
"extruderValues",
"anyExtruderWithMaterial",
"anyExtruderNrWithOrDefault",
"resolveOrValue",
"defaultExtruderPosition",
"valueFromContainer",
"extruderValueFromContainer",
"math",
"round",
"max",
"ceil",
"min",
"sqrt",
"log",
"tan",
"cos",
"sin",
"atan",
"acos",
"asin",
"floor",
"sum",
"len",
"radians",
"degrees"
]
DELIMITERS = [r'\+', '-', '=', '/', '\*', r'\(', r'\)', r'\[', r'\]', '{', '}', ' ', '^']
class Formulas(Linter):
"""Finds Typos in the definition files and their formulas."""
def __init__(self, file: Path, settings: dict) -> None:
super().__init__(file, settings)
self._cura_correction_strings = FORMULA_NAMES + list(self.getCuraSettingList())
self._definition = {}
def getCuraSettingList(self) -> list:
settings_list = []
with open(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "resources", "definitions", "fdmprinter.def.json")) as data:
json_data = json.load(data)
settings_list += self.extractKeys(json_data)
with open(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "resources", "definitions", "fdmextruder.def.json")) as data:
json_data = json.load(data)
settings_list += self.extractKeys(json_data)
return settings_list
def extractKeys(self, json_obj, parent_key=''):
keys_with_value = []
for key, values in json_obj.items():
new_key = key
if isinstance(values, dict):
if 'label' in values:
keys_with_value.append(new_key)
keys_with_value.extend(self.extractKeys(values, new_key))
return keys_with_value
def check(self) -> Iterator[Diagnostic]:
if self._settings["checks"].get("diagnostic-incorrect-formula", False):
for check in self.checkFormulas():
yield check
yield
def checkFormulas(self) -> Iterator[Diagnostic]:
self._loadDefinitionFiles(self._file)
self._content = self._file.read_text()
definition_name = list(self._definition.keys())[0]
definition = self._definition[definition_name]
if "overrides" in definition:
for key, value_dict in definition["overrides"].items():
for value in value_dict:
if value in ("enable", "resolve", "value", "minimum_value_warning", "maximum_value_warning",
"maximum_value", "minimum_value"):
key_incorrect = self.checkValueIncorrect(key)
if key_incorrect:
found = self._appendCorrections(key, key)
value_incorrect = self.checkValueIncorrect(value_dict[value])
if value_incorrect:
found = self._appendCorrections(key, value_dict[value])
if key_incorrect or value_incorrect:
if len(found.group().splitlines()) > 1:
replacements = []
else:
replacements = [Replacement(
file=self._file,
offset=found.span(1)[0],
length=len(found.group()),
replacement_text=self._replacement_text)]
yield Diagnostic(
file=self._file,
diagnostic_name="diagnostic-incorrect-formula",
message=f"Given formula {found.group()} seems incorrect, Do you mean {self._correct_formula}? please correct the formula and try again.",
level="Error",
offset=found.span(0)[0],
replacements=replacements
)
yield
def _appendCorrections(self, key, incorrectString):
if self._file.suffix == '.cfg':
key_with_incorrectValue = re.compile(r'(\b' + key + r'\b\s*=\s*[^=\n]+.*)')
else:
key_with_incorrectValue = re.compile(r'.*(\"' + key + r'\"[\s\:\S]*?)\{[\s\S]*?\},?')
found = key_with_incorrectValue.search(self._content)
if len(found.group().splitlines()) > 1:
self._replacement_text = ''
else:
self._replacement_text = found.group().replace(incorrectString, self._correct_formula).strip(' ')
return found
def _loadDefinitionFiles(self, definition_file) -> None:
""" Loads definition file contents into self._definition. Also load parent definition if it exists. """
definition_name = Path(definition_file.stem).stem
if not definition_file.exists() or definition_name in self._definition:
return
if definition_file.suffix == ".json":
# Load definition file into dictionary
self._definition[definition_name] = json.loads(definition_file.read_text())
if definition_file.suffix == ".cfg":
self._definition[definition_name] = self._parseCfg(definition_file)
def _parseCfg(self, file_path:Path) -> dict:
config = ConfigParser()
config.read([file_path])
file_data ={}
overrides = {}
available_sections = ["values"]
for section in available_sections:
if config.has_section(section):
options = config.options(section)
for option in options:
values ={}
values["value"] = config.get(section, option)
overrides[option] = values
file_data["overrides"]= overrides# Process the value here
return file_data
def checkValueIncorrect(self, formula) -> bool:
if isinstance(formula, str):
self._correct_formula = self._correctTyposInFormula(formula)
return self._correct_formula != formula
else:
return False
def _correctTyposInFormula(self, formula):
pattern = '|'.join(DELIMITERS)
tokens = re.split(pattern, formula)
output = formula
for token in tokens:
if '(' not in token and ')' not in token:
cleaned_token = re.sub(r'[^\w\s]', '', token)
possible_matches = difflib.get_close_matches(cleaned_token, self._cura_correction_strings, n=1, cutoff=0.8)
if possible_matches:
output = output.replace(cleaned_token, possible_matches[0])
return output

View File

@ -1,9 +1,42 @@
from typing import Iterator import re
from typing import Iterator, Tuple
from ..diagnostic import Diagnostic from ..diagnostic import Diagnostic
from .linter import Linter from .linter import Linter
from pathlib import Path
from configparser import ConfigParser
class Profile(Linter): class Profile(Linter):
MAX_SIZE_OF_NAME = 20
def __init__(self, file: Path, settings: dict) -> None:
""" Finds issues in the parent directory"""
super().__init__(file, settings)
self._content = self._file.read_text()
def check(self) -> Iterator[Diagnostic]: def check(self) -> Iterator[Diagnostic]:
yield if self._file.exists() and self._settings["checks"].get("diagnostic-long-profile-names", False):
for check in self.checklengthofProfileName():
yield check
def checklengthofProfileName(self) -> Iterator[Diagnostic]:
""" check the name of profile and where it is found"""
name_of_profile, found = self._getprofileName()
if len(name_of_profile) > Profile.MAX_SIZE_OF_NAME:
yield Diagnostic(
file=self._file,
diagnostic_name="diagnostic-long-profile-names",
message = f"The profile name **{name_of_profile}** exceeds the maximum length limit. For optimal results, please limit it to 20 characters or fewer.",
level="Warning",
offset = found.span(0)[0]
)
def _getprofileName(self) -> Tuple[str, bool]:
config = ConfigParser()
config.read([self._file])
name_of_profile = config.get("general", "name")
redefined = re.compile(re.escape(name_of_profile))
found = redefined.search(self._content)
return name_of_profile, found

View File

@ -19,6 +19,7 @@ def main() -> None:
parser.add_argument("--report", required=False, type=Path, help="Path where the diagnostic report should be stored") 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("--format", action="store_true", help="Format the files")
parser.add_argument("--diagnose", action="store_true", help="Diagnose the files") parser.add_argument("--diagnose", action="store_true", help="Diagnose the files")
parser.add_argument("--deleted", action="store_true", help="Check for deleted files")
parser.add_argument("--fix", action="store_true", help="Attempt to apply the suggested fixes on 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") parser.add_argument("Files", metavar="F", type=Path, nargs="+", help="Files or directories to format")
@ -41,12 +42,26 @@ def main() -> None:
settings = yaml.load(f, yaml.FullLoader) settings = yaml.load(f, yaml.FullLoader)
full_body_check = {"Diagnostics": []} full_body_check = {"Diagnostics": []}
comments_check = {"Error Files": []}
for file in files: for file in files:
if not path.exists(file): if not path.exists(file):
print(f"Can't find the file: {file}") print(f"Can't find the file: {file}")
return return
if args.deleted:
for file in args.Files:
if file not in files:
deletedFiles = diagnoseIssuesWithFile(file, settings)
comments_check["Error Files"].extend([d.toDict() for d in deletedFiles])
results = yaml.dump(comments_check, default_flow_style=False, indent=4, width=240)
if report:
report.write_text(results)
else:
print(results)
if to_fix or to_diagnose: if to_fix or to_diagnose:
for file in files: for file in files:
diagnostics = diagnoseIssuesWithFile(file, settings) diagnostics = diagnoseIssuesWithFile(file, settings)
@ -82,7 +97,6 @@ def diagnoseIssuesWithFile(file: Path, settings: dict) -> List[Diagnostic]:
return linter_results return linter_results
def applyFixesToFile(file, settings, full_body_check) -> None: def applyFixesToFile(file, settings, full_body_check) -> None:
if not file.exists(): if not file.exists():
return return

View File

@ -206,9 +206,9 @@ chardet==3.0.4 \
idna==2.8 \ idna==2.8 \
--hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \
--hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c
attrs==21.2.0 \ attrs==21.3.0 \
--hash=sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1 \ --hash=sha256:8f7335278dedd26b58c38e006338242cc0977f06d51579b2b8b87b9b33bff66c \
--hash=sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb --hash=sha256:50f3c9b216dc9021042f71b392859a773b904ce1a029077f58f6598272432045
requests==2.22.0 \ requests==2.22.0 \
--hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \ --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \
--hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31
@ -222,9 +222,9 @@ constantly==15.1.0 \
hyperlink==21.0.0 \ hyperlink==21.0.0 \
--hash=sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b \ --hash=sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b \
--hash=sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4 --hash=sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4
incremental==21.3.0 \ incremental==22.10.0 \
--hash=sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57 \ --hash=sha256:b864a1f30885ee72c5ac2835a761b8fe8aa9c28b9395cacf27286602688d3e51 \
--hash=sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321 --hash=sha256:912feeb5e0f7e0188e6f42241d2f450002e11bbc0937c65865045854c24c0bd0
zope.interface==5.4.0 \ zope.interface==5.4.0 \
--hash=sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3 \ --hash=sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3 \
--hash=sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54 \ --hash=sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54 \

View File

@ -1303,6 +1303,24 @@
} }
} }
}, },
"GenericPETCF": {
"package_info": {
"package_id": "GenericPETCF",
"package_type": "material",
"display_name": "Generic PETCF",
"description": "The generic PET-CF profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "8.6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericPETG": { "GenericPETG": {
"package_info": { "package_info": {
"package_id": "GenericPETG", "package_id": "GenericPETG",
@ -1657,14 +1675,14 @@
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0", "package_version": "1.4.0",
"sdk_version": "8.6.0", "sdk_version": "8.6.0",
"website": "https://ultimaker.com/products/materials/abs", "website": "https://ultimaker.com/materials/s-series-abs/",
"author": { "author": {
"author_id": "UltimakerPackages", "author_id": "UltimakerPackages",
"display_name": "UltiMaker", "display_name": "UltiMaker",
"email": "materials@ultimaker.com", "email": "materials@ultimaker.com",
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.", "description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials" "support_website": "https://ultimaker.com/in/cura/materials/ultimaker-abs/printing-guidelines"
} }
} }
}, },
@ -1676,14 +1694,14 @@
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0", "package_version": "1.4.0",
"sdk_version": "8.6.0", "sdk_version": "8.6.0",
"website": "https://ultimaker.com/products/materials/breakaway", "website": "https://ultimaker.com/materials/s-series-breakaway/",
"author": { "author": {
"author_id": "UltimakerPackages", "author_id": "UltimakerPackages",
"display_name": "UltiMaker", "display_name": "UltiMaker",
"email": "materials@ultimaker.com", "email": "materials@ultimaker.com",
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.", "description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials" "support_website": "https://ultimaker.com/in/cura/materials/ultimaker-breakaway/printing-guidelines"
} }
} }
}, },
@ -1695,14 +1713,14 @@
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0", "package_version": "1.4.0",
"sdk_version": "8.6.0", "sdk_version": "8.6.0",
"website": "https://ultimaker.com/products/materials/abs", "website": "https://ultimaker.com/materials/s-series-cpe/",
"author": { "author": {
"author_id": "UltimakerPackages", "author_id": "UltimakerPackages",
"display_name": "UltiMaker", "display_name": "UltiMaker",
"email": "materials@ultimaker.com", "email": "materials@ultimaker.com",
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.", "description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials" "support_website": "https://ultimaker.com/in/cura/materials/ultimaker-cpe/printing-guidelines"
} }
} }
}, },
@ -1714,14 +1732,14 @@
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0", "package_version": "1.4.0",
"sdk_version": "8.6.0", "sdk_version": "8.6.0",
"website": "https://ultimaker.com/products/materials/cpe", "website": "https://ultimaker.com/materials/s-series-cpe-plus/",
"author": { "author": {
"author_id": "UltimakerPackages", "author_id": "UltimakerPackages",
"display_name": "UltiMaker", "display_name": "UltiMaker",
"email": "materials@ultimaker.com", "email": "materials@ultimaker.com",
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.", "description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials" "support_website": "https://ultimaker.com/in/cura/materials/ultimaker-cpe-plus/printing-guidelines"
} }
} }
}, },
@ -1733,14 +1751,14 @@
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0", "package_version": "1.4.0",
"sdk_version": "8.6.0", "sdk_version": "8.6.0",
"website": "https://ultimaker.com/products/materials/abs", "website": "https://ultimaker.com/materials/s-series-nylon/",
"author": { "author": {
"author_id": "UltimakerPackages", "author_id": "UltimakerPackages",
"display_name": "UltiMaker", "display_name": "UltiMaker",
"email": "materials@ultimaker.com", "email": "materials@ultimaker.com",
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.", "description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials" "support_website": "https://ultimaker.com/in/cura/materials/ultimaker-nylon/printing-guidelines"
} }
} }
}, },
@ -1752,14 +1770,52 @@
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0", "package_version": "1.4.0",
"sdk_version": "8.6.0", "sdk_version": "8.6.0",
"website": "https://ultimaker.com/products/materials/pc", "website": "https://ultimaker.com/materials/s-series-pc/",
"author": { "author": {
"author_id": "UltimakerPackages", "author_id": "UltimakerPackages",
"display_name": "UltiMaker", "display_name": "UltiMaker",
"email": "materials@ultimaker.com", "email": "materials@ultimaker.com",
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.", "description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials" "support_website": "https://ultimaker.com/in/cura/materials/ultimaker-pc/printing-guidelines"
}
}
},
"UltimakerPETCF": {
"package_info": {
"package_id": "UltimakerPETCF",
"package_type": "material",
"display_name": "Ultimaker PETCF",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "8.6.0",
"website": "https://ultimaker.com/materials/s-series-pet-carbon-fiber/",
"author": {
"author_id": "UltimakerPackages",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/in/cura/materials/ultimaker-pet-cf/printing-guidelines"
}
}
},
"UltimakerPETG": {
"package_info": {
"package_id": "UltimakerPETG",
"package_type": "material",
"display_name": "Ultimaker PETG",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "8.6.0",
"website": "https://ultimaker.com/materials/s-series-petg/",
"author": {
"author_id": "UltimakerPackages",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/in/cura/materials/ultimaker-petg/printing-guidelines"
} }
} }
}, },
@ -1771,14 +1827,14 @@
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0", "package_version": "1.4.0",
"sdk_version": "8.6.0", "sdk_version": "8.6.0",
"website": "https://ultimaker.com/products/materials/abs", "website": "https://ultimaker.com/materials/s-series-pla/",
"author": { "author": {
"author_id": "UltimakerPackages", "author_id": "UltimakerPackages",
"display_name": "UltiMaker", "display_name": "UltiMaker",
"email": "materials@ultimaker.com", "email": "materials@ultimaker.com",
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.", "description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials" "support_website": "https://ultimaker.com/in/cura/materials/ultimaker-pla/printing-guidelines"
} }
} }
}, },
@ -1790,14 +1846,14 @@
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0", "package_version": "1.4.0",
"sdk_version": "8.6.0", "sdk_version": "8.6.0",
"website": "https://ultimaker.com/products/materials/pp", "website": "https://ultimaker.com/materials/s-series-pp/",
"author": { "author": {
"author_id": "UltimakerPackages", "author_id": "UltimakerPackages",
"display_name": "UltiMaker", "display_name": "UltiMaker",
"email": "materials@ultimaker.com", "email": "materials@ultimaker.com",
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.", "description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials" "support_website": "https://ultimaker.com/in/cura/materials/ultimaker-pp/printing-guidelines"
} }
} }
}, },
@ -1809,33 +1865,14 @@
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0", "package_version": "1.4.0",
"sdk_version": "8.6.0", "sdk_version": "8.6.0",
"website": "https://ultimaker.com/products/materials/abs", "website": "https://ultimaker.com/materials/s-series-pva/",
"author": { "author": {
"author_id": "UltimakerPackages", "author_id": "UltimakerPackages",
"display_name": "UltiMaker", "display_name": "UltiMaker",
"email": "materials@ultimaker.com", "email": "materials@ultimaker.com",
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.", "description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials" "support_website": "https://ultimaker.com/in/cura/materials/ultimaker-pva/printing-guidelines"
}
}
},
"UltimakerTPU": {
"package_info": {
"package_id": "UltimakerTPU",
"package_type": "material",
"display_name": "Ultimaker TPU 95A",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "8.6.0",
"website": "https://ultimaker.com/products/materials/tpu-95a",
"author": {
"author_id": "UltimakerPackages",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials"
} }
} }
}, },
@ -1847,14 +1884,166 @@
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0", "package_version": "1.4.0",
"sdk_version": "8.6.0", "sdk_version": "8.6.0",
"website": "https://ultimaker.com/products/materials/tough-pla", "website": "https://ultimaker.com/materials/s-series-tough-pla/",
"author": { "author": {
"author_id": "UltimakerPackages", "author_id": "UltimakerPackages",
"display_name": "UltiMaker", "display_name": "UltiMaker",
"email": "materials@ultimaker.com", "email": "materials@ultimaker.com",
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.", "description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials" "support_website": "https://ultimaker.com/in/cura/materials/ultimaker-tough-pla/printing-guidelines"
}
}
},
"UltimakerTPU": {
"package_info": {
"package_id": "UltimakerTPU",
"package_type": "material",
"display_name": "Ultimaker TPU 95A",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "8.6.0",
"website": "https://ultimaker.com/materials/s-series-tpu-95a/",
"author": {
"author_id": "UltimakerPackages",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/in/cura/materials/ultimaker-tpu-95a/printing-guidelines"
}
}
},
"UltimakerPPSCF": {
"package_info": {
"package_id": "UltimakerPPSCF",
"package_type": "material",
"display_name": "Ultimaker PPS-CF",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"sdk_version": "8.6.0",
"website": "https://ultimaker.com/materials/factor-series-pps-carbon-fiber/",
"author": {
"author_id": "UltimakerPackages",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://support.ultimaker.com/s/article/How-to-print-with-UltiMaker-PPS-CF"
}
}
},
"ULTIMAKERBASCFMETHOD": {
"package_info": {
"package_id": "ULTIMAKERBASCFMETHOD",
"package_type": "material",
"display_name": "Ultimaker ABS-CF",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "2.0.0",
"sdk_version": "8.6.0",
"website": "https://ultimaker.com/materials/method-series-abs-carbon-fiber/",
"author": {
"author_id": "UltimakerPackages",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://support.ultimaker.com/s/article/How-to-print-with-Method-ABS-CF"
}
}
},
"ULTIMAKERABSRMETHOD": {
"package_info": {
"package_id": "ULTIMAKERABSRMETHOD",
"package_type": "material",
"display_name": "Ultimaker ABS-R",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "2.0.0",
"sdk_version": "8.6.0",
"website": "https://ultimaker.com/materials/method-series-abs-r/",
"author": {
"author_id": "UltimakerPackages",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://support.ultimaker.com/s/article/How-to-print-with-Method-ABS-R"
}
}
},
"ULTIMAKERASAMETHOD": {
"package_info": {
"package_id": "ULTIMAKERASAMETHOD",
"package_type": "material",
"display_name": "Ultimaker ASA",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "2.0.0",
"sdk_version": "8.6.0",
"website": "https://ultimaker.com/materials/method-series-asa/",
"author": {
"author_id": "UltimakerPackages",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://support.ultimaker.com/s/article/How-to-print-with-Method-ASA"
}
}
},
"ULTIMAKERNYLON12CFMETHOD": {
"package_info": {
"package_id": "ULTIMAKERNYLON12CFMETHOD",
"package_type": "material",
"display_name": "Ultimaker Nylon12 Carbon Fiber",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "2.0.0",
"sdk_version": "8.6.0",
"website": "https://ultimaker.com/materials/method-series-nylon-12-carbon-fiber/",
"author": {
"author_id": "UltimakerPackages",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://support.ultimaker.com/s/article/How-to-print-with-Method-Nylon12-CF"
}
}
},
"ULTIMAKERRAPIDRINSEMETHOD": {
"package_info": {
"package_id": "ULTIMAKERRAPIDRINSEMETHOD",
"package_type": "material",
"display_name": "Ultimaker RapidRinse",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "2.0.0",
"sdk_version": "8.6.0",
"website": "https://ultimaker.com/materials/method-series-rapidrinse/",
"author": {
"author_id": "UltimakerPackages",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://support.ultimaker.com/s/article/How-to-print-with-Method-RapidRinse"
}
}
},
"ULTIMAKERSR30METHOD": {
"package_info": {
"package_id": "ULTIMAKERSR30METHOD",
"package_type": "material",
"display_name": "Ultimaker SR-30",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "2.0.0",
"sdk_version": "8.6.0",
"website": "https://ultimaker.com/materials/method-series-sr-30/",
"author": {
"author_id": "UltimakerPackages",
"display_name": "UltiMaker",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://support.ultimaker.com/s/article/How-to-print-with-Method-SR-30"
} }
} }
}, },

1
resources/conandata.yml Normal file
View File

@ -0,0 +1 @@
version: "5.8.0"

62
resources/conanfile.py Normal file
View File

@ -0,0 +1,62 @@
import os
from conan import ConanFile
from conan.tools.files import copy, update_conandata
from conan.tools.scm import Version
from conan.errors import ConanInvalidConfiguration
required_conan_version = ">=1.58.0 <2.0.0"
class CuraResource(ConanFile):
name = "cura_resources"
license = ""
author = "UltiMaker"
url = "https://github.com/Ultimaker/cura"
description = "Cura Resources"
topics = ("conan", "cura")
settings = "os", "compiler", "build_type", "arch"
no_copy_source = True
@property
def _shared_resources(self):
return ["definitions", "extruders", "images", "intent", "meshes", "quality", "variants"]
def set_version(self):
if not self.version:
self.version = self.conan_data["version"]
def export(self):
copy(self, pattern="LICENSE*", src=os.path.join(self.recipe_folder, ".."), dst=self.export_folder,
keep_path=False)
update_conandata(self, {"version": self.version})
def export_sources(self):
for shared_resources in self._shared_resources:
copy(self, pattern="*", src=os.path.join(self.recipe_folder, shared_resources),
dst=os.path.join(self.export_sources_folder, shared_resources))
def validate(self):
if Version(self.version) <= Version("4"):
raise ConanInvalidConfiguration("Only versions 5+ are support")
def layout(self):
self.cpp.source.resdirs = self._shared_resources
self.cpp.package.resdirs = [f"res/{res}" for res in self._shared_resources]
def package(self):
copy(self, "*", os.path.join(self.export_sources_folder),
os.path.join(self.package_folder, "res"))
def package_info(self):
self.cpp_info.includedirs = []
self.runenv_info.append_path("CURA_RESOURCES", os.path.join(self.package_folder, "res"))
self.runenv_info.append_path("CURA_ENGINE_SEARCH_PATH", os.path.join(self.package_folder, "res", "definitions"))
self.runenv_info.append_path("CURA_ENGINE_SEARCH_PATH", os.path.join(self.package_folder, "res", "extruders"))
self.env_info.CURA_RESOURCES.append(os.path.join(self.package_folder, "res"))
self.env_info.CURA_ENGINE_SEARCH_PATH.append(os.path.join(self.package_folder, "res", "definitions"))
self.env_info.CURA_ENGINE_SEARCH_PATH.append(os.path.join(self.package_folder, "res", "definitions"))
def package_id(self):
self.info.clear()

View File

@ -67,13 +67,13 @@
"layer_height_0": { "value": "round(machine_nozzle_size / 1.5, 2)" }, "layer_height_0": { "value": "round(machine_nozzle_size / 1.5, 2)" },
"layer_start_x": "layer_start_x":
{ {
"default_value": 105.0, "enabled": false,
"enabled": false "value": 105.0
}, },
"layer_start_y": "layer_start_y":
{ {
"default_value": 27.0, "enabled": false,
"enabled": false "value": 27.0
}, },
"line_width": { "value": "round(machine_nozzle_size * 0.875, 2)" }, "line_width": { "value": "round(machine_nozzle_size * 0.875, 2)" },
"machine_acceleration": { "default_value": 3000 }, "machine_acceleration": { "default_value": 3000 },
@ -130,7 +130,6 @@
"machine_min_cool_heat_time_window": { "default_value": 15.0 }, "machine_min_cool_heat_time_window": { "default_value": 15.0 },
"machine_name": { "default_value": "Mark2_for_Ultimaker2" }, "machine_name": { "default_value": "Mark2_for_Ultimaker2" },
"machine_nozzle_cool_down_speed": { "default_value": 1.5 }, "machine_nozzle_cool_down_speed": { "default_value": 1.5 },
"machine_nozzle_head_distance": { "default_value": 5 },
"machine_nozzle_heat_up_speed": { "default_value": 3.5 }, "machine_nozzle_heat_up_speed": { "default_value": 3.5 },
"machine_nozzle_size": { "default_value": 0.4 }, "machine_nozzle_size": { "default_value": 0.4 },
"machine_show_variants": { "default_value": true }, "machine_show_variants": { "default_value": true },

View File

@ -0,0 +1,246 @@
{
"version": 2,
"name": "AnkerMake M5C",
"inherits": "fdmprinter",
"metadata":
{
"visible": true,
"author": "just-trey",
"manufacturer": "AnkerMake",
"file_formats": "text/x-gcode",
"platform": "ankermake_m5c_platform.obj",
"has_machine_quality": true,
"machine_extruder_trains": { "0": "ankermake_m5c_extruder_0" },
"platform_texture": "ankermake_m5c.png",
"preferred_material": "generic_pla",
"preferred_quality_type": "normal"
},
"overrides":
{
"acceleration_enabled": { "value": true },
"acceleration_infill": { "value": 5000 },
"acceleration_layer_0": { "value": 2500 },
"acceleration_prime_tower": { "value": 5000 },
"acceleration_print": { "value": 5000 },
"acceleration_print_layer_0": { "value": 2500 },
"acceleration_roofing": { "value": 2500 },
"acceleration_skirt_brim": { "value": 2500 },
"acceleration_support": { "value": 5000 },
"acceleration_support_bottom": { "value": 5000 },
"acceleration_support_infill": { "value": 5000 },
"acceleration_support_interface": { "value": 5000 },
"acceleration_support_roof": { "value": 5000 },
"acceleration_topbottom": { "value": 2500 },
"acceleration_travel_layer_0": { "value": 2500 },
"acceleration_wall": { "value": 5000 },
"acceleration_wall_x": { "value": 5000 },
"adhesion_type": { "default_value": "skirt" },
"alternate_extra_perimeter": { "value": true },
"bottom_layers": { "value": 3 },
"bottom_skin_expand_distance": { "value": 0.84 },
"bottom_skin_preshrink": { "value": 0.84 },
"bottom_thickness": { "value": 0.8 },
"bridge_fan_speed_2": { "value": 100 },
"bridge_fan_speed_3": { "value": 100 },
"bridge_settings_enabled": { "value": true },
"bridge_skin_density_2": { "value": 80 },
"bridge_skin_material_flow": { "value": 100 },
"bridge_skin_material_flow_2": { "value": 80 },
"bridge_skin_speed": { "value": 20 },
"bridge_skin_speed_2": { "value": 50 },
"bridge_skin_speed_3": { "value": 50 },
"bridge_wall_material_flow": { "value": 100 },
"bridge_wall_speed": { "value": 20 },
"connect_infill_polygons": { "value": false },
"cool_fan_full_at_height": { "value": 0.14 },
"cool_min_layer_time": { "value": 6 },
"cool_min_speed": { "value": 30 },
"cross_infill_pocket_size": { "value": 8 },
"expand_skins_expand_distance": { "value": 0.84 },
"fill_outline_gaps": { "value": false },
"gantry_height": { "value": 25 },
"gradual_infill_step_height": { "value": 2 },
"infill_angles":
{
"value": [
90
]
},
"infill_extruder_nr": { "value": -1 },
"infill_line_distance": { "value": 8 },
"infill_material_flow": { "value": 90 },
"infill_pattern": { "value": "'lines' if infill_sparse_density >= 25 else 'grid'" },
"infill_sparse_density": { "value": 10 },
"infill_sparse_thickness": { "value": 0.25 },
"infill_wipe_dist": { "value": 0.1 },
"initial_bottom_layers": { "value": 3 },
"jerk_enabled": { "value": true },
"jerk_infill": { "value": 15 },
"jerk_layer_0": { "value": 15 },
"jerk_prime_tower": { "value": 15 },
"jerk_print": { "value": 15 },
"jerk_print_layer_0": { "value": 15 },
"jerk_roofing": { "value": 15 },
"jerk_skirt_brim": { "value": 15 },
"jerk_support": { "value": 15 },
"jerk_support_bottom": { "value": 15 },
"jerk_support_infill": { "value": 15 },
"jerk_support_interface": { "value": 15 },
"jerk_support_roof": { "value": 15 },
"jerk_topbottom": { "value": 15 },
"jerk_travel": { "value": 15 },
"jerk_travel_layer_0": { "value": 15 },
"jerk_wall": { "value": 15 },
"jerk_wall_0": { "value": 15 },
"jerk_wall_x": { "value": 15 },
"machine_buildplate_type": { "value": "glass" },
"machine_depth": { "value": 220 },
"machine_heated_bed": { "value": true },
"machine_height": { "value": 250 },
"machine_max_jerk_e": { "value": 5 },
"machine_max_jerk_xy": { "value": 30 },
"machine_max_jerk_z": { "value": 0.3 },
"machine_name": { "default_value": "AnkerMake M5" },
"machine_shape": { "value": "rectangular" },
"machine_show_variants": { "value": false },
"machine_start_gcode": { "default_value": "M104 S{material_print_temperature_layer_0} ; set final nozzle temp\nM190 S{material_bed_temperature_layer_0} ; set and wait for nozzle temp to stabilize\nM109 S{material_print_temperature_layer_0} ; wait for nozzle temp to stabilize\nG28 ;Home\nG1 E10 F3600; push out retracted filament(fix for over retraction after prime)" },
"machine_width": { "value": 220 },
"material_diameter": { "default_value": 1.75 },
"material_flow_layer_0": { "value": 120 },
"material_no_load_move_factor": { "value": 0.94 },
"minimum_interface_area": { "value": 10 },
"minimum_support_area": { "value": "2 if support_structure == 'normal' else 0" },
"retract_at_layer_change": { "value": true },
"retraction_amount": { "value": 0.8 },
"retraction_combing": { "value": "noskin" },
"retraction_combing_max_distance": { "value": 3 },
"retraction_extrusion_window": { "value": 0.8 },
"retraction_min_travel": { "value": 0.8 },
"retraction_prime_speed": { "value": 60 },
"retraction_retract_speed": { "value": 60 },
"retraction_speed": { "value": 60 },
"roofing_angles": { "value": [] },
"roofing_monotonic": { "value": false },
"roofing_pattern": { "value": "zigzag" },
"skin_material_flow": { "value": 97 },
"skin_monotonic": { "default_value": true },
"skirt_brim_speed":
{
"maximum_value_warning": "550",
"value": 50
},
"skirt_line_count": { "value": 3 },
"small_feature_max_length": { "value": 9.42 },
"small_hole_max_size": { "value": 3 },
"speed_infill":
{
"maximum_value_warning": "550",
"value": 270
},
"speed_layer_0":
{
"maximum_value_warning": "550",
"value": 50
},
"speed_prime_tower":
{
"maximum_value_warning": "550",
"value": 500
},
"speed_print":
{
"maximum_value_warning": "550",
"value": 500
},
"speed_print_layer_0":
{
"maximum_value_warning": "550",
"value": 50
},
"speed_roofing":
{
"maximum_value_warning": "550",
"value": 150
},
"speed_support":
{
"maximum_value_warning": "550",
"value": 250
},
"speed_support_bottom":
{
"maximum_value_warning": "550",
"value": 166.667
},
"speed_support_infill":
{
"maximum_value_warning": "550",
"value": 250
},
"speed_support_interface":
{
"maximum_value_warning": "550",
"value": 166.667
},
"speed_support_roof":
{
"maximum_value_warning": "550",
"value": 166.667
},
"speed_topbottom":
{
"maximum_value_warning": "550",
"value": 150
},
"speed_travel":
{
"maximum_value_warning": "550",
"value": 500
},
"speed_travel_layer_0":
{
"maximum_value_warning": "550",
"value": 150
},
"speed_wall":
{
"maximum_value_warning": "550",
"value": 250
},
"speed_wall_0":
{
"maximum_value_warning": "550",
"value": 150
},
"speed_wall_x":
{
"maximum_value_warning": "550",
"value": 250
},
"speed_wall_x_roofing": { "maximum_value_warning": "550" },
"support_bottom_distance": { "value": 0.2 },
"support_brim_enable": { "value": false },
"support_brim_line_count": { "value": 20 },
"support_brim_width": { "value": 8 },
"support_infill_angles": { "value": [] },
"support_infill_rate": { "value": 30 },
"support_initial_layer_line_distance": { "value": 1.333 },
"support_line_distance": { "value": 1.333 },
"support_offset": { "value": 2 },
"support_top_distance": { "value": 0.2 },
"support_xy_distance": { "value": 0.8 },
"support_xy_overrides_z": { "value": "xy_overrides_z" },
"top_layers": { "value": 4 },
"top_skin_expand_distance": { "value": 0.84 },
"top_skin_preshrink": { "value": 0.84 },
"travel_avoid_distance": { "value": 0.63 },
"wall_0_extruder_nr": { "value": -1 },
"wall_extruder_nr": { "value": -1 },
"wall_line_width_0": { "value": 0.44 },
"wall_overhang_angle": { "value": 45 },
"wall_overhang_speed_factor": { "value": 40 },
"wall_thickness": { "value": 0.84 },
"wall_x_extruder_nr": { "value": -1 },
"zig_zaggify_infill": { "value": true }
}
}

View File

@ -65,7 +65,6 @@
"machine_heated_bed": { "default_value": true }, "machine_heated_bed": { "default_value": true },
"machine_height": { "default_value": 340 }, "machine_height": { "default_value": 340 },
"machine_name": { "default_value": "Atom 3" }, "machine_name": { "default_value": "Atom 3" },
"machine_nozzle_head_distance": { "default_value": 6 },
"machine_shape": { "default_value": "elliptic" }, "machine_shape": { "default_value": "elliptic" },
"machine_show_variants": { "default_value": true }, "machine_show_variants": { "default_value": true },
"machine_start_gcode": { "default_value": ";MACHINE START CODE\nG21 ;metric values\nG90 ;absolute positioning\nG28 ;home\nG1 Z5 F9000\n;MACHINE START CODE" }, "machine_start_gcode": { "default_value": ";MACHINE START CODE\nG21 ;metric values\nG90 ;absolute positioning\nG28 ;home\nG1 Z5 F9000\n;MACHINE START CODE" },

View File

@ -0,0 +1,18 @@
{
"version": 2,
"name": "Creality CR-M4",
"inherits": "creality_base",
"metadata":
{
"visible": true,
"quality_definition": "creality_base"
},
"overrides":
{
"gantry_height": { "value": 35 },
"machine_depth": { "default_value": 450 },
"machine_height": { "default_value": 470 },
"machine_name": { "default_value": "Creality CR-M4" },
"machine_width": { "default_value": 450 }
}
}

View File

@ -0,0 +1,59 @@
{
"version": 2,
"name": "Creality Ender-3 V3 KE",
"inherits": "creality_base",
"metadata":
{
"visible": true,
"manufacturer": "Creality3D",
"file_formats": "text/x-gcode",
"platform": "creality_ender3.3mf",
"first_start_actions": [ "MachineSettingsAction" ],
"has_machine_quality": true,
"has_materials": true,
"has_variants": true,
"machine_extruder_trains": { "0": "creality_base_extruder_0" },
"preferred_material": "generic_pla",
"preferred_quality_type": "standard",
"preferred_variant_name": "0.4mm Nozzle",
"quality_definition": "creality_base",
"variants_name": "Nozzle Size"
},
"overrides":
{
"gantry_height": { "value": 38 },
"machine_depth": { "default_value": 220 },
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z5 ;Raise Z more\nG90 ;Absolute positionning\n\nG1 X2 Y218 F3000 ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\n\nM84 X Y E ;Disable all steppers but Z" },
"machine_head_with_fans_polygon":
{
"default_value": [
[-20, 10],
[10, 10],
[10, -10],
[-20, -10]
]
},
"machine_heated_bed": { "default_value": true },
"machine_height": { "default_value": 240 },
"machine_max_acceleration_e": { "value": 5000 },
"machine_max_acceleration_x": { "value": 8000.0 },
"machine_max_acceleration_y": { "value": 8000.0 },
"machine_max_acceleration_z": { "value": 500.0 },
"machine_max_feedrate_e": { "value": 100 },
"machine_max_feedrate_x": { "value": 500 },
"machine_max_feedrate_y": { "value": 500 },
"machine_max_feedrate_z": { "value": 30 },
"machine_name": { "default_value": "Creality Ender-3 V3 KE" },
"machine_start_gcode": { "default_value": "M220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\n\nG28 ;Home\n\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 X-2.0 Y20 Z0.28 F5000.0 ;Move to start position\nM109 S{material_print_temperature_layer_0}\nG1 X-2.0 Y145.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X-1.7 Y145.0 Z0.28 F5000.0 ;Move to side a little\nG1 X-1.7 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder\nG1 E-1 F1800 ;Retract a bit\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 E0 F1800" },
"machine_width": { "default_value": 220 },
"material_print_temp_wait": { "default_value": false },
"retraction_amount": { "default_value": 0.8 },
"retraction_combing": { "value": "no_outer_surfaces" },
"retraction_combing_max_distance": { "value": 5.0 },
"retraction_extrusion_window": { "value": "retraction_amount" },
"retraction_min_travel": { "value": 2.0 },
"retraction_speed": { "default_value": 40 },
"speed_layer_0": { "value": 100 },
"speed_print": { "value": 300 }
}
}

View File

@ -45,7 +45,7 @@
"machine_max_feedrate_y": { "value": 500 }, "machine_max_feedrate_y": { "value": 500 },
"machine_max_feedrate_z": { "value": 30 }, "machine_max_feedrate_z": { "value": 30 },
"machine_name": { "default_value": "Creality Ender-3 V3 SE" }, "machine_name": { "default_value": "Creality Ender-3 V3 SE" },
"machine_start_gcode": { "default_value": "M220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\n\nG28 ;Home\n\nM420 S1; Use saved mesh leveling data\n\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nM190 S{material_bed_temperature_layer_0} ; Set bed temperature and wait\nM109 S{material_print_temperature_layer_0} ; Set hotend temperature and wait\nG1 X10.1 Y145.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y145.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder\nG1 E-1.0000 F1800 ;Retract a bit\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 E0.0000 F1800 \n" }, "machine_start_gcode": { "default_value": "M220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\n\nG28 ;Home\n\nM420 S1; Use saved mesh leveling data\n\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 X-3 Y20 Z0.28 F5000.0 ;Move to start position\nM190 S{material_bed_temperature_layer_0} ; Set bed temperature and wait\nM109 S{material_print_temperature_layer_0} ; Set hotend temperature and wait\nG1 X-3 Y100.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X-2 Y100.0 Z0.28 F5000.0 ;Move to side a little\nG1 X-2 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder\nG1 E-1.0000 F1800 ;Retract a bit\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 E0.0000 F1800 \n" },
"machine_width": { "default_value": 220 }, "machine_width": { "default_value": 220 },
"retraction_amount": { "value": 0.8 }, "retraction_amount": { "value": 0.8 },
"retraction_speed": { "default_value": 40 }, "retraction_speed": { "default_value": 40 },

View File

@ -13,7 +13,7 @@
"cool_min_layer_time": { "value": 5 }, "cool_min_layer_time": { "value": 5 },
"gantry_height": { "value": 25 }, "gantry_height": { "value": 25 },
"machine_depth": { "default_value": 225 }, "machine_depth": { "default_value": 225 },
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positionning\n\nG1 X0 Y0 ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\n\nM84 X Y E ;Disable all steppers but Z\n" }, "machine_end_gcode": { "default_value": "G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positioning\n\nG1 X0 Y0 ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\n\nM84 X Y E ;Disable all steppers but Z\n" },
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {
"default_value": [ "default_value": [

View File

@ -0,0 +1,55 @@
{
"version": 2,
"name": "Creality K1 Max",
"inherits": "creality_base",
"metadata":
{
"visible": true,
"author": "Itay Grudev",
"manufacturer": "Creality3D",
"file_formats": "text/x-gcode",
"first_start_actions": [ "MachineSettingsAction" ],
"has_machine_quality": true,
"has_materials": true,
"has_variants": true,
"machine_extruder_trains": { "0": "creality_k1max_extruder_0" },
"preferred_material": "generic_pla",
"preferred_quality_type": "standard",
"preferred_variant_name": "0.4mm Nozzle",
"quality_definition": "creality_base",
"variants_name": "Nozzle Size"
},
"overrides":
{
"gantry_height": { "value": 45 },
"machine_depth": { "default_value": 300 },
"machine_end_gcode": { "default_value": "END_PRINT" },
"machine_head_with_fans_polygon":
{
"default_value": [
[-50, 40],
[-50, -62],
[25, 40],
[25, -62]
]
},
"machine_heated_bed": { "default_value": true },
"machine_height": { "default_value": 300 },
"machine_max_acceleration_e": { "value": 5000 },
"machine_max_acceleration_x": { "value": 20000.0 },
"machine_max_acceleration_y": { "value": 20000.0 },
"machine_max_acceleration_z": { "value": 500.0 },
"machine_max_feedrate_e": { "value": 100 },
"machine_max_feedrate_x": { "value": 800 },
"machine_max_feedrate_y": { "value": 800 },
"machine_max_feedrate_z": { "value": 30 },
"machine_max_jerk_e": { "value": 2.5 },
"machine_max_jerk_xy": { "value": 9 },
"machine_max_jerk_z": { "value": 2 },
"machine_name": { "default_value": "Creality K1 Max" },
"machine_start_gcode": { "default_value": "M140 S0\nM104 S0 \nSTART_PRINT EXTRUDER_TEMP={material_print_temperature_layer_0} BED_TEMP={material_bed_temperature_layer_0}\n" },
"machine_width": { "default_value": 300 },
"retraction_amount": { "default_value": 0.5 },
"retraction_speed": { "default_value": 40 }
}
}

View File

@ -35,7 +35,6 @@
"machine_max_feedrate_y": { "default_value": 300 }, "machine_max_feedrate_y": { "default_value": 300 },
"machine_max_feedrate_z": { "default_value": 40 }, "machine_max_feedrate_z": { "default_value": 40 },
"machine_name": { "default_value": "Diytech 220" }, "machine_name": { "default_value": "Diytech 220" },
"machine_nozzle_head_distance": { "default_value": 3 },
"machine_nozzle_tip_outer_diameter": { "default_value": 1 }, "machine_nozzle_tip_outer_diameter": { "default_value": 1 },
"machine_start_gcode": { "default_value": "G21\nG90\nM82\nM107\nG28\nG1 Z15 F200\nT0\nG92 E0\nG1 E16 F250\nG92 E0\n" }, "machine_start_gcode": { "default_value": "G21\nG90\nM82\nM107\nG28\nG1 Z15 F200\nT0\nG92 E0\nG1 E16 F250\nG92 E0\n" },
"machine_use_extruder_offset_to_offset_coords": { "default_value": true }, "machine_use_extruder_offset_to_offset_coords": { "default_value": true },

View File

@ -52,13 +52,13 @@
"layer_height_0": { "value": "round(machine_nozzle_size / 1.5, 2)" }, "layer_height_0": { "value": "round(machine_nozzle_size / 1.5, 2)" },
"layer_start_x": "layer_start_x":
{ {
"default_value": 180.0, "enabled": false,
"enabled": false "value": 180.0
}, },
"layer_start_y": "layer_start_y":
{ {
"default_value": 160.0, "enabled": false,
"enabled": false "value": 160.0
}, },
"line_width": { "value": "round(machine_nozzle_size * 0.875, 2)" }, "line_width": { "value": "round(machine_nozzle_size * 0.875, 2)" },
"machine_acceleration": { "default_value": 3000 }, "machine_acceleration": { "default_value": 3000 },
@ -122,7 +122,6 @@
"machine_name": { "default_value": "dxu" }, "machine_name": { "default_value": "dxu" },
"machine_nozzle_cool_down_speed": { "default_value": 1.5 }, "machine_nozzle_cool_down_speed": { "default_value": 1.5 },
"machine_nozzle_expansion_angle": { "default_value": 45 }, "machine_nozzle_expansion_angle": { "default_value": 45 },
"machine_nozzle_head_distance": { "default_value": 5 },
"machine_nozzle_heat_up_speed": { "default_value": 3.5 }, "machine_nozzle_heat_up_speed": { "default_value": 3.5 },
"machine_nozzle_size": { "default_value": 0.4 }, "machine_nozzle_size": { "default_value": 0.4 },
"machine_show_variants": { "default_value": true }, "machine_show_variants": { "default_value": true },

View File

@ -0,0 +1,74 @@
{
"version": 2,
"name": "Eazao M500",
"inherits": "fdmprinter",
"metadata":
{
"visible": true,
"author": "Eazao",
"manufacturer": "Eazao",
"file_formats": "text/x-gcode",
"has_machine_quality": false,
"has_materials": false,
"machine_extruder_trains": { "0": "eazao_m500_extruder_0" },
"preferred_quality_type": "normal"
},
"overrides":
{
"acceleration_print": { "value": 300 },
"acceleration_travel": { "value": 300 },
"adhesion_type": { "default_value": "'none'" },
"bottom_layers": { "value": 2 },
"cool_fan_enabled": { "value": false },
"infill_sparse_density": { "value": 0 },
"initial_bottom_layers": { "value": 2 },
"jerk_print": { "value": 10 },
"jerk_travel": { "value": "jerk_print" },
"jerk_travel_layer_0": { "value": "jerk_travel" },
"layer_height": { "value": 1.0 },
"layer_height_0": { "value": 1.0 },
"line_width": { "value": 3.0 },
"machine_acceleration": { "value": 300 },
"machine_center_is_zero": { "default_value": false },
"machine_depth": { "default_value": 320 },
"machine_end_gcode": { "default_value": "G92 Z0\nG1 F1500 Z10\nG28 X0 Y300 ;move X Y to min endstops\nM82\nM84 ;steppers off\n" },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_heated_bed": { "default_value": false },
"machine_height": { "default_value": 520 },
"machine_max_acceleration_e": { "value": 500 },
"machine_max_acceleration_x": { "value": 500 },
"machine_max_acceleration_y": { "value": 500 },
"machine_max_acceleration_z": { "value": 50 },
"machine_max_feedrate_e": { "value": 25 },
"machine_max_feedrate_x": { "value": 100 },
"machine_max_feedrate_y": { "value": 100 },
"machine_max_feedrate_z": { "value": 5 },
"machine_max_jerk_e": { "value": 5 },
"machine_max_jerk_xy": { "value": 10 },
"machine_max_jerk_z": { "value": 0.3 },
"machine_name": { "default_value": "Eazao M500" },
"machine_start_gcode": { "default_value": "G21 ;set units to millimeters\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG28 ;Home\nG1 Z25.0 F1500 ;move the platform down 15mm\nG92 E0 ;extruder reset\nG1 F300 E5\nG92 E0 ;The extruder is forced to zero again\nM302\nM163 S0 P0.85; Set Mix Factor\nM163 S1 P0.15; Set Mix Factor\nM164 S0" },
"machine_width": { "default_value": 320 },
"magic_spiralize": { "value": true },
"material_final_print_temperature": { "value": "0" },
"material_initial_print_temperature": { "value": "0" },
"material_print_temperature": { "value": "0" },
"material_print_temperature_layer_0": { "value": "0" },
"optimize_wall_printing_order": { "value": "True" },
"retraction_amount": { "value": 7 },
"retraction_combing": { "value": "'noskin'" },
"retraction_count_max": { "value": 100 },
"retraction_enable": { "value": false },
"retraction_extrusion_window": { "value": 10 },
"retraction_hop": { "value": 0.2 },
"speed_print": { "value": 20.0 },
"speed_travel": { "value": 20.0 },
"speed_wall": { "value": 20.0 },
"speed_wall_0": { "value": 20.0 },
"speed_wall_x": { "value": 20.0 },
"speed_z_hop": { "value": "machine_max_feedrate_z" },
"top_bottom_pattern": { "value": "concentric" },
"top_bottom_thickness": { "value": 3 },
"wall_thickness": { "value": 5.0 }
}
}

View File

@ -0,0 +1,74 @@
{
"version": 2,
"name": "Eazao M600",
"inherits": "fdmprinter",
"metadata":
{
"visible": true,
"author": "Eazao",
"manufacturer": "Eazao",
"file_formats": "text/x-gcode",
"has_machine_quality": false,
"has_materials": false,
"machine_extruder_trains": { "0": "eazao_m600_extruder_0" },
"preferred_quality_type": "normal"
},
"overrides":
{
"acceleration_print": { "value": 300 },
"acceleration_travel": { "value": 300 },
"adhesion_type": { "default_value": "'none'" },
"bottom_layers": { "value": 2 },
"cool_fan_enabled": { "value": false },
"infill_sparse_density": { "value": 0 },
"initial_bottom_layers": { "value": 2 },
"jerk_print": { "value": 10 },
"jerk_travel": { "value": "jerk_print" },
"jerk_travel_layer_0": { "value": "jerk_travel" },
"layer_height": { "value": 1.0 },
"layer_height_0": { "value": 1.0 },
"line_width": { "value": 3.0 },
"machine_acceleration": { "value": 300 },
"machine_center_is_zero": { "default_value": false },
"machine_depth": { "default_value": 420 },
"machine_end_gcode": { "default_value": "G92 Z0\nG1 F1500 Z10\nG28 X0 Y400 ;move X Y to min endstops\nM82\nM84 ;steppers off\n" },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_heated_bed": { "default_value": false },
"machine_height": { "default_value": 620 },
"machine_max_acceleration_e": { "value": 500 },
"machine_max_acceleration_x": { "value": 500 },
"machine_max_acceleration_y": { "value": 500 },
"machine_max_acceleration_z": { "value": 50 },
"machine_max_feedrate_e": { "value": 25 },
"machine_max_feedrate_x": { "value": 100 },
"machine_max_feedrate_y": { "value": 100 },
"machine_max_feedrate_z": { "value": 5 },
"machine_max_jerk_e": { "value": 5 },
"machine_max_jerk_xy": { "value": 10 },
"machine_max_jerk_z": { "value": 0.3 },
"machine_name": { "default_value": "Eazao M600" },
"machine_start_gcode": { "default_value": "G21 ;set units to millimeters\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG28 ;Home\nG1 Z25.0 F1500 ;move the platform down 15mm\nG92 E0 ;extruder reset\nG1 F300 E5\nG92 E0 ;The extruder is forced to zero again\nM302\nM163 S0 P0.87; Set Mix Factor\nM163 S1 P0.13; Set Mix Factor\nM164 S0" },
"machine_width": { "default_value": 420 },
"magic_spiralize": { "value": true },
"material_final_print_temperature": { "value": "0" },
"material_initial_print_temperature": { "value": "0" },
"material_print_temperature": { "value": "0" },
"material_print_temperature_layer_0": { "value": "0" },
"optimize_wall_printing_order": { "value": "True" },
"retraction_amount": { "value": 7 },
"retraction_combing": { "value": "'noskin'" },
"retraction_count_max": { "value": 100 },
"retraction_enable": { "value": false },
"retraction_extrusion_window": { "value": 10 },
"retraction_hop": { "value": 0.2 },
"speed_print": { "value": 20.0 },
"speed_travel": { "value": 20.0 },
"speed_wall": { "value": 20.0 },
"speed_wall_0": { "value": 20.0 },
"speed_wall_x": { "value": 20.0 },
"speed_z_hop": { "value": "machine_max_feedrate_z" },
"top_bottom_pattern": { "value": "concentric" },
"top_bottom_thickness": { "value": 3 },
"wall_thickness": { "value": 5.0 }
}
}

View File

@ -0,0 +1,74 @@
{
"version": 2,
"name": "Eazao M700",
"inherits": "fdmprinter",
"metadata":
{
"visible": true,
"author": "Eazao",
"manufacturer": "Eazao",
"file_formats": "text/x-gcode",
"has_machine_quality": false,
"has_materials": false,
"machine_extruder_trains": { "0": "eazao_m700_extruder_0" },
"preferred_quality_type": "normal"
},
"overrides":
{
"acceleration_print": { "value": 300 },
"acceleration_travel": { "value": 300 },
"adhesion_type": { "default_value": "none" },
"bottom_layers": { "value": 2 },
"cool_fan_enabled": { "value": false },
"infill_sparse_density": { "value": 0 },
"initial_bottom_layers": { "value": 2 },
"jerk_print": { "value": 10 },
"jerk_travel": { "value": "jerk_print" },
"jerk_travel_layer_0": { "value": "jerk_travel" },
"layer_height": { "value": 1.0 },
"layer_height_0": { "value": 1.0 },
"line_width": { "value": 3.0 },
"machine_acceleration": { "value": 300 },
"machine_center_is_zero": { "default_value": false },
"machine_depth": { "default_value": 520 },
"machine_end_gcode": { "default_value": "G92 Z0\nG1 F1500 Z10\nG28 X0 Y500 ;move X Y to min endstops\nM82\nM84 ;steppers off\n" },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_heated_bed": { "default_value": false },
"machine_height": { "default_value": 720 },
"machine_max_acceleration_e": { "value": 500 },
"machine_max_acceleration_x": { "value": 500 },
"machine_max_acceleration_y": { "value": 500 },
"machine_max_acceleration_z": { "value": 50 },
"machine_max_feedrate_e": { "value": 25 },
"machine_max_feedrate_x": { "value": 100 },
"machine_max_feedrate_y": { "value": 100 },
"machine_max_feedrate_z": { "value": 5 },
"machine_max_jerk_e": { "value": 5 },
"machine_max_jerk_xy": { "value": 10 },
"machine_max_jerk_z": { "value": 0.3 },
"machine_name": { "default_value": "Eazao M700" },
"machine_start_gcode": { "default_value": "G21 ;set units to millimeters\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG28 ;Home\nG1 Z25.0 F1500 ;move the platform down 15mm\nG92 E0 ;extruder reset\nG1 F300 E5\nG92 E0 ;The extruder is forced to zero again\nM302\nM163 S0 P0.87; Set Mix Factor\nM163 S1 P0.13; Set Mix Factor\nM164 S0" },
"machine_width": { "default_value": 520 },
"magic_spiralize": { "value": true },
"material_final_print_temperature": { "value": "0" },
"material_initial_print_temperature": { "value": "0" },
"material_print_temperature": { "value": "0" },
"material_print_temperature_layer_0": { "value": "0" },
"optimize_wall_printing_order": { "value": "True" },
"retraction_amount": { "value": 7 },
"retraction_combing": { "value": "'noskin'" },
"retraction_count_max": { "value": 100 },
"retraction_enable": { "value": false },
"retraction_extrusion_window": { "value": 10 },
"retraction_hop": { "value": 0.2 },
"speed_print": { "value": 20.0 },
"speed_travel": { "value": 20.0 },
"speed_wall": { "value": 20.0 },
"speed_wall_0": { "value": 20.0 },
"speed_wall_x": { "value": 20.0 },
"speed_z_hop": { "value": "machine_max_feedrate_z" },
"top_bottom_pattern": { "value": "concentric" },
"top_bottom_thickness": { "value": 3 },
"wall_thickness": { "value": 5.0 }
}
}

View File

@ -0,0 +1,74 @@
{
"version": 2,
"name": "Eazao Potter",
"inherits": "fdmprinter",
"metadata":
{
"visible": true,
"author": "Eazao",
"manufacturer": "Eazao",
"file_formats": "text/x-gcode",
"has_machine_quality": false,
"has_materials": false,
"machine_extruder_trains": { "0": "eazao_potter_extruder_0" },
"preferred_quality_type": "normal"
},
"overrides":
{
"acceleration_print": { "value": 300 },
"acceleration_travel": { "value": 300 },
"adhesion_type": { "default_value": "'none'" },
"bottom_layers": { "value": 3 },
"cool_fan_enabled": { "value": false },
"infill_sparse_density": { "value": 0 },
"initial_bottom_layers": { "value": 3 },
"jerk_print": { "value": 10 },
"jerk_travel": { "value": "jerk_print" },
"jerk_travel_layer_0": { "value": "jerk_travel" },
"layer_height": { "value": 1.0 },
"layer_height_0": { "value": 1.0 },
"line_width": { "value": 3.0 },
"machine_acceleration": { "value": 300 },
"machine_center_is_zero": { "default_value": false },
"machine_depth": { "default_value": 167 },
"machine_end_gcode": { "default_value": "G92 Z0 E0\nG1 F1500 Z10 E-2\nM82\nM84 ;steppers off\n" },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_heated_bed": { "default_value": false },
"machine_height": { "default_value": 280 },
"machine_max_acceleration_e": { "value": 500 },
"machine_max_acceleration_x": { "value": 500 },
"machine_max_acceleration_y": { "value": 500 },
"machine_max_acceleration_z": { "value": 50 },
"machine_max_feedrate_e": { "value": 25 },
"machine_max_feedrate_x": { "value": 100 },
"machine_max_feedrate_y": { "value": 100 },
"machine_max_feedrate_z": { "value": 5 },
"machine_max_jerk_e": { "value": 5 },
"machine_max_jerk_xy": { "value": 10 },
"machine_max_jerk_z": { "value": 0.3 },
"machine_name": { "default_value": "Eazao Potter" },
"machine_start_gcode": { "default_value": "G21 ;set units to millimeters\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG28 ;Home\nG1 Z25.0 F1500 ;move the platform down 25mm\nG92 E0 ;extruder reset\nG1 F1500 E2\nG92 E0 ;The extruder is forced to zero again\nM302\nM163 S0 P0.85; Set Mix Factor\nM163 S1 P0.15; Set Mix Factor\nM164 S0" },
"machine_width": { "default_value": 167 },
"magic_spiralize": { "value": true },
"material_final_print_temperature": { "value": "0" },
"material_initial_print_temperature": { "value": "0" },
"material_print_temperature": { "value": "0" },
"material_print_temperature_layer_0": { "value": "0" },
"optimize_wall_printing_order": { "value": "True" },
"retraction_amount": { "value": 7 },
"retraction_combing": { "value": "'noskin'" },
"retraction_count_max": { "value": 100 },
"retraction_enable": { "value": false },
"retraction_extrusion_window": { "value": 10 },
"retraction_hop": { "value": 0.2 },
"speed_print": { "value": 25.0 },
"speed_travel": { "value": 25.0 },
"speed_wall": { "value": 25.0 },
"speed_wall_0": { "value": 25.0 },
"speed_wall_x": { "value": 25.0 },
"speed_z_hop": { "value": "machine_max_feedrate_z" },
"top_bottom_pattern": { "value": "concentric" },
"top_bottom_thickness": { "value": 3 },
"wall_thickness": { "value": 3.0 }
}
}

View File

@ -9,9 +9,8 @@
"manufacturer": "Eazao", "manufacturer": "Eazao",
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"has_machine_quality": false, "has_machine_quality": false,
"has_materials": true, "has_materials": false,
"machine_extruder_trains": { "0": "eazao_zero_extruder_0" }, "machine_extruder_trains": { "0": "eazao_zero_extruder_0" },
"preferred_material": "generic_pla",
"preferred_quality_type": "normal" "preferred_quality_type": "normal"
}, },
"overrides": "overrides":
@ -20,11 +19,10 @@
"acceleration_print": { "value": 300 }, "acceleration_print": { "value": 300 },
"acceleration_travel": { "value": 300 }, "acceleration_travel": { "value": 300 },
"adhesion_type": { "default_value": "none" }, "adhesion_type": { "default_value": "none" },
"bottom_layers": { "value": 2 }, "bottom_layers": { "value": 3 },
"cool_fan_enabled": { "value": false }, "cool_fan_enabled": { "value": false },
"infill_sparse_density": { "value": 0 }, "infill_sparse_density": { "value": 0 },
"initial_bottom_layers": { "value": 2 }, "initial_bottom_layers": { "value": 3 },
"jerk_enabled": { "value": false },
"jerk_print": { "value": 10 }, "jerk_print": { "value": 10 },
"jerk_travel": { "value": "jerk_print" }, "jerk_travel": { "value": "jerk_print" },
"jerk_travel_layer_0": { "value": "jerk_travel" }, "jerk_travel_layer_0": { "value": "jerk_travel" },
@ -34,7 +32,7 @@
"machine_acceleration": { "value": 300 }, "machine_acceleration": { "value": 300 },
"machine_center_is_zero": { "default_value": false }, "machine_center_is_zero": { "default_value": false },
"machine_depth": { "default_value": 150 }, "machine_depth": { "default_value": 150 },
"machine_end_gcode": { "default_value": "G92 E10\nG1 E-10 F300\nG28 X0 Y0 ;move X Y to min endstops\nM82\nM84 ;steppers off\n" }, "machine_end_gcode": { "default_value": "G92 Z0 E0\nG1 F1500 E-2\nG1 F1500 Z10\nG28 X0;move X Y to min endstops\nM82\nM84 ;steppers off\n" },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_heated_bed": { "default_value": false }, "machine_heated_bed": { "default_value": false },
"machine_height": { "default_value": 240 }, "machine_height": { "default_value": 240 },
@ -49,9 +47,10 @@
"machine_max_jerk_e": { "value": 5 }, "machine_max_jerk_e": { "value": 5 },
"machine_max_jerk_xy": { "value": 10 }, "machine_max_jerk_xy": { "value": 10 },
"machine_max_jerk_z": { "value": 0.3 }, "machine_max_jerk_z": { "value": 0.3 },
"machine_name": { "default_value": "EAZAO Zero" }, "machine_name": { "default_value": "Eazao Zero" },
"machine_start_gcode": { "default_value": "G21 \nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG28 ;Home \nG1 Z15.0 F1500 ;move the platform down 15mm\nG92 E0 \nG1 F300 E10\nG92 E0\nM302\nM163 S0 P0.9; Set Mix Factor\nM163 S1 P0.1; Set Mix Factor\nM164 S0\n" }, "machine_start_gcode": { "default_value": "G21 \nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG28 ;Home \nG1 Z15.0 F1500 ;move the platform down 15mm\nG92 E0 \nG1 F1500 E2\nG92 E0\nM302\nM163 S0 P0.8; Set Mix Factor\nM163 S1 P0.2; Set Mix Factor\nM164 S0\n" },
"machine_width": { "default_value": 150 }, "machine_width": { "default_value": 150 },
"magic_spiralize": { "value": true },
"material_final_print_temperature": { "value": "0" }, "material_final_print_temperature": { "value": "0" },
"material_initial_print_temperature": { "value": "0" }, "material_initial_print_temperature": { "value": "0" },
"material_print_temperature": { "value": "0" }, "material_print_temperature": { "value": "0" },
@ -64,18 +63,10 @@
"retraction_enable": { "value": false }, "retraction_enable": { "value": false },
"retraction_extrusion_window": { "value": 10 }, "retraction_extrusion_window": { "value": 10 },
"retraction_hop": { "value": 0.2 }, "retraction_hop": { "value": 0.2 },
"retraction_hop_enabled": { "value": false }, "speed_print": { "value": 25.0 },
"retraction_speed": { "value": 25 },
"speed_print": { "value": 20.0 },
"speed_travel": { "value": 20.0 },
"speed_wall": { "value": 20.0 },
"speed_wall_0": { "value": 20.0 },
"speed_wall_x": { "value": 20.0 },
"speed_z_hop": { "value": "machine_max_feedrate_z" }, "speed_z_hop": { "value": "machine_max_feedrate_z" },
"top_bottom_thickness": { "value": 0 }, "top_bottom_pattern": { "value": "concentric" },
"travel_avoid_other_parts": { "value": true }, "top_bottom_thickness": { "value": 3 },
"travel_avoid_supports": { "value": false },
"travel_retract_before_outer_wall": { "value": false },
"wall_thickness": { "value": 3.0 } "wall_thickness": { "value": 3.0 }
} }
} }

View File

@ -11,7 +11,7 @@
"overrides": "overrides":
{ {
"machine_depth": { "default_value": 210 }, "machine_depth": { "default_value": 210 },
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-10 X5 Y5 Z3 F3000 ;Retract\nG90 ;Absolute positionning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" }, "machine_end_gcode": { "default_value": "G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-10 X5 Y5 Z3 F3000 ;Retract\nG90 ;Absolute positioning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" },
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {
"value": [ "value": [

View File

@ -24,7 +24,7 @@
"brim_width": { "default_value": 5 }, "brim_width": { "default_value": 5 },
"gantry_height": { "value": 30 }, "gantry_height": { "value": 30 },
"machine_depth": { "default_value": 235 }, "machine_depth": { "default_value": 235 },
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F1600 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positionning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" }, "machine_end_gcode": { "default_value": "G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F1600 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positioning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {

View File

@ -29,7 +29,7 @@
"gantry_height": { "value": 30 }, "gantry_height": { "value": 30 },
"machine_always_write_active_tool": { "default_value": true }, "machine_always_write_active_tool": { "default_value": true },
"machine_depth": { "default_value": 235 }, "machine_depth": { "default_value": 235 },
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-80 Z0.2 F1600 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positionning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" }, "machine_end_gcode": { "default_value": "G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-80 Z0.2 F1600 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positioning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" },
"machine_extruder_count": { "default_value": 2 }, "machine_extruder_count": { "default_value": 2 },
"machine_extruders_share_heater": { "default_value": true }, "machine_extruders_share_heater": { "default_value": true },
"machine_extruders_share_nozzle": { "default_value": true }, "machine_extruders_share_nozzle": { "default_value": true },

View File

@ -11,7 +11,7 @@
"overrides": "overrides":
{ {
"machine_depth": { "default_value": 235 }, "machine_depth": { "default_value": 235 },
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-10 X5 Y5 Z3 F3000 ;Retract\nG90 ;Absolute positionning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" }, "machine_end_gcode": { "default_value": "G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-10 X5 Y5 Z3 F3000 ;Retract\nG90 ;Absolute positioning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" },
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {
"value": [ "value": [

View File

@ -11,7 +11,7 @@
"overrides": "overrides":
{ {
"machine_depth": { "default_value": 235 }, "machine_depth": { "default_value": 235 },
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-10 X5 Y5 Z3 F3000 ;Retract\nG90 ;Absolute positionning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" }, "machine_end_gcode": { "default_value": "G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-10 X5 Y5 Z3 F3000 ;Retract\nG90 ;Absolute positioning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" },
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {
"value": [ "value": [

View File

@ -12,7 +12,7 @@
{ {
"infill_overlap": { "value": "0 if infill_sparse_density < 40.01 and infill_pattern != 'concentric' else -5" }, "infill_overlap": { "value": "0 if infill_sparse_density < 40.01 and infill_pattern != 'concentric' else -5" },
"machine_depth": { "default_value": 235 }, "machine_depth": { "default_value": 235 },
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-8 X5 Y5 Z3 F3000 ;Retract\nG90 ;Absolute positionning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" }, "machine_end_gcode": { "default_value": "G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-8 X5 Y5 Z3 F3000 ;Retract\nG90 ;Absolute positioning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" },
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {
"value": [ "value": [

View File

@ -22,7 +22,7 @@
"initial_layer_line_width_factor": { "value": "100.0 if resolveOrValue('adhesion_type') == 'raft' else 125 if line_width < 0.5 else 110" }, "initial_layer_line_width_factor": { "value": "100.0 if resolveOrValue('adhesion_type') == 'raft' else 125 if line_width < 0.5 else 110" },
"machine_acceleration": { "value": 5000 }, "machine_acceleration": { "value": 5000 },
"machine_depth": { "default_value": 230 }, "machine_depth": { "default_value": 230 },
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z2 ;Raise Z more\nG90 ;Absolute positionning\nG1 X0 Y{machine_depth - 5} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" }, "machine_end_gcode": { "default_value": "G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z2 ;Raise Z more\nG90 ;Absolute positioning\nG1 X0 Y{machine_depth - 5} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {

View File

@ -11,7 +11,7 @@
"overrides": "overrides":
{ {
"machine_depth": { "default_value": 235 }, "machine_depth": { "default_value": 235 },
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-10 X5 Y5 Z3 F3000 ;Retract\nG90 ;Absolute positionning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" }, "machine_end_gcode": { "default_value": "G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-10 X5 Y5 Z3 F3000 ;Retract\nG90 ;Absolute positioning\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" },
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {
"value": [ "value": [

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