Compare commits

..
Author SHA1 Message Date
Hanif Koh 8b2ae48e4a Keep the Exposed Band of a Sub-Cell Step Beside the Neighbouring Layer's Outer Wall
On a shallow surface the band of a layer that the layer above leaves exposed is
narrower than a grid cell, so whether the layer is the topmost occupant of any
cell flickers from layer to layer and the band's segments, inner walls and solid
infill hidden by role, came and went in a dashed ring.

That band is always in the same place: the strip just outside the outer wall of
the layer above, or of the layer below for the underside of an overhang. The
classifier now keeps the outer-wall cell map of the previous and next layers as
well as the current one, rotating like the footprints, and keeps any segment
whose midpoint lies within a line and a half, or a cell if larger, of a
neighbouring layer's outer wall. It can only add segments, so no hole is opened
by it; what it over-keeps is the covered strip under the neighbour's wall.
2026-09-23 12:53:58 +08:00
Hanif Koh 8095904bc2 Catch Only Allocation and Thread Failures Around the Shell Classification
The fallback that marks every segment as shell caught everything, which would
also have hidden a logic error. It now catches what the classification can
really throw on a huge print: std::bad_alloc from its grids and std::system_error
from a worker thread that cannot be launched.
2026-09-23 12:34:56 +08:00
Hanif Koh b7c86befe1 Keep the Surfaces the Roles Name in the Shell-Only Drag Mode
The shell test asks whether at least half of the cells a segment crosses lie on
the footprint's boundary. On a curved wall the boundary is a staircase of cells,
and an outer-wall segment that runs past an inside corner of that staircase
crosses cells whose four neighbours are all filled, so a short segment there
failed the test and vanished, leaving a hole with the inner wall showing through
it. A shallow dome does the same to its top skin: one layer's ring lies within
the same cells as the next layer's, so it is neither a boundary nor the topmost
occupant, and the bridges under it showed through.

Outer walls and the top and bottom skins are the surface by definition, so the
mode now keeps them whatever the grid says, and the grid decides only for the
roles that can be either: inner walls, the prime tower, supports, skirt and brim.
2026-09-23 12:34:56 +08:00
Hanif Koh 526a91d323 Add a Shell-Only Drag Mode That Keeps the Visible Surface of the Print
Outer walls is a role filter, which loses the prime tower, whose every segment
shares one role, and every top and bottom surface between the range's ends. A
fifth mode, Shell only, keeps what a geometric classification marks as the
visible surface of the print, of one layer in every N, plus whatever a view from
straight above or below sees of the layers it skips, so that a step does not
vanish.

The classification runs once per load, the first time the mode is needed, and
is purely geometric: each layer is rasterized into a half-millimetre occupancy
grid and closed so that sparse infill reads as solid, a cell is on the shell
when any of its six neighbours is empty, and a segment is kept when at least
half of the cells it crosses are. The closing dilates each connected component
on its own and leaves a cell two components reach empty, so the gap between two
close objects is never bridged. The first inner wall beside an outer wall is
kept as well, since the step of a sloped surface is narrower than a cell, and
the interior infill roles and gap fill are never taken, whatever the geometry
says. The layers are split across up to eight workers, and an allocation
failure on a huge print falls back to keeping everything but the hidden infill.

Separate from the other modes so that it can be dropped on its own.
2026-09-23 12:14:16 +08:00
Hanif Koh 493896260e Let the User Choose What the Preview Draws While Dragging
The solid model is one answer to a preview that cannot keep up with a drag; a
reviewer asked for the two that keep the drag view made of toolpaths. The
checkbox is now a combobox, "Simplify preview while dragging", with Off, Solid
model, Skip layers and Outer walls, and a spin, "Draw one layer in every N",
for the toolpath modes. Off is the default, and with it nothing is built.

Skip layers keeps one layer in every N. Outer walls keeps the outer and overhang
perimeters of those layers, so the prime tower and supports, whose segments have
other roles, drop out of it. Both keep the bottom and top layers of the visible
range whole, as the solid model does, so the faces the range cuts open stay what
was printed there.

libvgcode takes the choice as EReducedDetailMode and a stride; the sets are
rebuilt when either changes, and drawing from the reduced one is still a buffer
binding. A mode change reaches the loaded preview at once, as the checkbox did.
2026-09-23 12:14:16 +08:00
Hanif Koh ca83497bb6 Release the Reduced Set When Unused and Share the Drag Check With the Scene Cache
The reduced index buffers were only re-uploaded while the preference was on, so
the last set stayed allocated until the next load once it was switched off, and
their size was missing from get_used_gpu_memory(). They are now uploaded on every
rebuild, empty when nothing was built, and counted.

The scene cache and the solid model both asked whether the user was dragging,
with different lists: the cache knew about gizmos and the rectangle selection,
the solid model about the navigator and the sliders. One is_user_interacting()
now answers both.

The tooltip says that negative volumes are not cut out of the solid model, since
load_shells() drops every non-model-part volume, and the preferences handler
keeps the dimming comment with the branch it documents.
2026-09-23 12:13:49 +08:00
Hanif Koh 6a985744c4 Offer the Sliced Objects as a Solid Model While Dragging
The G-code preview draws every toolpath segment as an instanced box, and its frame cost is
linear in the number of segments drawn. On a plate of large objects that is enough that
dragging the camera or a preview slider cannot keep up, and no amount of per-segment work
changes that; only drawing fewer segments does.

The objects themselves are cheaper: a mesh costs its triangles once, however many layers it
has, and the preview already loads the objects as shells for its translucent ghost. A new
preference, "Only render solid model when dragging" (off by default), draws those shells
opaque in their filament colours instead of the toolpaths while the user drags.

The visible layer range still holds. The shells are cut at the range's top and bottom through
the gouraud shader's z range, and libvgcode keeps a second index buffer holding just the
range's bottom and top layers, filled in the same walk as the full one, which is drawn
afterwards so that it caps the cut with what was really printed there. Switching between the
two sets is a buffer binding, never a rebuild. The prime tower is added to the shells from its
sliced mesh while the preference is on, positioned as the print placed it; it keeps its opaque
colour and so stays out of the translucent ghost. Supports have no mesh and are not shown.

Dragging is the camera, the navigator or either slider being held; a slider reports it from
ImGui's active id, since its dirty flag is raised and consumed inside one frame. A wheel step
holds the solid model for a 150 ms settle time, with the frame that restores the toolpaths
scheduled for when it runs out. The switch is decided at the top of the frame, before the
cached scene is consulted, and a frame that switches redraws the scene. A drag cut short by
focus or capture loss is ended explicitly, and a button release wakes the idle loop, since on
some platforms no idle event follows it until the next input.

With the preference off, nothing is built and the preview is unchanged.
2026-09-23 12:13:49 +08:00
3619 changed files with 273405 additions and 219170 deletions
-139
View File
@@ -1,139 +0,0 @@
name: Daily OFL OTA Update
# This workflow is intended for creating and publishing the OrcaFilamentLibrary (OFL) OPC package to
# https://github.com/OrcaSlicer/orcaslicer-profiles, which generates an OTA update.
# This cronjob runs daily at 00:00 UTC every day and scans main plus every release/vX.Y.Z branch for
# changes to resources/profiles/OrcaFilamentLibrary since that branch's own last successful run. Any
# branch with no changes is skipped; each changed branch gets its own post_merge_profiles.yml dispatch.
#
# OFL has no dedicated FOLDER_MERGERS grant (it isn't merged through the PR merge-bot delegation
# scheme), so post_merge_profiles.yml is dispatched with an explicit `vendor` input, which that
# workflow trusts and uses to bypass the FOLDER_MERGERS check for this trigger. That same explicit-
# vendor-dispatch path is also what makes post_merge_profiles.yml call the OTA auto-publish API after
# uploading - see post_merge_profiles.yml for both sides of that contract.
#
# At the start of each run, the pending-publish table is cleared up to a captured
# timestamp (POST /api/v1/ota/ofl/pending/clear?timestamp=...). Changes merged after
# that timestamp remain pending for the next run.
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
permissions:
actions: write # list this workflow's past runs and dispatch post_merge_profiles.yml
contents: read
env:
VENDOR: OrcaFilamentLibrary
jobs:
daily-job:
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' }}
runs-on: ubuntu-24.04
steps:
- name: Capture start timestamp and clear OFL pending queue
id: start
shell: bash
env:
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
run: |
set -euo pipefail
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
timestamp="$(date -u +%s)"
echo "timestamp=$timestamp" >> "$GITHUB_OUTPUT"
resp_file="$RUNNER_TEMP/ota-pending-clear-response.json"
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
"${OTA_API_BASE_URL%/}/api/v1/ota/ofl/pending/clear?timestamp=$timestamp" \
-H "Authorization: Bearer $OTA_API_KEY")"
body="$(cat "$resp_file")"
echo "$body"
if [ "$status" != "200" ]; then
echo "::error::OTA pending-clear call failed with HTTP $status"
exit 1
fi
- name: Checkout repository
uses: actions/checkout@v7
with:
# Full history: the per-branch "since last successful run" check below
# needs to look arbitrarily far back if a prior run failed or was skipped.
fetch-depth: 0
- name: Fetch all branches
shell: bash
run: git fetch origin '+refs/heads/*:refs/remotes/origin/*'
- name: Scan branches and publish changed OFL profiles
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SCAN_UNTIL: ${{ steps.start.outputs.timestamp }}
run: |
set -euo pipefail
mapfile -t branches < <(
gh api "repos/${{ github.repository }}/branches" --paginate --jq '.[].name' \
| grep -E '^(main|release/v[0-9]+\.[0-9]+\.[0-9]+)$' | sort -u
)
for branch in "${branches[@]}"; do
echo "::group::$branch"
# post_merge_profiles.yml's own run history, not this workflow's: this
# workflow only ever runs against main (schedule, or workflow_dispatch
# --ref main), so its head branch never varies - filtering ITS history
# by $branch would never match anything except main. post_merge_profiles.yml
# genuinely runs per-branch (this dispatch below sets --ref "$branch"),
# so its history is the real per-branch checkpoint. It also means a
# failed publish naturally gets retried tomorrow: the checkpoint only
# advances on a run that actually succeeded.
# --method GET is required, not cosmetic: gh api defaults to POST
# whenever -f fields are present unless a method is given
# explicitly, and POST on this list-runs endpoint 404s - confirmed
# on real Actions infrastructure, not just reasoned about.
since="$(gh api --method GET "repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
-f status=success -f branch="$branch" -f per_page=1 \
--jq '.workflow_runs[0].run_started_at // empty')"
if [ -z "$since" ]; then
echo "No prior successful run for $branch; checking OFL changes up to $SCAN_UNTIL."
changed_files="$(git log --until="$SCAN_UNTIL" --name-only --pretty=format: "origin/$branch" -- \
resources/profiles/OrcaFilamentLibrary resources/profiles/OrcaFilamentLibrary.json \
| sed '/^$/d')"
else
changed_files="$(git log --since="$since" --until="$SCAN_UNTIL" --name-only --pretty=format: "origin/$branch" -- \
resources/profiles/OrcaFilamentLibrary resources/profiles/OrcaFilamentLibrary.json \
| sed '/^$/d')"
fi
if [ -n "$changed_files" ]; then
echo "OFL changed on $branch from ${since:-the beginning} through $SCAN_UNTIL:"
echo "$changed_files"
changed=true
else
echo "No OFL changes on $branch through $SCAN_UNTIL."
changed=false
fi
if [ "$changed" = true ]; then
# Tolerate a per-branch failure (e.g. a pre-existing release branch
# whose post_merge_profiles.yml predates the vendor/auto_publish
# inputs) rather than aborting the whole scan under set -e.
if ! gh workflow run post_merge_profiles.yml \
--repo "${{ github.repository }}" \
--ref "$branch" \
-f vendor="$VENDOR" -f auto_publish=true; then
echo "::warning::failed to dispatch post_merge_profiles.yml for $branch - its post_merge_profiles.yml at this ref may predate the vendor/auto_publish inputs"
fi
fi
echo "::endgroup::"
done
+8 -52
View File
@@ -5,9 +5,8 @@
# re-sliced on its own to see whether it changes the G-code
# harness - the GUI-vs-CLI parity harness (metrics only, never fails)
# Both test the latest successful build_all.yml Linux AppImage from main, with
# sources checked out at the commit that build was made from; a manual run can
# name another branch, or pin one build by its run id. Nothing here gates a
# build or a PR.
# sources checked out at the commit that build was made from. Nothing here
# gates a build or a PR.
name: Parity Nightly
on:
@@ -21,13 +20,9 @@ on:
required: false
default: "main"
build_branch:
description: "branch whose newest successful build_all artifact to test (a PR build is the PR merged into its base; sources are checked out at the PR head)"
description: "branch whose latest successful build_all artifact to test"
required: false
default: "main"
build_run_id:
description: "build_all run id to test instead of build_branch's newest (same PR caveat)"
required: false
default: ""
fixtures:
description: "harness fixture ids, space-separated (empty = all)"
required: false
@@ -55,53 +50,14 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
BRANCH: ${{ inputs.build_branch || 'main' }}
RUN_ID: ${{ inputs.build_run_id }}
SCHEDULED: ${{ github.event_name == 'schedule' }}
run: |
set -euo pipefail
if [ -n "$RUN_ID" ]; then
[[ $RUN_ID =~ ^[0-9]+$ ]] || { echo "build_run_id must be a numeric run id, got '$RUN_ID'" >&2; exit 1; }
# a pinned build is read directly, not through a search; it must come
# from this repository, because the later jobs check out its commit here
found=$(gh api "repos/$GH_REPO/actions/runs/$RUN_ID" --jq \
'select(.path == ".github/workflows/build_all.yml" and .conclusion == "success"
and .head_repository.full_name == env.GH_REPO)
| "\(.id) \(.head_sha) \(.created_at)"')
[ -n "$found" ] || { echo "run $RUN_ID is not a successful build_all run of $GH_REPO" >&2; exit 1; }
else
# GitHub serves filtered run listings (branch=, status=, head_sha=, ...)
# from a search index that has returned weeks-old results, while the
# unfiltered listing stays current, so list unfiltered and filter here.
# The repository check keeps out fork PRs whose branch has the same
# name. A feature branch is normally built only for its PR, and a PR
# build compiles the PR merged into its base rather than head_sha, so
# a build of the branch itself (push or dispatch) is preferred when
# the same page has one.
pick='([.workflow_runs[] | select(.head_branch == env.BRANCH and .conclusion == "success"
and .head_repository.full_name == env.GH_REPO)]
| map(select(.event != "pull_request"))[0] // .[0])
| select(.) | "\(.id) \(.head_sha) \(.created_at)"'
# a page of 100 runs spans about a day and a half; a manual run may
# target a branch that last built weeks ago
pages=3
if [ "$SCHEDULED" != true ]; then pages=20; fi
found=""
for page in $(seq "$pages"); do
found=$(gh api "repos/$GH_REPO/actions/workflows/build_all.yml/runs?per_page=100&page=$page" --jq "$pick")
if [ -n "$found" ]; then break; fi
done
[ -n "$found" ] || { echo "no successful $BRANCH build among the last $((pages * 100)) build_all runs; pass build_run_id to test an older one" >&2; exit 1; }
fi
read -r run_id head_sha created <<< "$found"
# the nightly fails rather than report on a stale build
if [ "$SCHEDULED" = true ] && [ $(( $(date +%s) - $(date -d "$created" +%s) )) -gt 172800 ]; then
echo "newest $BRANCH build $run_id is from $created, over 48 hours old" >&2
exit 1
fi
printf 'run_id=%s\nhead_sha=%s\n' "$run_id" "$head_sha" >> "$GITHUB_OUTPUT"
gh run list --workflow build_all.yml \
--branch "${{ inputs.build_branch || 'main' }}" \
--status success --limit 1 --json databaseId,headSha \
--jq '"run_id=\(.[0].databaseId)\nhead_sha=\(.[0].headSha)"' \
>> "$GITHUB_OUTPUT"
cat "$GITHUB_OUTPUT"
echo "Testing build [$run_id](https://github.com/$GH_REPO/actions/runs/$run_id) of \`$head_sha\`, built $created" >> "$GITHUB_STEP_SUMMARY"
effect:
name: Override sweep effect stage (shard ${{ matrix.shard }})
+10 -215
View File
@@ -8,19 +8,6 @@ name: Post-merge profiles
# only then does it become a live OTA update - this workflow does none of that
# last part (no changelog, no R2, no webhook).
#
# A workflow_dispatch carrying a `vendor` input (e.g. the daily OFL cron - OFL has
# no FOLDER_MERGERS grant, since it isn't merged through the PR merge-bot delegation
# scheme) publishes that vendor directly and skips the FOLDER_MERGERS check below.
# workflow_dispatch is already a trusted, explicit trigger, unlike the automatic
# push-diff path the FOLDER_MERGERS check exists to gate.
#
# Separately, an ordinary push whose diff touches an OrcaFilamentLibrary company
# folder (resources/profiles/OrcaFilamentLibrary/filament/<Company>/**) records
# that PR as pending via POST /api/v1/ota/ofl/pending, regardless of whether
# OrcaFilamentLibrary as a whole is authorized to publish in this same run - a
# partner's OTA Manager dashboard should see a merged PR immediately, well
# before the daily cron actually builds and publishes it.
#
# Asset contract expected by OrcaCloud's release scanner:
# ^(\d+\.\d+\.\d+)_([^_]+)_(\d+(?:\.\d+){3})_(\d{12})\.zip$
# <orca_ver>_<vendor>_<profile_version>_<UTC yyyymmddHHMM>.zip (zip root: <vendor>.opc)
@@ -30,39 +17,16 @@ name: Post-merge profiles
on:
push:
branches:
# once v2.5.0 stable is released, this will be removed, so nightly won't receive OTA updates.
- main
# release/vX.Y.Z point-release branches only, not the release/vX.Y working
# branch profile PRs land on first - "v*.*.*" requires two literal dots,
# which release/vX.Y (one dot) doesn't have.
- release/v*.*.*
- release/*
paths:
- 'resources/profiles/**'
- '.github/workflows/post_merge_profiles.yml'
workflow_dispatch:
inputs:
vendor:
description: >-
Publish only this vendor, bypassing the FOLDER_MERGERS grant check.
For trusted explicit dispatches only (e.g. the OFL nightly cron).
Leave empty to fall back to diffing the triggering commit.
required: false
type: string
auto_publish:
description: >-
After publishing, also call the OTA auto-publish API to go live
immediately, skipping the human changelog/Publish step. Separate
from `vendor` on purpose: a maintainer can dispatch with just
`vendor` set to rebuild/republish an asset without it going live.
Only the OFL nightly cron should set this to true.
required: false
type: boolean
default: false
permissions:
contents: read
pull-requests: read # commits/{sha}/pulls lookup in the OFL-pending step
# One run per branch; let a run finish rather than cancel it, since it publishes.
concurrency:
@@ -102,38 +66,8 @@ jobs:
shell: bash
env:
FOLDER_MERGERS: ${{ vars.FOLDER_MERGERS }}
DISPATCH_VENDOR: ${{ github.event_name == 'workflow_dispatch' && inputs.vendor || '' }}
run: |
set -euo pipefail
# A vendor has a manifest plus either a preset directory or a version
# field; this drops non-vendor files such as blacklist.json. Shared by
# both the explicit-dispatch path below and the push-diff path further
# down, so the definition of "valid vendor" can't drift between them.
is_valid_vendor() {
local v="$1"
local json="resources/profiles/$v.json"
[ -f "$json" ] && { [ -d "resources/profiles/$v" ] || jq -e '.version' "$json" >/dev/null 2>&1; }
}
# Explicit vendor dispatch (e.g. the OFL cron): trust the caller and
# skip both the git-diff detection and the FOLDER_MERGERS check below.
if [ -n "$DISPATCH_VENDOR" ]; then
v="$DISPATCH_VENDOR"
# Becomes part of the release asset filename and the OTA API's
# payload; keep it to the same charset every real vendor name uses.
if ! [[ "$v" =~ ^[A-Za-z0-9]+$ ]]; then
echo "::error::vendor '$v' must be alphanumeric"
exit 1
fi
if ! is_valid_vendor "$v"; then
echo "::error::vendor '$v' has no resources/profiles/$v.json with a profile directory or version field"
exit 1
fi
echo "vendors=$v" >> "$GITHUB_OUTPUT"
exit 0
fi
base='${{ github.event.before }}'
head='${{ github.sha }}'
# Zero SHA (branch created / force push) or manual dispatch: fall back
@@ -141,10 +75,6 @@ jobs:
if [ -z "$base" ] || [ "$base" = "0000000000000000000000000000000000000000" ] || ! git cat-file -e "$base^{commit}" 2>/dev/null; then
base="$head^"
fi
# Exposed so the OFL-pending step below can reuse this exact diff
# range instead of re-deriving it (and drifting from this logic).
echo "base=$base" >> "$GITHUB_OUTPUT"
echo "head=$head" >> "$GITHUB_OUTPUT"
mapfile -t candidates < <(
git diff --name-only "$base" "$head" -- resources/profiles \
| sed -nE 's#^resources/profiles/([^/]+)/.*#\1#p; s#^resources/profiles/([^/]+)\.json$#\1#p' \
@@ -154,7 +84,10 @@ jobs:
vendors=()
for v in "${candidates[@]:-}"; do
[ -n "$v" ] || continue
if is_valid_vendor "$v"; then
json="resources/profiles/$v.json"
# A vendor has a manifest plus either a preset directory or a version
# field; this drops non-vendor files such as blacklist.json.
if [ -f "$json" ] && { [ -d "resources/profiles/$v" ] || jq -e '.version' "$json" >/dev/null 2>&1; }; then
vendors+=("$v")
fi
done
@@ -168,9 +101,6 @@ jobs:
# sibling bundle JSON are covered by at least one FOLDER_MERGERS
# grant. The account part is intentionally ignored here: this is a
# post-merge safety check, not an authorization check for a command.
# An ineligible vendor (e.g. OrcaFilamentLibrary, which has no grant)
# is dropped on its own - it never blocks other vendors in the same
# push from publishing.
grants=()
while IFS= read -r raw_line; do
line="${raw_line#"${raw_line%%[![:space:]]*}"}"
@@ -197,29 +127,23 @@ jobs:
return 1
}
authorized=()
unauthorized=()
for v in "${vendors[@]}"; do
if is_granted "resources/profiles/$v" && is_granted "resources/profiles/$v.json"; then
authorized+=("$v")
else
if ! is_granted "resources/profiles/$v" || ! is_granted "resources/profiles/$v.json"; then
unauthorized+=("$v")
fi
done
if [ "${#unauthorized[@]}" -ne 0 ]; then
echo "::warning::skipping vendor(s) with no FOLDER_MERGERS grant (no asset built or published for them this run): ${unauthorized[*]}"
echo "vendors=" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "vendors=${authorized[*]}" >> "$GITHUB_OUTPUT"
echo "vendors=${vendors[*]}" >> "$GITHUB_OUTPUT"
- name: Resolve Orca version
id: orca
# Unconditional: needed both by the vendor-publish pipeline below (only
# when vendors is non-empty) and by the OFL-pending step at the end
# (which runs whenever OFL itself changed, even if vendors ends up
# empty because OFL has no FOLDER_MERGERS grant). Cheap and harmless
# to always resolve - version.inc is present on every commit.
if: steps.vendors.outputs.vendors != ''
shell: bash
run: |
set -euo pipefail
@@ -318,132 +242,3 @@ jobs:
echo "### Published to \`$repo\` release \`$RELEASE_TAG\`"
for f in "$ASSET_DIR"/*.zip; do echo "- \`$(basename "$f")\`"; done
} >> "$GITHUB_STEP_SUMMARY"
- name: Notify OTA auto-publish
# Gated on auto_publish specifically, not just "vendor was dispatched":
# a maintainer manually dispatching with vendor=OrcaFilamentLibrary (e.g.
# to rebuild/republish an asset while debugging) must not silently go
# live. Only a caller that explicitly opts in with auto_publish=true
# (the OFL nightly cron) skips the human changelog/Publish step.
if: >-
steps.vendors.outputs.vendors != '' && github.event_name == 'workflow_dispatch'
&& (inputs.auto_publish == true || inputs.auto_publish == 'true')
shell: bash
env:
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
ASSET_DIR: ${{ steps.pkg.outputs.dir }}
run: |
set -euo pipefail
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
mapfile -t zip_files < <(cd "$ASSET_DIR" && ls -1 *.zip)
filenames_json="$(printf '%s\n' "${zip_files[@]}" | jq -R . | jq -s .)"
payload="$(jq -n --argjson filenames "$filenames_json" '{filenames: $filenames}')"
resp_file="$RUNNER_TEMP/ota-auto-publish-response.json"
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
"${OTA_API_BASE_URL%/}/api/v1/ota/auto-publish" \
-H "Authorization: Bearer $OTA_API_KEY" \
-H 'Content-Type: application/json' \
-d "$payload")"
body="$(cat "$resp_file")"
echo "$body"
if [ "$status" != "200" ]; then
echo "::error::OTA auto-publish call failed with HTTP $status"
exit 1
fi
# A 200 can still carry per-file "error" results (e.g. NOT_FOUND); the
# asset is already safely published to the profiles release above, but
# it never went live, so treat that as a failure worth surfacing loudly.
error_count="$(jq '[.results[] | select(.status == "error")] | length' <<< "$body")"
if [ "$error_count" != "0" ]; then
jq -r '.results[] | select(.status == "error") | "::error::\(.filename): \(.code) - \(.message)"' <<< "$body"
exit 1
fi
- name: Record OFL pending changes
# A real merge, never the cron's explicit-vendor dispatch (that's
# automation publishing, not a new merge to report). This covers two
# trigger shapes: an ordinary push, and a vendor-less workflow_dispatch
# - the latter is exactly what pr-merge-bot.yml's re-dispatch after a
# successful /bot merge looks like (a GITHUB_TOKEN-authored merge fires
# no push event at all, which is why that re-dispatch exists). Both
# land in the same diff-fallback path in "Resolve changed vendors", so
# base/head/orca_ver are already correctly populated either way - only
# this condition needs widening.
# Placed last in the job on purpose: a failure here must never block
# the vendor-publish pipeline above, which a step failing earlier in
# the job would do (subsequent steps without always() get skipped).
if: >-
github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && !inputs.vendor)
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
run: |
set -euo pipefail
base='${{ steps.vendors.outputs.base }}'
head='${{ steps.vendors.outputs.head }}'
orca_ver='${{ steps.orca.outputs.orca_ver }}'
# Only real vendor subdirectories under filament/, e.g.
# .../filament/Qidi/x.json -> "Qidi". This naturally excludes loose
# top-level files (.../filament/Generic PLA @System.json - no further
# slash to match) and is further filtered below to drop "base", the
# shared @base/@System inheritance folder, not a partner company.
mapfile -t ofl_companies < <(
git diff --name-only "$base" "$head" -- resources/profiles/OrcaFilamentLibrary/filament \
| sed -nE 's#^resources/profiles/OrcaFilamentLibrary/filament/([^/]+)/.*#\1#p' \
| grep -vx 'base' \
| sort -u
)
if [ "${#ofl_companies[@]}" -eq 0 ]; then
echo "No OFL company folders changed in this push."
exit 0
fi
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
# The head commit's own merged PR, not a per-commit walk: this
# assumes the ordinary one-PR-per-push shape every other merge path
# in this repo already assumes (pr-merge-bot.yml's re-dispatch logic
# does the same). A merge commit's parents don't matter here - this
# API call works the same regardless of merge strategy.
pr_json="$(gh api "repos/${{ github.repository }}/commits/$head/pulls" \
--jq '[.[] | select(.merged_at != null)] | sort_by(.merged_at) | last // empty')"
if [ -z "$pr_json" ]; then
echo "::warning::push $head touches OFL compan(y/ies) (${ofl_companies[*]}) but has no associated merged PR; skipping pending record(s)"
exit 0
fi
pr_number="$(jq -r '.number' <<< "$pr_json")"
pr_url="$(jq -r '.html_url' <<< "$pr_json")"
pr_title="$(jq -r '.title' <<< "$pr_json")"
for company in "${ofl_companies[@]}"; do
payload="$(jq -n --arg vendor "$company" --arg ver "$orca_ver" --argjson pr "$pr_number" \
--arg url "$pr_url" --arg title "$pr_title" \
'{vendor: $vendor, orcaSlicerVersion: $ver, prNumber: $pr, prUrl: $url, prTitle: $title}')"
resp_file="$RUNNER_TEMP/ofl-pending-$company.json"
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
"${OTA_API_BASE_URL%/}/api/v1/ota/ofl/pending" \
-H "Authorization: Bearer $OTA_API_KEY" \
-H 'Content-Type: application/json' \
-d "$payload")"
body="$(cat "$resp_file")"
echo "$body"
if [ "$status" != "200" ]; then
echo "::error::OFL pending record failed for vendor=$company (PR #$pr_number): HTTP $status"
exit 1
fi
done
+2 -10
View File
@@ -56,9 +56,9 @@ You can do this in Environment Variables settings.
endif ()
if (APPLE)
# if CMAKE_OSX_DEPLOYMENT_TARGET is not set, set it to 12.0 (the lowest Xcode 27 accepts)
# if CMAKE_OSX_DEPLOYMENT_TARGET is not set, set it to 11.3
if (NOT CMAKE_OSX_DEPLOYMENT_TARGET)
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING "Minimum OS X deployment version" FORCE)
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.3" CACHE STRING "Minimum OS X deployment version" FORCE)
endif ()
message(STATUS "CMAKE_OSX_DEPLOYMENT_TARGET: ${CMAKE_OSX_DEPLOYMENT_TARGET}")
endif ()
@@ -70,9 +70,6 @@ if (POLICY CMP0092)
cmake_policy(SET CMP0092 NEW)
endif ()
# project() reads this, so set it first.
set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/ClangClShowIncludes.cmake")
project(OrcaSlicer)
# Backward compatibility for old CMake versions
@@ -279,11 +276,6 @@ if (APPLE)
endif()
SET(CMAKE_XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.orcaslicer.OrcaSlicer")
# The macOS CI jobs build with Ninja (build_release_macos.sh -x), so the Xcode generator
# is not covered. Xcode adds -Wshorten-64-to-32 by default ("Implicit Conversion to 32 Bit
# Type"); Ninja/-Wall does not, and under -Werror it fails Xcode builds on code CI accepts.
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_64_TO_32_BIT_CONVERSION "NO")
message(STATUS "Orca: IS_CROSS_COMPILE: ${IS_CROSS_COMPILE}")
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(CMAKE_INSTALL_RPATH "$ORIGIN")
+2 -2
View File
@@ -53,7 +53,7 @@ while getopts ":dpa:snt:xbc:i:j:Tuh" opt; do
echo " -s: Build slicer only"
echo " -u: Build universal app only (requires existing arm64 and x86_64 app bundles)"
echo " -n: Nightly build"
echo " -t: Specify minimum version of the target platform, default is 12.0"
echo " -t: Specify minimum version of the target platform, default is 11.3"
echo " -x: Use Ninja Multi-Config CMake generator, default is Xcode"
echo " -b: Build without reconfiguring CMake"
echo " -c: Set CMake build configuration, default is Release"
@@ -95,7 +95,7 @@ if [ -z "$DEPS_CMAKE_GENERATOR" ]; then
fi
if [ -z "$OSX_DEPLOYMENT_TARGET" ]; then
export OSX_DEPLOYMENT_TARGET="12.0"
export OSX_DEPLOYMENT_TARGET="11.3"
fi
if [ -z "$CMAKE_IGNORE_PREFIX_PATH" ]; then
-10
View File
@@ -1,10 +0,0 @@
# ccache does not parse the -clang: arguments CMake uses for clang-cl's gcc-style
# depfile, so a cache hit writes the object and no depfile, and Ninja then records
# no headers for that object. ccache reproduces /showIncludes output on a hit.
foreach (_lang C CXX)
if (CMAKE_${_lang}_COMPILER_ID STREQUAL "Clang" AND
CMAKE_${_lang}_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
set(CMAKE_DEPFILE_FLAGS_${_lang} "/showIncludes")
set(CMAKE_${_lang}_DEPFILE_FORMAT msvc)
endif ()
endforeach ()
+2 -12
View File
@@ -26,9 +26,9 @@ endif()
cmake_minimum_required(VERSION 3.2)
if (APPLE)
# if CMAKE_OSX_DEPLOYMENT_TARGET is not set, set it to 12.0 (the lowest Xcode 27 accepts)
# if CMAKE_OSX_DEPLOYMENT_TARGET is not set, set it to 11.3
if (NOT CMAKE_OSX_DEPLOYMENT_TARGET)
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING "Minimum OS X deployment version" FORCE)
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.3" CACHE STRING "Minimum OS X deployment version" FORCE)
endif ()
message(STATUS "CMAKE_OSX_DEPLOYMENT_TARGET: ${CMAKE_OSX_DEPLOYMENT_TARGET}")
@@ -38,14 +38,6 @@ if(POLICY CMP0135) # DOWNLOAD_EXTRACT_TIMESTAMP
cmake_policy(SET CMP0135 NEW)
endif()
# project() reads this, so set it first. scripts/flatpak/make_deps_tar.sh packs deps/
# without cmake/, so the file is missing in a Flatpak build.
set(_rules_override "${CMAKE_CURRENT_LIST_DIR}/../cmake/modules/ClangClShowIncludes.cmake")
if (EXISTS "${_rules_override}")
set(CMAKE_USER_MAKE_RULES_OVERRIDE "${_rules_override}")
endif ()
unset(_rules_override)
project(OrcaSlicer-deps)
# Backward compatibility for old CMake versions
@@ -228,7 +220,6 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE)
-DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER}
-DCMAKE_C_COMPILER_LAUNCHER:STRING=${CMAKE_C_COMPILER_LAUNCHER}
-DCMAKE_CXX_COMPILER_LAUNCHER:STRING=${CMAKE_CXX_COMPILER_LAUNCHER}
-DCMAKE_USER_MAKE_RULES_OVERRIDE:STRING=${CMAKE_USER_MAKE_RULES_OVERRIDE}
-DCMAKE_TOOLCHAIN_FILE:STRING=${CMAKE_TOOLCHAIN_FILE}
-DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS}
-DCMAKE_SHARED_LINKER_FLAGS:STRING=${CMAKE_SHARED_LINKER_FLAGS}
@@ -276,7 +267,6 @@ else()
-DCMAKE_IGNORE_PREFIX_PATH:STRING=${CMAKE_IGNORE_PREFIX_PATH}
-DCMAKE_C_COMPILER_LAUNCHER:STRING=${CMAKE_C_COMPILER_LAUNCHER}
-DCMAKE_CXX_COMPILER_LAUNCHER:STRING=${CMAKE_CXX_COMPILER_LAUNCHER}
-DCMAKE_USER_MAKE_RULES_OVERRIDE:STRING=${CMAKE_USER_MAKE_RULES_OVERRIDE}
-DBUILD_SHARED_LIBS:BOOL=OFF
${_cmake_osx_arch}
"${_configs_line}"
-137
View File
@@ -1,137 +0,0 @@
# Deferred Page Construction: High Level Design
## Why it exists
The main window is a notebook of tabs, and only one of them is on screen when the first
frame appears. Others are opened later in the session, some never, and some only exist
for certain printers. Building every tab before the first frame makes each startup pay for
tabs the user may never open.
This subsystem builds a tab the first time it is shown, and builds the rest in small units
while the user is idle after startup. Startup pays only for what the first frame shows,
the other tabs are usually ready before anyone opens them, and a click that lands in the
middle of the idle build waits for one unit at most. Work that is not a tab, such as a
dialog or the 3D view's GL resources, uses the same machinery.
## The parts
The parts are independent. A holder can hold anything a factory makes, a placeholder page
is a holder with a widget, a staged object can live outside a holder, and the scheduler
knows none of them; it runs tasks, which the main window makes from the holders.
### The holder: `Lazy<T>`
A holder keeps one object and the factory that makes it. The rest of the app reads the
object if it exists, makes sure it exists now when about to show or navigate to it, or runs
something once it exists. A type with one instance in the app gets these as statics
through `LazyInstance<T>`, so callers need no reference to the main window, and all of them
are harmless while no holder exists.
The holder guarantees that callers see the object only once it is completely built. A
build cannot re-enter itself, so a nested request finds nothing yet, and a factory that
returns null or a unit that throws leaves the holder and the scheduler able to carry on.
The holder does not own the object; its wx parent does, as for any window. The holder has
no wx dependency and is unit-tested.
### The placeholder page: `LazyPage<Panel>`
The notebook needs a page object for a tab to exist and for tabs to be inserted and
removed by pointer, and the placeholder is that object. It builds the real panel inside
itself the first time it is shown and forwards showing and hiding afterwards, so a panel's
own show handling stays its activation hook. Nothing builds while the main window is
hidden; the window's first show builds the start page. A page that is out of the book is
not prebuilt. A panel built while its page is hidden stays hidden, and gets the theming
the window applied before the panel existed.
### Staged construction: `StagedBuild`
A constructor too big to be one unit builds a skeleton and queues the rest as steps, which
run one per unit. A child panel's steps can be forwarded to its parent, and the parent is
complete only once the child is. Nothing may use what a step builds before the last step
has run, so staged panels follow these constraints:
- members created in steps start out null, so a partly built panel can be destroyed;
- timers, event handlers and destructors that touch step content check that the panel is
complete first;
- nothing takes focus while off screen, since a unit may run while the user is typing
elsewhere;
- a widget added by a step keeps its place in the sizer through an empty slot the skeleton
creates.
### The scheduler: `IdleScheduler` and `PrebuildQueue`
The queue holds tasks in order, and a slice runs units of the first pending task until
the task finishes, the time budget is spent, or input arrives. It has no wx dependency and
is unit-tested with a fake clock. A task whose work goes away, such as a tab removed from
the book, is skipped, and becomes pending again if the work comes back.
A slice runs only once the user has been idle for a short quiet time, and each slice is its
own timer message, so paint, timers and input queued in between are handled before the
next slice. Posting slices as pending events would not do that, because wx drains every
pending event before the next native message. On GTK a timer that is always due starves
the lower-priority sources that repaint and deliver posted events, so slices are a few
milliseconds apart. On Windows a slice also waits while the native queue holds input,
not counting mouse moves, which Windows synthesizes whenever a window appears under the
cursor. A slice never runs inside a `wxYield()`, where it would build pages in the middle of
the code that yielded. A unit cannot be interrupted, so the largest unit bounds how long a
click can wait.
When nothing is pending the timer stops and the subsystem costs nothing.
The main window owns the scheduler because it owns what the tasks build, and clearing the
queue with the window keeps a task from outliving its object. Each owner provides its own
tasks, such as a tab, a dialog, the Prepare tab's settings page one option group at a
time, the Prepare page's layout at the size the book gives its pages, or the 3D view's GL
resources.
### The 3D view's GL resources
OpenGL is loaded on the Prepare tab's canvas. When the start page is not Prepare, loading
it is an idle task that makes the context current on the hidden canvas, so the start page
paints first and Prepare never appears. Loading it on a shown canvas under `Freeze()` holds
back the start page's paint, and on GTK `Freeze()` cannot hide the canvas, which is a
native child window or a Wayland subsurface drawn outside GTK. A hidden Windows child
window keeps its device context, macOS attaches the context to a hidden view, and GTK
creates the canvas's surface when the widget is realized, so on GTK the task realizes the
canvas first. If the context cannot be made current, the canvas's first render loads the
resources.
## Rules
**Before the first frame.** Only the start page and the Prepare tab's plater are built
before the first frame. Everything else goes through a holder.
**Reaching a lazy object.** Callers use the type's statics. Reading it if built is for
things the object can live without, such as a rescale, a color change or a status refresh.
Making sure it exists is for navigating to it or showing it. Running something once it is
built is for state it would not fetch for itself on construction. A panel that pulls its
state when constructed only ever needs to be read if built.
**Unit size.** A unit should fit in one slice on a fast machine. A constructor over that is
staged, and a single widget over it is accepted unless the widget itself can be split.
**Order.** Tasks run cheapest and most likely to be opened first. Each holder's order is
given where it is created, with gaps so a new task fits between its neighbors. A negative
order is never prebuilt, for something few sessions open that costs more to build unasked
than it saves.
## Adopting it
A lazy tab needs a panel type deriving from `LazyInstance`, a placeholder page the main
window creates once with its order and registers for the idle build, and every use of the
panel outside the main window going through the statics. Its constructor has to cope with
the main window already existing and the user being busy elsewhere, so it takes no focus
while off screen, and it does all of its own setup, since the main window does nothing to a
panel after creating it.
To stage a heavy constructor, keep the skeleton in the constructor, move the rest into
steps in its original order, and follow the staged-construction constraints. Measure the
units; a step that is still one big widget is split inside the widget or accepted.
## Verifying
The log lists the queue when it is registered, reports each completed task at info level
and each slice and unit at debug level, and reports every on-demand build with its units
and time. A task's completion line counts only the slice it finished in. A run from the
configured start page should show every registered task complete in order, with no unit
longer than intended. A click on a tab during the idle build should show an on-demand
build for what was left, with the slices resuming once the user is idle again.
+119
View File
@@ -0,0 +1,119 @@
# G-code preview while dragging
The sliced preview draws every toolpath segment of the plate as an instanced box. On a large
plate that is tens of millions of segments, and the frame is GPU-bound: the cost is the number of
instances drawn, not anything the CPU does per frame. Dragging the camera over such a plate cannot
keep up. The `preview_reduced_detail_mode` preference (*Graphics > G-code Preview*, off by
default) lets the preview draw less while the user drags and put the full toolpaths back when they
let go.
| Preference | Values | Effect |
|---|---|---|
| `preview_reduced_detail_mode` | `off`, `solid`, `layers`, `outer_walls`, `shell` | what is drawn while dragging |
| `preview_reduced_detail_layer_stride` | 1–20 | one layer in every N is kept by the toolpath modes |
libvgcode (`src/libvgcode`) builds and binds the reduced toolpath set, `GCodeViewer` maps the
preferences onto it and draws the solid model, and `GLCanvas3D` decides when the user is dragging.
The OpenGL ES path keeps a single set and ignores the preference.
## Two sets, one walk
`ViewerImpl::update_enabled_entities()` walks the visible vertex range once and fills two segment
index buffers side by side: the **full** set and the **reduced** set (segments and options).
Building them together is what makes switching free: starting or ending a drag is a buffer
binding, never a rebuild. A change of mode or stride does rebuild. Nothing is built while the mode
is off, and the reduced buffers are then uploaded empty so that the last set does not stay
allocated.
Whatever the mode leaves out, the bottom and top layers of the visible range are kept whole: they
are the faces the range cuts open, and the top is what the user is looking at.
### Modes
- `EndLayersOnly` (`solid` in the preference) keeps only the two end layers. `GCodeViewer` then
draws the sliced objects and the prime tower as opaque solids, see below.
- `LayersOnly` (`layers`) keeps every role of one layer in every stride.
- `OuterWallsOnly` (`outer_walls`) keeps the outer and overhang perimeters of one layer in every
stride. The prime tower and supports have other roles and are left out.
- `ShellOnly` (`shell`) keeps what the shell extraction below marks as visible surface, of one
layer in every stride, plus whatever a view from above or below sees of the skipped layers, so
that a step does not vanish. It is the only mode that knows the prime tower's outside from its
inside.
## The solid model
The preview already loads the sliced objects as shells for its translucent ghost.
`GCodeViewer::render_solid_model()` draws those shells opaque, in their filament colors, with the
`gouraud` shader, whose z range cuts them to the visible layer range. The two toolpath layers of
the reduced set are drawn afterwards and cap the cut with what was really printed there. The
shells hold only the objects, so while this mode is on the prime tower is added from its sliced
mesh, positioned as the print placed it. It is added or removed on its own when the mode changes,
without reloading the objects, keeps its opaque color so that it never appears among the
translucent shells, and stays out of their bounding box. Supports have no mesh and are not shown,
and `load_shells()` drops every non-model-part volume, so a negative volume is not cut out.
A plate whose shells are not loaded keeps drawing toolpaths, since the solid model would leave
only the end layers.
## Shell extraction
`ViewerImpl::update_shell_bitset()` classifies every extrusion segment once per load, on demand
the first time the shell mode needs it, and records the result in four bit sets. It is purely
geometric so that the wipe tower, whose every segment shares one role, works as well as the
objects.
Each layer is rasterized into a coarse 2D **occupancy grid** over the print's footprint: cells
are 0.5 mm, or coarser so that the grid is at most 1024 cells across. The footprint is then
**closed** with a radius of 2.5 mm so that sparse infill and support read as the solid area they
belong to, while holes wider than 5 mm stay open. The closing dilates each 8-connected component
separately and leaves a cell that two components both reach empty, so the gap between two objects
standing close together is never bridged and both of their facing walls stay on the shell;
fragments under eight cells do not spread and are absorbed by whatever reaches them. A separable
erosion shrinks the result back, and the raw cells are OR-ed in again so that a closing never
loses one.
A cell is a **shell cell** when it is filled and any of its six neighbours, four in the layer,
one below, one above, is not. A segment is on the shell when at least half of the cells it
crosses are shell cells: walls run along the shell, infill only touches it at the ends. The
interior infill roles and gap fill are excluded regardless, since short infill segments hugging a
wall would otherwise pass by the thousand.
Two refinements keep sloped surfaces closed:
- **Near-wall segments.** The step between one layer's outer wall and the next is often
narrower than a cell. An inner wall (`Perimeter`) segment whose midpoint lies within a line and
a half of an outer or overhang perimeter of the same layer is kept as well. So is any segment,
whatever its role, whose midpoint lies within that reach, or a cell if larger, of an outer wall
of the layer above or below: that strip is the exposed band of the step, which the cell tests
cannot see when it is narrower than a cell.
- **Top and bottom visibility.** The same pass records the highest and lowest layer occupying
each cell over the whole print. A segment whose layer is the topmost occupant of any cell it
crosses is visible from above, and likewise from below with the lowest. These segments are kept
even when their layer is skipped by the stride.
The layer range is split across up to eight `std::async` workers, each owning its grids. An
allocation failure on a huge print falls back to marking every segment as shell, which leaves out
only the hidden infill roles.
## Deciding that the user is dragging
`GLCanvas3D::_update_preview_interaction()` runs at the top of every preview frame, before the
canvas decides whether to reuse its cached scene, so that the switch lands in that frame. Dragging
is `GLCanvas3D::is_user_interacting()`, the same answer the scene cache reads: the camera, the
navigator, a gizmo, the rectangle selection or either slider being held. A slider reports this from
ImGui's active id rather than its dirty flag, which is raised and consumed inside one frame. A
wheel step has no duration, so it holds the reduced set for a 150 ms settle time instead, and the
frame that restores the toolpaths is scheduled for when that time runs out, since the render timer
only wakes the idle loop. A drag cut short by focus or capture loss is ended explicitly, and a
button release wakes the idle loop, because on some platforms nothing else would until the next
input.
## Reused scene frames
`GLCanvas3D` keeps its last scene pass for frames that only rebuild the overlay (`SceneCache`). Its
key covers the canvas size, the camera and hover state, not what the toolpath sets draw, so a frame
that reuses the scene must never be one on which the set is switched.
`_update_preview_interaction()` therefore reports whether the bound set changed, and a frame on
which it did redraws the scene. The canvas neither captures nor reuses the scene while the user
drags, so no reduced frame outlives a drag, and the frame that ends a wheel's settle time is
requested as a full frame.
-123
View File
@@ -1,123 +0,0 @@
# Multiline infill — High Level Design
## Purpose and scope
`fill_multiline` prints every sparse infill wall as N adjacent lines instead of
one, so a wall is `d1 = N * spacing` thick. Only internal sparse infill uses it.
Each pattern first builds its single-line centerlines at N times the usual line
spacing (so the density holds), and `multiline_fill()` then replaces each
centerline by the lines of that wall: the centerline itself when N is odd, and
closed outlines around it at every `spacing` out to `d1 / 2`. The outlines are
clipped to the fill region contracted by half a line width, then connected like
any other infill.
Outlines of centerlines that cross each other overlap at every crossing, which
over-extrudes the wall intersections. The line-crossing patterns Grid,
Triangles, Tri-hexagon and Cubic therefore build centerlines that never cross
(`FillRectilinear::fill_surface_trapezoidal()`), and so do Adaptive Cubic and
Support Cubic (`FillAdaptive`); the other patterns outline their usual
centerlines.
## Non-crossing centerlines
The crossing lines are resolved into x-monotone paths, the levels of the line
arrangement: walking along x, the k-th path is always the k-th line from the
bottom. At every crossing, the two paths bounce off each other instead of
passing through. Adjacent paths meet only at crossings, so their outlines touch
there and nowhere overlap.
Where two paths meet, each is cut short by a line perpendicular to the bisector
of its bend, `d1 / 2` from the crossing. The two cut segments are parallel and
`d1` apart, so the outermost lines of the two walls sit exactly `spacing` apart,
like the lines inside a wall. Where three lines meet at one point, the middle
path runs straight through and the outer two are cut `d1` from it.
Each pattern builds its rows along x in a rotated frame. Grid lines run at ±45°
there, and its rows are trapezoid waves that transpose on alternate layers. The three families of Triangles, Tri-hexagon and
Cubic run at 0°, 60° and 120°. Those rows rotate by 120° every layer about a
3-fold center of the arrangement, so each family takes every role in turn.
The pattern is phased on fixed positions, so it lines up across layers and
across the regions of one layer. Rounding the corners with
`sparse_infill_smooth_factor` happens before `multiline_fill()`.
## Cubic
Single-line Cubic draws the three families at the same spacing `h` and shifts
them with z: by `+dx`, `-dx` and `+dx`, `dx = z / sqrt(2)`. The multiline paths
follow the same lines. In the frame where one family is horizontal, the other two
cross in rows `h` apart, alternating by half a period, at height
`tau = -3 * dx (mod h)` above the horizontal line below them. The crossings split
every band between horizontal lines into up-pointing triangles of height `tau`,
down-pointing triangles of height `h - tau`, and hexagons. At `tau = 0` (and `h`)
all three families meet at common points, as in Triangles. At `tau = h / 2` the
triangles are equal, as in Tri-hexagon. The origin of that frame is always a
3-fold center, whatever z is, so the per-layer rotation keeps the lines in place.
Each band holds two paths that touch at its crossings: the upper one takes the
V below the crossing and runs along the top horizontal line, and the lower one
takes the inverted V above it and runs along the bottom line. Both are the same function
of `tau`, the lower one mirrored with `h - tau`. `cubic_upper_level()` builds one
period of the upper path as the lower envelope of five lines, clipped from below:
- the two slanted lines through the crossings,
- the horizontal line, lowered when the triangle above it is less than `1.5 * d1` high,
- the two chamfers where the path turns onto and off the horizontal line, `d1 / 2`
from those crossings,
- the flat cut into the V at the crossing.
The cut height `clamp(tau - d1 / 2, 0, h - d1) + d1` is what makes the pattern
continuous in z. While both triangles are at least `1.5 * d1` high, every
crossing is a pair of bends `d1 / 2` from it, as in Tri-hexagon. When a triangle
is thinner, its three paths stack like a triple crossing. The path through it
flattens toward its base line and lies on it once the triangle is under `d1 / 2`
high, and the paths beside it are pushed `d1` away. The layout thus reaches the
Triangles one where the families meet. Adjacent paths stay at least `d1` apart
at every `tau` and at every density up to 100%.
## Adaptive Cubic
Adaptive Cubic and Support Cubic take their lines from an octree of cubes
standing on a corner. On each layer every cube cuts its three mid-planes into
segments of the same three 60° families as Cubic, but the pattern is not
periodic. Smaller cubes near the surface add finer lines, and a finer line ends
where it meets the wall of its coarser cube, so the lines form crossings and
T-junctions. `FillAdaptive::multiline_paths()` builds the paths from these
segments directly, for each fill region and within `4 * d1` of it.
At a crossing the two paths bounce as in Cubic. At a T-junction the through line
runs straight on and the path of the ending line stops there. Every path still
runs left to right in the frame where one family is horizontal, and that family
rotates with the layer.
Every line of every cube size lies on one fine lattice, so crossings closer than
a few `d1` are the corners of one small triangle of that lattice, as in Cubic.
The cuts follow the Cubic rules without a closed formula:
- The two bends of a crossing are cut `d1` apart, `d1 / 2` each, perpendicular
to their bisector, so their walls touch. A cut goes no further than the path
end, and the other bend takes the rest of `d1`.
- At the tip of a small triangle, between the two slanted families, a cut also
goes no further than the neighbouring bend turning the other way, and the
path beyond that bend is kept a wall away from it. The bends onto the
horizontal family are not limited this way: pushing their paths apart would
open gaps between walls that should touch.
- A cut moves the path only where the cut line lies beyond it, near its bend.
The sharp bends between the two slanted families are cut after the bends onto
the horizontal family, so the tip of a small triangle wins, as in Cubic.
- A path stopping at a T-junction is trimmed until it is `d1` less half a line
spacing from every other path, so that its end overlaps the wall it stops on
by half a line and bonds to it. The paths are trimmed one at a time against
the others as already trimmed, so two ends facing each other meet instead of
both backing off. A path stopping on the line of another is trimmed before
that one, so it gives way and the other still reaches the line it stops on. A
second round trims every path again from its full length, so an end grows
back where the ends it gave way to were trimmed later, and a last round only
shortens them, keeping them that far apart. Paths shorter than `d1` are left
out.
- A line that ends on another less than `2 * d1` past a crossing stops at that
crossing instead, the shorter one where both do. The path along such a stub
would be trimmed away, leaving a hole between the walls that were cut to
touch it.
Short paths enclosed by coarser lines still print as closed outlines, but most
paths run on across several cells.
+13 -17
View File
@@ -76,10 +76,9 @@ and the count of errors the original parse hit.
Each entry is one preset **in source form**: what its JSON sub-file states and nothing
that resolving it derives — the preset's own config diff, the name of the preset it
inherits, the names of the presets it includes, and the parse metadata (name, sub-path,
description, instantiation, setting and filament ids, renames). Non-instantiated base
presets are stored too; the children that inherit from or include them cannot resolve
without them.
inherits, and the parse metadata (name, sub-path, description, instantiation, setting
and filament ids, renames). Non-instantiated base presets are stored too; the children
that inherit from them cannot resolve without them.
**The payload names its own keys.** The dictionary holds the distinct `opt_key`s the
file uses, the `ConfigOptionType` each was written as, and the distinct enum *value
@@ -162,9 +161,9 @@ cache nothing can invalidate is worse than no cache.
Vendors load in a fixed order, because filament inheritance crosses exactly one
boundary: any vendor's filament may inherit from the shared Orca filament library,
and nothing else reaches across vendors — an `include` is always vendor-local. The
library therefore goes first, alone; every other vendor follows in parallel, resolving
against it; and the results are merged in a stable order:
and nothing else reaches across vendors. The library therefore goes first, alone;
every other vendor follows in parallel, resolving against it; and the results are
merged in a stable order:
```mermaid
flowchart LR
@@ -212,16 +211,13 @@ and its cache was never written back.
Serving from a cache is not a memory-image restore. The entries are deserialized and
then installed one by one — inheritance resolved against the presets installed before
them and the currently loaded filament library, includes layered in, configs flattened
onto the collection defaults, validated and registered — by the same function the JSON
path calls straight after parsing a sub-file. An `include` layers what the included
base states, between the parent and the preset's own keys: the base's diff against the
default, taken when the base itself was installed and before the per-variant padding
`inherits` sees, so only what a template sets reaches the presets including it. The two
paths share everything below the parse, which is what makes a cache-loaded bundle
indistinguishable from a JSON-loaded one by construction rather than by test coverage.
Installation also rebuilds each preset's file path from the local data directory, so a
shipped cache never carries the generating machine's paths.
them and the currently loaded filament library, configs flattened onto the collection
defaults, validated and registered — by the same function the JSON path calls straight
after parsing a sub-file. The two paths share everything below the parse, which is what
makes a cache-loaded bundle indistinguishable from a JSON-loaded one by construction
rather than by test coverage. Installation also rebuilds each preset's file path from
the local data directory, so a shipped cache never carries the generating machine's
paths.
App upgrades work because a cache normally survives one. Only a deliberate
`CACHE_VERSION` bump makes an installed cache unreadable, and that is handled at
-197
View File
@@ -1,197 +0,0 @@
# Prime tower sparse layers — High Level Design
## Purpose and scope
A prime tower exists to absorb filament changes, but it is planned on every
object layer below the topmost change, not only on the layers that purge. The
layers in between carry no filament change and print nothing but a block of the
tower's own footprint to keep its top level. They are called sparse layers, and
on a print with few changes they are most of the tower: they cost time, filament
and a travel to the tower on every layer.
Two settings trade that cost against something else. `wipe_tower_no_sparse_layers`
drops them, which sinks the tower below the model. `wipe_tower_sparse_layers_combination`
merges runs of them into fewer, thicker layers, which keeps the tower level with
the model. Both are off by default, and with both off the tower prints one layer
per object layer as it always has.
The decisions belong to tower planning and G-code emission. They do not change
sliced object geometry, but they do change the emitted G-code, the filament and
time estimates, and — for the compacted case — whether a plate is printable at
all. Changing either setting invalidates the tower step.
## What a sparse layer is
`ToolOrdering::fill_wipe_tower_partitions` counts the filament changes per layer
and propagates that count downwards, so every layer below the topmost change is
marked as carrying a tower. It then fills any gap between two tower layers, so
the tower is continuous from the bed to its last purge. `wipe_tower_layer_height`
is the distance from the previous tower layer, which is the object's layer height
whenever the tower prints on every layer.
`Print::_make_wipe_tower` plans one tower layer per such object layer. A layer
whose only call keeps the current filament leaves no toolchange in the plan, and
the layer it generates is a single result whose initial and new tool are equal.
That is what `wipe_tower_layer_is_sparse` recognises, and it is the unit both
settings work on.
The plan stays one entry per tower layer in every case. The G-code emitter walks
`WipeTowerData::tool_changes` by layer index, advancing once per object layer
that carries a tower, so a planner that removed entries would silently shift
every later layer onto the wrong tower geometry. Layers that print nothing are
therefore still planned and still generated; they are marked, and the emitter
drops them.
## Shared rules
Tower planning, G-code emission and the plate validation all have to agree about
which layers print and where. They ask one set of free functions, declared beside
the tower classes, rather than each re-deriving the answer from the raw options:
- `wipe_tower_sparse_layers_skipped` — whether sparse layers are really dropped.
Smooth timelapse and clumping detection park the nozzle on the tower every
layer, so with either of them on no layer is ever dropped and the option reads
as off everywhere.
- `wipe_tower_sparse_layers_combined` — whether runs are really merged. The same
two rule it out, and so does `wipe_tower_no_sparse_layers`: dropping the layers
outright is the stronger answer to the same problem, so the two settings are
exclusive and the GUI greys out the second while the first is on.
- `wipe_tower_layer_is_sparse`, `wipe_tower_layer_is_combined_away` — per-layer
questions the emitter asks about generated results.
- `compute_compacted_wipe_tower_z` — the tower's print z per planned layer when
it is compacted.
- `combine_sparse_wipe_tower_layers` and its `combine_sparse_wipe_tower_plan`
wrapper — the merge rule, applied to either generator's plan.
Both tower generators are driven through these. `WipeTower` (Type 1, the block
tower) and `WipeTower2` (Type 2, the default) keep separate plans with the same
per-layer shape — print z, layer height, toolchanges, and a `combined_away` flag
— so one template covers both.
## Dropping sparse layers
With `wipe_tower_no_sparse_layers`, the tower only grows on layers that carry a
real change. It therefore falls one layer height behind the object for every
sparse layer, and by the top of a tall print it can sit far below the model. The
nozzle has to reach down to it at each purge.
`compute_compacted_wipe_tower_z` derives that z once, from the generated results,
so the emitter and the validator cannot disagree. Emission descends to it, but
only once the nozzle is parked over the tower: descending while still over the
model would drive the nozzle into the print, so a descent that would do that is
deferred until after the travel to the tower. Extrusions emitted without an
explicit z — the nozzle-change wipe in particular — are pulled down to the
compacted z for the same reason.
Reaching down is only safe if nothing tall stands near the tower. `Print.hpp`
carries the clearance rule: a keep-out zone grown from the tower's footprint by
the spiral z-hop envelope, and a per-object limit on how high an object may rise
near it, tiered by the nozzle cone, the head body, the rod and the lid. The same
rule serves the precise check on real extrusions, the pre-slice estimate that
feeds the plater, and the outlines the plater draws while an object is dragged,
so that the ring the user sees touches the object's outline exactly when the
check trips.
## Merging sparse layers
With `wipe_tower_sparse_layers_combination`, no layer is dropped and nothing is
compacted: the tower keeps following the object, and the nozzle never descends.
Instead a run of consecutive sparse layers prints once, on the run's last layer,
at the accumulated height of everything it covers — the same way infill
combination merges sparse infill. The layers below it in the run print nothing.
`combine_sparse_wipe_tower_plan` runs before the tower's depths are planned,
because the heights it rewrites feed the extrusion flow of every later pass. It
raises `height` in place on the layer that prints a run and sets `combined_away`
on the rest; generation then proceeds unchanged, and the flag is copied onto the
results so the emitter can drop them.
Four constraints shape the rule:
- **Whole layers only.** A tower layer is entered at the object's z, so a merged
layer has to end on an object layer boundary. The merged height is therefore a
sum of whole layer heights, never a clamped value.
- **The nozzle's maximum layer height.** A run stops growing as soon as one more
layer would pass `max_layer_height` for the nozzle printing it — three quarters
of the nozzle diameter when that is left at 0, as elsewhere in slicing. The cap
is read through the filament-to-nozzle map, since `max_layer_height` is per
nozzle while the tower indexes filaments. This is what makes the setting inert
at common layer heights: two 0.2 mm layers are 0.4 mm and do not fit under a
0.3 mm maximum, so nothing merges until the layer height is 0.15 mm or below,
or the maximum is raised.
- **A filament change purges at its own z.** A layer with a real change can
neither be merged away nor absorb the run below it, so a run always ends on its
own last sparse layer and the change above it is untouched.
- **The first layer stays on the bed.** It carries the brim and is never merged.
A run holds one filament throughout — that is what makes it sparse — so the cap
is uniform across it, and the tower reserves depth only for the purges above a
layer, so a run has one footprint and the merged layer covers exactly the area
the layers it replaces would have.
## Emission and accounting
`WipeTowerIntegration` drops a layer whose results are marked, for both settings,
through the same `ignore_sparse` path in `tool_change` and
`is_empty_wipe_tower_gcode`. A dropped layer emits no travel to the tower and no
extrusion.
Filament used is accumulated by the generators while they write, so a layer that
will be dropped must not be charged. Type 1 asks `layer_is_printed` at each of
its accumulation points; Type 2 guards the equivalent block in `finish_layer`,
which also stops a merged-away layer from adding height of its own — the layer
that prints the run carries all of it.
A merged layer is the only case where the tower's layer height differs from the
object layer it sits on, and therefore the only case where the height the
exporter already emitted for that layer is wrong for the tower. Both generators
do declare a height, but each hardcodes a tag dialect — the block tower forces
the BBL tag, the other writes the compatible one — while the G-code processor
reads only the tag its printer uses. On a non-BBL printer with a Type 1 tower the
declaration is dropped, and the merged layer is drawn and costed as a thin one.
`WipeTowerIntegration::tower_height_tag` therefore declares it at export time,
where the printer is known, and only when the tower's own G-code does not already
carry the tag that will be read. The object's height returns on the next object
path, because emission forces the processor role to the tower on any layer that
carries one.
## Constraints
A layer that prints nothing prints nothing at all, including any interface work
the tower planner scheduled there. The Type 1 block planner marks a layer as a
contact layer when a filament category stops or starts being used relative to the
layer below, and a sparse layer immediately above a change qualifies. Merging a
run, like dropping its layers, replaces that interface with the run's single
layer. Both settings are off by default for this among other reasons.
Neither setting changes what the tower is for. A plate that needs a tower on
every layer — smooth timelapse, clumping detection — gets one, and the settings
read as off rather than compacting or merging in one place and not another.
## Implementation and verification
- [WipeTower.hpp](../../src/libslic3r/GCode/WipeTower.hpp) declares the shared
rules and the plan-merging template;
[WipeTower.cpp](../../src/libslic3r/GCode/WipeTower.cpp) implements them and
the Type 1 tower, [WipeTower2.cpp](../../src/libslic3r/GCode/WipeTower2.cpp)
the Type 2 tower.
- [ToolOrdering.cpp](../../src/libslic3r/GCode/ToolOrdering.cpp) decides which
layers carry a tower at all, and
[Print.cpp](../../src/libslic3r/Print.cpp) plans it and runs the clearance
check whose rule lives in [Print.hpp](../../src/libslic3r/Print.hpp).
- [GCode.cpp](../../src/libslic3r/GCode.cpp) emits the tower, drops the layers
that print nothing, and declares a merged layer's height;
[PrintConfig.cpp](../../src/libslic3r/PrintConfig.cpp) defines the settings and
[ConfigManipulation.cpp](../../src/slic3r/GUI/ConfigManipulation.cpp) their
mutual exclusion.
- [GLCanvas3D.cpp](../../src/slic3r/GUI/GLCanvas3D.cpp) and
[PartPlate.cpp](../../src/slic3r/GUI/PartPlate.cpp) draw the compacted tower's
keep-out outlines live while the user drags.
- [Rule tests](../../tests/libslic3r/test_wipe_tower.cpp) cover the gating of
both settings, the per-layer predicates, the compacted z, the merge rule's run
flushing, height conservation, the nozzle cap and the first-layer exemption,
and the clearance geometry the plater draws.
- [Slicing tests](../../tests/fff_print/test_wipe_tower.cpp) slice a real print
and check that a run folds, that the tower still covers the object exactly
once, that a run too thin for the cap is left alone, and that a merged layer
declares its height in the tag the printer's processor reads.
+10354 -8806
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,7 @@
"name": "AliZ PA-CF @P1-X1",
"inherits": "AliZ PA-CF @base",
"from": "system",
"setting_id": "cN0uesF0mZf3KSkj",
"setting_id": "AliZ003_00",
"instantiation": "true",
"enable_pressure_advance": [
"1"
@@ -3,7 +3,7 @@
"name": "AliZ PETG @P1-X1",
"inherits": "AliZ PETG @base",
"from": "system",
"setting_id": "cZp23cC4ong9q6dn",
"setting_id": "AliZ001_00",
"instantiation": "true",
"enable_pressure_advance": [
"1"
@@ -3,7 +3,7 @@
"name": "AliZ PETG-CF @P1-X1",
"inherits": "AliZ PETG-CF @base",
"from": "system",
"setting_id": "HWW5mejsB0SfEfWw",
"setting_id": "AZ01-1_00",
"instantiation": "true",
"enable_pressure_advance": [
"1"
@@ -3,7 +3,7 @@
"name": "AliZ PETG-Metal @P1-X1",
"inherits": "AliZ PETG-Metal @base",
"from": "system",
"setting_id": "JqD0blE4fxaNfXqB",
"setting_id": "AZ01-2_00",
"instantiation": "true",
"enable_pressure_advance": [
"1"
@@ -3,7 +3,7 @@
"name": "AliZ PLA @P1-X1",
"inherits": "AliZ PLA @base",
"from": "system",
"setting_id": "fRELapY8YiP4qrBW",
"setting_id": "AliZ002_00",
"instantiation": "true",
"enable_pressure_advance": [
"1"
@@ -3,7 +3,7 @@
"name": "BETA ABS @BBL A1",
"inherits": "BETA ABS @base",
"from": "system",
"setting_id": "dUGRrmxU89pRaJ2Z",
"setting_id": "BABB00_07",
"instantiation": "true",
"fan_max_speed": [
"20"
@@ -3,7 +3,7 @@
"name": "BETA ABS @BBL H2D",
"inherits": "BETA ABS @base",
"from": "system",
"setting_id": "oxZKTEMd8TpyoH39",
"setting_id": "BABB00_11",
"instantiation": "true",
"chamber_temperatures": [
"65"
@@ -3,7 +3,7 @@
"name": "BETA ABS @BBL P1P",
"inherits": "BETA ABS @base",
"from": "system",
"setting_id": "MPYyfCKMsJZy8GaY",
"setting_id": "BABB00_03",
"instantiation": "true",
"fan_max_speed": [
"20"
@@ -3,7 +3,7 @@
"name": "BETA ABS @BBL X1C",
"inherits": "BETA ABS @base",
"from": "system",
"setting_id": "vMA7EhgNDT4MwMRr",
"setting_id": "BABB00",
"instantiation": "true",
"fan_max_speed": [
"60"
@@ -0,0 +1,21 @@
{
"type": "filament",
"name": "BETA ABS @base",
"inherits": "fdm_filament_abs",
"from": "system",
"filament_id": "OFjUXn67",
"instantiation": "false",
"description": "When printing this filament, there's a risk of warping and low layer adhesion strength. To get better results, please refer to this wiki: Printing Tips for High Temp / Engineering materials.",
"filament_cost": [
"24.99"
],
"filament_flow_ratio": [
"0.95"
],
"filament_vendor": [
"BETA"
],
"impact_strength_z": [
"7.4"
]
}
@@ -3,7 +3,7 @@
"name": "BETA ASA @BBL A1 0.4 nozzle",
"inherits": "BETA ASA @base",
"from": "system",
"setting_id": "LevRG7t9mTlEseaB",
"setting_id": "BASB00_09",
"instantiation": "true",
"chamber_temperatures": [
"60"
@@ -3,7 +3,7 @@
"name": "BETA ASA @BBL H2D 0.4 nozzle",
"inherits": "BETA ASA @base",
"from": "system",
"setting_id": "3NBkWTXmqi8eRUhi",
"setting_id": "BASB00_13",
"instantiation": "true",
"chamber_temperatures": [
"65"
@@ -3,7 +3,7 @@
"name": "BETA ASA @BBL X1C 0.4 nozzle",
"inherits": "BETA ASA @base",
"from": "system",
"setting_id": "0nWTEZkH7NKXgCib",
"setting_id": "BASB00_02",
"instantiation": "true",
"chamber_temperatures": [
"60"
@@ -53,53 +53,5 @@
],
"textured_plate_temp_initial_layer": [
"100"
],
"activate_air_filtration": [
"1"
],
"close_additional_fan_first_x_layers": [
"3"
],
"retraction_distances_when_ec": [
"0"
],
"slow_down_min_speed": [
"20"
],
"temperature_vitrification": [
"100"
],
"filament_start_gcode": [
"; Filament gcode\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}"
],
"filament_end_gcode": [
"; filament end gcode \n\n"
],
"volumetric_speed_coefficients": [
"0 0 0 0 0 0"
],
"filament_adhesiveness_category": [
"200"
],
"filament_dev_ams_drying_time": [
"12",
"8.0",
"12",
"8.0"
],
"filament_dev_ams_drying_heat_distortion_temperature": [
"100"
],
"filament_dev_drying_cooling_temperature": [
"85"
],
"filament_dev_ams_drying_temperature": [
"65",
"80",
"65",
"80"
],
"filament_dev_drying_softening_temperature": [
"85"
]
}
@@ -3,7 +3,7 @@
"name": "BETA HIPS @BBL H2D",
"inherits": "BETA HIPS @base",
"from": "system",
"setting_id": "9nN2uWxlYH1c507A",
"setting_id": "BHIB00_06",
"instantiation": "true",
"filament_deretraction_speed": [
"nil",
@@ -3,7 +3,7 @@
"name": "BETA HIPS @BBL X1C",
"inherits": "BETA HIPS @base",
"from": "system",
"setting_id": "mTcT8sCciWyX5enc",
"setting_id": "BHIB00_00",
"instantiation": "true",
"filament_max_volumetric_speed": [
"8",
@@ -0,0 +1,14 @@
{
"type": "filament",
"name": "BETA HIPS @base",
"inherits": "fdm_filament_hips",
"from": "system",
"filament_id": "OFlmxlDG",
"instantiation": "false",
"filament_is_support": [
"1"
],
"filament_vendor": [
"BETA"
]
}
@@ -3,7 +3,7 @@
"name": "BETA PAHT-CF @BBL H2D",
"inherits": "BETA PAHT-CF @base",
"from": "system",
"setting_id": "YbYCVQd5IR0sQR8S",
"setting_id": "BPAB00_01",
"instantiation": "true",
"chamber_temperatures": [
"60"
@@ -3,7 +3,7 @@
"name": "BETA PAHT-CF @BBL P1P",
"inherits": "BETA PAHT-CF @base",
"from": "system",
"setting_id": "5O5fzmFrT3O1k1H0",
"setting_id": "BPAB00_10",
"instantiation": "true",
"filament_flow_ratio": [
"0.96",
@@ -3,7 +3,7 @@
"name": "BETA PAHT-CF @BBL X1C",
"inherits": "BETA PAHT-CF @base",
"from": "system",
"setting_id": "gB5HLYTDCooFy2W5",
"setting_id": "BPAB00",
"instantiation": "true",
"chamber_temperatures": [
"60"
@@ -0,0 +1,48 @@
{
"type": "filament",
"name": "BETA PAHT-CF @base",
"inherits": "fdm_filament_pa",
"from": "system",
"filament_id": "OF6qw3Wb",
"instantiation": "false",
"description": "When printing this filament, there's a risk of nozzle clogging, oozing, warping and low layer adhesion strength. To get better results, please refer to this wiki: Printing Tips for High Temp / Engineering materials.",
"fan_cooling_layer_time": [
"5"
],
"fan_max_speed": [
"30"
],
"fan_min_speed": [
"10"
],
"filament_cost": [
"94.99"
],
"filament_density": [
"1.06"
],
"filament_flow_ratio": [
"0.96"
],
"filament_type": [
"PA-CF"
],
"filament_vendor": [
"BETA"
],
"full_fan_speed_layer": [
"2"
],
"impact_strength_z": [
"13.3"
],
"overhang_fan_speed": [
"40"
],
"overhang_fan_threshold": [
"0%"
],
"temperature_vitrification": [
"180"
]
}
@@ -3,7 +3,7 @@
"name": "BETA PEBA 90A @BBL A1",
"inherits": "BETA PEBA 90A @base",
"from": "system",
"setting_id": "A57Sbar56Ah1NiU7",
"setting_id": "BPBB00_04",
"instantiation": "true",
"filament_density": [
"1.22"
@@ -3,7 +3,7 @@
"name": "BETA PEBA 90A @BBL A1M",
"inherits": "BETA PEBA 90A @base",
"from": "system",
"setting_id": "OoLkWS147R0AasZc",
"setting_id": "BPBB00_05",
"instantiation": "true",
"filament_density": [
"1.22"
@@ -3,7 +3,7 @@
"name": "BETA PEBA 90A @BBL H2D",
"inherits": "BETA PEBA 90A @base",
"from": "system",
"setting_id": "91b8WTzvOky9wAVK",
"setting_id": "BPBB00_02",
"instantiation": "true",
"additional_cooling_fan_speed": [
"100"
@@ -3,7 +3,7 @@
"name": "BETA PEBA 90A @BBL P1P",
"inherits": "BETA PEBA 90A @base",
"from": "system",
"setting_id": "ydudEBLRmj1WSXqB",
"setting_id": "BPBB00_01",
"instantiation": "true",
"filament_max_volumetric_speed": [
"2.8",
@@ -3,7 +3,7 @@
"name": "BETA PEBA 90A @BBL X1C",
"inherits": "BETA PEBA 90A @base",
"from": "system",
"setting_id": "F1hTal3M92tBjv2N",
"setting_id": "BPBB00_00",
"instantiation": "true",
"filament_max_volumetric_speed": [
"2.8",
@@ -0,0 +1,33 @@
{
"type": "filament",
"name": "BETA PEBA 90A @base",
"inherits": "fdm_filament_tpu",
"from": "system",
"filament_id": "OFI2MKy7",
"instantiation": "false",
"description": "This filament is too soft and not compatible with the AMS. Printing it is of many requirements, and to get better printing quality, please refer to this wiki: TPU printing guide.",
"filament_cost": [
"59.99"
],
"filament_max_volumetric_speed": [
"2.8"
],
"filament_vendor": [
"BETA"
],
"impact_strength_z": [
"87.3"
],
"nozzle_temperature": [
"225"
],
"nozzle_temperature_initial_layer": [
"225"
],
"nozzle_temperature_range_high": [
"240"
],
"slow_down_layer_time": [
"14"
]
}
@@ -3,7 +3,7 @@
"name": "BETA PETG @BBL A1",
"inherits": "BETA PETG @base",
"from": "system",
"setting_id": "EpYzxZwVHvLaseku",
"setting_id": "BPGB00_06",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG @BBL A1M 0.4 nozzle",
"inherits": "BETA PETG @base",
"from": "system",
"setting_id": "5V0hyhzFoDU8Tfwm",
"setting_id": "BPGB00_03",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG @BBL H2D 0.4 nozzle",
"inherits": "BETA PETG @base",
"from": "system",
"setting_id": "KnKvUsD4rSpQU413",
"setting_id": "BPGB00_09",
"instantiation": "true",
"fan_max_speed": [
"30"
@@ -3,7 +3,7 @@
"name": "BETA PETG @BBL X1C",
"inherits": "BETA PETG @base",
"from": "system",
"setting_id": "uM6wHdt0N5ftZRge",
"setting_id": "BPGB00_01",
"instantiation": "true",
"fan_min_speed": [
"20"
@@ -92,48 +92,5 @@
],
"filament_start_gcode": [
"; filament start gcode\n{if (bed_temperature[current_extruder] >80)||(bed_temperature_initial_layer[current_extruder] >80)}M106 P3 S255\n{elsif (bed_temperature[current_extruder] >60)||(bed_temperature_initial_layer[current_extruder] >60)}M106 P3 S180\n{endif}\n\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}"
],
"close_additional_fan_first_x_layers": [
"3"
],
"retraction_distances_when_ec": [
"0"
],
"filament_end_gcode": [
"; filament end gcode \n\n"
],
"volumetric_speed_coefficients": [
"0 0 0 0 0 0"
],
"filament_adhesiveness_category": [
"300"
],
"filament_dev_chamber_drying_bed_temperature": [
"80"
],
"filament_dev_ams_drying_time": [
"12",
"12",
"12",
"12"
],
"filament_dev_ams_drying_ams_limitations": [
"1",
"0"
],
"filament_dev_ams_drying_heat_distortion_temperature": [
"75"
],
"filament_dev_drying_cooling_temperature": [
"55"
],
"filament_dev_ams_drying_temperature": [
"65",
"65",
"55",
"55"
],
"filament_dev_drying_softening_temperature": [
"60"
]
}
@@ -3,7 +3,7 @@
"name": "BETA PETG Fluorescence @BBL A1",
"inherits": "BETA PETG Fluorescence @base",
"from": "system",
"setting_id": "ZtyTtAWsZnLGsM0O",
"setting_id": "BPGB02_06",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Fluorescence @BBL A1M 0.4 nozzle",
"inherits": "BETA PETG Fluorescence @base",
"from": "system",
"setting_id": "VUSunTT0OJa7iOSd",
"setting_id": "BPGB02_03",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Fluorescence @BBL H2D 0.4 nozzle",
"inherits": "BETA PETG Fluorescence @base",
"from": "system",
"setting_id": "JHg4y0pxAOtoJSuw",
"setting_id": "BPGB02_09",
"instantiation": "true",
"fan_max_speed": [
"30"
@@ -3,7 +3,7 @@
"name": "BETA PETG Fluorescence @BBL X1C",
"inherits": "BETA PETG Fluorescence @base",
"from": "system",
"setting_id": "pCetfFvyLLw3vS2j",
"setting_id": "BPGB02_01",
"instantiation": "true",
"fan_min_speed": [
"20"
@@ -92,48 +92,5 @@
],
"filament_start_gcode": [
"; filament start gcode\n{if (bed_temperature[current_extruder] >80)||(bed_temperature_initial_layer[current_extruder] >80)}M106 P3 S255\n{elsif (bed_temperature[current_extruder] >60)||(bed_temperature_initial_layer[current_extruder] >60)}M106 P3 S180\n{endif}\n\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}"
],
"close_additional_fan_first_x_layers": [
"3"
],
"retraction_distances_when_ec": [
"0"
],
"filament_end_gcode": [
"; filament end gcode \n\n"
],
"volumetric_speed_coefficients": [
"0 0 0 0 0 0"
],
"filament_adhesiveness_category": [
"300"
],
"filament_dev_chamber_drying_bed_temperature": [
"80"
],
"filament_dev_ams_drying_time": [
"12",
"12",
"12",
"12"
],
"filament_dev_ams_drying_ams_limitations": [
"1",
"0"
],
"filament_dev_ams_drying_heat_distortion_temperature": [
"75"
],
"filament_dev_drying_cooling_temperature": [
"55"
],
"filament_dev_ams_drying_temperature": [
"65",
"65",
"55",
"55"
],
"filament_dev_drying_softening_temperature": [
"60"
]
}
@@ -3,7 +3,7 @@
"name": "BETA PETG Glitter @BBL A1",
"inherits": "BETA PETG Glitter @base",
"from": "system",
"setting_id": "cz5dC7OrXDlX1Ctj",
"setting_id": "BPGB03_06",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Glitter @BBL A1M 0.4 nozzle",
"inherits": "BETA PETG Glitter @base",
"from": "system",
"setting_id": "PozWnOfZ2l7HqTpx",
"setting_id": "BPGB03_03",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Glitter @BBL H2D 0.4 nozzle",
"inherits": "BETA PETG Glitter @base",
"from": "system",
"setting_id": "ijW4zvNAQ3DIK984",
"setting_id": "BPGB03_09",
"instantiation": "true",
"fan_max_speed": [
"30"
@@ -3,7 +3,7 @@
"name": "BETA PETG Glitter @BBL X1C",
"inherits": "BETA PETG Glitter @base",
"from": "system",
"setting_id": "UI220qldPoLY8VZN",
"setting_id": "BPGB03_01",
"instantiation": "true",
"fan_min_speed": [
"20"
@@ -92,48 +92,5 @@
],
"filament_start_gcode": [
"; filament start gcode\n{if (bed_temperature[current_extruder] >80)||(bed_temperature_initial_layer[current_extruder] >80)}M106 P3 S255\n{elsif (bed_temperature[current_extruder] >60)||(bed_temperature_initial_layer[current_extruder] >60)}M106 P3 S180\n{endif}\n\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}"
],
"close_additional_fan_first_x_layers": [
"3"
],
"retraction_distances_when_ec": [
"0"
],
"filament_end_gcode": [
"; filament end gcode \n\n"
],
"volumetric_speed_coefficients": [
"0 0 0 0 0 0"
],
"filament_adhesiveness_category": [
"300"
],
"filament_dev_chamber_drying_bed_temperature": [
"80"
],
"filament_dev_ams_drying_time": [
"12",
"12",
"12",
"12"
],
"filament_dev_ams_drying_ams_limitations": [
"1",
"0"
],
"filament_dev_ams_drying_heat_distortion_temperature": [
"75"
],
"filament_dev_drying_cooling_temperature": [
"55"
],
"filament_dev_ams_drying_temperature": [
"65",
"65",
"55",
"55"
],
"filament_dev_drying_softening_temperature": [
"60"
]
}
@@ -3,7 +3,7 @@
"name": "BETA PETG Glow @BBL A1",
"inherits": "BETA PETG Glow @base",
"from": "system",
"setting_id": "LudMQeiSJOLw0Aoy",
"setting_id": "BPGB04_06",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Glow @BBL A1M 0.4 nozzle",
"inherits": "BETA PETG Glow @base",
"from": "system",
"setting_id": "cl1Rl9hgrahhhSd3",
"setting_id": "BPGB04_03",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Glow @BBL H2D 0.4 nozzle",
"inherits": "BETA PETG Glow @base",
"from": "system",
"setting_id": "3s9w3mH6M4OdbrUV",
"setting_id": "BPGB04_09",
"instantiation": "true",
"fan_max_speed": [
"30"
@@ -3,7 +3,7 @@
"name": "BETA PETG Glow @BBL X1C",
"inherits": "BETA PETG Glow @base",
"from": "system",
"setting_id": "wv5FRKdnsHaIFA7L",
"setting_id": "BPGB04_01",
"instantiation": "true",
"fan_min_speed": [
"20"
@@ -92,48 +92,5 @@
],
"filament_start_gcode": [
"; filament start gcode\n{if (bed_temperature[current_extruder] >80)||(bed_temperature_initial_layer[current_extruder] >80)}M106 P3 S255\n{elsif (bed_temperature[current_extruder] >60)||(bed_temperature_initial_layer[current_extruder] >60)}M106 P3 S180\n{endif}\n\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}"
],
"close_additional_fan_first_x_layers": [
"3"
],
"retraction_distances_when_ec": [
"0"
],
"filament_end_gcode": [
"; filament end gcode \n\n"
],
"volumetric_speed_coefficients": [
"0 0 0 0 0 0"
],
"filament_adhesiveness_category": [
"300"
],
"filament_dev_chamber_drying_bed_temperature": [
"80"
],
"filament_dev_ams_drying_time": [
"12",
"12",
"12",
"12"
],
"filament_dev_ams_drying_ams_limitations": [
"1",
"0"
],
"filament_dev_ams_drying_heat_distortion_temperature": [
"75"
],
"filament_dev_drying_cooling_temperature": [
"55"
],
"filament_dev_ams_drying_temperature": [
"65",
"65",
"55",
"55"
],
"filament_dev_drying_softening_temperature": [
"60"
]
}
@@ -3,7 +3,7 @@
"name": "BETA PETG Gradient @BBL A1",
"inherits": "BETA PETG Gradient @base",
"from": "system",
"setting_id": "L3hZggzEZUgUv0Qv",
"setting_id": "BPGB05_06",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Gradient @BBL A1M 0.4 nozzle",
"inherits": "BETA PETG Gradient @base",
"from": "system",
"setting_id": "zsITnsNFRG7IE6sR",
"setting_id": "BPGB05_03",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Gradient @BBL H2D 0.4 nozzle",
"inherits": "BETA PETG Gradient @base",
"from": "system",
"setting_id": "GqmCuPg6baUxeoYS",
"setting_id": "BPGB05_09",
"instantiation": "true",
"fan_max_speed": [
"30"
@@ -3,7 +3,7 @@
"name": "BETA PETG Gradient @BBL X1C",
"inherits": "BETA PETG Gradient @base",
"from": "system",
"setting_id": "oYJjUiaCI4UBZ0ur",
"setting_id": "BPGB05_01",
"instantiation": "true",
"fan_min_speed": [
"20"
@@ -92,48 +92,5 @@
],
"filament_start_gcode": [
"; filament start gcode\n{if (bed_temperature[current_extruder] >80)||(bed_temperature_initial_layer[current_extruder] >80)}M106 P3 S255\n{elsif (bed_temperature[current_extruder] >60)||(bed_temperature_initial_layer[current_extruder] >60)}M106 P3 S180\n{endif}\n\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}"
],
"close_additional_fan_first_x_layers": [
"3"
],
"retraction_distances_when_ec": [
"0"
],
"filament_end_gcode": [
"; filament end gcode \n\n"
],
"volumetric_speed_coefficients": [
"0 0 0 0 0 0"
],
"filament_adhesiveness_category": [
"300"
],
"filament_dev_chamber_drying_bed_temperature": [
"80"
],
"filament_dev_ams_drying_time": [
"12",
"12",
"12",
"12"
],
"filament_dev_ams_drying_ams_limitations": [
"1",
"0"
],
"filament_dev_ams_drying_heat_distortion_temperature": [
"75"
],
"filament_dev_drying_cooling_temperature": [
"55"
],
"filament_dev_ams_drying_temperature": [
"65",
"65",
"55",
"55"
],
"filament_dev_drying_softening_temperature": [
"60"
]
}
@@ -3,7 +3,7 @@
"name": "BETA PETG HF @BBL A1",
"inherits": "BETA PETG HF @base",
"from": "system",
"setting_id": "T2Ea25pM2u9BM7Oa",
"setting_id": "BPGB06_03",
"instantiation": "true",
"fan_cooling_layer_time": [
"15"
@@ -3,7 +3,7 @@
"name": "BETA PETG HF @BBL A1M",
"inherits": "BETA PETG HF @base",
"from": "system",
"setting_id": "nO5R86dkDle1m5Pq",
"setting_id": "BPGB06_06",
"instantiation": "true",
"fan_cooling_layer_time": [
"15"
@@ -3,7 +3,7 @@
"name": "BETA PETG HF @BBL H2D 0.4 nozzle",
"inherits": "BETA PETG HF @base",
"from": "system",
"setting_id": "YOBguayM9PiwkpfT",
"setting_id": "BPGB06_09",
"instantiation": "true",
"counter_coef_2": [
"0.007"
@@ -3,7 +3,7 @@
"name": "BETA PETG HF @BBL X1C",
"inherits": "BETA PETG HF @base",
"from": "system",
"setting_id": "EG3h0IHVO1yyVNMs",
"setting_id": "BPGB06_00",
"instantiation": "true",
"fan_cooling_layer_time": [
"20"
@@ -85,48 +85,5 @@
],
"filament_start_gcode": [
"; filament start gcode\n{if (bed_temperature[current_extruder] >80)||(bed_temperature_initial_layer[current_extruder] >80)}M106 P3 S255\n{elsif (bed_temperature[current_extruder] >60)||(bed_temperature_initial_layer[current_extruder] >60)}M106 P3 S180\n{endif}\n\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}"
],
"close_additional_fan_first_x_layers": [
"3"
],
"retraction_distances_when_ec": [
"0"
],
"filament_end_gcode": [
"; filament end gcode \n\n"
],
"volumetric_speed_coefficients": [
"0 0 0 0 0 0"
],
"filament_adhesiveness_category": [
"300"
],
"filament_dev_chamber_drying_bed_temperature": [
"80"
],
"filament_dev_ams_drying_time": [
"12",
"12",
"12",
"12"
],
"filament_dev_ams_drying_ams_limitations": [
"1",
"0"
],
"filament_dev_ams_drying_heat_distortion_temperature": [
"75"
],
"filament_dev_drying_cooling_temperature": [
"55"
],
"filament_dev_ams_drying_temperature": [
"65",
"65",
"55",
"55"
],
"filament_dev_drying_softening_temperature": [
"60"
]
}
@@ -3,7 +3,7 @@
"name": "BETA PETG Heat Color Change @BBL A1",
"inherits": "BETA PETG Heat Color Change @base",
"from": "system",
"setting_id": "ZhHASdXFywqbRuBf",
"setting_id": "BPGB01_06",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Heat Color Change @BBL A1M 0.4 nozzle",
"inherits": "BETA PETG Heat Color Change @base",
"from": "system",
"setting_id": "YiFgzCJDAOW9YSjZ",
"setting_id": "BPGB01_03",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Heat Color Change @BBL H2D 0.4 nozzle",
"inherits": "BETA PETG Heat Color Change @base",
"from": "system",
"setting_id": "DpWgpTiKdm70Xt1i",
"setting_id": "BPGB01_09",
"instantiation": "true",
"fan_max_speed": [
"30"
@@ -3,7 +3,7 @@
"name": "BETA PETG Heat Color Change @BBL X1C",
"inherits": "BETA PETG Heat Color Change @base",
"from": "system",
"setting_id": "3lJe6ftSHQDkQZ1S",
"setting_id": "BPGB01_01",
"instantiation": "true",
"fan_min_speed": [
"20"
@@ -92,48 +92,5 @@
],
"filament_start_gcode": [
"; filament start gcode\n{if (bed_temperature[current_extruder] >80)||(bed_temperature_initial_layer[current_extruder] >80)}M106 P3 S255\n{elsif (bed_temperature[current_extruder] >60)||(bed_temperature_initial_layer[current_extruder] >60)}M106 P3 S180\n{endif}\n\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}"
],
"close_additional_fan_first_x_layers": [
"3"
],
"retraction_distances_when_ec": [
"0"
],
"filament_end_gcode": [
"; filament end gcode \n\n"
],
"volumetric_speed_coefficients": [
"0 0 0 0 0 0"
],
"filament_adhesiveness_category": [
"300"
],
"filament_dev_chamber_drying_bed_temperature": [
"80"
],
"filament_dev_ams_drying_time": [
"12",
"12",
"12",
"12"
],
"filament_dev_ams_drying_ams_limitations": [
"1",
"0"
],
"filament_dev_ams_drying_heat_distortion_temperature": [
"75"
],
"filament_dev_drying_cooling_temperature": [
"55"
],
"filament_dev_ams_drying_temperature": [
"65",
"65",
"55",
"55"
],
"filament_dev_drying_softening_temperature": [
"60"
]
}
@@ -3,7 +3,7 @@
"name": "BETA PETG Marble @BBL A1",
"inherits": "BETA PETG Marble @base",
"from": "system",
"setting_id": "tSCDuE4dE44PyIwb",
"setting_id": "BPGB07_06",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Marble @BBL A1M 0.4 nozzle",
"inherits": "BETA PETG Marble @base",
"from": "system",
"setting_id": "n6dguOo5ZleD6Ec4",
"setting_id": "BPGB07_03",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Marble @BBL H2D 0.4 nozzle",
"inherits": "BETA PETG Marble @base",
"from": "system",
"setting_id": "RxCTxdvym4iNOB5j",
"setting_id": "BPGB07_09",
"instantiation": "true",
"fan_max_speed": [
"30"
@@ -3,7 +3,7 @@
"name": "BETA PETG Marble @BBL X1C",
"inherits": "BETA PETG Marble @base",
"from": "system",
"setting_id": "3BgsPAgilo1MgtOV",
"setting_id": "BPGB07_01",
"instantiation": "true",
"fan_min_speed": [
"20"
@@ -92,48 +92,5 @@
],
"filament_start_gcode": [
"; filament start gcode\n{if (bed_temperature[current_extruder] >80)||(bed_temperature_initial_layer[current_extruder] >80)}M106 P3 S255\n{elsif (bed_temperature[current_extruder] >60)||(bed_temperature_initial_layer[current_extruder] >60)}M106 P3 S180\n{endif}\n\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}"
],
"close_additional_fan_first_x_layers": [
"3"
],
"retraction_distances_when_ec": [
"0"
],
"filament_end_gcode": [
"; filament end gcode \n\n"
],
"volumetric_speed_coefficients": [
"0 0 0 0 0 0"
],
"filament_adhesiveness_category": [
"300"
],
"filament_dev_chamber_drying_bed_temperature": [
"80"
],
"filament_dev_ams_drying_time": [
"12",
"12",
"12",
"12"
],
"filament_dev_ams_drying_ams_limitations": [
"1",
"0"
],
"filament_dev_ams_drying_heat_distortion_temperature": [
"75"
],
"filament_dev_drying_cooling_temperature": [
"55"
],
"filament_dev_ams_drying_temperature": [
"65",
"65",
"55",
"55"
],
"filament_dev_drying_softening_temperature": [
"60"
]
}
@@ -3,7 +3,7 @@
"name": "BETA PETG Matte @BBL A1",
"inherits": "BETA PETG Matte @base",
"from": "system",
"setting_id": "zYkrzQk11C98R31F",
"setting_id": "BPGB08_06",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Matte @BBL A1M 0.4 nozzle",
"inherits": "BETA PETG Matte @base",
"from": "system",
"setting_id": "rzX5oP4037Ex5Chh",
"setting_id": "BPGB08_03",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Matte @BBL H2D 0.4 nozzle",
"inherits": "BETA PETG Matte @base",
"from": "system",
"setting_id": "sqz8Vluq7GMd3siv",
"setting_id": "BPGB08_09",
"instantiation": "true",
"fan_max_speed": [
"30"
@@ -3,7 +3,7 @@
"name": "BETA PETG Matte @BBL X1C",
"inherits": "BETA PETG Matte @base",
"from": "system",
"setting_id": "NXgqYCqHeVCgFF3I",
"setting_id": "BPGB08_01",
"instantiation": "true",
"fan_min_speed": [
"20"
@@ -92,48 +92,5 @@
],
"filament_start_gcode": [
"; filament start gcode\n{if (bed_temperature[current_extruder] >80)||(bed_temperature_initial_layer[current_extruder] >80)}M106 P3 S255\n{elsif (bed_temperature[current_extruder] >60)||(bed_temperature_initial_layer[current_extruder] >60)}M106 P3 S180\n{endif}\n\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}"
],
"close_additional_fan_first_x_layers": [
"3"
],
"retraction_distances_when_ec": [
"0"
],
"filament_end_gcode": [
"; filament end gcode \n\n"
],
"volumetric_speed_coefficients": [
"0 0 0 0 0 0"
],
"filament_adhesiveness_category": [
"300"
],
"filament_dev_chamber_drying_bed_temperature": [
"80"
],
"filament_dev_ams_drying_time": [
"12",
"12",
"12",
"12"
],
"filament_dev_ams_drying_ams_limitations": [
"1",
"0"
],
"filament_dev_ams_drying_heat_distortion_temperature": [
"75"
],
"filament_dev_drying_cooling_temperature": [
"55"
],
"filament_dev_ams_drying_temperature": [
"65",
"65",
"55",
"55"
],
"filament_dev_drying_softening_temperature": [
"60"
]
}
@@ -3,7 +3,7 @@
"name": "BETA PETG Metallic @BBL A1",
"inherits": "BETA PETG Metallic @base",
"from": "system",
"setting_id": "1baUIJ3P8n6DcyRc",
"setting_id": "BPGB09_06",
"instantiation": "true",
"fan_max_speed": [
"50"
@@ -3,7 +3,7 @@
"name": "BETA PETG Metallic @BBL A1M 0.4 nozzle",
"inherits": "BETA PETG Metallic @base",
"from": "system",
"setting_id": "LMsVDqsChBksLNqL",
"setting_id": "BPGB09_03",
"instantiation": "true",
"fan_max_speed": [
"50"

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