mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 13:32:44 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba5ecdfea9 | ||
|
|
e5aa9a8cfc | ||
|
|
29d4513694 | ||
|
|
5ede9711f5 | ||
|
|
6bcb809dd0 | ||
|
|
33dfb66aa5 | ||
|
|
ef7bfeda9c | ||
|
|
47ccca7f72 | ||
|
|
04e13200aa |
@@ -1,2 +1,7 @@
|
|||||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||||
* text=auto
|
* text=auto
|
||||||
|
|
||||||
|
# Shell scripts are run by Git Bash on Windows CI, which cannot read a script
|
||||||
|
# with CRLF line endings: it fails on the first line. Windows checkouts default
|
||||||
|
# to core.autocrlf=true, so keep these LF whatever the platform.
|
||||||
|
*.sh text eol=lf
|
||||||
|
|||||||
@@ -162,6 +162,14 @@ jobs:
|
|||||||
retention-days: 5
|
retention-days: 5
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Build system preset cache (macOS)
|
||||||
|
if: runner.os == 'macOS' && !inputs.macos-combine-only
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
shell: bash
|
||||||
|
# The bundle was already packed from resources/, so the caches have to be
|
||||||
|
# installed into it here; the source tree keeps its JSONs for later jobs.
|
||||||
|
run: ./scripts/build_preset_cache.sh -b build/${{ inputs.arch }} build/${{ inputs.arch }}/OrcaSlicer/OrcaSlicer.app/Contents/Resources/profiles
|
||||||
|
|
||||||
- name: Pack macOS app bundle ${{ inputs.arch }}
|
- name: Pack macOS app bundle ${{ inputs.arch }}
|
||||||
if: runner.os == 'macOS' && !inputs.macos-combine-only
|
if: runner.os == 'macOS' && !inputs.macos-combine-only
|
||||||
working-directory: ${{ github.workspace }}
|
working-directory: ${{ github.workspace }}
|
||||||
@@ -390,6 +398,13 @@ jobs:
|
|||||||
if ($arch -eq "arm64") { .\build_release_vs.bat slicer arm64 tests } else { .\build_release_vs.bat slicer tests }
|
if ($arch -eq "arm64") { .\build_release_vs.bat slicer arm64 tests } else { .\build_release_vs.bat slicer tests }
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Build system preset cache (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: cmd
|
||||||
|
# Shipped into both the already-installed tree (portable zip, MSIX) and
|
||||||
|
# the checkout cpack re-installs from when it builds the NSIS installer.
|
||||||
|
run: scripts\build_preset_cache.bat "%BUILD_DIR%" "resources\profiles" "%BUILD_DIR%\OrcaSlicer\resources\profiles"
|
||||||
|
|
||||||
- name: Pack unit tests Win
|
- name: Pack unit tests Win
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
working-directory: ${{ github.workspace }}
|
working-directory: ${{ github.workspace }}
|
||||||
@@ -539,6 +554,20 @@ jobs:
|
|||||||
retention-days: 5
|
retention-days: 5
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Build system preset cache (Linux)
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Both were packed from resources/ before the caches existed, so the
|
||||||
|
# AppImage is unpacked first and the caches shipped into it and into
|
||||||
|
# the package tree; the source tree keeps its JSONs for later steps.
|
||||||
|
appimage=$(find build -maxdepth 1 -name "OrcaSlicer_Linux_AppImage*.AppImage" | head -1)
|
||||||
|
chmod +x "$appimage"
|
||||||
|
"$appimage" --appimage-extract
|
||||||
|
./scripts/build_preset_cache.sh -b build build/package/resources/profiles squashfs-root/resources/profiles
|
||||||
|
appimagetool=$(find build -name "appimagetool.AppImage" | head -1)
|
||||||
|
ARCH=$(uname -m) "$appimagetool" --appimage-extract-and-run squashfs-root "$appimage"
|
||||||
|
rm -rf squashfs-root
|
||||||
# Ship the freshly-built validator so slice_check_linux (build_all.yml)
|
# Ship the freshly-built validator so slice_check_linux (build_all.yml)
|
||||||
# can slice-sweep the shipped profiles with this PR's engine. Taken from
|
# can slice-sweep the shipped profiles with this PR's engine. Taken from
|
||||||
# the aarch64 leg so the sweep also exercises the arm build; x86_64 on
|
# the aarch64 leg so the sweep also exercises the arm build; x86_64 on
|
||||||
|
|||||||
@@ -49,3 +49,4 @@ internal_docs/
|
|||||||
# Python bytecode
|
# Python bytecode
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
*.opc
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ ctest --test-dir ./tests/fff_print
|
|||||||
- C++17, selective C++20. PascalCase classes, snake_case functions/variables
|
- C++17, selective C++20. PascalCase classes, snake_case functions/variables
|
||||||
- `#pragma once` for headers. Smart pointers and RAII preferred
|
- `#pragma once` for headers. Smart pointers and RAII preferred
|
||||||
- Parallelization via TBB — be mindful of shared state
|
- Parallelization via TBB — be mindful of shared state
|
||||||
|
- Always use `SetSizerAndFit(sizer)` instead of `SetSizer(sizer)` on top level window. Unless `SetSizer` must be called before the full layout is built, call `sizer->SetSizeHints(window)` afterwards in this case.
|
||||||
|
|
||||||
## Key Entry Points
|
## Key Entry Points
|
||||||
|
|
||||||
@@ -59,7 +60,31 @@ ctest --test-dir ./tests/fff_print
|
|||||||
|
|
||||||
## Localization & translations
|
## Localization & translations
|
||||||
|
|
||||||
- Translation catalogs live in `localization/i18n/<lang>/OrcaSlicer_<lang>.po`.
|
Catalogs live in `localization/i18n/<lang>/OrcaSlicer_<lang>.po`; the template is `OrcaSlicer.pot`.
|
||||||
- When creating or reviewing translations, use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language and terms that must stay in English (brand/product names, acronyms, file formats, G-code, macros/variables) are not translated.
|
See the [Localization guide](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_guide.md) for the human-facing version of these principles.
|
||||||
- If a term's established translation changes, update both the affected `.po` files and the glossary so they stay in sync.
|
|
||||||
- Only edit `msgstr` (never `msgid`); keep placeholders (`%s`, `%1%`, `\n`), context (`msgctxt`), and file encoding/line endings intact.
|
### Terminology
|
||||||
|
|
||||||
|
- Use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language, and terms that must stay in English (brand/product names, acronyms, materials, file formats, G-code tokens, macros/variables/identifiers) are not translated.
|
||||||
|
- If a term's established translation changes, update both the affected `.po` files and the glossary (`localization_glossary.tsv`, then regenerate) so they stay in sync.
|
||||||
|
- Translate the *meaning*, not the words. Check what the string actually controls before translating it — English reuses one word for different things. `Flow ratio` (multiplier), `Flow Rate` (throughput) and `Flow Dynamics` (pressure compensation) are three different terms; `extruder` may mean the toolhead, the feeder motor, or the nozzle depending on the string.
|
||||||
|
- Reuse one template per recurring message shape (`Failed to connect to …`, `Are you sure you want to …?`), even where the English wording varies.
|
||||||
|
|
||||||
|
### Editing rules
|
||||||
|
|
||||||
|
- Only edit `msgstr` — **never** change `msgid`, and never "fix" wrong English in the translation alone. Report the source string instead.
|
||||||
|
- Preserve exactly: placeholders (`%s`, `%d`, `%1%`, `%zu`, `%%`), every `\n` (count *and* position, including leading/trailing), leading/trailing spaces, HTML tags, `℃`, and the file's encoding and line endings.
|
||||||
|
- **Never reorder positional arguments** in a `c-format` string. If the msgid is `%d` then `%s`, that order must hold — swapping them breaks at runtime.
|
||||||
|
- `msgctxt` separates homonyms — always read it. `Back`/`Camera View` is the rear view of the 3D navigator, while `Back`/`Navigation` is the go-back button; `Top` exists in the *Alignment*, *Layers* and *Camera View* senses.
|
||||||
|
- When a string needs disambiguating, add context in the source (`_L_CONTEXT`/`_u8L_CONTEXT`), don't work around it in the translation.
|
||||||
|
- A literal `%` inside a string xgettext flagged `possible-c-format` will fail `msgfmt`. Fix it with a `// xgettext:no-c-format, no-boost-format` comment above the string in the source — do not mangle the translation or use `%%` in text that is never passed through printf.
|
||||||
|
- Plural entries: read `nplurals` from the catalog's `Plural-Forms` header (it is **not** always 2 — ja/ko/zh/th/vi use 1, ru/cs/pl/lt use 3, uk uses 4). Each form must be genuinely inflected for its quantity; repeating one sentence across all forms is a bug in Slavic/Baltic languages, though it is correct for Turkish and Hungarian.
|
||||||
|
- An entry whose `msgstr` equals its `msgid` is untranslated even though it is not empty; a plural entry with any empty form is likewise incomplete.
|
||||||
|
- Mark machine-produced translations with an `# AI Translated` translator comment. Don't add it to a human translation you didn't actually rewrite.
|
||||||
|
- Don't reflow or re-wrap unrelated entries — keep the diff limited to the strings you changed.
|
||||||
|
|
||||||
|
### Verifying
|
||||||
|
|
||||||
|
- `scripts/run_gettext.bat --full` (Windows) regenerates the template, merges every catalog and compiles the `.mo` files. It must exit 0.
|
||||||
|
- Or check a single catalog with `msgfmt --check-format -o <out>.mo localization/i18n/<lang>/OrcaSlicer_<lang>.po`.
|
||||||
|
- Fuzzy entries are not shown to users. If you correct one, clear its `fuzzy` flag, otherwise the fix never ships.
|
||||||
|
|||||||
@@ -567,6 +567,8 @@ if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
|
|||||||
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target OrcaSlicer
|
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target OrcaSlicer
|
||||||
echo "Building OrcaSlicer_profile_validator .."
|
echo "Building OrcaSlicer_profile_validator .."
|
||||||
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target OrcaSlicer_profile_validator
|
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target OrcaSlicer_profile_validator
|
||||||
|
echo "Building generate_system_cache ..."
|
||||||
|
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target generate_system_cache
|
||||||
./scripts/run_gettext.sh
|
./scripts/run_gettext.sh
|
||||||
fi
|
fi
|
||||||
if [[ -n "${BUILD_TESTS}" ]] ; then
|
if [[ -n "${BUILD_TESTS}" ]] ; then
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
# System Preset Cache — High Level Design
|
||||||
|
|
||||||
|
## Why it exists
|
||||||
|
|
||||||
|
OrcaSlicer ships tens of thousands of system preset JSON files. Every launch used to
|
||||||
|
parse all of them: read each vendor profile, walk its machine, process and filament
|
||||||
|
sub-files, resolve inheritance, and build the preset collections from scratch. That
|
||||||
|
parse dominated startup, and it produced the same result every time, because system
|
||||||
|
presets only change when the app is updated or a profile update is installed.
|
||||||
|
|
||||||
|
The preset cache replaces that parse with a read. Each vendor's fully-resolved presets
|
||||||
|
are serialized once — at build time, in CI — into a single binary file that the app
|
||||||
|
loads directly into memory. Nothing is recomputed at startup unless something changed.
|
||||||
|
|
||||||
|
The cache is **only ever an optimization**. Every rule below exists to guarantee that a
|
||||||
|
cache is either provably equivalent to parsing the JSONs, or rejected. There is no
|
||||||
|
"mostly right" cache.
|
||||||
|
|
||||||
|
## The unit is one vendor
|
||||||
|
|
||||||
|
A cache covers exactly one vendor. `BBL.opc` sits beside `BBL.json` and holds
|
||||||
|
everything `BBL.json` and the `BBL/` sub-file tree would have produced.
|
||||||
|
|
||||||
|
Per-vendor granularity is what makes the system practical:
|
||||||
|
|
||||||
|
- A vendor whose profile is bumped invalidates only its own cache. The other 60-odd
|
||||||
|
vendors keep theirs.
|
||||||
|
- The setup wizard, which loads vendors one at a time, gets the same speedup as
|
||||||
|
startup without a second code path.
|
||||||
|
- A vendor with no cache, or a broken one, costs only that vendor a parse.
|
||||||
|
|
||||||
|
A cache holds *system* presets only. User presets, project settings and modified
|
||||||
|
presets are never serialized — they have their own storage and their own lifecycle.
|
||||||
|
|
||||||
|
## Where the files live
|
||||||
|
|
||||||
|
| Location | Contents on a shipped build | Role |
|
||||||
|
|---|---|---|
|
||||||
|
| `resources/profiles/` | `<vendor>.opc` alone — the profile and its preset JSONs both pruned | What the app ships with; the fallback everything falls back to |
|
||||||
|
| `<data_dir>/system/` | `<vendor>.opc` alone, or `<vendor>.json` + `<vendor>/` after an update | What the user has installed |
|
||||||
|
| `<data_dir>/system/` (dev build) | `<vendor>.json` + `<vendor>/` + `<vendor>.opc` written at runtime | A developer tree caches as it parses |
|
||||||
|
|
||||||
|
Two forms of the same vendor therefore exist, and the system's central rule is that
|
||||||
|
**a vendor's cache is the whole of it**. Where a cache ships or is installed, no profile
|
||||||
|
and no preset JSONs sit beside it: the cache carries the presets, the vendor profile,
|
||||||
|
and the version stamp that says which release it came from. A vendor is "installed" if
|
||||||
|
either form is present, and its installed version is read from whichever form is there.
|
||||||
|
|
||||||
|
What stays beside the caches in `resources/profiles/` is everything that is not a
|
||||||
|
preset: each vendor's directory of printer thumbnails, cover images, bed models and
|
||||||
|
hotend meshes, which are read from disk by path and were never part of the cache. Files
|
||||||
|
that are not vendors at all, `blacklist.json` chief among them, are untouched.
|
||||||
|
|
||||||
|
The alternative — shipping both and treating the cache as a sidecar — was rejected. It
|
||||||
|
doubles the installed size, and it creates a class of bug where the two disagree and
|
||||||
|
the app's behavior depends on which one a given code path happened to read.
|
||||||
|
|
||||||
|
## What a cache file is
|
||||||
|
|
||||||
|
A fixed-size header followed by one binary stream.
|
||||||
|
|
||||||
|
The header carries a magic number, the cache format version, the payload size and a
|
||||||
|
CRC32 of the payload. It exists so that a truncated download, a half-written file or a
|
||||||
|
file from an entirely different program is rejected in microseconds, before anything
|
||||||
|
tries to interpret it.
|
||||||
|
|
||||||
|
The payload opens with the stamps that decide whether the cache may be used at all —
|
||||||
|
format version, schema fingerprint, vendor name, vendor version, filament library
|
||||||
|
version — and then the vendor's data: the vendor profiles, the five preset collections
|
||||||
|
(print, SLA print, filament, SLA material, printer), the config and filament-id lookup
|
||||||
|
maps, the obsolete-preset lists, and the count of errors the original parse hit.
|
||||||
|
|
||||||
|
Two deliberate choices in the layout:
|
||||||
|
|
||||||
|
- **Stamps come first**, so the question "what version is this vendor installed at?"
|
||||||
|
can be answered by reading the first kilobyte. The updater asks that question for
|
||||||
|
every vendor on every launch; reading tens of megabytes to answer it would give back
|
||||||
|
the startup time the cache saved.
|
||||||
|
- **Defaults are not stored.** Every collection reconstructs its default presets the
|
||||||
|
way the JSON path does, and the cache carries only what a parse would have added on
|
||||||
|
top. This keeps the cache a record of the vendor's data, not a memory image of the
|
||||||
|
program's state.
|
||||||
|
|
||||||
|
## When a cache may be used
|
||||||
|
|
||||||
|
A cache is accepted only if every gate below passes. Any failure means "parse the
|
||||||
|
JSONs instead" — never a hard error, never a partial load.
|
||||||
|
|
||||||
|
**1. Integrity.** Magic number, plausible size, CRC32 over the payload.
|
||||||
|
|
||||||
|
**2. Cache format version.** A single integer bumped by hand whenever the binary layout
|
||||||
|
changes in a way nothing else would catch: reordering or retyping a serialized field,
|
||||||
|
or changing what the cache's own stamps mean.
|
||||||
|
|
||||||
|
**3. Schema fingerprint.** A checksum over the app version and the entire print-config
|
||||||
|
option schema — every option's key, type, wire ordinal and enum values. This is the
|
||||||
|
gate that makes the cache safe across development: adding a config option, changing its
|
||||||
|
type, or reordering the enum values of an existing one all change the fingerprint, so
|
||||||
|
caches from before the change are rejected without anyone having to remember to bump
|
||||||
|
anything. It also means a cache never crosses app versions.
|
||||||
|
|
||||||
|
**4. Vendor identity and version.** The cache names the vendor it holds and the profile
|
||||||
|
version it was built from. It is accepted only if that version is at least as new as
|
||||||
|
the profile now on disk. Where no profile sits beside the cache — the shipped,
|
||||||
|
cache-only form — the comparison is skipped, because nothing on disk can be newer than
|
||||||
|
a cache that is the installation.
|
||||||
|
|
||||||
|
**5. Filament library version.** Every vendor's filaments inherit from the shared Orca
|
||||||
|
filament library, so a vendor's cache is only valid against the library it was resolved
|
||||||
|
against. Bumping the library invalidates every vendor's cache, which is correct and
|
||||||
|
is why the library's version is stamped into all of them.
|
||||||
|
|
||||||
|
A vendor profile with no parsable version is never cached and never served from a
|
||||||
|
cache. There would be no way to tell later whether the cache had gone stale, and a
|
||||||
|
cache nothing can invalidate is worse than no cache.
|
||||||
|
|
||||||
|
## How a vendor is loaded
|
||||||
|
|
||||||
|
When the app loads a vendor, it tries, in order:
|
||||||
|
|
||||||
|
1. The cache in the directory it was asked to load from — normally `<data_dir>/system/`.
|
||||||
|
2. The shipped cache in `resources/profiles/`.
|
||||||
|
3. Parsing the JSONs — from the data directory if the profile is installed there, and
|
||||||
|
from `resources/profiles/` otherwise, which on a shipped build only has JSONs for a
|
||||||
|
vendor that has no cache.
|
||||||
|
|
||||||
|
The second tier is what makes app upgrades work. After an upgrade, a cache the previous
|
||||||
|
version installed fails the fingerprint gate; the new build's own shipped cache answers
|
||||||
|
instead, and the user never sees a parse. The stale installed file is simply ignored
|
||||||
|
until the next profile update overwrites it.
|
||||||
|
|
||||||
|
If a parse does happen and the vendor's profile carries a version, the app writes the
|
||||||
|
cache back beside where it looked for the vendor. That is how a developer build warms
|
||||||
|
itself up on second launch, and how a vendor delivered by a profile update becomes
|
||||||
|
cached without waiting for the next release.
|
||||||
|
|
||||||
|
## How a vendor is installed
|
||||||
|
|
||||||
|
Installing copies from `resources/profiles/` into `<data_dir>/system/`. A shipped build
|
||||||
|
offers only a cache and a source tree only JSONs, but a partially-generated tree can
|
||||||
|
have both, at different versions, so the installer picks the form that ships at the
|
||||||
|
**newer version** and installs only that one:
|
||||||
|
|
||||||
|
- Cache newer or equal, and readable → copy the `.opc`, and delete any profile and
|
||||||
|
vendor directory a previous install left behind, so nothing can shadow it.
|
||||||
|
- Profile newer, or the cache unreadable or absent → copy the profile and the vendor's
|
||||||
|
preset JSONs exactly as the app did before caches existed, and delete any stale `.opc`.
|
||||||
|
|
||||||
|
The result is that only one form of a vendor is ever present, and it is the newest one
|
||||||
|
the build has. This matters most for the update check, which compares what is installed
|
||||||
|
against what installing *would* lay down: if those two disagreed about which form
|
||||||
|
counts, a vendor could reinstall on every launch forever, or silently never update.
|
||||||
|
|
||||||
|
Profile updates delivered over the air always arrive as JSONs, and they win — an
|
||||||
|
updated vendor's real profile lands in the data directory, the shipped cache is older
|
||||||
|
and gets rejected, and the vendor is parsed and re-cached.
|
||||||
|
|
||||||
|
## How the caches are produced
|
||||||
|
|
||||||
|
Cache generation is a build step, not something a user ever runs.
|
||||||
|
|
||||||
|
One script per platform does the whole job, and CI calls it once on each. It builds a
|
||||||
|
small dev-utility that loads a profiles directory exactly as the app would, with cache
|
||||||
|
writing enabled, dropping a `<vendor>.opc` beside every vendor profile it parses; then
|
||||||
|
it copies those caches into each packaged application it was pointed at and deletes
|
||||||
|
every preset JSON they replace — the vendor's own profile included. Only a vendor that
|
||||||
|
actually has a cache is pruned, so a vendor the generator skipped keeps its JSONs and is
|
||||||
|
simply parsed at startup.
|
||||||
|
|
||||||
|
Because the schema fingerprint includes the app version, caches must be generated by
|
||||||
|
the same build that ships them. Generation runs after the build, in the same job.
|
||||||
|
|
||||||
|
## Behavior when things go wrong
|
||||||
|
|
||||||
|
The system is designed so that no cache problem is fatal:
|
||||||
|
|
||||||
|
- **Corrupt, truncated or foreign file** — rejected at the header, vendor parsed.
|
||||||
|
- **Cache from another app version or schema** — rejected at the fingerprint, vendor
|
||||||
|
parsed or served from the shipped cache.
|
||||||
|
- **Stale cache** — rejected on the version stamps, vendor parsed and re-cached.
|
||||||
|
- **Failure part-way through reading** — the bundle is reset to a clean state before
|
||||||
|
falling back, so a half-loaded cache can never leak into the parsed result.
|
||||||
|
- **A vendor that can be neither read nor parsed** — logged, and left out. The setup
|
||||||
|
wizard drops that vendor from its list and opens with the rest; startup records the
|
||||||
|
error alongside the vendors that did load. One broken vendor never takes the app down.
|
||||||
|
|
||||||
|
The one genuine limit: on a shipped build a vendor is its cache and nothing else, so a
|
||||||
|
rejected cache has nothing to fall back to for that vendor. This is by design — the
|
||||||
|
alternative is shipping every preset twice — and it is why the acceptance gates are
|
||||||
|
conservative and why CI generates the caches with the same build that ships them. The
|
||||||
|
recovery path is a profile update, which delivers real JSONs.
|
||||||
|
|
||||||
|
It also means nothing may quietly assume a `<vendor>.json` exists. Discovery, version
|
||||||
|
checks and the update decision all read whichever form is present, and a code path that
|
||||||
|
enumerates only `*.json` will find no vendors at all in a packaged build.
|
||||||
|
|
||||||
|
## Maintenance rules
|
||||||
|
|
||||||
|
- **Adding or changing a config option** needs nothing. The fingerprint covers it.
|
||||||
|
- **Changing what a cache serializes**, or the order it serializes it in, requires
|
||||||
|
bumping the cache format version by hand.
|
||||||
|
- **Bumping a vendor profile's version** invalidates that vendor's cache and nothing
|
||||||
|
else. Bumping the filament library invalidates all of them.
|
||||||
|
- **Caches are never committed.** They are build artifacts, generated per build,
|
||||||
|
ignored by git.
|
||||||
|
|
||||||
|
## Where this lives in the tree
|
||||||
|
|
||||||
|
| Area | Files |
|
||||||
|
|---|---|
|
||||||
|
| Cache format, read/write, load and save | `src/libslic3r/PresetBundle.{hpp,cpp}` |
|
||||||
|
| Per-preset serialization | `src/libslic3r/Preset.{hpp,cpp}` |
|
||||||
|
| Vendor discovery, installed/shipped versions, installation | `src/libslic3r/PresetBundle.cpp` |
|
||||||
|
| Update and reinstall decisions | `src/slic3r/Utils/PresetUpdater.cpp` |
|
||||||
|
| Setup wizard and printer-selection dialog | `src/slic3r/GUI/ConfigWizard.cpp`, `src/slic3r/GUI/WebGuideDialog.cpp` |
|
||||||
|
| Generator tool | `src/dev-utils/generate_system_cache.cpp` |
|
||||||
|
| Build and packaging script | `scripts/build_preset_cache.{sh,bat}` |
|
||||||
|
| Tests | `tests/libslic3r/test_vendor_cache.cpp` |
|
||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-23 15:24-0300\n"
|
"POT-Creation-Date: 2026-07-26 21:59-0300\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -982,7 +982,7 @@ msgstr ""
|
|||||||
#, possible-boost-format
|
#, possible-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
||||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||||
"Thank you."
|
"Thank you."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3385,7 +3385,6 @@ msgstr ""
|
|||||||
msgid "Innerloop"
|
msgid "Innerloop"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. TRN To be shown in the main menu View->Top
|
|
||||||
msgid "Top"
|
msgid "Top"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5470,10 +5469,24 @@ msgstr ""
|
|||||||
msgid "Align to Y axis"
|
msgid "Align to Y axis"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "Camera View"
|
||||||
|
msgid "Front"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "Camera View"
|
msgctxt "Camera View"
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. TRN To be shown in the main menu View->Top
|
||||||
|
msgctxt "Camera View"
|
||||||
|
msgid "Top"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. TRN To be shown in the main menu View->Bottom
|
||||||
|
msgctxt "Camera View"
|
||||||
|
msgid "Bottom"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "Camera View"
|
msgctxt "Camera View"
|
||||||
msgid "Left"
|
msgid "Left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -5854,19 +5867,13 @@ msgstr ""
|
|||||||
msgid "Top View"
|
msgid "Top View"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. TRN To be shown in the main menu View->Bottom
|
|
||||||
msgid "Bottom"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Bottom View"
|
msgid "Bottom View"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Front"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Front View"
|
msgid "Front View"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "Camera View"
|
||||||
msgid "Rear"
|
msgid "Rear"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -14729,7 +14736,7 @@ msgstr ""
|
|||||||
msgid "Retract amount after wipe"
|
msgid "Retract amount after wipe"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, possible-c-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The length of fast retraction after wipe, relative to retraction length.\n"
|
"The length of fast retraction after wipe, relative to retraction length.\n"
|
||||||
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
||||||
|
|||||||
+2289
-1422
File diff suppressed because it is too large
Load Diff
+2210
-1391
File diff suppressed because it is too large
Load Diff
+1021
-921
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-23 15:24-0300\n"
|
"POT-Creation-Date: 2026-07-26 21:59-0300\n"
|
||||||
"PO-Revision-Date: 2026-06-17 15:44-0300\n"
|
"PO-Revision-Date: 2026-06-17 15:44-0300\n"
|
||||||
"Last-Translator: Alexandre Folle de Menezes\n"
|
"Last-Translator: Alexandre Folle de Menezes\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -978,7 +978,7 @@ msgstr ""
|
|||||||
#, boost-format
|
#, boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
||||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||||
"Thank you."
|
"Thank you."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3381,7 +3381,6 @@ msgstr ""
|
|||||||
msgid "Innerloop"
|
msgid "Innerloop"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. TRN To be shown in the main menu View->Top
|
|
||||||
msgid "Top"
|
msgid "Top"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5466,10 +5465,24 @@ msgstr ""
|
|||||||
msgid "Align to Y axis"
|
msgid "Align to Y axis"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "Camera View"
|
||||||
|
msgid "Front"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "Camera View"
|
msgctxt "Camera View"
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. TRN To be shown in the main menu View->Top
|
||||||
|
msgctxt "Camera View"
|
||||||
|
msgid "Top"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. TRN To be shown in the main menu View->Bottom
|
||||||
|
msgctxt "Camera View"
|
||||||
|
msgid "Bottom"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "Camera View"
|
msgctxt "Camera View"
|
||||||
msgid "Left"
|
msgid "Left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -5850,19 +5863,13 @@ msgstr ""
|
|||||||
msgid "Top View"
|
msgid "Top View"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. TRN To be shown in the main menu View->Bottom
|
|
||||||
msgid "Bottom"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Bottom View"
|
msgid "Bottom View"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Front"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Front View"
|
msgid "Front View"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "Camera View"
|
||||||
msgid "Rear"
|
msgid "Rear"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -14725,7 +14732,7 @@ msgstr ""
|
|||||||
msgid "Retract amount after wipe"
|
msgid "Retract amount after wipe"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, c-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The length of fast retraction after wipe, relative to retraction length.\n"
|
"The length of fast retraction after wipe, relative to retraction length.\n"
|
||||||
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-23 15:24-0300\n"
|
"POT-Creation-Date: 2026-07-26 21:59-0300\n"
|
||||||
"PO-Revision-Date: \n"
|
"PO-Revision-Date: \n"
|
||||||
"Last-Translator: Ian A. Bassi <>\n"
|
"Last-Translator: Ian A. Bassi <>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -983,11 +983,11 @@ msgstr "Conector"
|
|||||||
#, boost-format
|
#, boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
||||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||||
"Thank you."
|
"Thank you."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Los objetos(%1%) tienen conectores duplicados. Es posible que falten algunos conectores en el resultado del laminado.\n"
|
"Los objetos(%1%) tienen conectores duplicados. Es posible que falten algunos conectores en el resultado del laminado.\n"
|
||||||
"Informe al equipo de PrusaSlicer sobre el escenario en el que se produjo este problema.\n"
|
"Informe al equipo de OrcaSlicer sobre el escenario en el que se produjo este problema.\n"
|
||||||
"Gracias."
|
"Gracias."
|
||||||
|
|
||||||
msgid "Cut by Plane"
|
msgid "Cut by Plane"
|
||||||
@@ -3459,7 +3459,6 @@ msgstr "Recámara"
|
|||||||
msgid "Innerloop"
|
msgid "Innerloop"
|
||||||
msgstr "Bucle interno"
|
msgstr "Bucle interno"
|
||||||
|
|
||||||
#. TRN To be shown in the main menu View->Top
|
|
||||||
msgid "Top"
|
msgid "Top"
|
||||||
msgstr "Superior"
|
msgstr "Superior"
|
||||||
|
|
||||||
@@ -5630,10 +5629,27 @@ msgstr "Evitar la zona de calibración del extrusor"
|
|||||||
msgid "Align to Y axis"
|
msgid "Align to Y axis"
|
||||||
msgstr "Alinear con el eje Y"
|
msgstr "Alinear con el eje Y"
|
||||||
|
|
||||||
|
# AI Translated
|
||||||
|
msgctxt "Camera View"
|
||||||
|
msgid "Front"
|
||||||
|
msgstr "Frontal"
|
||||||
|
|
||||||
msgctxt "Camera View"
|
msgctxt "Camera View"
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Posterior"
|
msgstr "Posterior"
|
||||||
|
|
||||||
|
# AI Translated
|
||||||
|
#. TRN To be shown in the main menu View->Top
|
||||||
|
msgctxt "Camera View"
|
||||||
|
msgid "Top"
|
||||||
|
msgstr "Superior"
|
||||||
|
|
||||||
|
# AI Translated
|
||||||
|
#. TRN To be shown in the main menu View->Bottom
|
||||||
|
msgctxt "Camera View"
|
||||||
|
msgid "Bottom"
|
||||||
|
msgstr "Inferior"
|
||||||
|
|
||||||
msgctxt "Camera View"
|
msgctxt "Camera View"
|
||||||
msgid "Left"
|
msgid "Left"
|
||||||
msgstr "Izquierda"
|
msgstr "Izquierda"
|
||||||
@@ -6020,19 +6036,14 @@ msgstr "Vista por Defecto"
|
|||||||
msgid "Top View"
|
msgid "Top View"
|
||||||
msgstr "Vista superior"
|
msgstr "Vista superior"
|
||||||
|
|
||||||
#. TRN To be shown in the main menu View->Bottom
|
|
||||||
msgid "Bottom"
|
|
||||||
msgstr "Inferior"
|
|
||||||
|
|
||||||
msgid "Bottom View"
|
msgid "Bottom View"
|
||||||
msgstr "Vista inferior"
|
msgstr "Vista inferior"
|
||||||
|
|
||||||
msgid "Front"
|
|
||||||
msgstr "Frontal"
|
|
||||||
|
|
||||||
msgid "Front View"
|
msgid "Front View"
|
||||||
msgstr "Vista frontal"
|
msgstr "Vista frontal"
|
||||||
|
|
||||||
|
# AI Translated
|
||||||
|
msgctxt "Camera View"
|
||||||
msgid "Rear"
|
msgid "Rear"
|
||||||
msgstr "Posterior"
|
msgstr "Posterior"
|
||||||
|
|
||||||
@@ -15490,7 +15501,7 @@ msgstr "La longitud de la retracción rápida antes de la purga, en relación co
|
|||||||
msgid "Retract amount after wipe"
|
msgid "Retract amount after wipe"
|
||||||
msgstr "Cantidad de retracción después de la limpieza"
|
msgstr "Cantidad de retracción después de la limpieza"
|
||||||
|
|
||||||
#, c-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The length of fast retraction after wipe, relative to retraction length.\n"
|
"The length of fast retraction after wipe, relative to retraction length.\n"
|
||||||
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
||||||
@@ -20733,6 +20744,15 @@ msgstr ""
|
|||||||
"Evita la deformación\n"
|
"Evita la deformación\n"
|
||||||
"¿Sabías que al imprimir materiales propensos a la deformación como el ABS, aumentar adecuadamente la temperatura de la cama térmica puede reducir la probabilidad de deformaciones?"
|
"¿Sabías que al imprimir materiales propensos a la deformación como el ABS, aumentar adecuadamente la temperatura de la cama térmica puede reducir la probabilidad de deformaciones?"
|
||||||
|
|
||||||
|
#~ msgid "Bottom"
|
||||||
|
#~ msgstr "Inferior"
|
||||||
|
|
||||||
|
#~ msgid "Front"
|
||||||
|
#~ msgstr "Frontal"
|
||||||
|
|
||||||
|
#~ msgid "Rear"
|
||||||
|
#~ msgstr "Posterior"
|
||||||
|
|
||||||
#~ msgid "Enter"
|
#~ msgid "Enter"
|
||||||
#~ msgstr "Enter"
|
#~ msgstr "Enter"
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1686
-1169
File diff suppressed because it is too large
Load Diff
+1737
-1171
File diff suppressed because it is too large
Load Diff
+3324
-1663
File diff suppressed because it is too large
Load Diff
+2749
-1592
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+6445
-3151
File diff suppressed because it is too large
Load Diff
+3017
-1698
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+7058
-3414
File diff suppressed because it is too large
Load Diff
+1168
-506
File diff suppressed because it is too large
Load Diff
+2267
-1391
File diff suppressed because it is too large
Load Diff
+2462
-1449
File diff suppressed because it is too large
Load Diff
+4065
-2211
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -70,7 +70,7 @@
|
|||||||
"wipe_tower_no_sparse_layers": "0",
|
"wipe_tower_no_sparse_layers": "0",
|
||||||
"wipe_tower_cone_angle": "30",
|
"wipe_tower_cone_angle": "30",
|
||||||
"wipe_tower_wall_type": "rib",
|
"wipe_tower_wall_type": "rib",
|
||||||
"wipe_tower_extra_rib_length": "8",
|
"wipe_tower_extra_rib_length": "0",
|
||||||
"prime_tower_width": "35",
|
"prime_tower_width": "35",
|
||||||
"prime_volume": "30",
|
"prime_volume": "30",
|
||||||
"wall_generator": "arachne",
|
"wall_generator": "arachne",
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
@echo off
|
||||||
|
rem Build the per-vendor system preset caches (one <vendor>.opc per vendor) by
|
||||||
|
rem running the generate_system_cache.exe dev tool against a profiles directory,
|
||||||
|
rem and make every profiles directory named on the command line ship-ready:
|
||||||
|
rem install the caches into it and delete the preset JSONs they replace, so a
|
||||||
|
rem build ships one copy of its presets instead of two.
|
||||||
|
rem
|
||||||
|
rem scripts\build_preset_cache.bat [build_dir] [target_dir ...]
|
||||||
|
rem
|
||||||
|
rem build_dir defaults to "build"
|
||||||
|
rem target_dir profiles directories to ship into. Caches are generated into
|
||||||
|
rem the source tree's resources\profiles, which is what every
|
||||||
|
rem packaging step copies from; a target may be that same
|
||||||
|
rem directory, which then only gets pruned.
|
||||||
|
rem
|
||||||
|
rem Shipping deletes, so it is a CI packaging step. A vendor's own <vendor>.json
|
||||||
|
rem goes along with its preset JSONs: the cache carries the vendor profile and
|
||||||
|
rem the version it was built at, so discovery, version checks and installing all
|
||||||
|
rem read it there. Only a vendor that has a cache is pruned, so non-vendor JSONs
|
||||||
|
rem (blacklist.json) are left alone, as are the vendor directories themselves -
|
||||||
|
rem thumbnails, covers and bed models still live there.
|
||||||
|
rem
|
||||||
|
rem set CONFIG=<cfg> to pin the build config for multi-config generators
|
||||||
|
rem (default: the config of the tool already in the build tree, else Release)
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
set "REPO_ROOT=%~dp0.."
|
||||||
|
set "BUILD_DIR=%~1"
|
||||||
|
if "%BUILD_DIR%"=="" set "BUILD_DIR=build"
|
||||||
|
if not exist "%BUILD_DIR%\" (
|
||||||
|
echo ERROR: build tree not found: %BUILD_DIR% 1>&2
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
if not "%~1"=="" shift
|
||||||
|
|
||||||
|
rem Newest match wins: a stale binary silently produces a stale cache layout.
|
||||||
|
call :find_tool
|
||||||
|
if not defined CONFIG (
|
||||||
|
for %%c in (Debug Release RelWithDebInfo MinSizeRel) do (
|
||||||
|
echo !TOOL! | findstr /i "\\%%c\\" >nul && set "CONFIG=%%c"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not defined CONFIG set "CONFIG=Release"
|
||||||
|
|
||||||
|
echo Building generate_system_cache in %BUILD_DIR% (%CONFIG%)
|
||||||
|
cmake --build "%BUILD_DIR%" --config %CONFIG% --target generate_system_cache
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo ERROR: could not build generate_system_cache - configure the build tree with -DORCA_TOOLS=ON: 1>&2
|
||||||
|
echo cmake -S "%REPO_ROOT%" -B "%BUILD_DIR%" -DORCA_TOOLS=ON 1>&2
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
call :find_tool
|
||||||
|
if not defined TOOL (
|
||||||
|
echo ERROR: generate_system_cache.exe not found under %BUILD_DIR% - build with -DORCA_TOOLS=ON 1>&2
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
set "PROFILES=%REPO_ROOT%\resources\profiles"
|
||||||
|
if not exist "%PROFILES%\" (
|
||||||
|
echo ERROR: profiles directory not found: %PROFILES% 1>&2
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
for %%d in ("%PROFILES%") do set "PROFILES=%%~fd"
|
||||||
|
|
||||||
|
rem Add the slicer's runtime DLL directory to PATH so generate_system_cache.exe
|
||||||
|
rem can resolve its dependencies (TKernel.dll etc.) without a full install step.
|
||||||
|
set "DLL_DIR="
|
||||||
|
for /f "delims=" %%f in ('dir /s /b "%BUILD_DIR%\TKernel.dll" 2^>nul') do (
|
||||||
|
if not defined DLL_DIR set "DLL_DIR=%%~dpf"
|
||||||
|
)
|
||||||
|
if defined DLL_DIR set "PATH=%DLL_DIR%;%PATH%"
|
||||||
|
|
||||||
|
echo Generating per-vendor preset caches in %PROFILES%
|
||||||
|
rem Start clean so vendors that went away - and caches written by older tool
|
||||||
|
rem versions - don't linger next to the freshly generated ones.
|
||||||
|
del /q "%PROFILES%\*.opc" 2>nul
|
||||||
|
del /q "%PROFILES%\*.cache" 2>nul
|
||||||
|
"%TOOL%" --path "%PROFILES%" --log_level 2
|
||||||
|
if errorlevel 1 exit /b %errorlevel%
|
||||||
|
|
||||||
|
:next_target
|
||||||
|
if "%~1"=="" exit /b 0
|
||||||
|
call :ship "%~1"
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
shift
|
||||||
|
goto :next_target
|
||||||
|
|
||||||
|
:ship
|
||||||
|
set "TARGET=%~1"
|
||||||
|
if not exist "%TARGET%\" (
|
||||||
|
echo ERROR: profiles directory not found: %TARGET% 1>&2
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
for %%d in ("%TARGET%") do set "TARGET=%%~fd"
|
||||||
|
if /i not "%TARGET%"=="%PROFILES%" copy /y "%PROFILES%\*.opc" "%TARGET%\" >nul
|
||||||
|
|
||||||
|
set /a SHIPPED=0
|
||||||
|
set /a PRUNED=0
|
||||||
|
for %%c in ("%PROFILES%\*.opc") do (
|
||||||
|
set /a SHIPPED+=1
|
||||||
|
set "VENDOR=%%~nc"
|
||||||
|
if exist "%TARGET%\!VENDOR!.json" (
|
||||||
|
del /q "%TARGET%\!VENDOR!.json"
|
||||||
|
set /a PRUNED+=1
|
||||||
|
)
|
||||||
|
if exist "%TARGET%\!VENDOR!\" (
|
||||||
|
for /f %%n in ('dir /s /b "%TARGET%\!VENDOR!\*.json" 2^>nul ^| find /c /v ""') do set /a PRUNED+=%%n
|
||||||
|
del /s /q "%TARGET%\!VENDOR!\*.json" >nul 2>&1
|
||||||
|
rem Deepest first, so a directory the delete above emptied goes too; rd
|
||||||
|
rem refuses the ones still holding covers or meshes.
|
||||||
|
for /f "delims=" %%d in ('dir /s /b /ad "%TARGET%\!VENDOR!" 2^>nul ^| sort /r') do rd "%%d" 2>nul
|
||||||
|
)
|
||||||
|
)
|
||||||
|
echo %TARGET%: !SHIPPED! caches, dropped !PRUNED! preset JSONs
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:find_tool
|
||||||
|
set "TOOL="
|
||||||
|
for /f "delims=" %%f in ('dir /s /b /o-d "%BUILD_DIR%\generate_system_cache.exe" 2^>nul') do (
|
||||||
|
if not defined TOOL set "TOOL=%%f"
|
||||||
|
)
|
||||||
|
exit /b 0
|
||||||
Executable
+143
@@ -0,0 +1,143 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build the per-vendor system preset caches (one <vendor>.opc per vendor) by
|
||||||
|
# running the generate_system_cache dev tool against a profiles directory, and
|
||||||
|
# make every profiles directory named on the command line ship-ready: install
|
||||||
|
# the caches into it and delete the preset JSONs they replace, so a build ships
|
||||||
|
# one copy of its presets instead of two.
|
||||||
|
#
|
||||||
|
# ./scripts/build_preset_cache.sh # caches into resources/profiles
|
||||||
|
# ./scripts/build_preset_cache.sh -b build/arm64 # search this build tree for the tool
|
||||||
|
# ./scripts/build_preset_cache.sh <dir> [<dir> ...] # and ship into these profiles dirs
|
||||||
|
#
|
||||||
|
# Caches are generated into the source tree's resources/profiles, which is what
|
||||||
|
# every packaging step copies from. Shipping deletes, so it is a CI packaging
|
||||||
|
# step: pass packaged output directories, or the checkout of a build that is
|
||||||
|
# about to be packaged from it.
|
||||||
|
#
|
||||||
|
# A vendor's own <vendor>.json goes along with its preset JSONs: the cache
|
||||||
|
# carries the vendor profile and the version it was built at, so discovery,
|
||||||
|
# version checks and installing all read it there. A shipped vendor is its cache
|
||||||
|
# and nothing else. Only a vendor that has a cache is pruned, so an ungenerated
|
||||||
|
# vendor keeps its JSONs and is simply parsed at startup; non-vendor JSONs
|
||||||
|
# (blacklist.json) are left alone, as are the vendor directories themselves —
|
||||||
|
# thumbnails, covers and bed models still live there.
|
||||||
|
#
|
||||||
|
# -b <dir> build tree holding the tool
|
||||||
|
# (default: build/arm64, build/x86_64, or build — first that exists)
|
||||||
|
# -p <dir> profiles directory to generate caches into
|
||||||
|
# (default: <repo>/resources/profiles)
|
||||||
|
# -c <cfg> build config for multi-config generators
|
||||||
|
# (default: the config of the tool already in the build tree, else
|
||||||
|
# the build tree's CMAKE_BUILD_TYPE)
|
||||||
|
# -n skip the rebuild and run the tool already in the build tree
|
||||||
|
# -l <level> tool log level (default: 2)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_root="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
build_dir=""
|
||||||
|
profiles_dir=""
|
||||||
|
config=""
|
||||||
|
build_tool=1
|
||||||
|
log_level=2
|
||||||
|
|
||||||
|
while getopts "b:p:c:l:nh" opt; do
|
||||||
|
case $opt in
|
||||||
|
b) build_dir="$OPTARG" ;;
|
||||||
|
p) profiles_dir="$OPTARG" ;;
|
||||||
|
c) config="$OPTARG" ;;
|
||||||
|
n) build_tool=0 ;;
|
||||||
|
l) log_level="$OPTARG" ;;
|
||||||
|
h) sed -n '2,${/^#/!q;p;}' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||||
|
*) exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
shift $((OPTIND - 1))
|
||||||
|
|
||||||
|
if [ -z "$build_dir" ]; then
|
||||||
|
for candidate in "$repo_root/build/arm64" "$repo_root/build/x86_64" "$repo_root/build"; do
|
||||||
|
if [ -d "$candidate" ]; then build_dir="$candidate"; break; fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [ -z "$build_dir" ] || [ ! -d "$build_dir" ]; then
|
||||||
|
echo "ERROR: build tree not found (pass -b <build_dir>)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Newest match wins: multi-config trees keep one binary per config, and a stale
|
||||||
|
# one silently produces a stale cache layout.
|
||||||
|
find_tool() {
|
||||||
|
local best="" f
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -n "$f" ] || continue
|
||||||
|
if [ -z "$best" ] || [ "$f" -nt "$best" ]; then best="$f"; fi
|
||||||
|
done < <(find "$build_dir" -name generate_system_cache -type f 2>/dev/null)
|
||||||
|
printf '%s' "$best"
|
||||||
|
}
|
||||||
|
|
||||||
|
tool=$(find_tool)
|
||||||
|
if [ -z "$config" ]; then
|
||||||
|
case "$tool" in
|
||||||
|
*/Debug/*) config=Debug ;;
|
||||||
|
*/Release/*) config=Release ;;
|
||||||
|
*/RelWithDebInfo/*) config=RelWithDebInfo ;;
|
||||||
|
*/MinSizeRel/*) config=MinSizeRel ;;
|
||||||
|
*) config=$(sed -n 's/^CMAKE_BUILD_TYPE:[A-Z]*=\(.\+\)$/\1/p' "$build_dir/CMakeCache.txt" 2>/dev/null | head -1 || true) ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$build_tool" = 1 ]; then
|
||||||
|
echo "Building generate_system_cache in $build_dir${config:+ ($config)}"
|
||||||
|
build_args=(--build "$build_dir" --target generate_system_cache)
|
||||||
|
if [ -n "$config" ]; then build_args+=(--config "$config"); fi
|
||||||
|
if ! cmake "${build_args[@]}"; then
|
||||||
|
echo "ERROR: could not build generate_system_cache — configure the build tree with -DORCA_TOOLS=ON:" >&2
|
||||||
|
echo " cmake -S \"$repo_root\" -B \"$build_dir\" -DORCA_TOOLS=ON" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
tool=$(find_tool)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$tool" ]; then
|
||||||
|
echo "ERROR: generate_system_cache not found under $build_dir — build with -DORCA_TOOLS=ON" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$profiles_dir" ]; then profiles_dir="$repo_root/resources/profiles"; fi
|
||||||
|
if [ ! -d "$profiles_dir" ]; then
|
||||||
|
echo "ERROR: profiles directory not found: $profiles_dir" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
profiles_dir=$(cd "$profiles_dir" && pwd -P)
|
||||||
|
|
||||||
|
# Start clean so vendors that went away — and caches written by older tool
|
||||||
|
# versions — don't linger next to the freshly generated ones.
|
||||||
|
echo "Generating per-vendor preset caches in $profiles_dir"
|
||||||
|
rm -f "$profiles_dir"/*.opc "$profiles_dir"/*.cache
|
||||||
|
"$tool" --path "$profiles_dir" --log_level "$log_level"
|
||||||
|
|
||||||
|
for target in "$@"; do
|
||||||
|
resolved=$(cd "$target" 2>/dev/null && pwd -P) || {
|
||||||
|
echo "ERROR: profiles directory not found: $target" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if [ "$resolved" != "$profiles_dir" ]; then
|
||||||
|
cp "$profiles_dir"/*.opc "$resolved"/
|
||||||
|
fi
|
||||||
|
|
||||||
|
pruned=0
|
||||||
|
shipped=0
|
||||||
|
for cache in "$profiles_dir"/*.opc; do
|
||||||
|
vendor=$(basename "$cache" .opc)
|
||||||
|
shipped=$(( shipped + 1 ))
|
||||||
|
if [ -f "$resolved/$vendor.json" ]; then
|
||||||
|
rm -f "$resolved/$vendor.json"
|
||||||
|
pruned=$(( pruned + 1 ))
|
||||||
|
fi
|
||||||
|
[ -d "$resolved/$vendor" ] || continue
|
||||||
|
n=$(find "$resolved/$vendor" -name '*.json' | wc -l)
|
||||||
|
find "$resolved/$vendor" -name '*.json' -delete
|
||||||
|
find "$resolved/$vendor" -type d -empty -delete
|
||||||
|
pruned=$(( pruned + n ))
|
||||||
|
done
|
||||||
|
echo "$resolved: $shipped caches, dropped $pruned preset JSONs"
|
||||||
|
done
|
||||||
@@ -20,6 +20,16 @@ if (SLIC3R_ENC_CHECK)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ORCA_TOOLS)
|
||||||
|
set(_DEV_DEFS -DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x602 -DBOOST_SYSTEM_USE_UTF8)
|
||||||
|
|
||||||
|
# generate_system_cache: pre-generates per-vendor <vendor>.opc files under resources/profiles for CI bundling.
|
||||||
|
add_executable(generate_system_cache generate_system_cache.cpp)
|
||||||
|
target_link_libraries(generate_system_cache libslic3r boost_headeronly)
|
||||||
|
target_compile_definitions(generate_system_cache PRIVATE ${_DEV_DEFS})
|
||||||
|
|
||||||
|
endif()
|
||||||
|
|
||||||
# Function that adds source file encoding check to a target
|
# Function that adds source file encoding check to a target
|
||||||
# using the above encoding-check binary
|
# using the above encoding-check binary
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
|
#include "libslic3r/Preset.hpp"
|
||||||
|
#include "libslic3r/Utils.hpp"
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/program_options.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
namespace po = boost::program_options;
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
po::options_description desc("OrcaSlicer System Cache Generator\nUsage");
|
||||||
|
// clang-format off
|
||||||
|
desc.add_options()
|
||||||
|
("help,h", "Show help")
|
||||||
|
#ifdef __APPLE__
|
||||||
|
("path,p", po::value<std::string>()->default_value("../../../../../../../resources/profiles"), "Path to profiles directory")
|
||||||
|
#else
|
||||||
|
("path,p", po::value<std::string>()->default_value("../../../resources/profiles"), "Path to profiles directory")
|
||||||
|
#endif
|
||||||
|
("log_level,l", po::value<int>()->default_value(2), "Log level (0=trace, 2=info, 4=error)");
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
po::variables_map vm;
|
||||||
|
try {
|
||||||
|
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||||
|
if (vm.count("help")) { std::cout << desc << "\n"; return 0; }
|
||||||
|
po::notify(vm);
|
||||||
|
} catch (const po::error& e) {
|
||||||
|
std::cerr << "Error: " << e.what() << "\n" << desc << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string profiles_path = vm["path"].as<std::string>();
|
||||||
|
const int log_level = vm["log_level"].as<int>();
|
||||||
|
|
||||||
|
if (!fs::exists(profiles_path) || !fs::is_directory(profiles_path)) {
|
||||||
|
std::cerr << "Error: '" << profiles_path << "' is not a valid directory\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_logging_level(log_level);
|
||||||
|
set_data_dir(profiles_path);
|
||||||
|
set_resources_dir(fs::path(profiles_path).parent_path().make_preferred().string());
|
||||||
|
|
||||||
|
const fs::path user_dir = fs::path(data_dir()) / PRESET_USER_DIR;
|
||||||
|
if (!fs::exists(user_dir))
|
||||||
|
fs::create_directories(user_dir);
|
||||||
|
|
||||||
|
AppConfig app_config;
|
||||||
|
app_config.set("preset_folder", "default");
|
||||||
|
|
||||||
|
auto preset_bundle = std::make_unique<PresetBundle>();
|
||||||
|
preset_bundle->set_is_validation_mode(true);
|
||||||
|
preset_bundle->set_default_suppressed(true);
|
||||||
|
preset_bundle->set_generate_vendor_caches(true);
|
||||||
|
|
||||||
|
std::cout << "Loading system presets from: " << profiles_path << "\n";
|
||||||
|
|
||||||
|
try {
|
||||||
|
// In validation mode data_dir() is the profiles directory set above, so the
|
||||||
|
// loader writes each <vendor>.opc next to its <vendor>.json as it parses it.
|
||||||
|
preset_bundle->load_presets(app_config, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
std::cerr << "Failed to load presets: " << ex.what() << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cache_count = 0;
|
||||||
|
for (auto& entry : fs::directory_iterator(profiles_path))
|
||||||
|
if (boost::iends_with(entry.path().string(), ".opc"))
|
||||||
|
++ cache_count;
|
||||||
|
if (cache_count == 0) {
|
||||||
|
std::cerr << "No vendor cache files were generated under " << profiles_path << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::cout << "Generated " << cache_count << " vendor cache file(s) under " << profiles_path << "\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+14
-8
@@ -32,15 +32,13 @@ static void append_and_translate(ExPolygons &dst, const ExPolygons &src, const P
|
|||||||
for (; dst_idx < dst.size(); ++dst_idx)
|
for (; dst_idx < dst.size(); ++dst_idx)
|
||||||
dst[dst_idx].translate(instance_shift);
|
dst[dst_idx].translate(instance_shift);
|
||||||
}
|
}
|
||||||
// BBS: generate brim area by objs
|
// Orca: Translate the brim area into print coordinates and store it per instance.
|
||||||
static void append_and_translate(ExPolygons& dst, const ExPolygons& src,
|
static void append_and_translate(const ExPolygons& src, const PrintInstance& instance,
|
||||||
const PrintInstance& instance, size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
|
size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
|
||||||
ExPolygons srcShifted = src;
|
ExPolygons srcShifted = src;
|
||||||
Point instance_shift = instance.shift_without_plate_offset();
|
Point instance_shift = instance.shift_without_plate_offset();
|
||||||
for (size_t src_idx = 0; src_idx < srcShifted.size(); ++src_idx)
|
for (ExPolygon& expoly : srcShifted)
|
||||||
srcShifted[src_idx].translate(instance_shift);
|
expoly.translate(instance_shift);
|
||||||
srcShifted = diff_ex(srcShifted, dst);
|
|
||||||
//expolygons_append(dst, temp2);
|
|
||||||
expolygons_append(brimAreaMap[{ instance.print_object->id(), instance_idx }], std::move(srcShifted));
|
expolygons_append(brimAreaMap[{ instance.print_object->id(), instance_idx }], std::move(srcShifted));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,7 +570,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
|||||||
for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) {
|
for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) {
|
||||||
const PrintInstance& instance = object->instances()[instance_idx];
|
const PrintInstance& instance = object->instances()[instance_idx];
|
||||||
if (!brim_area_object.empty())
|
if (!brim_area_object.empty())
|
||||||
append_and_translate(brim_area, brim_area_object, instance, instance_idx, brimAreaMap);
|
append_and_translate(brim_area_object, instance, instance_idx, brimAreaMap);
|
||||||
append_and_translate(no_brim_area, no_brim_area_object, instance);
|
append_and_translate(no_brim_area, no_brim_area_object, instance);
|
||||||
append_and_translate(holes, holes_object, instance);
|
append_and_translate(holes, holes_object, instance);
|
||||||
append_and_translate(objectIslands, objectIsland, instance);
|
append_and_translate(objectIslands, objectIsland, instance);
|
||||||
@@ -875,6 +873,14 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
|||||||
ExPolygons islands_area_ex = outer_inner_brim_area(print,
|
ExPolygons islands_area_ex = outer_inner_brim_area(print,
|
||||||
float(flow.scaled_spacing()), brimAreaMap, objPrintVec, printExtruders);
|
float(flow.scaled_spacing()), brimAreaMap, objPrintVec, printExtruders);
|
||||||
|
|
||||||
|
if (!print.config().combine_brims) {
|
||||||
|
ExPolygons claimed_area;
|
||||||
|
for (auto& [_, areas] : brimAreaMap) {
|
||||||
|
areas = diff_ex(areas, claimed_area);
|
||||||
|
expolygons_append(claimed_area, areas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// BBS: Find boundingbox of the first layer
|
// BBS: Find boundingbox of the first layer
|
||||||
for (const ObjectID printObjID : print.print_object_ids()) {
|
for (const ObjectID printObjID : print.print_object_ids()) {
|
||||||
BoundingBox bbx;
|
BoundingBox bbx;
|
||||||
|
|||||||
@@ -248,6 +248,8 @@ set(lisbslic3r_sources
|
|||||||
GCode/Thumbnails.hpp
|
GCode/Thumbnails.hpp
|
||||||
GCode/ToolOrdering.cpp
|
GCode/ToolOrdering.cpp
|
||||||
GCode/ToolOrdering.hpp
|
GCode/ToolOrdering.hpp
|
||||||
|
GCode/OrderingStrategies.cpp
|
||||||
|
GCode/OrderingStrategies.hpp
|
||||||
GCode/WipeTower2.cpp
|
GCode/WipeTower2.cpp
|
||||||
GCode/WipeTower2.hpp
|
GCode/WipeTower2.hpp
|
||||||
GCode/WipeTower.cpp
|
GCode/WipeTower.cpp
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
#define slic3r_Config_hpp_
|
#define slic3r_Config_hpp_
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <algorithm>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
@@ -28,6 +27,9 @@
|
|||||||
|
|
||||||
#include <cereal/access.hpp>
|
#include <cereal/access.hpp>
|
||||||
#include <cereal/types/base_class.hpp>
|
#include <cereal/types/base_class.hpp>
|
||||||
|
// The serialize() members below archive ConfigOption hierarchies through
|
||||||
|
// cereal::base_class, whose registration machinery lives in polymorphic.hpp.
|
||||||
|
#include <cereal/types/polymorphic.hpp>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
struct FloatOrPercent
|
struct FloatOrPercent
|
||||||
@@ -781,14 +783,10 @@ public:
|
|||||||
this->values[i] = rhs_vec->values[i];
|
this->values[i] = rhs_vec->values[i];
|
||||||
modified = true;
|
modified = true;
|
||||||
} else {
|
} else {
|
||||||
// Orca: a negative slot (failed variant lookup) must not silently collapse the
|
if ((i < default_index.size()) && (default_index[i] < default_value.size()))
|
||||||
// whole array to the first slot's value — the int-vs-size_t comparison used to
|
|
||||||
// promote -1 past the bounds check. Keep the slot's own value (get_at-style
|
|
||||||
// clamp) when no valid index is available.
|
|
||||||
if ((i < default_index.size()) && (default_index[i] >= 0) && (size_t(default_index[i]) < default_value.size()))
|
|
||||||
this->values[i] = default_value[default_index[i]];
|
this->values[i] = default_value[default_index[i]];
|
||||||
else
|
else
|
||||||
this->values[i] = default_value[std::min(i, default_value.size() - 1)];
|
this->values[i] = default_value[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return modified;
|
return modified;
|
||||||
@@ -2111,11 +2109,6 @@ public:
|
|||||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||||
this->value = rhs->getInt();
|
this->value = rhs->getInt();
|
||||||
// Orca: options embedded in a StaticPrintConfig are constructed without a keys_map;
|
|
||||||
// adopt the source's so a later serialize() can emit names.
|
|
||||||
if (this->keys_map == nullptr)
|
|
||||||
if (auto rhs_generic = dynamic_cast<const ConfigOptionEnumGeneric *>(rhs))
|
|
||||||
this->keys_map = rhs_generic->keys_map;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string serialize() const override
|
std::string serialize() const override
|
||||||
@@ -2172,12 +2165,7 @@ public:
|
|||||||
if (rhs->type() != this->type())
|
if (rhs->type() != this->type())
|
||||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||||
// rhs could be of the following type: ConfigOptionEnumsGeneric
|
// rhs could be of the following type: ConfigOptionEnumsGeneric
|
||||||
auto rhs_enums = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs);
|
this->values = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs)->values;
|
||||||
this->values = rhs_enums->values;
|
|
||||||
// Orca: options embedded in a StaticPrintConfig are constructed without a keys_map;
|
|
||||||
// adopt the source's so a later serialize() emits names instead of empty tokens.
|
|
||||||
if (this->keys_map == nullptr)
|
|
||||||
this->keys_map = rhs_enums->keys_map;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string serialize() const override
|
std::string serialize() const override
|
||||||
|
|||||||
+218
-163
@@ -13,8 +13,8 @@
|
|||||||
#include "GCode/PrintExtents.hpp"
|
#include "GCode/PrintExtents.hpp"
|
||||||
#include "GCode/Thumbnails.hpp"
|
#include "GCode/Thumbnails.hpp"
|
||||||
#include "GCode/WipeTower.hpp"
|
#include "GCode/WipeTower.hpp"
|
||||||
#include "GCode/WipeTower2.hpp"
|
|
||||||
#include "ShortestPath.hpp"
|
#include "ShortestPath.hpp"
|
||||||
|
#include "GCode/OrderingStrategies.hpp"
|
||||||
#include "Print.hpp"
|
#include "Print.hpp"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
@@ -889,65 +889,6 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type2 tower-local point -> bed frame. The rib-wall offset is tower-local, so it
|
|
||||||
// rotates with the tower (unlike the BBL tower in append_tcr, which never rotates).
|
|
||||||
Vec2f WipeTowerIntegration::transform_wt2_pt(const Vec2f &pt) const
|
|
||||||
{
|
|
||||||
const float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
|
|
||||||
return Eigen::Rotation2Df(alpha) * (pt + m_rib_offset) + m_wipe_tower_pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printable-area bounds for tower-approach routing, in object coordinates (shared by
|
|
||||||
// the BBL avoid-perimeter path in append_tcr and the Type2 skip-points router).
|
|
||||||
// Multi-nozzle: clamp the travel bounds to the region every extruder can reach
|
|
||||||
// (get_extruder_shared_printable_polygon) instead of the full bed. Gated on the
|
|
||||||
// multi-nozzle predicate so every existing single/dual printer keeps the historic
|
|
||||||
// full-printable_area routing byte-identical.
|
|
||||||
BoundingBox WipeTowerIntegration::printer_travel_bounds(GCode &gcodegen) const
|
|
||||||
{
|
|
||||||
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
|
||||||
BoundingBox printer_bbx;
|
|
||||||
if (is_multi_nozzle_printer(gcodegen.m_config)) {
|
|
||||||
printer_bbx = get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon());
|
|
||||||
printer_bbx.min = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.min) + plate_origin_2d);
|
|
||||||
printer_bbx.max = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.max) + plate_origin_2d);
|
|
||||||
} else {
|
|
||||||
Points bed_points;
|
|
||||||
for (const auto& p : gcodegen.m_config.printable_area.values)
|
|
||||||
bed_points.push_back(wipe_tower_point_to_object_point(gcodegen, p.cast<float>() + plate_origin_2d));
|
|
||||||
printer_bbx = BoundingBox(bed_points);
|
|
||||||
}
|
|
||||||
return printer_bbx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// With skip points enabled the Type2 tower wall has an opening at each toolchange's
|
|
||||||
// entry (tcr.start_pos): route the approach around the tower's bounding box so the
|
|
||||||
// nozzle enters through that opening instead of dragging across the printed wall
|
|
||||||
// (append_tcr parity). Emits only the waypoints leading up to the opening — the
|
|
||||||
// caller still travels to start_wipe_pos itself. Returns an empty string when the
|
|
||||||
// gap wall is off (option off or cone wall) or the approach already starts inside
|
|
||||||
// the tower: such hops never cross the wall and must stay direct.
|
|
||||||
std::string WipeTowerIntegration::travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const
|
|
||||||
{
|
|
||||||
if (!WipeTower2::use_gap_wall(gcodegen.m_config))
|
|
||||||
return {};
|
|
||||||
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
|
||||||
// Transform the tower-local bbx corners exactly like the tcr points; a rotated
|
|
||||||
// tower gets a conservative axis-aligned envelope.
|
|
||||||
Polygon avoid_points = scaled(m_wipe_tower_bbx).polygon();
|
|
||||||
for (auto& p : avoid_points.points)
|
|
||||||
p = wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(unscale(p).cast<float>()) + plate_origin_2d);
|
|
||||||
BoundingBox avoid_bbx(avoid_points.points);
|
|
||||||
if (avoid_bbx.contains(route_start))
|
|
||||||
return {};
|
|
||||||
Polyline travel_polyline = generate_path_to_wipe_tower(route_start, start_wipe_pos, avoid_bbx, printer_travel_bounds(gcodegen));
|
|
||||||
std::string gcode;
|
|
||||||
// The polyline's last point is start_wipe_pos itself — emitted by the caller.
|
|
||||||
for (size_t i = 0; i + 1 < travel_polyline.points.size(); ++i)
|
|
||||||
gcode += gcodegen.travel_to(travel_polyline.points[i], erMixed, "Travel to a Wipe Tower");
|
|
||||||
return gcode;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_filament_id, double z) const
|
std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_filament_id, double z) const
|
||||||
{
|
{
|
||||||
if (new_filament_id != -1 && new_filament_id != tcr.new_tool)
|
if (new_filament_id != -1 && new_filament_id != tcr.new_tool)
|
||||||
@@ -1058,7 +999,6 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
std::string change_filament_gcode = gcodegen.config().change_filament_gcode.value;
|
std::string change_filament_gcode = gcodegen.config().change_filament_gcode.value;
|
||||||
|
|
||||||
bool is_used_travel_avoid_perimeter = gcodegen.m_config.prime_tower_skip_points.value;
|
bool is_used_travel_avoid_perimeter = gcodegen.m_config.prime_tower_skip_points.value;
|
||||||
if (is_nozzle_change && !tcr.nozzle_change_result.is_extruder_change) is_used_travel_avoid_perimeter = false;
|
|
||||||
|
|
||||||
// add nozzle change gcode into change filament gcode
|
// add nozzle change gcode into change filament gcode
|
||||||
std::string nozzle_change_gcode_trans;
|
std::string nozzle_change_gcode_trans;
|
||||||
@@ -1321,7 +1261,24 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
Vec2f gcode_last_pos2d{gcode_last_pos[0], gcode_last_pos[1]};
|
Vec2f gcode_last_pos2d{gcode_last_pos[0], gcode_last_pos[1]};
|
||||||
Point gcode_last_pos2d_object = gcodegen.gcode_to_point(gcode_last_pos2d.cast<double>() + plate_origin_2d.cast<double>());
|
Point gcode_last_pos2d_object = gcodegen.gcode_to_point(gcode_last_pos2d.cast<double>() + plate_origin_2d.cast<double>());
|
||||||
Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d);
|
Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d);
|
||||||
BoundingBox avoid_bbx, printer_bbx = printer_travel_bounds(gcodegen);
|
BoundingBox avoid_bbx, printer_bbx;
|
||||||
|
{
|
||||||
|
// set printer_bbx
|
||||||
|
// Multi-nozzle: clamp the avoid-perimeter travel bounds to the region every
|
||||||
|
// extruder can reach (get_extruder_shared_printable_polygon) instead of the full
|
||||||
|
// bed. Gated on the multi-nozzle predicate so H2D and every existing single/dual
|
||||||
|
// printer keep the historic full-printable_area routing byte-identical.
|
||||||
|
if (is_multi_nozzle_printer(gcodegen.m_config)) {
|
||||||
|
printer_bbx = get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon());
|
||||||
|
printer_bbx.min = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.min) + plate_origin_2d);
|
||||||
|
printer_bbx.max = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.max) + plate_origin_2d);
|
||||||
|
} else {
|
||||||
|
Pointfs bed_pointsf = gcodegen.m_config.printable_area.values;
|
||||||
|
Points bed_points;
|
||||||
|
for (auto p : bed_pointsf) { bed_points.push_back(wipe_tower_point_to_object_point(gcodegen, p.cast<float>() + plate_origin_2d)); }
|
||||||
|
printer_bbx = BoundingBox(bed_points);
|
||||||
|
}
|
||||||
|
}
|
||||||
{
|
{
|
||||||
// set avoid_bbx
|
// set avoid_bbx
|
||||||
avoid_bbx = scaled(m_wipe_tower_bbx);
|
avoid_bbx = scaled(m_wipe_tower_bbx);
|
||||||
@@ -1351,23 +1308,20 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do unretract after setting current extruder_id
|
// do unretract after setting current extruder_id
|
||||||
// BBS pattern: the wipe tower shifts the toolchange start position outward for the
|
// PETG filaments on a device with a filament switcher get a small (2 mm) pre-extrusion
|
||||||
// tower-interface (contact) pre-extrusion and for the PETG-with-filament-switcher case;
|
// before the tool change. has_filament_switcher is a develop-only key read defensively from the
|
||||||
// the pre-extrusion material itself is laid down here as extra unretract on the approach.
|
// full config (Orca does not carry it as a static PrintConfig member — same convention as
|
||||||
// has_filament_switcher is a develop-only key read defensively from the full config (Orca
|
// enable_filament_dynamic_map); no shipping profile sets it (grep resources/profiles = 0), so
|
||||||
// does not carry it as a static PrintConfig member — same convention as
|
// is_petg_pre_extrusion is always false -> extra_unretract stays 0 -> byte-identical to the plain
|
||||||
// enable_filament_dynamic_map); no shipping profile sets it, so is_petg_pre_extrusion is
|
// unretract() fleet-wide. The tower-interface contact pre-extrusion length (the
|
||||||
// always false fleet-wide.
|
// is_contact_pre_extrusion branch) is NOT applied here; it is only computed as the guard used to
|
||||||
|
// give the contact path priority over PETG.
|
||||||
const ConfigOptionBool* has_filament_switcher_opt = gcodegen.m_print->full_print_config().option<ConfigOptionBool>("has_filament_switcher");
|
const ConfigOptionBool* has_filament_switcher_opt = gcodegen.m_print->full_print_config().option<ConfigOptionBool>("has_filament_switcher");
|
||||||
bool is_contact_pre_extrusion = tcr.is_contact && gcodegen.m_config.enable_tower_interface_features;
|
bool is_contact_pre_extrusion = tcr.is_contact && gcodegen.m_config.enable_tower_interface_features;
|
||||||
bool is_petg_pre_extrusion = !is_contact_pre_extrusion
|
bool is_petg_pre_extrusion = !is_contact_pre_extrusion
|
||||||
&& gcodegen.config().filament_type.get_at(tcr.new_tool) == "PETG"
|
&& gcodegen.config().filament_type.get_at(tcr.new_tool) == "PETG"
|
||||||
&& has_filament_switcher_opt && has_filament_switcher_opt->value;
|
&& has_filament_switcher_opt && has_filament_switcher_opt->value;
|
||||||
float extra_unretract = 0.f;
|
float extra_unretract = is_petg_pre_extrusion ? 2.f : 0.f;
|
||||||
if (is_contact_pre_extrusion)
|
|
||||||
extra_unretract = gcodegen.m_config.filament_tower_interface_pre_extrusion_length.get_at(tcr.new_tool);
|
|
||||||
else if (is_petg_pre_extrusion)
|
|
||||||
extra_unretract = 2.f;
|
|
||||||
std::string toolchange_unretract_str = (extra_unretract > 0.f) ? gcodegen.unretract(extra_unretract) : gcodegen.unretract();
|
std::string toolchange_unretract_str = (extra_unretract > 0.f) ? gcodegen.unretract(extra_unretract) : gcodegen.unretract();
|
||||||
check_add_eol(toolchange_unretract_str);
|
check_add_eol(toolchange_unretract_str);
|
||||||
|
|
||||||
@@ -1465,16 +1419,20 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
|
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
|
||||||
float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
|
float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
|
||||||
|
|
||||||
// Priming lines are absolute bed moves; everything else is tower-local
|
auto transform_wt_pt = [&alpha, this](const Vec2f &pt) -> Vec2f {
|
||||||
// (transform_wt2_pt).
|
Vec2f out = Eigen::Rotation2Df(alpha) * pt;
|
||||||
|
out += m_wipe_tower_pos;
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
Vec2f start_pos = tcr.start_pos;
|
Vec2f start_pos = tcr.start_pos;
|
||||||
Vec2f end_pos = tcr.end_pos;
|
Vec2f end_pos = tcr.end_pos;
|
||||||
if (!tcr.priming) {
|
if (!tcr.priming) {
|
||||||
start_pos = transform_wt2_pt(start_pos);
|
start_pos = transform_wt_pt(start_pos);
|
||||||
end_pos = transform_wt2_pt(end_pos);
|
end_pos = transform_wt_pt(end_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : Vec2f(m_wipe_tower_pos + Eigen::Rotation2Df(alpha) * m_rib_offset);
|
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
|
||||||
float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
|
float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
|
||||||
Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
||||||
|
|
||||||
@@ -1504,22 +1462,16 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
|| is_ramming
|
|| is_ramming
|
||||||
|| tool_change_on_wipe_tower);
|
|| tool_change_on_wipe_tower);
|
||||||
|
|
||||||
const Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d);
|
if (should_travel_to_tower || gcodegen.m_need_change_layer_lift_z) {
|
||||||
const bool travel_to_tower_now = should_travel_to_tower || gcodegen.m_need_change_layer_lift_z;
|
|
||||||
if (travel_to_tower_now) {
|
|
||||||
// FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges,
|
// FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges,
|
||||||
// then we could simplify the condition and make it more readable.
|
// then we could simplify the condition and make it more readable.
|
||||||
gcode += gcodegen.retract();
|
gcode += gcodegen.retract();
|
||||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||||
if (!tcr.priming && gcodegen.last_pos_defined())
|
gcode += gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed, "Travel to a Wipe Tower");
|
||||||
gcode += travel_to_tower_gap(gcodegen, gcodegen.last_pos(), start_wipe_pos);
|
|
||||||
gcode += gcodegen.travel_to(start_wipe_pos, erMixed, "Travel to a Wipe Tower");
|
|
||||||
gcode += gcodegen.unretract();
|
gcode += gcodegen.unretract();
|
||||||
} else {
|
} else {
|
||||||
// When this is multiextruder printer without any ramming, we can just change
|
// When this is multiextruder printer without any ramming, we can just change
|
||||||
// the tool without travelling to the tower. The tower entry travel then lives
|
// the tool without travelling to the tower.
|
||||||
// inside the tcr gcode; with skip points on it is rerouted below, once the
|
|
||||||
// toolchange gcode (and the head position it ends at) is known.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (will_go_down) {
|
if (will_go_down) {
|
||||||
@@ -1542,36 +1494,6 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
toolchange_temp_override = interface_temp;
|
toolchange_temp_override = interface_temp;
|
||||||
}
|
}
|
||||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override); // TODO: toolchange_z vs print_z
|
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override); // TODO: toolchange_z vs print_z
|
||||||
if (!travel_to_tower_now && !tcr.priming && WipeTower2::use_gap_wall(gcodegen.m_config)) {
|
|
||||||
// The tool changed in place (multi-tool printer without ramming), so the
|
|
||||||
// tower entry is the tcr's own positioning move — a straight line across
|
|
||||||
// the printed wall. Route it around the tower and in through the wall
|
|
||||||
// opening instead, riding at the end of the change_filament_gcode
|
|
||||||
// substitution so the generator's positioning move degrades to a
|
|
||||||
// zero-length one (append_tcr parity: travel after the filament change,
|
|
||||||
// retracted, with the new filament).
|
|
||||||
Vec3f last_gcode_pos = gcodegen.writer().get_position().cast<float>();
|
|
||||||
Point route_start;
|
|
||||||
bool have_start = false;
|
|
||||||
if (GCodeProcessor::get_last_position_from_gcode(toolchange_gcode_str, last_gcode_pos)) {
|
|
||||||
// A custom change_filament_gcode may have moved the head (tool docks
|
|
||||||
// etc.); recover the real position from the emitted gcode.
|
|
||||||
route_start = gcodegen.gcode_to_point(Vec2d(last_gcode_pos.x(), last_gcode_pos.y()) + plate_origin_2d.cast<double>());
|
|
||||||
have_start = true;
|
|
||||||
} else if (gcodegen.last_pos_defined()) {
|
|
||||||
route_start = gcodegen.last_pos();
|
|
||||||
have_start = true;
|
|
||||||
}
|
|
||||||
if (have_start) {
|
|
||||||
gcodegen.set_last_pos(route_start);
|
|
||||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
|
||||||
std::string travel = travel_to_tower_gap(gcodegen, route_start, start_wipe_pos);
|
|
||||||
travel += gcodegen.travel_to(start_wipe_pos, erMixed, "Travel to a Wipe Tower");
|
|
||||||
check_add_eol(travel);
|
|
||||||
toolchange_gcode_str += travel;
|
|
||||||
gcodegen.set_last_pos(start_wipe_pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (gcodegen.config().enable_prime_tower) {
|
if (gcodegen.config().enable_prime_tower) {
|
||||||
deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true);
|
deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true);
|
||||||
Vec3d position{gcodegen.writer().get_position()};
|
Vec3d position{gcodegen.writer().get_position()};
|
||||||
@@ -1757,7 +1679,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
// Prepare a future wipe.
|
// Prepare a future wipe.
|
||||||
gcodegen.m_wipe.reset_path();
|
gcodegen.m_wipe.reset_path();
|
||||||
for (const Vec2f& wipe_pt : tcr.wipe_path)
|
for (const Vec2f& wipe_pt : tcr.wipe_path)
|
||||||
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(wipe_pt) + plate_origin_2d));
|
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt) + plate_origin_2d));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let the planner know we are traveling between objects.
|
// Let the planner know we are traveling between objects.
|
||||||
@@ -2883,6 +2805,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
m_role_based_fan_marker_layer.fill(-1);
|
m_role_based_fan_marker_layer.fill(-1);
|
||||||
|
|
||||||
m_fan_mover.release();
|
m_fan_mover.release();
|
||||||
|
m_ordering_cache.clear();
|
||||||
|
|
||||||
m_writer.set_is_bbl_machine(is_bbl_printers);
|
m_writer.set_is_bbl_machine(is_bbl_printers);
|
||||||
|
|
||||||
@@ -3203,11 +3126,20 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
// In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z.
|
// In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z.
|
||||||
// Therefore initialize the printing extruders from there.
|
// Therefore initialize the printing extruders from there.
|
||||||
this->set_extruders(tool_ordering.all_extruders());
|
this->set_extruders(tool_ordering.all_extruders());
|
||||||
print_object_instances_ordering =
|
print_object_instances_ordering =
|
||||||
// By default, order object instances using a nearest neighbor search.
|
// By default, order object instances using nearest-neighbor chaining plus
|
||||||
print.config().print_order == PrintOrder::Default ? chain_print_object_instances(print)
|
// 2-opt and crossing-removal post-processing.
|
||||||
|
(print.config().print_order == PrintOrder::Default ? chain_print_object_instances(print)
|
||||||
|
// Snake: serpentine row traversal + 2-opt
|
||||||
|
: (print.config().print_order == PrintOrder::Snake ? chain_print_object_instances_snake(print)
|
||||||
|
// Best of all: run every strategy, pick the shortest total path
|
||||||
|
: (print.config().print_order == PrintOrder::BestOfStrategies ? chain_print_object_instances_best_of(print)
|
||||||
// Otherwise same order as the object list
|
// Otherwise same order as the object list
|
||||||
: sort_object_instances_by_model_order(print);
|
: sort_object_instances_by_model_order(print))));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
if (initial_extruder_id == (unsigned int)-1) {
|
if (initial_extruder_id == (unsigned int)-1) {
|
||||||
// Nothing to print!
|
// Nothing to print!
|
||||||
@@ -5598,7 +5530,9 @@ LayerResult GCode::process_layer(
|
|||||||
//Calibration Layer-specific GCode
|
//Calibration Layer-specific GCode
|
||||||
switch (print.calib_mode()) {
|
switch (print.calib_mode()) {
|
||||||
case CalibMode::Calib_PA_Tower: {
|
case CalibMode::Calib_PA_Tower: {
|
||||||
gcode += writer().set_pressure_advance(print.calib_params().start + static_cast<int>(print_z) * print.calib_params().step);
|
gcode += writer().set_pressure_advance(this->interpolate_value_across_layers(static_cast<float>(print.calib_params().start),
|
||||||
|
static_cast<float>(print.calib_params().end),
|
||||||
|
static_cast<float>(print.calib_params().step)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CalibMode::Calib_Temp_Tower: {
|
case CalibMode::Calib_Temp_Tower: {
|
||||||
@@ -5606,7 +5540,12 @@ LayerResult GCode::process_layer(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CalibMode::Calib_VFA_Tower: {
|
case CalibMode::Calib_VFA_Tower: {
|
||||||
auto _speed = print.calib_params().start + std::floor(print_z / 5.0) * print.calib_params().step;
|
// Step the outer wall speed from start to end across the tower's layers. Plater::calib_VFA sizes the
|
||||||
|
// geometry so each speed step spans one visual block (a fixed number of layers), so the layer-based
|
||||||
|
// stepping stays aligned with the blocks regardless of nozzle size / layer height.
|
||||||
|
float _speed = this->interpolate_value_across_layers(static_cast<float>(print.calib_params().start),
|
||||||
|
static_cast<float>(print.calib_params().end),
|
||||||
|
static_cast<float>(print.calib_params().step));
|
||||||
m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloatsNullable({std::round(_speed)}));
|
m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloatsNullable({std::round(_speed)}));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -6038,41 +5977,128 @@ LayerResult GCode::process_layer(
|
|||||||
if (m_farthest_point_timelapse.enabled)
|
if (m_farthest_point_timelapse.enabled)
|
||||||
compute_farthest_point(layers, most_used_extruder, support_filaments);
|
compute_farthest_point(layers, most_used_extruder, support_filaments);
|
||||||
|
|
||||||
std::map<unsigned int, std::vector<InstanceToPrint>> filament_to_print_instances;
|
// Per filament: instances to print, and the visit sequence over them. Island-level ordering
|
||||||
|
// may visit an instance more than once per layer; otherwise one visit per instance.
|
||||||
|
std::map<unsigned int, std::pair<std::vector<InstanceToPrint>, std::vector<InstanceVisit>>> filament_to_print_instances;
|
||||||
{
|
{
|
||||||
|
// Order individual islands rather than whole instances. Off for by-object sequencing,
|
||||||
|
// sequential printing, and the explicit AsObjectList order, which tour whole instances.
|
||||||
|
const bool island_level_ordering = print.config().print_sequence != PrintSequence::ByObject &&
|
||||||
|
single_object_instance_idx == size_t(-1) &&
|
||||||
|
print.config().print_order != PrintOrder::AsObjectList;
|
||||||
for (unsigned int filament_id : layer_tools.extruders) {
|
for (unsigned int filament_id : layer_tools.extruders) {
|
||||||
auto objects_by_extruder_it = by_extruder.find(filament_id);
|
auto objects_by_extruder_it = by_extruder.find(filament_id);
|
||||||
if (objects_by_extruder_it == by_extruder.end()) continue;
|
if (objects_by_extruder_it == by_extruder.end()) continue;
|
||||||
|
|
||||||
|
auto &filament_plan = filament_to_print_instances[filament_id];
|
||||||
|
|
||||||
|
if (!island_level_ordering) {
|
||||||
|
// One visit per instance, printing all of its islands.
|
||||||
|
filament_plan.first = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
|
||||||
|
filament_plan.second.reserve(filament_plan.first.size());
|
||||||
|
for (size_t i = 0; i < filament_plan.first.size(); ++i)
|
||||||
|
filament_plan.second.push_back({i, {}, true});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int plate_idx = print.get_plate_index();
|
int plate_idx = print.get_plate_index();
|
||||||
Point wt_pos(print.config().wipe_tower_x.get_at(plate_idx), print.config().wipe_tower_y.get_at(plate_idx));
|
Point wt_pos(print.config().wipe_tower_x.get_at(plate_idx), print.config().wipe_tower_y.get_at(plate_idx));
|
||||||
|
|
||||||
|
// Build the instances and one tour node per non-empty island (a single node for
|
||||||
|
// instances without chainable islands). Positions quantized to 1 mm so small
|
||||||
|
// centroid drift between layers still hits the tour cache below.
|
||||||
std::vector<GCode::ObjectByExtruder> &objects_by_extruder = objects_by_extruder_it->second;
|
std::vector<GCode::ObjectByExtruder> &objects_by_extruder = objects_by_extruder_it->second;
|
||||||
std::vector<const PrintObject *> print_objects;
|
std::vector<InstanceToPrint> &instances = filament_plan.first;
|
||||||
for (int obj_idx = 0; obj_idx < objects_by_extruder.size(); obj_idx++) {
|
std::vector<IslandOrderNode> nodes;
|
||||||
auto &object_by_extruder = objects_by_extruder[obj_idx];
|
std::vector<size_t> node_instances;
|
||||||
|
auto quantize_to_mm = [](const Point &pt) -> Point {
|
||||||
|
const coord_t grid = coord_t(scale_(1.));
|
||||||
|
// Round to the nearest 1 mm symmetrically (integer division truncates toward
|
||||||
|
// zero, which would make the bucket straddling the origin twice as wide).
|
||||||
|
auto q = [grid](coord_t v) -> coord_t {
|
||||||
|
return ((v >= 0 ? v + grid / 2 : v - grid / 2) / grid) * grid;
|
||||||
|
};
|
||||||
|
return Point(q(pt.x()), q(pt.y()));
|
||||||
|
};
|
||||||
|
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
|
||||||
if (object_by_extruder.islands.empty() && (object_by_extruder.support == nullptr || object_by_extruder.support->empty())) continue;
|
if (object_by_extruder.islands.empty() && (object_by_extruder.support == nullptr || object_by_extruder.support->empty())) continue;
|
||||||
|
|
||||||
print_objects.push_back(print.get_object(obj_idx));
|
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
|
||||||
|
const PrintObject *print_object = layers[layer_id].original_object;
|
||||||
|
if (print_object == nullptr)
|
||||||
|
continue;
|
||||||
|
const Layer *obj_layer = layers[layer_id].object_layer;
|
||||||
|
std::vector<ObjectByExtruder::Island> &islands = object_by_extruder.islands;
|
||||||
|
const bool islands_chainable = obj_layer != nullptr && islands.size() == obj_layer->lslices.size() + 1;
|
||||||
|
for (size_t instance_id = 0; instance_id < print_object->instances().size(); ++instance_id) {
|
||||||
|
const size_t instance_idx = instances.size();
|
||||||
|
instances.emplace_back(object_by_extruder, layer_id, *print_object, instance_id,
|
||||||
|
print_object->instances()[instance_id].model_instance->get_labeled_id());
|
||||||
|
const Point &shift = print_object->instances()[instance_id].shift;
|
||||||
|
const size_t first_node = nodes.size();
|
||||||
|
if (islands_chainable)
|
||||||
|
for (size_t i = 0; i + 1 < islands.size(); ++i)
|
||||||
|
if (!islands[i].by_region.empty()) {
|
||||||
|
nodes.push_back({print_object->id(), instance_id, i,
|
||||||
|
quantize_to_mm(obj_layer->lslices[i].contour.centroid() + shift)});
|
||||||
|
node_instances.emplace_back(instance_idx);
|
||||||
|
}
|
||||||
|
if (nodes.size() == first_node) {
|
||||||
|
// No chainable islands: tour the whole instance as one stop.
|
||||||
|
nodes.push_back({print_object->id(), instance_id, size_t(-1), quantize_to_mm(shift)});
|
||||||
|
node_instances.emplace_back(instance_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const PrintInstance *> new_ordering = chain_print_object_instances(print_objects, &wt_pos);
|
// Reuse the cached tour while this filament's island layout is unchanged.
|
||||||
std::reverse(new_ordering.begin(), new_ordering.end());
|
auto &cache_entry = m_ordering_cache[filament_id];
|
||||||
|
if (!(cache_entry.first == nodes)) {
|
||||||
|
cache_entry.first = nodes;
|
||||||
|
Points node_points;
|
||||||
|
node_points.reserve(nodes.size());
|
||||||
|
for (const IslandOrderNode &node : nodes)
|
||||||
|
node_points.emplace_back(node.pos);
|
||||||
|
std::vector<size_t> tour = order_points_with_strategy(node_points, print.config().print_order, &wt_pos);
|
||||||
|
// Chained starting near the wipe tower, reversed so the layer ends near it.
|
||||||
|
std::reverse(tour.begin(), tour.end());
|
||||||
|
|
||||||
if (print.config().print_sequence == PrintSequence::ByObject) {
|
// Group consecutive tour stops of the same instance into visits.
|
||||||
filament_to_print_instances[filament_id] = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
|
std::vector<InstanceVisit> visits;
|
||||||
} else {
|
std::vector<bool> instance_seen(instances.size(), false);
|
||||||
|
std::vector<int> last_visit_of_instance(instances.size(), -1);
|
||||||
// PrintSequence::ByLayer to use global ordering ( per object ordering ) if intra-layer order PrintOrder::AsObjectList is specified while keeping behaviour of PrintSequence::ByLayer
|
for (size_t node_idx : tour) {
|
||||||
const std::vector<const PrintInstance*>* ordering_for_filament = (print.config().print_order == PrintOrder::AsObjectList && ordering != nullptr) ? ordering: &new_ordering;
|
const size_t instance_idx = node_instances[node_idx];
|
||||||
filament_to_print_instances[filament_id] = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering_for_filament, single_object_instance_idx);
|
if (visits.empty() || visits.back().instance_idx != instance_idx) {
|
||||||
|
visits.push_back({instance_idx, {}, !instance_seen[instance_idx]});
|
||||||
|
instance_seen[instance_idx] = true;
|
||||||
|
}
|
||||||
|
if (nodes[node_idx].island_idx != size_t(-1))
|
||||||
|
visits.back().islands.emplace_back(nodes[node_idx].island_idx);
|
||||||
|
last_visit_of_instance[instance_idx] = int(visits.size()) - 1;
|
||||||
|
}
|
||||||
|
// The trailing catch-all island has no geometry to chain by; append it to the
|
||||||
|
// instance's last visit.
|
||||||
|
for (size_t i = 0; i < instances.size(); ++i) {
|
||||||
|
if (last_visit_of_instance[i] < 0)
|
||||||
|
continue;
|
||||||
|
InstanceVisit &last_visit = visits[size_t(last_visit_of_instance[i])];
|
||||||
|
if (last_visit.islands.empty())
|
||||||
|
// A visit without explicit islands already prints everything.
|
||||||
|
continue;
|
||||||
|
std::vector<ObjectByExtruder::Island> &islands = instances[i].object_by_extruder.islands;
|
||||||
|
if (!islands.back().by_region.empty())
|
||||||
|
last_visit.islands.emplace_back(islands.size() - 1);
|
||||||
|
}
|
||||||
|
cache_entry.second = std::move(visits);
|
||||||
}
|
}
|
||||||
|
filament_plan.second = cache_entry.second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<size_t> layer_object_label_ids;
|
std::set<size_t> layer_object_label_ids;
|
||||||
for (auto iter = filament_to_print_instances.begin(); iter != filament_to_print_instances.end(); ++iter) {
|
for (auto iter = filament_to_print_instances.begin(); iter != filament_to_print_instances.end(); ++iter) {
|
||||||
for (const InstanceToPrint &instance : iter->second) {
|
for (const InstanceToPrint &instance : iter->second.first) {
|
||||||
layer_object_label_ids.insert(instance.label_object_id);
|
layer_object_label_ids.insert(instance.label_object_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6142,7 +6168,7 @@ LayerResult GCode::process_layer(
|
|||||||
|
|
||||||
if (print.config().print_sequence == PrintSequence::ByLayer && m_enable_exclude_object && print.config().support_object_skip_flush.value) {
|
if (print.config().print_sequence == PrintSequence::ByLayer && m_enable_exclude_object && print.config().support_object_skip_flush.value) {
|
||||||
std::vector<size_t> filament_instances_id;
|
std::vector<size_t> filament_instances_id;
|
||||||
for (InstanceToPrint &instance : filament_to_print_instances[extruder_id]) filament_instances_id.emplace_back(instance.label_object_id);
|
for (InstanceToPrint &instance : filament_to_print_instances[extruder_id].first) filament_instances_id.emplace_back(instance.label_object_id);
|
||||||
m_filament_instances_code = _encode_label_ids_to_base64(filament_instances_id);
|
m_filament_instances_code = _encode_label_ids_to_base64(filament_instances_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6223,7 +6249,9 @@ LayerResult GCode::process_layer(
|
|||||||
if (layer_tools.has_wipe_tower && m_wipe_tower)
|
if (layer_tools.has_wipe_tower && m_wipe_tower)
|
||||||
m_last_processor_extrusion_role = erWipeTower;
|
m_last_processor_extrusion_role = erWipeTower;
|
||||||
|
|
||||||
std::vector<InstanceToPrint> &instances_to_print = filament_to_print_instances[extruder_id];
|
auto &filament_plan = filament_to_print_instances[extruder_id];
|
||||||
|
std::vector<InstanceToPrint> &instances_to_print = filament_plan.first;
|
||||||
|
const std::vector<InstanceVisit> &instance_visits = filament_plan.second;
|
||||||
|
|
||||||
// We are almost ready to print. However, we must go through all the objects twice to print the overridden extrusions first (infill/perimeter wiping feature):
|
// We are almost ready to print. However, we must go through all the objects twice to print the overridden extrusions first (infill/perimeter wiping feature):
|
||||||
std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
|
std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
|
||||||
@@ -6231,10 +6259,11 @@ LayerResult GCode::process_layer(
|
|||||||
if (is_anything_overridden && print_wipe_extrusions == 0)
|
if (is_anything_overridden && print_wipe_extrusions == 0)
|
||||||
gcode+="; PURGING FINISHED\n";
|
gcode+="; PURGING FINISHED\n";
|
||||||
|
|
||||||
for (InstanceToPrint &instance_to_print : instances_to_print) {
|
for (const InstanceVisit &visit : instance_visits) {
|
||||||
|
InstanceToPrint &instance_to_print = instances_to_print[visit.instance_idx];
|
||||||
const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id];
|
const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id];
|
||||||
const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id];
|
const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id];
|
||||||
if (print_wipe_extrusions == (is_anything_overridden ? 1 : 0)) {
|
if (visit.first_visit && print_wipe_extrusions == (is_anything_overridden ? 1 : 0)) {
|
||||||
gcode += generate_object_skirt_group(print, instance_to_print.print_object, instance_to_print.instance_id, layer_tools, layer, extruder_id);
|
gcode += generate_object_skirt_group(print, instance_to_print.print_object, instance_to_print.instance_id, layer_tools, layer, extruder_id);
|
||||||
gcode += generate_object_brim(print, instance_to_print.print_object, instance_to_print.instance_id, first_layer);
|
gcode += generate_object_brim(print, instance_to_print.print_object, instance_to_print.instance_id, first_layer);
|
||||||
}
|
}
|
||||||
@@ -6287,7 +6316,7 @@ LayerResult GCode::process_layer(
|
|||||||
m_avoid_crossing_perimeters.use_external_mp_once();
|
m_avoid_crossing_perimeters.use_external_mp_once();
|
||||||
m_last_obj_copy = this_object_copy;
|
m_last_obj_copy = this_object_copy;
|
||||||
this->set_origin(unscale(offset));
|
this->set_origin(unscale(offset));
|
||||||
if (instance_to_print.object_by_extruder.support != nullptr) {
|
if (visit.first_visit && instance_to_print.object_by_extruder.support != nullptr) {
|
||||||
m_layer = layers[instance_to_print.layer_id].support_layer;
|
m_layer = layers[instance_to_print.layer_id].support_layer;
|
||||||
m_object_layer_over_raft = false;
|
m_object_layer_over_raft = false;
|
||||||
|
|
||||||
@@ -6321,9 +6350,42 @@ LayerResult GCode::process_layer(
|
|||||||
m_layer = layer_to_print.layer();
|
m_layer = layer_to_print.layer();
|
||||||
m_object_layer_over_raft = object_layer_over_raft;
|
m_object_layer_over_raft = object_layer_over_raft;
|
||||||
}
|
}
|
||||||
//FIXME order islands?
|
|
||||||
// Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
|
// Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
|
||||||
for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
|
// Island print order. Use the islands the tour assigned to this visit; if none,
|
||||||
|
// chain all islands nearest-neighbor from the current nozzle position (last_pos(),
|
||||||
|
// in this instance's frame after set_origin() above). Empty islands are skipped;
|
||||||
|
// the trailing catch-all island has no centroid to chain by and always goes last.
|
||||||
|
std::vector<ObjectByExtruder::Island> &islands = instance_to_print.object_by_extruder.islands;
|
||||||
|
std::vector<size_t> island_order = visit.islands;
|
||||||
|
if (island_order.empty()) {
|
||||||
|
island_order.reserve(islands.size());
|
||||||
|
if (layer_to_print.object_layer != nullptr && islands.size() == layer_to_print.object_layer->lslices.size() + 1) {
|
||||||
|
for (size_t i = 0; i + 1 < islands.size(); ++i)
|
||||||
|
if (!islands[i].by_region.empty())
|
||||||
|
island_order.emplace_back(i);
|
||||||
|
if (island_order.size() > 1) {
|
||||||
|
Points island_centroids;
|
||||||
|
island_centroids.reserve(island_order.size());
|
||||||
|
for (size_t i : island_order)
|
||||||
|
island_centroids.emplace_back(layer_to_print.object_layer->lslices[i].contour.centroid());
|
||||||
|
const Point start_near = this->last_pos();
|
||||||
|
std::vector<size_t> chain = chain_points(island_centroids, this->last_pos_defined() ? &start_near : nullptr);
|
||||||
|
std::vector<size_t> ordered;
|
||||||
|
ordered.reserve(island_order.size());
|
||||||
|
for (size_t k : chain)
|
||||||
|
ordered.emplace_back(island_order[k]);
|
||||||
|
island_order = std::move(ordered);
|
||||||
|
}
|
||||||
|
if (!islands.back().by_region.empty())
|
||||||
|
island_order.emplace_back(islands.size() - 1);
|
||||||
|
} else {
|
||||||
|
// Unexpected islands layout, keep the stored order.
|
||||||
|
for (size_t i = 0; i < islands.size(); ++i)
|
||||||
|
island_order.emplace_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t island_idx : island_order) {
|
||||||
|
ObjectByExtruder::Island &island = islands[island_idx];
|
||||||
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast<unsigned int>(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region;
|
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast<unsigned int>(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region;
|
||||||
// When starting a new object, use the external motion planner for the first travel move.
|
// When starting a new object, use the external motion planner for the first travel move.
|
||||||
const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
|
const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
|
||||||
@@ -8288,29 +8350,22 @@ std::string GCode::extrusion_role_to_string_for_parser(const ExtrusionRole & rol
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the interpolated value for the current layer between start_value and end_value.
|
// Calculate the interpolated value for the current layer between start_value and end_value.
|
||||||
// Step will create equal layers steps from first to last value.
|
// Step > 0 splits the range into equal-width bands from first to last value (both inclusive).
|
||||||
// Step = 0 means gradual interpolation finishing at last value.
|
// Step = 0 means gradual interpolation finishing at last value.
|
||||||
float GCode::interpolate_value_across_layers(float start_value, float end_value, float step) const
|
float GCode::interpolate_value_across_layers(float start_value, float end_value, float step) const
|
||||||
{
|
{
|
||||||
if (m_layer_index <= 1) {
|
if (m_layer_index <= 1) {
|
||||||
return start_value;
|
return start_value;
|
||||||
}
|
}
|
||||||
else {
|
const float ratio = m_layer_index / (m_layer_count - 1.f);
|
||||||
bool use_steps = step > 0.f;
|
if (step > 0.f) {
|
||||||
if (use_steps) {
|
// Discrete equal-width bands. band is clamped to the last band so the result can't overshoot the range:
|
||||||
if (start_value > end_value) {
|
// at the top layer ratio * n_bands == n_bands, which would otherwise index one band past the end.
|
||||||
start_value += step;
|
const int n_bands = std::lround(std::abs(end_value - start_value) / step) + 1;
|
||||||
} else {
|
const int band = std::min(n_bands - 1, static_cast<int>(ratio * n_bands));
|
||||||
end_value += step;
|
return start_value + (end_value >= start_value ? 1.f : -1.f) * band * step;
|
||||||
}
|
|
||||||
}
|
|
||||||
float ratio = m_layer_index / (m_layer_count - 1.f);
|
|
||||||
float value = start_value + ratio * (end_value - start_value);
|
|
||||||
if (use_steps) {
|
|
||||||
value = trunc(value / step) * step;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
return start_value + ratio * (end_value - start_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string encodeBase64(uint64_t value)
|
std::string encodeBase64(uint64_t value)
|
||||||
|
|||||||
+34
-3
@@ -132,9 +132,6 @@ private:
|
|||||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||||
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const BoundingBox &printer_bbx) const;
|
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const BoundingBox &printer_bbx) const;
|
||||||
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||||
std::string travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const;
|
|
||||||
Vec2f transform_wt2_pt(const Vec2f &pt) const;
|
|
||||||
BoundingBox printer_travel_bounds(GCode &gcodegen) const;
|
|
||||||
|
|
||||||
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
||||||
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
||||||
@@ -542,6 +539,40 @@ private:
|
|||||||
// Cache for custom seam enforcers/blockers for each layer.
|
// Cache for custom seam enforcers/blockers for each layer.
|
||||||
SeamPlacer m_seam_placer;
|
SeamPlacer m_seam_placer;
|
||||||
|
|
||||||
|
// One stop of the island-level tour: consecutive islands of a single instance. An instance
|
||||||
|
// can have several visits per layer when its islands are toured non-consecutively.
|
||||||
|
struct InstanceVisit
|
||||||
|
{
|
||||||
|
// Index into the per-filament InstanceToPrint vector.
|
||||||
|
size_t instance_idx;
|
||||||
|
// Islands to print, in order (indices into ObjectByExtruder::islands). Empty: print all
|
||||||
|
// islands, ordered at extrusion time.
|
||||||
|
std::vector<size_t> islands;
|
||||||
|
// First visit of this instance this layer; skirt, brim and support are emitted here.
|
||||||
|
bool first_visit;
|
||||||
|
};
|
||||||
|
|
||||||
|
// One node of the island-level tour, also used as cache key: identity plus quantized position.
|
||||||
|
struct IslandOrderNode
|
||||||
|
{
|
||||||
|
ObjectID object_id;
|
||||||
|
size_t instance_id;
|
||||||
|
// Index into ObjectByExtruder::islands, or size_t(-1) for an instance without chainable
|
||||||
|
// islands (e.g. support only), which is toured as a single stop.
|
||||||
|
size_t island_idx;
|
||||||
|
// Island centroid in G-code coordinates, quantized to 1 mm for cache stability.
|
||||||
|
Point pos;
|
||||||
|
bool operator==(const IslandOrderNode &rhs) const {
|
||||||
|
return object_id == rhs.object_id && instance_id == rhs.instance_id &&
|
||||||
|
island_idx == rhs.island_idx && pos == rhs.pos;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cache the per-filament island tour to avoid recomputing while the layer's island layout is
|
||||||
|
// unchanged. Key: filament_id. Value: {nodes the tour was computed from, resulting visits}.
|
||||||
|
std::map<unsigned int, std::pair<std::vector<IslandOrderNode>, std::vector<InstanceVisit>>>
|
||||||
|
m_ordering_cache;
|
||||||
|
|
||||||
ExtrusionQualityEstimator m_extrusion_quality_estimator;
|
ExtrusionQualityEstimator m_extrusion_quality_estimator;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1450,8 +1450,8 @@ void GCodeProcessor::run_post_process()
|
|||||||
// flag) runs none of this. It is pure data construction — it only fills m_filament_blocks /
|
// flag) runs none of this. It is pure data construction — it only fills m_filament_blocks /
|
||||||
// m_extruder_blocks / m_machine_*_gcode_*_line_id and never touches the exported g-code, so even
|
// m_extruder_blocks / m_machine_*_gcode_*_line_id and never touches the exported g-code, so even
|
||||||
// the enable_pre_heating fleet stays byte-identical (nothing reads the blocks until the injection
|
// the enable_pre_heating fleet stays byte-identical (nothing reads the blocks until the injection
|
||||||
// pass). The wipe tower emits the NOZZLE_CHANGE_* (ramming) and CP_TOOLCHANGE_WIPE markers this
|
// pass). In practice it also stays empty/degenerate today because no template/code yet emits the
|
||||||
// builder keys off; the MACHINE_*_GCODE_* markers come from the machine g-code templates.
|
// MACHINE_*_GCODE_* / NOZZLE_CHANGE_* / CP_TOOLCHANGE_WIPE markers it keys off.
|
||||||
m_filament_blocks.clear();
|
m_filament_blocks.clear();
|
||||||
m_extruder_blocks.clear();
|
m_extruder_blocks.clear();
|
||||||
m_machine_start_gcode_end_line_id = (unsigned int) (-1);
|
m_machine_start_gcode_end_line_id = (unsigned int) (-1);
|
||||||
|
|||||||
@@ -0,0 +1,435 @@
|
|||||||
|
// Print-object ordering strategies: implementation.
|
||||||
|
// Consolidates TSP post-processing, Snake, and Best-of-Strategies.
|
||||||
|
|
||||||
|
#include "OrderingStrategies.hpp"
|
||||||
|
#include "../Geometry.hpp"
|
||||||
|
#include "../ShortestPath.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
|
#include <numeric>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
/* ====================================================================
|
||||||
|
* TSP post-processing utilities
|
||||||
|
* ==================================================================== */
|
||||||
|
|
||||||
|
bool tsp_2opt_improve(std::vector<size_t>& path, const Points& centers, int max_passes)
|
||||||
|
{
|
||||||
|
size_t pn = path.size();
|
||||||
|
if (pn <= 2) return false;
|
||||||
|
|
||||||
|
// Pre-compute edge lengths once per pass to avoid redundant norm() calls.
|
||||||
|
auto recompute_edges = [&]() {
|
||||||
|
std::vector<double> el(pn);
|
||||||
|
for (size_t i = 0; i < pn; ++i) {
|
||||||
|
size_t ni = (i + 1) % pn;
|
||||||
|
el[i] = (centers[path[i]].cast<double>() - centers[path[ni]].cast<double>()).norm();
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
};
|
||||||
|
std::vector<double> el = recompute_edges();
|
||||||
|
|
||||||
|
// Pre-compute squared edge lengths for early rejection in the inner loop.
|
||||||
|
auto recompute_edges_sq = [&]() {
|
||||||
|
std::vector<double> elsq(pn);
|
||||||
|
for (size_t i = 0; i < pn; ++i) {
|
||||||
|
size_t ni = (i + 1) % pn;
|
||||||
|
elsq[i] = (centers[path[i]].cast<double>() - centers[path[ni]].cast<double>()).squaredNorm();
|
||||||
|
}
|
||||||
|
return elsq;
|
||||||
|
};
|
||||||
|
std::vector<double> elsq = recompute_edges_sq();
|
||||||
|
|
||||||
|
bool improved = false;
|
||||||
|
for (int pass = 0; max_passes <= 0 || pass < max_passes; ++pass) {
|
||||||
|
size_t best_i = pn, best_j = pn;
|
||||||
|
double best_gain = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < pn; ++i) {
|
||||||
|
const Vec2d& pi = centers[path[i]].cast<double>();
|
||||||
|
const Vec2d& p_in = centers[path[(i + 1) % pn]].cast<double>();
|
||||||
|
double d_i = el[i];
|
||||||
|
double d_i_sq = elsq[i];
|
||||||
|
|
||||||
|
for (size_t j = i + 2; j < pn; ++j) {
|
||||||
|
size_t j_next = (j + 1) % pn;
|
||||||
|
// Skip the swap that would reverse the entire cycle (removes both
|
||||||
|
// edges (0,1) and (pn-1,0), equivalent to traversing the cycle backwards).
|
||||||
|
if (i == 0 && j_next == 0) continue;
|
||||||
|
|
||||||
|
const Vec2d& pj = centers[path[j]].cast<double>();
|
||||||
|
const Vec2d& p_jn = centers[path[j_next]].cast<double>();
|
||||||
|
double d_j = el[j];
|
||||||
|
|
||||||
|
// Early rejection using squared distances (avoids 2 sqrt calls).
|
||||||
|
double new_a_sq = (pj - pi).squaredNorm();
|
||||||
|
double new_b_sq = (p_jn - p_in).squaredNorm();
|
||||||
|
if (new_a_sq >= d_i_sq && new_b_sq >= elsq[j]) continue;
|
||||||
|
|
||||||
|
double new_a = std::sqrt(new_a_sq);
|
||||||
|
double new_b = std::sqrt(new_b_sq);
|
||||||
|
double gain = d_i + d_j - new_a - new_b;
|
||||||
|
|
||||||
|
if (gain > best_gain) {
|
||||||
|
best_gain = gain;
|
||||||
|
best_i = i; best_j = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best_i == pn) break;
|
||||||
|
improved = true;
|
||||||
|
// Reverse the best swap segment
|
||||||
|
std::reverse(path.begin() + best_i + 1, path.begin() + best_j + 1);
|
||||||
|
|
||||||
|
// Recompute edge lengths after reversal
|
||||||
|
el = recompute_edges();
|
||||||
|
elsq = recompute_edges_sq();
|
||||||
|
}
|
||||||
|
return improved;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast bounding-box overlap test (rejects most non-intersecting pairs).
|
||||||
|
static inline bool bboxes_overlap(const Point& a, const Point& b, const Point& c, const Point& d)
|
||||||
|
{
|
||||||
|
return !(std::max(a.x(), b.x()) < std::min(c.x(), d.x()) ||
|
||||||
|
std::max(c.x(), d.x()) < std::min(a.x(), b.x()) ||
|
||||||
|
std::max(a.y(), b.y()) < std::min(c.y(), d.y()) ||
|
||||||
|
std::max(c.y(), d.y()) < std::min(a.y(), b.y()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers)
|
||||||
|
{
|
||||||
|
size_t pn = path.size();
|
||||||
|
if (pn <= 3) return false;
|
||||||
|
|
||||||
|
// Treat path as a cycle: include the closing edge (pn-1 -> 0), consistent with the other
|
||||||
|
// TSP helpers (2-opt, closing-edge rotation) that operate on the full cycle.
|
||||||
|
size_t n_edges = pn;
|
||||||
|
|
||||||
|
// Scan for first crossing; returns {i, j} or {npos, npos} if none.
|
||||||
|
auto find_crossing = [&]() -> std::pair<size_t, size_t> {
|
||||||
|
for (size_t i = 0; i < n_edges; ++i) {
|
||||||
|
const Point& ai = centers[path[i]];
|
||||||
|
const Point& bi = centers[path[(i + 1) % pn]];
|
||||||
|
|
||||||
|
for (size_t j = i + 2; j < n_edges; ++j) {
|
||||||
|
// Skip the (0, pn-1) pair: edges (0,1) and (pn-1,0) share node 0.
|
||||||
|
if (i == 0 && j == pn - 1) continue;
|
||||||
|
|
||||||
|
const Point& aj = centers[path[j]];
|
||||||
|
const Point& bj = centers[path[(j + 1) % pn]];
|
||||||
|
|
||||||
|
if (!bboxes_overlap(ai, bi, aj, bj)) continue;
|
||||||
|
if (Geometry::segments_intersect(ai, bi, aj, bj))
|
||||||
|
return {i, j};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Process crossings one at a time: find first, reverse it, restart scan.
|
||||||
|
// Cap iterations to prevent infinite loops on collinear/overlapping segments.
|
||||||
|
int max_iters = static_cast<int>(pn * pn);
|
||||||
|
bool improved = false;
|
||||||
|
while (max_iters-- > 0) {
|
||||||
|
auto [ci, cj] = find_crossing();
|
||||||
|
if (ci == std::numeric_limits<size_t>::max()) break;
|
||||||
|
improved = true;
|
||||||
|
std::reverse(path.begin() + ci + 1, path.begin() + cj + 1);
|
||||||
|
}
|
||||||
|
return improved;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tsp_rotate_minimize_closing(std::vector<size_t>& path, const Points& centers)
|
||||||
|
{
|
||||||
|
size_t pn = path.size();
|
||||||
|
size_t best_start = 0;
|
||||||
|
double best_closing2 = std::numeric_limits<double>::max();
|
||||||
|
for (size_t start = 0; start < pn; ++start) {
|
||||||
|
size_t last = (start + pn - 1) % pn;
|
||||||
|
double d2 = (centers[path[start]].cast<double>() - centers[path[last]].cast<double>()).squaredNorm();
|
||||||
|
if (d2 < best_closing2) { best_closing2 = d2; best_start = start; }
|
||||||
|
}
|
||||||
|
std::rotate(path.begin(), path.begin() + best_start, path.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====================================================================
|
||||||
|
* Snake ordering
|
||||||
|
* ==================================================================== */
|
||||||
|
|
||||||
|
struct SnakeRow { double avg_y; std::vector<size_t> indices; };
|
||||||
|
|
||||||
|
// --- Row threshold computation ---
|
||||||
|
// Extract unique Y values and use the median gap between them to determine
|
||||||
|
// the row threshold.
|
||||||
|
static double compute_row_threshold(const std::vector<double>& sorted_ys,
|
||||||
|
double y_min, double y_max,
|
||||||
|
size_t n,
|
||||||
|
double fraction_of_y_range,
|
||||||
|
double min_threshold_um)
|
||||||
|
{
|
||||||
|
constexpr double MIN_GAP_FILTER = 1.0; // ignore sub-micron gaps (coord_t = 1/100mm)
|
||||||
|
|
||||||
|
// Extract unique Y values
|
||||||
|
std::vector<double> unique_ys;
|
||||||
|
unique_ys.reserve(sorted_ys.size());
|
||||||
|
unique_ys.push_back(sorted_ys[0]);
|
||||||
|
for (size_t i = 1; i < sorted_ys.size(); ++i) {
|
||||||
|
if (sorted_ys[i] - sorted_ys[i - 1] > MIN_GAP_FILTER)
|
||||||
|
unique_ys.push_back(sorted_ys[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
double fallback_threshold = (y_max - y_min) * fraction_of_y_range;
|
||||||
|
if (unique_ys.size() <= 1) {
|
||||||
|
return std::max(fallback_threshold, min_threshold_um);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute gaps between consecutive unique Y values
|
||||||
|
std::vector<double> gaps;
|
||||||
|
gaps.reserve(unique_ys.size() - 1);
|
||||||
|
for (size_t i = 1; i < unique_ys.size(); ++i)
|
||||||
|
gaps.push_back(unique_ys[i] - unique_ys[i - 1]);
|
||||||
|
|
||||||
|
if (gaps.empty()) {
|
||||||
|
return std::max(fallback_threshold, min_threshold_um);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort gaps to find the median
|
||||||
|
std::sort(gaps.begin(), gaps.end());
|
||||||
|
double median_gap = gaps[gaps.size() / 2];
|
||||||
|
double min_gap = gaps.front();
|
||||||
|
|
||||||
|
// Threshold: half the gap between consecutive unique Y values.
|
||||||
|
double threshold = (median_gap < min_gap * 1.5) ? min_gap * 0.5 : median_gap * 0.5;
|
||||||
|
|
||||||
|
bool has_row_structure;
|
||||||
|
if (unique_ys.size() * 2 <= n) {
|
||||||
|
has_row_structure = true;
|
||||||
|
} else {
|
||||||
|
// Single-column or sparse: uniform gaps indicate a deliberate grid
|
||||||
|
double max_gap = *std::max_element(gaps.begin(), gaps.end());
|
||||||
|
has_row_structure = (max_gap < min_gap * 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_row_structure) {
|
||||||
|
// For grid-like data, use the gap-based threshold directly.
|
||||||
|
return threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::max(fallback_threshold, min_threshold_um);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Row grouping ---
|
||||||
|
// Bin points into rows by quantising Y / threshold
|
||||||
|
static std::vector<SnakeRow> group_into_rows(const Points& centers, double row_threshold)
|
||||||
|
{
|
||||||
|
size_t n = centers.size();
|
||||||
|
std::unordered_map<int64_t, std::vector<size_t>> row_map;
|
||||||
|
for (size_t i = 0; i < n; ++i) {
|
||||||
|
int64_t y_key = static_cast<int64_t>(std::floor(static_cast<double>(centers[i].y()) / row_threshold));
|
||||||
|
row_map[y_key].push_back(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<SnakeRow> rows;
|
||||||
|
rows.reserve(row_map.size());
|
||||||
|
for (auto& [key, indices] : row_map) {
|
||||||
|
double avg_y = std::accumulate(indices.begin(), indices.end(), 0.0,
|
||||||
|
[&](double acc, size_t idx) { return acc + static_cast<double>(centers[idx].y()); })
|
||||||
|
/ indices.size();
|
||||||
|
rows.push_back({avg_y, std::move(indices)});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(rows.begin(), rows.end(),
|
||||||
|
[](const SnakeRow& a, const SnakeRow& b) { return a.avg_y < b.avg_y; });
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort each row by X and greedily pick the direction (left->right or right->left)
|
||||||
|
// that minimises the transition distance from the previous row's endpoint.
|
||||||
|
static std::vector<size_t> build_serpentine_path(const Points& centers,
|
||||||
|
std::vector<SnakeRow>& rows)
|
||||||
|
{
|
||||||
|
std::vector<size_t> path;
|
||||||
|
path.reserve(centers.size());
|
||||||
|
|
||||||
|
for (size_t ri = 0; ri < rows.size(); ++ri) {
|
||||||
|
auto& row = rows[ri].indices;
|
||||||
|
std::sort(row.begin(), row.end(),
|
||||||
|
[&](size_t a, size_t b) { return centers[a].x() < centers[b].x(); });
|
||||||
|
|
||||||
|
if (ri == 0) {
|
||||||
|
path.insert(path.end(), row.begin(), row.end());
|
||||||
|
} else {
|
||||||
|
const Point& prev_end = centers[path.back()];
|
||||||
|
double dist_to_left = (prev_end.cast<double>() - centers[row.front()].cast<double>()).squaredNorm();
|
||||||
|
double dist_to_right = (prev_end.cast<double>() - centers[row.back()].cast<double>()).squaredNorm();
|
||||||
|
|
||||||
|
if (dist_to_left <= dist_to_right)
|
||||||
|
path.insert(path.end(), row.begin(), row.end());
|
||||||
|
else
|
||||||
|
path.insert(path.end(), row.rbegin(), row.rend());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row-based serpentine traversal: detect rows, bin points, snake through them.
|
||||||
|
static std::vector<size_t> row_serpentine_path(const Points& centers,
|
||||||
|
double fraction_of_y_range = 0.02,
|
||||||
|
double min_threshold_um = 1e4)
|
||||||
|
{
|
||||||
|
if (centers.empty()) return {};
|
||||||
|
|
||||||
|
size_t n = centers.size();
|
||||||
|
|
||||||
|
// Collect and sort Y coordinates.
|
||||||
|
std::vector<double> sorted_ys;
|
||||||
|
sorted_ys.reserve(n);
|
||||||
|
for (const auto& p : centers) sorted_ys.push_back(static_cast<double>(p.y()));
|
||||||
|
std::sort(sorted_ys.begin(), sorted_ys.end());
|
||||||
|
|
||||||
|
auto [ymin, ymax] = std::minmax_element(sorted_ys.begin(), sorted_ys.end());
|
||||||
|
double y_min = *ymin, y_max = *ymax;
|
||||||
|
|
||||||
|
double row_threshold = compute_row_threshold(sorted_ys, y_min, y_max, n,
|
||||||
|
fraction_of_y_range, min_threshold_um);
|
||||||
|
|
||||||
|
auto rows = group_into_rows(centers, row_threshold);
|
||||||
|
return build_serpentine_path(centers, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<size_t> snake_core(const Points& centers)
|
||||||
|
{
|
||||||
|
if (centers.empty()) return {};
|
||||||
|
|
||||||
|
std::vector<size_t> path = row_serpentine_path(centers);
|
||||||
|
|
||||||
|
for (int iter = 0; iter < 3; ++iter) {
|
||||||
|
bool improved = tsp_2opt_improve(path, centers);
|
||||||
|
improved |= tsp_remove_crossings(path, centers);
|
||||||
|
if (!improved) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<const PrintInstance*> chain_print_object_instances_snake(const std::vector<const PrintObject*>& print_objects, const Point* start_near)
|
||||||
|
{
|
||||||
|
return chain_instances_with_core(print_objects, start_near, snake_core);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<const PrintInstance*> chain_print_object_instances_snake(const Print& print)
|
||||||
|
{
|
||||||
|
return chain_print_object_instances_snake(print.objects().vector(), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====================================================================
|
||||||
|
* Best-of-strategies meta-strategy
|
||||||
|
* ==================================================================== */
|
||||||
|
|
||||||
|
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const std::vector<const PrintObject*>& print_objects, const Point* start_near)
|
||||||
|
{
|
||||||
|
if (print_objects.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Run all strategies.
|
||||||
|
std::vector<std::vector<const PrintInstance*>> candidates;
|
||||||
|
candidates.push_back(chain_print_object_instances(print_objects, start_near));
|
||||||
|
candidates.push_back(chain_print_object_instances_snake(print_objects, start_near));
|
||||||
|
|
||||||
|
// Compute metrics for each candidate.
|
||||||
|
struct Candidate { double total_len; double max_edge; };
|
||||||
|
std::vector<Candidate> metrics;
|
||||||
|
metrics.reserve(candidates.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < candidates.size(); ++i) {
|
||||||
|
double total = 0.0;
|
||||||
|
double mx = 0.0;
|
||||||
|
for (size_t j = 0; j < candidates[i].size(); ++j) {
|
||||||
|
size_t k = (j + 1) % candidates[i].size();
|
||||||
|
double d = (candidates[i][j]->shift.cast<double>() - candidates[i][k]->shift.cast<double>()).norm();
|
||||||
|
total += d;
|
||||||
|
if (d > mx) mx = d;
|
||||||
|
}
|
||||||
|
metrics.push_back({total, mx});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick shortest total path; tiebreak on smallest max edge.
|
||||||
|
auto best_it = std::min_element(metrics.begin(), metrics.end(),
|
||||||
|
[](const Candidate& a, const Candidate& b) {
|
||||||
|
return a.total_len < b.total_len ||
|
||||||
|
(a.total_len == b.total_len && a.max_edge < b.max_edge);
|
||||||
|
});
|
||||||
|
size_t best = static_cast<size_t>(std::distance(metrics.begin(), best_it));
|
||||||
|
|
||||||
|
return candidates[best];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const Print& print)
|
||||||
|
{
|
||||||
|
return chain_print_object_instances_best_of(print.objects().vector(), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====================================================================
|
||||||
|
* Island-level ordering entry point
|
||||||
|
* ==================================================================== */
|
||||||
|
|
||||||
|
std::vector<size_t> order_points_with_strategy(const Points& points, PrintOrder print_order, const Point* start_near)
|
||||||
|
{
|
||||||
|
if (points.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (print_order != PrintOrder::Snake && print_order != PrintOrder::BestOfStrategies)
|
||||||
|
// Nearest neighbor + post-processing; honours start_near natively.
|
||||||
|
return chain_points_with_postprocessing(points, start_near);
|
||||||
|
|
||||||
|
auto run_snake = [&points, start_near]() {
|
||||||
|
std::vector<size_t> path = snake_core(points);
|
||||||
|
if (start_near != nullptr && !path.empty()) {
|
||||||
|
// Start the cycle at the point closest to start_near.
|
||||||
|
size_t best_start = 0;
|
||||||
|
double best_d2 = std::numeric_limits<double>::max();
|
||||||
|
for (size_t k = 0; k < points.size(); ++k) {
|
||||||
|
double d2 = (points[k].cast<double>() - start_near->cast<double>()).squaredNorm();
|
||||||
|
if (d2 < best_d2) { best_d2 = d2; best_start = k; }
|
||||||
|
}
|
||||||
|
auto it = std::find(path.begin(), path.end(), best_start);
|
||||||
|
if (it != path.begin() && it != path.end())
|
||||||
|
std::rotate(path.begin(), it, path.end());
|
||||||
|
} else {
|
||||||
|
tsp_rotate_minimize_closing(path, points);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (print_order == PrintOrder::Snake)
|
||||||
|
return run_snake();
|
||||||
|
|
||||||
|
// Best-of: pick the shortest total cycle; tiebreak on smallest max edge.
|
||||||
|
std::vector<std::vector<size_t>> candidates;
|
||||||
|
candidates.emplace_back(chain_points_with_postprocessing(points, start_near));
|
||||||
|
candidates.emplace_back(run_snake());
|
||||||
|
|
||||||
|
size_t best = 0;
|
||||||
|
double best_len = std::numeric_limits<double>::max();
|
||||||
|
double best_edge = std::numeric_limits<double>::max();
|
||||||
|
for (size_t i = 0; i < candidates.size(); ++i) {
|
||||||
|
double len = tsp_cycle_path_length(candidates[i], points);
|
||||||
|
double edge = tsp_max_edge_length(candidates[i], points);
|
||||||
|
if (len < best_len || (len == best_len && edge < best_edge)) {
|
||||||
|
best_len = len; best_edge = edge; best = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return candidates[best];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
// Print-object ordering strategies and shared TSP post-processing utilities.
|
||||||
|
|
||||||
|
#ifndef slic3r_OrderingStrategies_hpp_
|
||||||
|
#define slic3r_OrderingStrategies_hpp_
|
||||||
|
|
||||||
|
#include "../libslic3r.h"
|
||||||
|
#include "../Point.hpp"
|
||||||
|
|
||||||
|
#ifndef SLIC3R_TEST_HARNESS
|
||||||
|
#include "../Print.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// --- Path improvement (operate on index vectors into `centers`) ---
|
||||||
|
|
||||||
|
// 2-opt improvement: reverses segments that reduce total cycle path length.
|
||||||
|
// Returns true if any improvement was made.
|
||||||
|
bool tsp_2opt_improve(std::vector<size_t>& path, const Points& centers, int max_passes = 10);
|
||||||
|
|
||||||
|
// Crossing removal: reverse any segment pair whose edges geometrically cross.
|
||||||
|
// Returns true if any crossing was removed.
|
||||||
|
bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers);
|
||||||
|
|
||||||
|
// Rotate the cycle so the closing edge (last -> first) is minimized.
|
||||||
|
void tsp_rotate_minimize_closing(std::vector<size_t>& path, const Points& centers);
|
||||||
|
|
||||||
|
// Total Euclidean path length of a cycle (including closing edge).
|
||||||
|
inline double tsp_cycle_path_length(const std::vector<size_t>& path, const Points& centers)
|
||||||
|
{
|
||||||
|
if (path.size() < 2) return 0.0;
|
||||||
|
double total = 0.0;
|
||||||
|
for (size_t i = 0; i < path.size(); ++i) {
|
||||||
|
size_t next = (i + 1) % path.size();
|
||||||
|
total += (centers[path[i]].cast<double>() - centers[path[next]].cast<double>()).norm();
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum edge length of a cycle (including closing edge).
|
||||||
|
inline double tsp_max_edge_length(const std::vector<size_t>& path, const Points& centers)
|
||||||
|
{
|
||||||
|
if (path.size() < 2) return 0.0;
|
||||||
|
double mx = 0.0;
|
||||||
|
for (size_t i = 0; i < path.size(); ++i) {
|
||||||
|
size_t next = (i + 1) % path.size();
|
||||||
|
double d = (centers[path[i]].cast<double>() - centers[path[next]].cast<double>()).norm();
|
||||||
|
if (d > mx) mx = d;
|
||||||
|
}
|
||||||
|
return mx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef SLIC3R_TEST_HARNESS
|
||||||
|
|
||||||
|
// --- Wrapper boilerplate ---
|
||||||
|
|
||||||
|
// Collect instance centers from PrintObjects, optionally pre-rotate to honour
|
||||||
|
// start_near, call a core algorithm, and map the result back to PrintInstance*.
|
||||||
|
template<typename CoreFn>
|
||||||
|
std::vector<const PrintInstance*> chain_instances_with_core(
|
||||||
|
const std::vector<const PrintObject*>& print_objects,
|
||||||
|
const Point* start_near,
|
||||||
|
CoreFn&& core_fn)
|
||||||
|
{
|
||||||
|
Points instance_centers;
|
||||||
|
std::vector<std::pair<size_t, size_t>> instances;
|
||||||
|
for (size_t i = 0; i < print_objects.size(); ++i) {
|
||||||
|
const PrintObject& object = *print_objects[i];
|
||||||
|
for (size_t j = 0; j < object.instances().size(); ++j) {
|
||||||
|
instance_centers.emplace_back(object.instances()[j].shift);
|
||||||
|
instances.emplace_back(i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance_centers.empty()) return {};
|
||||||
|
|
||||||
|
// If start_near is provided, pre-rotate so closest point is first.
|
||||||
|
if (start_near != nullptr) {
|
||||||
|
size_t best_start = 0;
|
||||||
|
double best_d2 = std::numeric_limits<double>::max();
|
||||||
|
for (size_t k = 0; k < instance_centers.size(); ++k) {
|
||||||
|
double d2 = (instance_centers[k].cast<double>() - start_near->cast<double>()).squaredNorm();
|
||||||
|
if (d2 < best_d2) { best_d2 = d2; best_start = k; }
|
||||||
|
}
|
||||||
|
std::rotate(instance_centers.begin(), instance_centers.begin() + best_start, instance_centers.end());
|
||||||
|
std::rotate(instances.begin(), instances.begin() + best_start, instances.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto path = core_fn(instance_centers);
|
||||||
|
|
||||||
|
// Rotate the cycle so the first element is the best starting point.
|
||||||
|
// When start_near is provided, pick the point closest to it (preserving
|
||||||
|
// the pre-rotation). Otherwise minimise the closing edge.
|
||||||
|
if (start_near != nullptr && !path.empty()) {
|
||||||
|
// Pre-rotation already put the closest point at index 0.
|
||||||
|
// Find where index 0 appears in the path and rotate it to the front.
|
||||||
|
auto it = std::find(path.begin(), path.end(), size_t(0));
|
||||||
|
if (it != path.begin())
|
||||||
|
std::rotate(path.begin(), it, path.end());
|
||||||
|
} else {
|
||||||
|
tsp_rotate_minimize_closing(path, instance_centers);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<const PrintInstance*> out;
|
||||||
|
out.reserve(path.size());
|
||||||
|
for (size_t step : path) {
|
||||||
|
out.emplace_back(&print_objects[instances[step].first]->instances()[instances[step].second]);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SLIC3R_TEST_HARNESS
|
||||||
|
|
||||||
|
// --- Core algorithms (operate on raw Points, return index permutations) ---
|
||||||
|
|
||||||
|
// Snake ordering: row grouping + serpentine traversal + post-processing.
|
||||||
|
std::vector<size_t> snake_core(const Points& centers);
|
||||||
|
|
||||||
|
#ifndef SLIC3R_TEST_HARNESS
|
||||||
|
|
||||||
|
// --- Production wrappers ---
|
||||||
|
|
||||||
|
// Snake ordering.
|
||||||
|
std::vector<const PrintInstance*> chain_print_object_instances_snake(const std::vector<const PrintObject*>& print_objects, const Point* start_near);
|
||||||
|
std::vector<const PrintInstance*> chain_print_object_instances_snake(const Print& print);
|
||||||
|
|
||||||
|
// Best-of-strategies: run all strategies and return the shortest result.
|
||||||
|
// Primary: shortest total path; secondary tiebreaker: smallest max edge.
|
||||||
|
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const std::vector<const PrintObject*>& print_objects, const Point* start_near);
|
||||||
|
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const Print& print);
|
||||||
|
|
||||||
|
// Order raw points with the selected strategy, returning an index permutation. Island-level
|
||||||
|
// counterpart of the chain_print_object_instances_* helpers. The returned cycle starts at the
|
||||||
|
// point closest to start_near; orders without a dedicated strategy use nearest-neighbor chaining.
|
||||||
|
std::vector<size_t> order_points_with_strategy(const Points& points, PrintOrder print_order, const Point* start_near);
|
||||||
|
|
||||||
|
#endif // SLIC3R_TEST_HARNESS
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif /* slic3r_OrderingStrategies_hpp_ */
|
||||||
@@ -143,8 +143,7 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
|
|||||||
double wipe_tower_y = print.config().wipe_tower_y.get_at(plate_idx) + plate_origin(1);
|
double wipe_tower_y = print.config().wipe_tower_y.get_at(plate_idx) + plate_origin(1);
|
||||||
Transform2d trafo =
|
Transform2d trafo =
|
||||||
Eigen::Translation2d(wipe_tower_x, wipe_tower_y) *
|
Eigen::Translation2d(wipe_tower_x, wipe_tower_y) *
|
||||||
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value)) *
|
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value));
|
||||||
Eigen::Translation2d(print.wipe_tower_data().rib_offset.cast<double>()); // tower-local rib-wall shift, zero unless rib
|
|
||||||
|
|
||||||
BoundingBoxf bbox;
|
BoundingBoxf bbox;
|
||||||
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
|
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
|
||||||
|
|||||||
+831
-1292
File diff suppressed because it is too large
Load Diff
+112
-112
@@ -12,7 +12,7 @@
|
|||||||
#include "libslic3r/Polyline.hpp"
|
#include "libslic3r/Polyline.hpp"
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
|
||||||
namespace Slic3r
|
namespace Slic3r
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -20,11 +20,6 @@ class WipeTowerWriter;
|
|||||||
class PrintConfig;
|
class PrintConfig;
|
||||||
enum GCodeFlavor : unsigned char;
|
enum GCodeFlavor : unsigned char;
|
||||||
|
|
||||||
// Cuts the tower wall polygon open at each skip point (a toolchange's entry position)
|
|
||||||
// so the entry travel can pass through instead of crossing the printed wall. Defined in
|
|
||||||
// WipeTower.cpp, shared by WipeTower and WipeTower2.
|
|
||||||
Polylines contrust_gap_for_skip_points(
|
|
||||||
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon);
|
|
||||||
|
|
||||||
class WipeTower
|
class WipeTower
|
||||||
{
|
{
|
||||||
@@ -89,6 +84,7 @@ public:
|
|||||||
bool priming;
|
bool priming;
|
||||||
|
|
||||||
bool is_tool_change{false};
|
bool is_tool_change{false};
|
||||||
|
bool is_contact{false};
|
||||||
Vec2f tool_change_start_pos;
|
Vec2f tool_change_start_pos;
|
||||||
|
|
||||||
// Pass a polyline so that normal G-code generator can do a wipe for us.
|
// Pass a polyline so that normal G-code generator can do a wipe for us.
|
||||||
@@ -112,7 +108,6 @@ public:
|
|||||||
// executing the gcode finish_layer_tcr.
|
// executing the gcode finish_layer_tcr.
|
||||||
bool is_finish_first = false;
|
bool is_finish_first = false;
|
||||||
|
|
||||||
bool is_contact = false;
|
|
||||||
NozzleChangeResult nozzle_change_result;
|
NozzleChangeResult nozzle_change_result;
|
||||||
|
|
||||||
// Sum the total length of the extrusion.
|
// Sum the total length of the extrusion.
|
||||||
@@ -127,8 +122,6 @@ public:
|
|||||||
}
|
}
|
||||||
return e_length;
|
return e_length;
|
||||||
}
|
}
|
||||||
// Orca: set by WipeTower2 (non-BBL tower) to force a travel to the tower even when the
|
|
||||||
// previous position is unknown; read by WipeTowerIntegration::append_tcr2 (GCode.cpp).
|
|
||||||
bool force_travel = false;
|
bool force_travel = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -169,12 +162,15 @@ public:
|
|||||||
bool priming,
|
bool priming,
|
||||||
size_t old_tool,
|
size_t old_tool,
|
||||||
bool is_finish,
|
bool is_finish,
|
||||||
bool is_tool_change, float purge_volume, bool is_contact) const;
|
bool is_tool_change,
|
||||||
|
float purge_volume,
|
||||||
|
bool is_contact = false) const;
|
||||||
|
|
||||||
ToolChangeResult construct_block_tcr(WipeTowerWriter& writer,
|
ToolChangeResult construct_block_tcr(WipeTowerWriter& writer,
|
||||||
bool priming,
|
bool priming,
|
||||||
size_t filament_id,
|
size_t filament_id,
|
||||||
bool is_finish, float purge_volume) const;
|
bool is_finish,
|
||||||
|
float purge_volume) const;
|
||||||
|
|
||||||
|
|
||||||
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
||||||
@@ -188,14 +184,9 @@ public:
|
|||||||
// Set the extruder properties.
|
// Set the extruder properties.
|
||||||
void set_extruder(size_t idx, const PrintConfig& config);
|
void set_extruder(size_t idx, const PrintConfig& config);
|
||||||
|
|
||||||
void set_shared_print_bed(const Polygons &bed) { m_shared_print_bed = bed; }
|
|
||||||
// Orca: has_filament_switcher is not a static PrintConfig member here, so it is pushed in from
|
|
||||||
// Print via a setter rather than read in the ctor. Device-set only.
|
|
||||||
void set_has_filament_switcher(bool v) { m_has_filament_switcher = v; }
|
|
||||||
// Appends into internal structure m_plan containing info about the future wipe tower
|
// Appends into internal structure m_plan containing info about the future wipe tower
|
||||||
// to be used before building begins. The entries must be added ordered in z.
|
// to be used before building begins. The entries must be added ordered in z.
|
||||||
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume_ec = 0.f, float wipe_volume_nc = 0.f, float prime_volume = 0.f);
|
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f, float prime_volume = 0.f);
|
||||||
|
|
||||||
|
|
||||||
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
|
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
|
||||||
void generate(std::vector<std::vector<ToolChangeResult>> &result);
|
void generate(std::vector<std::vector<ToolChangeResult>> &result);
|
||||||
@@ -228,6 +219,9 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_wipe_volume(std::vector<std::vector<float>>& wiping_matrix) {
|
||||||
|
wipe_volumes = wiping_matrix;
|
||||||
|
}
|
||||||
|
|
||||||
// Switch to a next layer.
|
// Switch to a next layer.
|
||||||
void set_layer(
|
void set_layer(
|
||||||
@@ -256,6 +250,7 @@ public:
|
|||||||
|
|
||||||
// Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
|
// Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
|
||||||
m_extrusion_flow = extrusion_flow(layer_height);
|
m_extrusion_flow = extrusion_flow(layer_height);
|
||||||
|
|
||||||
// Advance m_layer_info iterator, making sure we got it right
|
// Advance m_layer_info iterator, making sure we got it right
|
||||||
while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end())
|
while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end())
|
||||||
++m_layer_info;
|
++m_layer_info;
|
||||||
@@ -314,9 +309,20 @@ public:
|
|||||||
std::vector<float> get_used_filament() const { return m_used_filament_length; }
|
std::vector<float> get_used_filament() const { return m_used_filament_length; }
|
||||||
int get_number_of_toolchanges() const { return m_num_tool_changes; }
|
int get_number_of_toolchanges() const { return m_num_tool_changes; }
|
||||||
|
|
||||||
void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; }
|
void set_filament_map(const std::vector<int> &filament_map) { m_filament_map = filament_map; }
|
||||||
|
// Vortek H2C: filament_id → physical nozzle_id for carousel rotation detection
|
||||||
|
void set_filament_nozzle_map(const std::vector<int> &nozzle_map) { m_filament_nozzle_map = nozzle_map; }
|
||||||
|
|
||||||
|
void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; }
|
||||||
bool has_tpu_filament() const { return m_has_tpu_filament; }
|
bool has_tpu_filament() const { return m_has_tpu_filament; }
|
||||||
|
|
||||||
|
// Orca: has_filament_switcher is not a static PrintConfig member, so it is pushed in from Print
|
||||||
|
// via a setter rather than read in the ctor. Device-set only.
|
||||||
|
void set_has_filament_switcher(bool v) { m_has_filament_switcher = v; }
|
||||||
|
// The region every extruder can reach, used to clamp the PETG pre-extrusion offset to the
|
||||||
|
// printable bed.
|
||||||
|
void set_shared_print_bed(const Polygons &bed) { m_shared_print_bed = bed; }
|
||||||
|
|
||||||
struct FilamentParameters {
|
struct FilamentParameters {
|
||||||
std::string material = "PLA";
|
std::string material = "PLA";
|
||||||
int category;
|
int category;
|
||||||
@@ -325,15 +331,15 @@ public:
|
|||||||
bool is_support = false;
|
bool is_support = false;
|
||||||
int nozzle_temperature = 0;
|
int nozzle_temperature = 0;
|
||||||
int nozzle_temperature_initial_layer = 0;
|
int nozzle_temperature_initial_layer = 0;
|
||||||
// BBS: remove useless config
|
int interface_print_temperature = 0;
|
||||||
//float loading_speed = 0.f;
|
float loading_speed = 0.f;
|
||||||
//float loading_speed_start = 0.f;
|
float loading_speed_start = 0.f;
|
||||||
//float unloading_speed = 0.f;
|
float unloading_speed = 0.f;
|
||||||
//float unloading_speed_start = 0.f;
|
float unloading_speed_start = 0.f;
|
||||||
//float delay = 0.f ;
|
float delay = 0.f ;
|
||||||
//int cooling_moves = 0;
|
int cooling_moves = 0;
|
||||||
//float cooling_initial_speed = 0.f;
|
float cooling_initial_speed = 0.f;
|
||||||
//float cooling_final_speed = 0.f;
|
float cooling_final_speed = 0.f;
|
||||||
float ramming_line_width_multiplicator = 1.f;
|
float ramming_line_width_multiplicator = 1.f;
|
||||||
float ramming_step_multiplicator = 1.f;
|
float ramming_step_multiplicator = 1.f;
|
||||||
float max_e_speed = std::numeric_limits<float>::max();
|
float max_e_speed = std::numeric_limits<float>::max();
|
||||||
@@ -343,41 +349,41 @@ public:
|
|||||||
float retract_length;
|
float retract_length;
|
||||||
float retract_speed;
|
float retract_speed;
|
||||||
float wipe_dist;
|
float wipe_dist;
|
||||||
std::pair<float,float> max_e_ramming_speed;//[0]extruder change [1]nozzle change
|
float tower_interface_pre_extrusion_dist = 0.f;
|
||||||
std::pair<float, float> ramming_travel_time; // Travel time after ramming
|
float tower_interface_pre_extrusion_length = 0.f;
|
||||||
std::pair<std::vector<float>,std::vector<float>> precool_t;//Pre-cooling time, set to 0 to ensure the ramming speed is controlled solely by ramming volumetric speed.
|
// Outward shift of the wipe start for a PETG pre-extrusion on filament-switcher devices;
|
||||||
std::pair<std::vector<float>, std::vector<float>> precool_t_first_layer;
|
// set from filament_tower_interface_pre_extrusion_dist.
|
||||||
std::pair<int,int> precool_target_temp;
|
float petg_pre_extrusion_offset_dist = 0.f;
|
||||||
float filament_cooling_before_tower = 0.f;
|
float tower_ironing_area = 4.f;
|
||||||
float flat_iron_area;
|
float tower_interface_purge_length = 0.f;
|
||||||
float filament_tower_interface_print_temp;
|
// Distance (in mm of filament) that a hotend is allowed to pre-cool before the
|
||||||
float filament_tower_interface_pre_extrusion_dist = 0;
|
// tower is reached; drives the prime-tower heating-during-wipe model (multi-nozzle only).
|
||||||
float filament_tower_interface_pre_extrusion_length = 0;
|
float filament_cooling_before_tower = 0.f;
|
||||||
float filament_petg_pre_extrusion_offset_dist = 0;
|
// .first = extruder change, .second = nozzle change (carousel)
|
||||||
|
std::pair<float,float> max_e_ramming_speed{0.f, 0.f};
|
||||||
|
std::pair<float,float> ramming_travel_time{0.f, 0.f};
|
||||||
|
std::pair<int,int> precool_target_temp{0, 0};
|
||||||
|
std::pair<std::vector<float>,std::vector<float>> precool_t;
|
||||||
|
std::pair<std::vector<float>,std::vector<float>> precool_t_first_layer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void set_used_filament_ids(const std::vector<int> &used_filament_ids) { m_used_filament_ids = used_filament_ids; };
|
void set_used_filament_ids(const std::vector<int> &used_filament_ids) { m_used_filament_ids = used_filament_ids; };
|
||||||
void set_filament_categories(const std::vector<int> & filament_categories) { m_filament_categories = filament_categories;};
|
void set_filament_categories(const std::vector<int> & filament_categories) { m_filament_categories = filament_categories;};
|
||||||
void set_nozzle_group_result(const MultiNozzleUtils::LayeredNozzleGroupResult &multi_nozzle_group_result) { m_multi_nozzle_group_result = &multi_nozzle_group_result; };
|
std::vector<int> m_used_filament_ids;
|
||||||
std::vector<int> m_used_filament_ids;
|
|
||||||
std::vector<int> m_filament_categories;
|
std::vector<int> m_filament_categories;
|
||||||
const MultiNozzleUtils::LayeredNozzleGroupResult *m_multi_nozzle_group_result{nullptr};
|
|
||||||
|
|
||||||
enum class WipeTowerLayerType : unsigned char { Normal, Contact, Solid, Contact_UP};// Contact layer should be solid and reduce feed
|
|
||||||
|
|
||||||
struct WipeTowerBlock
|
struct WipeTowerBlock
|
||||||
{
|
{
|
||||||
int block_id{0};
|
int block_id{0};
|
||||||
int filament_adhesiveness_category{0};
|
int filament_adhesiveness_category{0};
|
||||||
std::vector<float> layer_depths;
|
std::vector<float> layer_depths;
|
||||||
//std::vector<bool> solid_infill;
|
std::vector<bool> solid_infill;
|
||||||
std::vector<float> finish_depth{0}; // the start pos of finish frame for every layer
|
std::vector<float> finish_depth{0}; // the start pos of finish frame for every layer
|
||||||
std::vector<WipeTowerLayerType> layers_type; // type of the layer, normal, Contact or Solid
|
|
||||||
float depth{0};
|
float depth{0};
|
||||||
float start_depth{0};
|
float start_depth{0};
|
||||||
float cur_depth{0};
|
float cur_depth{0};
|
||||||
int last_filament_change_id{-1};
|
int last_filament_change_id{-1};
|
||||||
int last_nozzle_change_id{-1};
|
int last_nozzle_change_id{-1};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -397,33 +403,25 @@ public:
|
|||||||
WipeTowerBlock* get_block_by_category(int filament_adhesiveness_category, bool create);
|
WipeTowerBlock* get_block_by_category(int filament_adhesiveness_category, bool create);
|
||||||
void add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change = false);
|
void add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change = false);
|
||||||
int get_filament_category(int filament_id);
|
int get_filament_category(int filament_id);
|
||||||
|
bool is_in_same_extruder(int filament_id_1, int filament_id_2);
|
||||||
|
// Vortek H2C: format BBS-compatible NOZZLE_CHANGE_START/END tag with OF/NF/ON/NN payload
|
||||||
|
std::string format_nozzle_change_tag(bool start, int old_filament_id, int new_filament_id) const;
|
||||||
void reset_block_status();
|
void reset_block_status();
|
||||||
int get_wall_filament_for_all_layer();
|
int get_wall_filament_for_all_layer();
|
||||||
// for generate new wipe tower
|
// for generate new wipe tower
|
||||||
void generate_new(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
|
void generate_new(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
|
||||||
|
|
||||||
void plan_tower_new();
|
void plan_tower_new();
|
||||||
void generate_wipe_tower_blocks(bool add_solid_flag);
|
void generate_wipe_tower_blocks();
|
||||||
void update_all_layer_depth(float wipe_tower_depth);
|
void update_all_layer_depth(float wipe_tower_depth);
|
||||||
void set_nozzle_last_layer_id();
|
|
||||||
void set_first_layer_flow_ratio(const float flow_ratio);
|
|
||||||
// Orca: default/initial-layer/travel acceleration are object-scope options here (PrintConfig
|
|
||||||
// members in BBS), so Print pushes the resolved per-variant columns in via this setter.
|
|
||||||
void set_accelerations(const std::vector<double> &normal, const std::vector<double> &first_layer_normal,
|
|
||||||
const std::vector<double> &travel, const std::vector<double> &first_layer_travel);
|
|
||||||
void calc_block_infill_gap();
|
|
||||||
ToolChangeResult tool_change_new(size_t new_tool, bool solid_change = false, bool solid_nozzlechange=false);
|
ToolChangeResult tool_change_new(size_t new_tool, bool solid_change = false, bool solid_nozzlechange=false);
|
||||||
NozzleChangeResult ramming(int old_filament_id, int new_filament_id, bool solid_change = false, bool extruder_change = true); // extruder_chang means nozzle_change
|
NozzleChangeResult nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_change = false);
|
||||||
ToolChangeResult finish_layer_new(bool extrude_perimeter = true, bool extrude_fill = true, bool extrude_fill_wall = true);
|
ToolChangeResult finish_layer_new(bool extrude_perimeter = true, bool extrude_fill = true, bool extrude_fill_wall = true);
|
||||||
ToolChangeResult finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true);
|
ToolChangeResult finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true);
|
||||||
ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true, WipeTowerLayerType layer_type = WipeTowerLayerType::Normal);
|
ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true ,bool interface_solid =false);
|
||||||
void toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_length,bool solid_toolchange=false);
|
void toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_length,bool solid_toolchange=false);
|
||||||
Vec2f get_rib_offset() const { return m_rib_offset; }
|
Vec2f get_rib_offset() const { return m_rib_offset; }
|
||||||
bool is_need_ramming(int filament_id_1, int filament_id_2, int layer_id) const;
|
|
||||||
bool is_same_extruder(int filament_id_1, int filament_id_2, int layer_id) const;
|
|
||||||
bool is_same_nozzle(int filament_id_1, int filament_id_2, int layer_id) const;
|
|
||||||
int get_nozzle_id(int filament_id, int layer_id) const;
|
|
||||||
int get_extruder_id(int filament_id, int layer_id) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum wipe_shape // A fill-in direction
|
enum wipe_shape // A fill-in direction
|
||||||
@@ -443,6 +441,7 @@ private:
|
|||||||
bool m_enable_wrapping_detection = false;
|
bool m_enable_wrapping_detection = false;
|
||||||
bool m_enable_timelapse_print = false;
|
bool m_enable_timelapse_print = false;
|
||||||
bool m_semm = true; // Are we using a single extruder multimaterial printer?
|
bool m_semm = true; // Are we using a single extruder multimaterial printer?
|
||||||
|
bool m_purge_in_prime_tower = false; // Do we purge in the prime tower?
|
||||||
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
|
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
|
||||||
float m_wipe_tower_width; // Width of the wipe tower.
|
float m_wipe_tower_width; // Width of the wipe tower.
|
||||||
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
|
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
|
||||||
@@ -460,11 +459,12 @@ private:
|
|||||||
float m_travel_speed = 0.f;
|
float m_travel_speed = 0.f;
|
||||||
float m_first_layer_speed = 0.f;
|
float m_first_layer_speed = 0.f;
|
||||||
size_t m_first_layer_idx = size_t(-1);
|
size_t m_first_layer_idx = size_t(-1);
|
||||||
Vec2f m_origin;
|
|
||||||
std::vector<int> m_last_layer_id;
|
std::vector<double> m_filaments_change_length;
|
||||||
std::pair<std::vector<double>,std::vector<double>> m_filaments_change_length;//[0]extruder change [1]nozzle change
|
|
||||||
size_t m_cur_layer_id;
|
size_t m_cur_layer_id;
|
||||||
NozzleChangeResult m_nozzle_change_result;
|
NozzleChangeResult m_nozzle_change_result;
|
||||||
|
std::vector<int> m_filament_map;
|
||||||
|
std::vector<int> m_filament_nozzle_map; // Vortek H2C: filament_id → physical nozzle_id
|
||||||
bool m_has_tpu_filament{false};
|
bool m_has_tpu_filament{false};
|
||||||
bool m_is_multi_extruder{false};
|
bool m_is_multi_extruder{false};
|
||||||
bool m_use_gap_wall{false};
|
bool m_use_gap_wall{false};
|
||||||
@@ -475,32 +475,33 @@ private:
|
|||||||
bool m_used_fillet{false};
|
bool m_used_fillet{false};
|
||||||
Vec2f m_rib_offset{Vec2f(0.f, 0.f)};
|
Vec2f m_rib_offset{Vec2f(0.f, 0.f)};
|
||||||
bool m_tower_framework{false};
|
bool m_tower_framework{false};
|
||||||
bool m_need_reverse_travel{false};
|
|
||||||
bool m_enable_tower_interface_features{false};
|
|
||||||
// G-code generator parameters.
|
// G-code generator parameters.
|
||||||
// BBS: remove useless config
|
float m_cooling_tube_retraction = 0.f;
|
||||||
//float m_cooling_tube_retraction = 0.f;
|
float m_cooling_tube_length = 0.f;
|
||||||
//float m_cooling_tube_length = 0.f;
|
float m_parking_pos_retraction = 0.f;
|
||||||
//float m_parking_pos_retraction = 0.f;
|
float m_extra_loading_move = 0.f;
|
||||||
//float m_extra_loading_move = 0.f;
|
|
||||||
float m_bridging = 0.f;
|
float m_bridging = 0.f;
|
||||||
bool m_no_sparse_layers = false;
|
bool m_no_sparse_layers = false;
|
||||||
// BBS: remove useless config
|
bool m_set_extruder_trimpot = false;
|
||||||
//bool m_set_extruder_trimpot = false;
|
|
||||||
bool m_adhesion = true;
|
bool m_adhesion = true;
|
||||||
GCodeFlavor m_gcode_flavor;
|
GCodeFlavor m_gcode_flavor;
|
||||||
bool m_is_multiple_nozzle = false;
|
|
||||||
std::vector<unsigned int> m_normal_accels;
|
// Multi-nozzle prime-tower heating during wipe. m_is_multiple_nozzle gates the whole
|
||||||
std::vector<unsigned int> m_first_layer_normal_accels;
|
// feature; it is false for every current (single-nozzle) printer (extruder_max_nozzle_count
|
||||||
std::vector<unsigned int> m_travel_accels;
|
// defaults to 1), so the pre-heat/pre-cool path is inert and wipe-tower g-code is unchanged.
|
||||||
std::vector<unsigned int> m_first_layer_travel_accels;
|
bool m_is_multiple_nozzle = false;
|
||||||
unsigned int m_max_accels;
|
std::vector<double> m_hotend_heating_rate; // config.hotend_heating_rate (deg/s per extruder)
|
||||||
bool m_accel_to_decel_enable;
|
std::vector<int> m_physical_extruder_map; // logical extruder -> physical tool number (M104 T param)
|
||||||
float m_accel_to_decel_factor;
|
|
||||||
bool m_enable_arc_fitting = true;
|
// Per-extruder printable-height clamp. m_printable_height = config.extruder_printable_height
|
||||||
std::vector<double> m_hotend_heating_rate;
|
// (per-extruder Z limit; empty for single-extruder printers, [320,325] for H2D). m_last_layer_id
|
||||||
std::vector<double> m_hotend_cooling_rate;
|
// records, per extruder, the last wipe-tower layer that uses it. is_valid_last_layer() is gated on
|
||||||
Polygons m_shared_print_bed;
|
// m_is_multi_extruder so single-extruder wipe-tower g-code is unchanged; the clamp only bites a
|
||||||
|
// multi-extruder wipe tower whose final per-extruder layer exceeds that extruder's printable
|
||||||
|
// height (near the Z limit).
|
||||||
|
std::vector<double> m_printable_height;
|
||||||
|
std::vector<int> m_last_layer_id;
|
||||||
|
|
||||||
// Bed properties
|
// Bed properties
|
||||||
enum {
|
enum {
|
||||||
@@ -511,11 +512,10 @@ private:
|
|||||||
float m_bed_width; // width of the bed bounding box
|
float m_bed_width; // width of the bed bounding box
|
||||||
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
|
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
|
||||||
|
|
||||||
float m_first_layer_flow_ratio;
|
|
||||||
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
|
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
|
||||||
float m_nozzle_change_perimeter_width = 0.4f * Width_To_Nozzle_Ratio;
|
float m_nozzle_change_perimeter_width = 0.4f * Width_To_Nozzle_Ratio;
|
||||||
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
|
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
|
||||||
std::unordered_map<int, std::pair<float,float>> m_block_infill_gap_width; // categories to infill_gap: toolchange gap, nozzlechange gap
|
|
||||||
// Extruder specific parameters.
|
// Extruder specific parameters.
|
||||||
std::vector<FilamentParameters> m_filpar;
|
std::vector<FilamentParameters> m_filpar;
|
||||||
|
|
||||||
@@ -528,52 +528,50 @@ private:
|
|||||||
// A fill-in direction (positive Y, negative Y) alternates with each layer.
|
// A fill-in direction (positive Y, negative Y) alternates with each layer.
|
||||||
wipe_shape m_current_shape = SHAPE_NORMAL;
|
wipe_shape m_current_shape = SHAPE_NORMAL;
|
||||||
size_t m_current_tool = 0;
|
size_t m_current_tool = 0;
|
||||||
// BBS
|
// Orca: support mmu wipe tower
|
||||||
//const std::vector<std::vector<float>> wipe_volumes;
|
std::vector<std::vector<float>> wipe_volumes;
|
||||||
|
|
||||||
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
|
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
|
||||||
bool m_current_layer_finished = false;
|
bool m_current_layer_finished = false;
|
||||||
bool m_left_to_right = true;
|
bool m_left_to_right = true;
|
||||||
float m_extra_spacing = 1.f;
|
float m_extra_spacing = 1.f;
|
||||||
float m_tpu_fixed_spacing = 2;
|
float m_tpu_fixed_spacing = 2;
|
||||||
float m_max_speed = 5400.f; // the maximum printing speed on the prime tower.
|
std::vector<Vec2f> m_wall_skip_points;
|
||||||
std::vector<std::vector<Vec2f>> m_wall_skip_points;
|
|
||||||
std::map<float,Polylines> m_outer_wall;
|
std::map<float,Polylines> m_outer_wall;
|
||||||
std::vector<double> m_printable_height;
|
|
||||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||||
bool is_valid_last_layer(int tool, int layer_id, double layer_z) const;
|
|
||||||
bool m_flat_ironing=false;
|
bool m_flat_ironing=false;
|
||||||
bool m_contact_ironing = false;
|
bool m_enable_tower_interface_features=false;
|
||||||
bool m_has_filament_switcher = false;
|
bool m_enable_tower_interface_cooldown_during_tower=false;
|
||||||
float m_contact_speed = 20 * 60.f;
|
// Filament-switcher device flag + shared printable bed for the PETG pre-extrusion offset.
|
||||||
std::vector<int> m_physical_extruder_map;
|
// m_has_filament_switcher is false for the whole shipping fleet (no profile sets the key), so
|
||||||
|
// the PETG branch in get_next_pos never runs -> no change fleet-wide.
|
||||||
|
bool m_has_filament_switcher=false;
|
||||||
|
Polygons m_shared_print_bed;
|
||||||
|
bool m_prev_layer_had_interface=false;
|
||||||
|
bool m_current_layer_has_interface=false;
|
||||||
// Calculates length of extrusion line to extrude given volume
|
// Calculates length of extrusion line to extrude given volume
|
||||||
float volume_to_length(float volume, float line_width, float layer_height) const {
|
float volume_to_length(float volume, float line_width, float layer_height) const {
|
||||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||||
}
|
}
|
||||||
// Calculates volume of extrusion line
|
|
||||||
float length_to_volume(float length,float line_width, float layer_height) const
|
|
||||||
{
|
|
||||||
return std::max(0.f, length * (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
|
||||||
}
|
|
||||||
// Calculates depth for all layers and propagates them downwards
|
// Calculates depth for all layers and propagates them downwards
|
||||||
void plan_tower();
|
void plan_tower();
|
||||||
|
|
||||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||||
void make_wipe_tower_square();
|
void make_wipe_tower_square();
|
||||||
|
|
||||||
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool solid_toolchange);
|
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool interface_layer, size_t interface_tool);
|
||||||
|
|
||||||
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
|
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
|
||||||
void save_on_last_wipe();
|
void save_on_last_wipe();
|
||||||
|
|
||||||
bool is_tpu_filament(int filament_id) const;
|
bool is_tpu_filament(int filament_id) const;
|
||||||
bool is_petg_filament(int filament_id) const;
|
bool is_petg_filament(int filament_id) const;
|
||||||
bool is_need_reverse_travel(int filament, bool extruder_change) const;
|
bool is_need_reverse_travel(int filament_id, bool extruder_change) const;
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
||||||
|
|
||||||
void set_for_wipe_tower_writer(WipeTowerWriter &writer);
|
|
||||||
|
|
||||||
// to store information about tool changes for a given layer
|
// to store information about tool changes for a given layer
|
||||||
struct WipeTowerInfo{
|
struct WipeTowerInfo{
|
||||||
@@ -586,7 +584,6 @@ private:
|
|||||||
float wipe_volume;
|
float wipe_volume;
|
||||||
float wipe_length;
|
float wipe_length;
|
||||||
float nozzle_change_depth{0};
|
float nozzle_change_depth{0};
|
||||||
float nozzle_change_length{0};
|
|
||||||
// BBS
|
// BBS
|
||||||
float purge_volume;
|
float purge_volume;
|
||||||
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f, float wl = 0, float pv = 0)
|
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f, float wl = 0, float pv = 0)
|
||||||
@@ -616,7 +613,7 @@ private:
|
|||||||
// ot -1 if there is no such toolchange.
|
// ot -1 if there is no such toolchange.
|
||||||
int first_toolchange_to_nonsoluble_nonsupport(
|
int first_toolchange_to_nonsoluble_nonsupport(
|
||||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
||||||
WipeTowerInfo::ToolChange set_toolchange(int old_tool, int new_tool, float layer_height, float wipe_volume, float purge_volume,int layer_id);
|
|
||||||
void toolchange_Unload(
|
void toolchange_Unload(
|
||||||
WipeTowerWriter &writer,
|
WipeTowerWriter &writer,
|
||||||
const box_coordinates &cleaning_box,
|
const box_coordinates &cleaning_box,
|
||||||
@@ -636,10 +633,13 @@ private:
|
|||||||
WipeTowerWriter &writer,
|
WipeTowerWriter &writer,
|
||||||
const box_coordinates &cleaning_box,
|
const box_coordinates &cleaning_box,
|
||||||
float wipe_volume);
|
float wipe_volume);
|
||||||
void get_wall_skip_points(const WipeTowerInfo &layer,int layer_id);
|
void get_wall_skip_points(const WipeTowerInfo &layer);
|
||||||
void get_all_wall_skip_points();
|
|
||||||
ToolChangeResult merge_tcr(ToolChangeResult &first, ToolChangeResult &second);
|
// Per-extruder printable-height clamp (see m_printable_height). is_valid_last_layer returns
|
||||||
float get_block_gap_width(int tool, bool is_nozzlechangle = false);
|
// false only for a multi-extruder wipe tower's final per-extruder layer that exceeds that
|
||||||
|
// extruder's printable height; returns true (no clamp) in every other case.
|
||||||
|
bool is_valid_last_layer(int tool, int layer_id, double layer_z) const;
|
||||||
|
void set_nozzle_last_layer_id();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+320
-259
@@ -24,6 +24,7 @@
|
|||||||
namespace Slic3r
|
namespace Slic3r
|
||||||
{
|
{
|
||||||
|
|
||||||
|
static constexpr float flat_iron_area = 4.f;
|
||||||
constexpr float flat_iron_speed = 10.f * 60.f;
|
constexpr float flat_iron_speed = 10.f * 60.f;
|
||||||
static const double wipe_tower_wall_infill_overlap = 0.0;
|
static const double wipe_tower_wall_infill_overlap = 0.0;
|
||||||
static constexpr double WIPE_TOWER_RESOLUTION = 0.1;
|
static constexpr double WIPE_TOWER_RESOLUTION = 0.1;
|
||||||
@@ -233,6 +234,24 @@ static Polygon rounding_rectangle(Polygon& polygon, double rounding = 2., double
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::pair<bool, Vec2f> ray_intersetion_line(const Vec2f& a, const Vec2f& v1, const Vec2f& b, const Vec2f& c)
|
||||||
|
{
|
||||||
|
const Vec2f v2 = c - b;
|
||||||
|
double denom = cross2(v1, v2);
|
||||||
|
if (fabs(denom) < EPSILON)
|
||||||
|
return {false, Vec2f(0, 0)};
|
||||||
|
const Vec2f v12 = (a - b);
|
||||||
|
double nume_a = cross2(v2, v12);
|
||||||
|
double nume_b = cross2(v1, v12);
|
||||||
|
double t1 = nume_a / denom;
|
||||||
|
double t2 = nume_b / denom;
|
||||||
|
if (t1 >= 0 && t2 >= 0 && t2 <= 1.) {
|
||||||
|
// Get the intersection point.
|
||||||
|
Vec2f res = a + t1 * v1;
|
||||||
|
return std::pair<bool, Vec2f>(true, res);
|
||||||
|
}
|
||||||
|
return std::pair<bool, Vec2f>(false, Vec2f{0, 0});
|
||||||
|
}
|
||||||
static Polygon scale_polygon(const std::vector<Vec2f>& points)
|
static Polygon scale_polygon(const std::vector<Vec2f>& points)
|
||||||
{
|
{
|
||||||
Polygon res;
|
Polygon res;
|
||||||
@@ -277,7 +296,6 @@ static Polygon generate_rectange(const Line& line, coord_t offset)
|
|||||||
return poly;
|
return poly;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Straight or arc-fitted wall segment used by WipeTowerWriter2::generate_path().
|
|
||||||
struct Segment
|
struct Segment
|
||||||
{
|
{
|
||||||
Vec2f start;
|
Vec2f start;
|
||||||
@@ -288,6 +306,234 @@ struct Segment
|
|||||||
bool is_valid() const { return start.y() < end.y(); }
|
bool is_valid() const { return start.y() < end.y(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::vector<Segment> remove_points_from_segment(const Segment& segment, const std::vector<Vec2f>& skip_points, double range)
|
||||||
|
{
|
||||||
|
std::vector<Segment> result;
|
||||||
|
result.push_back(segment);
|
||||||
|
float x = segment.start.x();
|
||||||
|
|
||||||
|
for (const Vec2f& point : skip_points) {
|
||||||
|
std::vector<Segment> newResult;
|
||||||
|
for (const auto& seg : result) {
|
||||||
|
if (point.y() + range <= seg.start.y() || point.y() - range >= seg.end.y()) {
|
||||||
|
newResult.push_back(seg);
|
||||||
|
} else {
|
||||||
|
if (point.y() - range > seg.start.y()) {
|
||||||
|
newResult.push_back(Segment(Vec2f(x, seg.start.y()), Vec2f(x, point.y() - range)));
|
||||||
|
}
|
||||||
|
if (point.y() + range < seg.end.y()) {
|
||||||
|
newResult.push_back(Segment(Vec2f(x, point.y() + range), Vec2f(x, seg.end.y())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = newResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.erase(std::remove_if(result.begin(), result.end(), [](const Segment& seg) { return !seg.is_valid(); }), result.end());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IntersectionInfo
|
||||||
|
{
|
||||||
|
Vec2f pos;
|
||||||
|
int idx;
|
||||||
|
int pair_idx; // gap_pair idx
|
||||||
|
float dis_from_idx;
|
||||||
|
bool is_forward;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PointWithFlag
|
||||||
|
{
|
||||||
|
Vec2f pos;
|
||||||
|
int pair_idx; // gap_pair idx
|
||||||
|
bool is_forward;
|
||||||
|
};
|
||||||
|
static IntersectionInfo move_point_along_polygon(
|
||||||
|
const std::vector<Vec2f>& points, const Vec2f& startPoint, int startIdx, float offset, bool forward, int pair_idx)
|
||||||
|
{
|
||||||
|
float remainingDistance = offset;
|
||||||
|
IntersectionInfo res;
|
||||||
|
int mod = points.size();
|
||||||
|
if (forward) {
|
||||||
|
int next = (startIdx + 1) % mod;
|
||||||
|
remainingDistance -= (points[next] - startPoint).norm();
|
||||||
|
if (remainingDistance <= 0) {
|
||||||
|
res.idx = startIdx;
|
||||||
|
res.pos = startPoint + (points[next] - startPoint).normalized() * offset;
|
||||||
|
res.pair_idx = pair_idx;
|
||||||
|
res.dis_from_idx = (points[startIdx] - res.pos).norm();
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
for (int i = (startIdx + 1) % mod; i != startIdx; i = (i + 1) % mod) {
|
||||||
|
float segmentLength = (points[(i + 1) % mod] - points[i]).norm();
|
||||||
|
if (remainingDistance <= segmentLength) {
|
||||||
|
float ratio = remainingDistance / segmentLength;
|
||||||
|
res.idx = i;
|
||||||
|
res.pos = points[i] + ratio * (points[(i + 1) % mod] - points[i]);
|
||||||
|
res.dis_from_idx = remainingDistance;
|
||||||
|
res.pair_idx = pair_idx;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
remainingDistance -= segmentLength;
|
||||||
|
}
|
||||||
|
res.idx = (startIdx - 1 + mod) % mod;
|
||||||
|
res.pos = points[startIdx];
|
||||||
|
res.pair_idx = pair_idx;
|
||||||
|
res.dis_from_idx = (res.pos - points[res.idx]).norm();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int next = (startIdx + 1) % mod;
|
||||||
|
remainingDistance -= (points[startIdx] - startPoint).norm();
|
||||||
|
if (remainingDistance <= 0) {
|
||||||
|
res.idx = startIdx;
|
||||||
|
res.pos = startPoint - (points[next] - points[startIdx]).normalized() * offset;
|
||||||
|
res.dis_from_idx = (res.pos - points[startIdx]).norm();
|
||||||
|
res.pair_idx = pair_idx;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
for (int i = (startIdx - 1 + mod) % mod; i != startIdx; i = (i - 1 + mod) % mod) {
|
||||||
|
float segmentLength = (points[(i + 1) % mod] - points[i]).norm();
|
||||||
|
if (remainingDistance <= segmentLength) {
|
||||||
|
float ratio = remainingDistance / segmentLength;
|
||||||
|
res.idx = i;
|
||||||
|
res.pos = points[(i + 1) % mod] - ratio * (points[(i + 1) % mod] - points[i]);
|
||||||
|
res.dis_from_idx = segmentLength - remainingDistance;
|
||||||
|
res.pair_idx = pair_idx;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
remainingDistance -= segmentLength;
|
||||||
|
}
|
||||||
|
res.idx = startIdx;
|
||||||
|
res.pos = points[res.idx];
|
||||||
|
res.pair_idx = pair_idx;
|
||||||
|
res.dis_from_idx = 0;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void insert_points(std::vector<PointWithFlag>& pl, int idx, Vec2f pos, int pair_idx, bool is_forward)
|
||||||
|
{
|
||||||
|
int next = (idx + 1) % pl.size();
|
||||||
|
Vec2f pos1 = pl[idx].pos;
|
||||||
|
Vec2f pos2 = pl[next].pos;
|
||||||
|
if ((pos - pos1).squaredNorm() < EPSILON) {
|
||||||
|
pl[idx].pair_idx = pair_idx;
|
||||||
|
pl[idx].is_forward = is_forward;
|
||||||
|
} else if ((pos - pos2).squaredNorm() < EPSILON) {
|
||||||
|
pl[next].pair_idx = pair_idx;
|
||||||
|
pl[next].is_forward = is_forward;
|
||||||
|
} else {
|
||||||
|
pl.insert(pl.begin() + idx + 1, PointWithFlag{pos, pair_idx, is_forward});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Polylines remove_points_from_polygon(
|
||||||
|
const Polygon& polygon, const std::vector<Vec2f>& skip_points, double range, bool is_left, Polygon& insert_skip_pg)
|
||||||
|
{
|
||||||
|
assert(polygon.size() > 2);
|
||||||
|
Polylines result;
|
||||||
|
std::vector<PointWithFlag> new_pl; // add intersection points for gaps, where bool indicates whether it's a gap point.
|
||||||
|
std::vector<IntersectionInfo> inter_info;
|
||||||
|
Vec2f ray = is_left ? Vec2f(-1, 0) : Vec2f(1, 0);
|
||||||
|
auto polygon_box = get_extents(polygon);
|
||||||
|
Point anchor_point = is_left ? Point{polygon_box.max[0], polygon_box.min[1]} : polygon_box.min; // rd:ld
|
||||||
|
std::vector<Vec2f> points;
|
||||||
|
{
|
||||||
|
points.reserve(polygon.points.size());
|
||||||
|
int idx = polygon.closest_point_index(anchor_point);
|
||||||
|
Polyline tmp_poly = polygon.split_at_index(idx);
|
||||||
|
for (auto& p : tmp_poly)
|
||||||
|
points.push_back(unscale(p).cast<float>());
|
||||||
|
points.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < skip_points.size(); i++) {
|
||||||
|
for (int j = 0; j < points.size(); j++) {
|
||||||
|
Vec2f& p1 = points[j];
|
||||||
|
Vec2f& p2 = points[(j + 1) % points.size()];
|
||||||
|
auto [is_inter, inter_pos] = ray_intersetion_line(skip_points[i], ray, p1, p2);
|
||||||
|
if (is_inter) {
|
||||||
|
IntersectionInfo forward = move_point_along_polygon(points, inter_pos, j, range, true, i);
|
||||||
|
IntersectionInfo backward = move_point_along_polygon(points, inter_pos, j, range, false, i);
|
||||||
|
backward.is_forward = false;
|
||||||
|
forward.is_forward = true;
|
||||||
|
inter_info.push_back(backward);
|
||||||
|
inter_info.push_back(forward);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert point to new_pl
|
||||||
|
for (const auto& p : points)
|
||||||
|
new_pl.push_back({p, -1});
|
||||||
|
std::sort(inter_info.begin(), inter_info.end(), [](const IntersectionInfo& lhs, const IntersectionInfo& rhs) {
|
||||||
|
if (rhs.idx == lhs.idx)
|
||||||
|
return lhs.dis_from_idx < rhs.dis_from_idx;
|
||||||
|
return lhs.idx < rhs.idx;
|
||||||
|
});
|
||||||
|
for (int i = inter_info.size() - 1; i >= 0; i--) {
|
||||||
|
insert_points(new_pl, inter_info[i].idx, inter_info[i].pos, inter_info[i].pair_idx, inter_info[i].is_forward);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// set insert_pg for wipe_path
|
||||||
|
for (auto& p : new_pl)
|
||||||
|
insert_skip_pg.points.push_back(scaled(p.pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
int beg = 0;
|
||||||
|
bool skip = true;
|
||||||
|
int i = beg;
|
||||||
|
Polyline pl;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (skip || new_pl[i].pair_idx == -1) {
|
||||||
|
pl.points.push_back(scaled(new_pl[i].pos));
|
||||||
|
i = (i + 1) % new_pl.size();
|
||||||
|
skip = false;
|
||||||
|
} else {
|
||||||
|
if (!pl.points.empty()) {
|
||||||
|
pl.points.push_back(scaled(new_pl[i].pos));
|
||||||
|
result.push_back(pl);
|
||||||
|
pl.points.clear();
|
||||||
|
}
|
||||||
|
int left = new_pl[i].pair_idx;
|
||||||
|
int j = (i + 1) % new_pl.size();
|
||||||
|
while (j != beg && new_pl[j].pair_idx != left) {
|
||||||
|
if (new_pl[j].pair_idx != -1 && !new_pl[j].is_forward)
|
||||||
|
left = new_pl[j].pair_idx;
|
||||||
|
j = (j + 1) % new_pl.size();
|
||||||
|
}
|
||||||
|
i = j;
|
||||||
|
skip = true;
|
||||||
|
}
|
||||||
|
} while (i != beg);
|
||||||
|
|
||||||
|
if (!pl.points.empty()) {
|
||||||
|
if (new_pl[i].pair_idx == -1)
|
||||||
|
pl.points.push_back(scaled(new_pl[i].pos));
|
||||||
|
result.push_back(pl);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Polylines contrust_gap_for_skip_points(
|
||||||
|
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon)
|
||||||
|
{
|
||||||
|
if (skip_points.empty()) {
|
||||||
|
insert_skip_polygon = polygon;
|
||||||
|
return Polylines{to_polyline(polygon)};
|
||||||
|
}
|
||||||
|
bool is_left = false;
|
||||||
|
const auto& pt = skip_points.front();
|
||||||
|
if (abs(pt.x()) < wt_width / 2.f) {
|
||||||
|
is_left = true;
|
||||||
|
}
|
||||||
|
return remove_points_from_polygon(polygon, skip_points, gap_length, is_left, insert_skip_polygon);
|
||||||
|
};
|
||||||
|
|
||||||
static Polygon generate_rectange_polygon(const Vec2f& wt_box_min, const Vec2f& wt_box_max)
|
static Polygon generate_rectange_polygon(const Vec2f& wt_box_min, const Vec2f& wt_box_max)
|
||||||
{
|
{
|
||||||
Polygon res;
|
Polygon res;
|
||||||
@@ -999,12 +1245,6 @@ WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool WipeTower2::use_gap_wall(const PrintConfig& config)
|
|
||||||
{
|
|
||||||
// The cone wall has its own fully separate generator with no gap machinery.
|
|
||||||
return config.prime_tower_skip_points.value && config.wipe_tower_wall_type.value != wtwCone;
|
|
||||||
}
|
|
||||||
|
|
||||||
WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& default_region_config,int plate_idx, Vec3d plate_origin, const std::vector<std::vector<float>>& wiping_matrix, size_t initial_tool) :
|
WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& default_region_config,int plate_idx, Vec3d plate_origin, const std::vector<std::vector<float>>& wiping_matrix, size_t initial_tool) :
|
||||||
m_semm(config.single_extruder_multi_material.value),
|
m_semm(config.single_extruder_multi_material.value),
|
||||||
m_enable_filament_ramming(config.enable_filament_ramming.value),
|
m_enable_filament_ramming(config.enable_filament_ramming.value),
|
||||||
@@ -1032,7 +1272,7 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
|
|||||||
m_rib_width(config.wipe_tower_rib_width),
|
m_rib_width(config.wipe_tower_rib_width),
|
||||||
m_extra_rib_length(config.wipe_tower_extra_rib_length),
|
m_extra_rib_length(config.wipe_tower_extra_rib_length),
|
||||||
m_wall_type((int)config.wipe_tower_wall_type),
|
m_wall_type((int)config.wipe_tower_wall_type),
|
||||||
m_use_gap_wall(use_gap_wall(config)),
|
m_flat_ironing(config.prime_tower_flat_ironing.value),
|
||||||
m_enable_tower_interface_features(config.enable_tower_interface_features.value),
|
m_enable_tower_interface_features(config.enable_tower_interface_features.value),
|
||||||
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value)
|
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value)
|
||||||
{
|
{
|
||||||
@@ -1102,7 +1342,6 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config)
|
|||||||
m_filpar[idx].is_soluble = (idx != size_t(m_wipe_tower_filament - 1));
|
m_filpar[idx].is_soluble = (idx != size_t(m_wipe_tower_filament - 1));
|
||||||
else
|
else
|
||||||
m_filpar[idx].is_soluble = config.filament_soluble.get_at(idx);
|
m_filpar[idx].is_soluble = config.filament_soluble.get_at(idx);
|
||||||
m_filpar[idx].is_support = config.filament_is_support.get_at(idx);
|
|
||||||
m_filpar[idx].temperature = config.nozzle_temperature.get_at(idx);
|
m_filpar[idx].temperature = config.nozzle_temperature.get_at(idx);
|
||||||
m_filpar[idx].first_layer_temperature = config.nozzle_temperature_initial_layer.get_at(idx);
|
m_filpar[idx].first_layer_temperature = config.nozzle_temperature_initial_layer.get_at(idx);
|
||||||
m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx);
|
m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx);
|
||||||
@@ -1239,11 +1478,11 @@ std::vector<WipeTower::ToolChangeResult> WipeTower2::prime(
|
|||||||
toolchange_Load(writer, cleaning_box); // Prime the tool.
|
toolchange_Load(writer, cleaning_box); // Prime the tool.
|
||||||
if (idx_tool + 1 == tools.size()) {
|
if (idx_tool + 1 == tools.size()) {
|
||||||
// Last tool should not be unloaded, but it should be wiped enough to become of a pure color.
|
// Last tool should not be unloaded, but it should be wiped enough to become of a pure color.
|
||||||
toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool], false, true);
|
toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool], false);
|
||||||
} else {
|
} else {
|
||||||
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
|
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
|
||||||
//writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200);
|
//writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200);
|
||||||
toolchange_Wipe(writer, cleaning_box , 20.f, false, true);
|
toolchange_Wipe(writer, cleaning_box , 20.f, false);
|
||||||
WipeTower::box_coordinates box = cleaning_box;
|
WipeTower::box_coordinates box = cleaning_box;
|
||||||
box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width);
|
box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width);
|
||||||
toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
|
toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
|
||||||
@@ -1286,9 +1525,8 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
|||||||
|
|
||||||
float wipe_area = 0.f;
|
float wipe_area = 0.f;
|
||||||
float wipe_volume = 0.f;
|
float wipe_volume = 0.f;
|
||||||
float ramming_depth = 0.f;
|
|
||||||
bool interface_layer = m_enable_tower_interface_features && m_current_layer_has_interface;
|
bool interface_layer = m_enable_tower_interface_features && m_current_layer_has_interface;
|
||||||
|
|
||||||
// Finds this toolchange info
|
// Finds this toolchange info
|
||||||
if (tool != (unsigned int)(-1))
|
if (tool != (unsigned int)(-1))
|
||||||
{
|
{
|
||||||
@@ -1296,7 +1534,6 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
|||||||
if ( b.new_tool == tool ) {
|
if ( b.new_tool == tool ) {
|
||||||
wipe_volume = b.wipe_volume;
|
wipe_volume = b.wipe_volume;
|
||||||
wipe_area = b.required_depth;
|
wipe_area = b.required_depth;
|
||||||
ramming_depth = b.ramming_depth;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1334,9 +1571,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
|||||||
writer.speed_override_backup();
|
writer.speed_override_backup();
|
||||||
writer.speed_override(100);
|
writer.speed_override(100);
|
||||||
|
|
||||||
// On a boundary wipe start this enters at the wall gap on the first wipe row;
|
Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed);
|
||||||
// toolchange_Unload() then climbs back up to the ram band along the box interior.
|
|
||||||
Vec2f initial_position = toolchange_entry_pos(m_depth_traversed, ramming_depth, is_first_layer());
|
|
||||||
writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
|
writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
|
||||||
|
|
||||||
// Increase the extruder driver current to allow fast ramming.
|
// Increase the extruder driver current to allow fast ramming.
|
||||||
@@ -1345,11 +1580,6 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
|||||||
|
|
||||||
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
|
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
|
||||||
if (tool != (unsigned int)-1){ // This is not the last change.
|
if (tool != (unsigned int)-1){ // This is not the last change.
|
||||||
// Without a ram — or with the boundary wipe start, where the ram band is
|
|
||||||
// quantized to whole rows — the box is planned as whole wipe rows; the wipe
|
|
||||||
// then fills it completely so adjacent purge blocks stay contiguous. Uses the
|
|
||||||
// old tool (m_current_tool before toolchange_Change).
|
|
||||||
const bool fill_box = !tool_ramming_enabled(m_current_tool) || boundary_wipe_start_enabled(m_current_tool);
|
|
||||||
auto new_tool_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature;
|
auto new_tool_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature;
|
||||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
|
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
|
||||||
(is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature),
|
(is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature),
|
||||||
@@ -1372,7 +1602,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
|||||||
writer.extrude_explicit(target_x, writer.y(), pre_len, 600.f);
|
writer.extrude_explicit(target_x, writer.y(), pre_len, 600.f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toolchange_Wipe(writer, cleaning_box, wipe_volume, interface_layer, false, fill_box); // Wipe the newly loaded filament until the end of the assigned wipe area.
|
toolchange_Wipe(writer, cleaning_box, wipe_volume, interface_layer); // Wipe the newly loaded filament until the end of the assigned wipe area.
|
||||||
if (interface_layer) {
|
if (interface_layer) {
|
||||||
int interface_temp = m_filpar[tool].interface_print_temperature;
|
int interface_temp = m_filpar[tool].interface_print_temperature;
|
||||||
if (!m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp)
|
if (!m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp)
|
||||||
@@ -1427,20 +1657,11 @@ void WipeTower2::toolchange_Unload(
|
|||||||
float remaining = xr - xl ; // keeps track of distance to the next turnaround
|
float remaining = xr - xl ; // keeps track of distance to the next turnaround
|
||||||
float e_done = 0; // measures E move done from each segment
|
float e_done = 0; // measures E move done from each segment
|
||||||
|
|
||||||
const bool do_ramming = tool_ramming_enabled(m_current_tool);
|
// Orca: Do ramming when SEMM and ramming is enabled or when multi tool head when ramming is enabled on the multi tool.
|
||||||
|
const bool do_ramming = (m_semm && m_enable_filament_ramming) || m_filpar[m_current_tool].multitool_ramming;
|
||||||
const bool cold_ramming = m_is_mk4mmu3;
|
const bool cold_ramming = m_is_mk4mmu3;
|
||||||
// Orca: see set_toolchange() — quantized ram band + wipe restart at the boundary.
|
|
||||||
const bool boundary_wipe_start = boundary_wipe_start_enabled(m_current_tool);
|
|
||||||
float planned_ramming_depth = 0.f;
|
|
||||||
if (boundary_wipe_start && m_layer_info != m_plan.end())
|
|
||||||
for (const auto& tch : m_layer_info->tool_changes)
|
|
||||||
if (tch.old_tool == m_current_tool) { planned_ramming_depth = tch.ramming_depth; break; }
|
|
||||||
|
|
||||||
if (do_ramming) {
|
if (do_ramming) {
|
||||||
if (boundary_wipe_start)
|
|
||||||
// The entry sits at the wall gap on the first wipe row past the reserved
|
|
||||||
// ram band; step inward first, then move to the band clear of the wall.
|
|
||||||
writer.travel(Vec2f(ramming_start_pos.x(), writer.y()));
|
|
||||||
writer.travel(ramming_start_pos); // move to starting position
|
writer.travel(ramming_start_pos); // move to starting position
|
||||||
if (! m_is_mk4mmu3)
|
if (! m_is_mk4mmu3)
|
||||||
writer.disable_linear_advance();
|
writer.disable_linear_advance();
|
||||||
@@ -1451,8 +1672,7 @@ void WipeTower2::toolchange_Unload(
|
|||||||
writer.set_position(ramming_start_pos);
|
writer.set_position(ramming_start_pos);
|
||||||
|
|
||||||
// if the ending point of the ram would end up in mid air, align it with the end of the wipe tower:
|
// if the ending point of the ram would end up in mid air, align it with the end of the wipe tower:
|
||||||
// (with a boundary wipe start the band is quantized to whole rows below, so no phase alignment is needed)
|
if (do_ramming && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) {
|
||||||
if (do_ramming && !boundary_wipe_start && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) {
|
|
||||||
|
|
||||||
// this is y of the center of previous sparse infill border
|
// this is y of the center of previous sparse infill border
|
||||||
float sparse_beginning_y = 0.f;
|
float sparse_beginning_y = 0.f;
|
||||||
@@ -1511,28 +1731,6 @@ void WipeTower2::toolchange_Unload(
|
|||||||
e_done = 0;
|
e_done = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Orca: quantize the ram band up to the whole reserved rows (BBL quantizes the
|
|
||||||
// old-tool purge the same way) so no unprinted void is left between the band and
|
|
||||||
// the wipe restarting at the boundary below it.
|
|
||||||
if (planned_ramming_depth > 0.f) {
|
|
||||||
const int reserved_rows = std::max(1, int(std::round(planned_ramming_depth / y_step)));
|
|
||||||
const float last_row_y = ramming_start_pos.y() + (reserved_rows - 1) * y_step;
|
|
||||||
// Same bead model as the ramming segments above: E per mm of ram line.
|
|
||||||
const float e_per_mm = 1.f / (volume_to_length(1.f, line_width, m_layer_height) * filament_area());
|
|
||||||
const float fill_feed = m_filpar[m_current_tool].ramming_speed.empty() ? 3000.f :
|
|
||||||
60.f * volume_to_length(m_filpar[m_current_tool].ramming_speed.back(), line_width, m_layer_height);
|
|
||||||
while (true) {
|
|
||||||
const float target_x = m_left_to_right ? xr : xl;
|
|
||||||
if (std::abs(target_x - writer.x()) > WT_EPSILON)
|
|
||||||
writer.ram(writer.x(), target_x, 0.f, 0.f, e_per_mm * std::abs(target_x - writer.x()), fill_feed);
|
|
||||||
if (writer.y() + 0.5f * y_step > last_row_y)
|
|
||||||
break;
|
|
||||||
writer.travel(writer.x(), writer.y() + y_step, 7200);
|
|
||||||
m_left_to_right = !m_left_to_right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2f end_of_ramming(writer.x(),writer.y());
|
Vec2f end_of_ramming(writer.x(),writer.y());
|
||||||
writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier
|
writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier
|
||||||
|
|
||||||
@@ -1640,27 +1838,10 @@ void WipeTower2::toolchange_Unload(
|
|||||||
// this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
|
// this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
|
||||||
// the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
|
// the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
|
||||||
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f + m_perimeter_width);
|
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f + m_perimeter_width);
|
||||||
if (planned_ramming_depth > 0.f) {
|
if (do_ramming)
|
||||||
// Orca: restart the wipe at the left-edge boundary on a fresh row below the
|
|
||||||
// quantized ram band so the entry scrub always runs at the wall gap (BBL keeps
|
|
||||||
// CP_TOOLCHANGE_WIPE starting at a box corner the same way). Same lattice
|
|
||||||
// formula as the no-ram branch below, offset by the ram band.
|
|
||||||
writer.travel(Vec2f(ramming_start_pos.x(),
|
|
||||||
cleaning_box.ld.y() + m_depth_traversed +
|
|
||||||
wipe_start_offset_after_ram(planned_ramming_depth, is_first_layer()) + m_perimeter_width), 2400.f);
|
|
||||||
m_left_to_right = true;
|
|
||||||
}
|
|
||||||
else if (do_ramming)
|
|
||||||
writer.travel(pos, 2400.f);
|
writer.travel(pos, 2400.f);
|
||||||
else {
|
else
|
||||||
// Orca: with no ram printed there is no ramming geometry to align with. Start the
|
writer.set_position(pos);
|
||||||
// first wipe row so the purge row lattice continues across the block boundary
|
|
||||||
// (previous box's last row top edge sits at its box top): with the planned depth
|
|
||||||
// of rows * dy, the last row's top edge then lands exactly on this box's top and
|
|
||||||
// no blank band is left between adjacent purge blocks.
|
|
||||||
writer.set_position(Vec2f(end_of_ramming.x(),
|
|
||||||
cleaning_box.ld.y() + m_depth_traversed + wipe_start_offset_after_ram(0.f, is_first_layer()) + m_perimeter_width));
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.resume_preview()
|
writer.resume_preview()
|
||||||
.flush_planner_queue();
|
.flush_planner_queue();
|
||||||
@@ -1739,9 +1920,7 @@ void WipeTower2::toolchange_Wipe(
|
|||||||
WipeTowerWriter2 &writer,
|
WipeTowerWriter2 &writer,
|
||||||
const WipeTower::box_coordinates &cleaning_box,
|
const WipeTower::box_coordinates &cleaning_box,
|
||||||
float wipe_volume,
|
float wipe_volume,
|
||||||
bool interface_layer,
|
bool interface_layer)
|
||||||
bool priming,
|
|
||||||
bool fill_box)
|
|
||||||
{
|
{
|
||||||
// Increase flow on first layer, slow down print.
|
// Increase flow on first layer, slow down print.
|
||||||
writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.18f : 1.f))
|
writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.18f : 1.f))
|
||||||
@@ -1750,7 +1929,7 @@ void WipeTower2::toolchange_Wipe(
|
|||||||
const float& xr = cleaning_box.rd.x();
|
const float& xr = cleaning_box.rd.x();
|
||||||
|
|
||||||
writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow);
|
writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow);
|
||||||
const float line_width = wipe_line_width();
|
const float line_width = m_perimeter_width * m_extra_flow;
|
||||||
writer.change_analyzer_line_width(line_width);
|
writer.change_analyzer_line_width(line_width);
|
||||||
|
|
||||||
// Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
|
// Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
|
||||||
@@ -1758,7 +1937,7 @@ void WipeTower2::toolchange_Wipe(
|
|||||||
// wipe until the end of the assigned area.
|
// wipe until the end of the assigned area.
|
||||||
|
|
||||||
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow;
|
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow;
|
||||||
float dy = wipe_row_spacing(is_first_layer()); // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow.
|
float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow.
|
||||||
// All the calculations in all other places take the spacing into account for all the layers.
|
// All the calculations in all other places take the spacing into account for all the layers.
|
||||||
|
|
||||||
// If spare layers are excluded->if 1 or less toolchange has been done, it must be sill the first layer, too.So slow down.
|
// If spare layers are excluded->if 1 or less toolchange has been done, it must be sill the first layer, too.So slow down.
|
||||||
@@ -1771,6 +1950,9 @@ void WipeTower2::toolchange_Wipe(
|
|||||||
m_left_to_right = !m_left_to_right;
|
m_left_to_right = !m_left_to_right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool do_ironing = m_flat_ironing && (!interface_layer || !m_enable_tower_interface_features);
|
||||||
|
const float ironing_area = m_filpar[m_current_tool].tower_ironing_area;
|
||||||
|
|
||||||
// now the wiping itself:
|
// now the wiping itself:
|
||||||
for (int i = 0; true; ++i) {
|
for (int i = 0; true; ++i) {
|
||||||
if (i!=0) {
|
if (i!=0) {
|
||||||
@@ -1781,45 +1963,22 @@ void WipeTower2::toolchange_Wipe(
|
|||||||
}
|
}
|
||||||
|
|
||||||
float traversed_x = writer.x();
|
float traversed_x = writer.x();
|
||||||
|
|
||||||
// BBS gap wall: iron the first few mm of the purge, then drag the retracted nozzle
|
|
||||||
// back out through the wall gap and scrub it with a dry spiral centred on the entry
|
|
||||||
// point so the toolchange start blob is not left on the wall (same sequence as the
|
|
||||||
// BBL tower's toolchange_wipe_new; the spiral self-disables when the filament's
|
|
||||||
// tower ironing area is 0). WT2's entry gap always sits at the left-edge entry
|
|
||||||
// point, so only iron when the purge actually starts there heading right (in-place
|
|
||||||
// toolchangers do; SEMM ram/cooling moves leave the nozzle mid-box, far from any gap).
|
|
||||||
if (i == 0 && m_use_gap_wall && !interface_layer && !priming && m_left_to_right &&
|
|
||||||
writer.x() - xl < 2.5f * line_width) {
|
|
||||||
float ironing_length = 3.f;
|
|
||||||
if (xr - writer.x() < ironing_length)
|
|
||||||
ironing_length = std::max(xr - writer.x(), 0.f);
|
|
||||||
const float retract_length = m_filpar[m_current_tool].retract_length;
|
|
||||||
const float retract_speed = m_filpar[m_current_tool].retract_speed * 60.f;
|
|
||||||
writer.extrude(writer.x() + ironing_length, writer.y(), wipe_speed);
|
|
||||||
writer.retract(retract_length, retract_speed);
|
|
||||||
writer.travel(writer.x() - 1.5f * ironing_length, writer.y(), 600.f);
|
|
||||||
writer.travel(writer.x() + 0.5f * ironing_length, writer.y(), 240.f);
|
|
||||||
const Vec2f iron_end(writer.x() + ironing_length, writer.y());
|
|
||||||
writer.spiral_flat_ironing(writer.pos(), m_filpar[m_current_tool].tower_ironing_area, m_perimeter_width, flat_iron_speed);
|
|
||||||
writer.travel(iron_end, wipe_speed);
|
|
||||||
writer.retract(-retract_length, retract_speed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_left_to_right)
|
if (m_left_to_right)
|
||||||
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||||
else
|
else
|
||||||
writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||||
|
|
||||||
|
if (i == 0 && do_ironing && ironing_area > 0.f) {
|
||||||
|
writer.travel(writer.x(), writer.y(), 600.f);
|
||||||
|
writer.spiral_flat_ironing(writer.pos(), ironing_area, m_perimeter_width, 10.f * 60.f);
|
||||||
|
}
|
||||||
|
|
||||||
if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*line_width)
|
if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*line_width)
|
||||||
break; // in case next line would not fit
|
break; // in case next line would not fit
|
||||||
|
|
||||||
traversed_x -= writer.x();
|
traversed_x -= writer.x();
|
||||||
x_to_wipe -= std::abs(traversed_x);
|
x_to_wipe -= std::abs(traversed_x);
|
||||||
// Orca: with no ram printed the box was planned as whole wipe rows; fill it
|
if (x_to_wipe < WT_EPSILON) {
|
||||||
// completely (quantizing the purge up to the planned rows) so the next block
|
|
||||||
// can start right above it without a blank band in between.
|
|
||||||
if (!fill_box && x_to_wipe < WT_EPSILON) {
|
|
||||||
writer.travel(m_left_to_right ? xl + 1.5f*line_width : xr - 1.5f*line_width, writer.y(), 7200);
|
writer.travel(m_left_to_right ? xl + 1.5f*line_width : xr - 1.5f*line_width, writer.y(), 7200);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1951,7 +2110,7 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
|
|||||||
poly = generate_support_cone_wall(writer, wt_box, feedrate, infill_cone, spacing);
|
poly = generate_support_cone_wall(writer, wt_box, feedrate, infill_cone, spacing);
|
||||||
} else {
|
} else {
|
||||||
WipeTower::box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
|
WipeTower::box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
|
||||||
poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int)wtwRib, true);
|
poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int)wtwRib, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// brim (first layer only)
|
// brim (first layer only)
|
||||||
@@ -2069,32 +2228,15 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// this is an actual toolchange - let's calculate depth to reserve on the wipe tower
|
// this is an actual toolchange - let's calculate depth to reserve on the wipe tower
|
||||||
const bool first_layer_plan = (m_plan.size() - 1) == m_first_layer_idx;
|
float width = m_wipe_tower_width - 3*m_perimeter_width;
|
||||||
m_plan.back().tool_changes.push_back(set_toolchange(old_tool, new_tool, layer_height_par, wipe_volume, first_layer_plan));
|
float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
|
||||||
}
|
|
||||||
|
|
||||||
WipeTower2::WipeTowerInfo::ToolChange WipeTower2::set_toolchange(size_t old_tool, size_t new_tool, float layer_height, float wipe_volume, bool first_layer_plan)
|
|
||||||
{
|
|
||||||
float width = m_wipe_tower_width - 3*m_perimeter_width;
|
|
||||||
float length_to_extrude = volume_to_length((m_semm ? 0.25f : m_filpar[old_tool].multitool_ramming_time) * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
|
|
||||||
m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
|
m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
|
||||||
layer_height);
|
layer_height_par);
|
||||||
// Orca: Reserve ramming depth only when toolchange_Unload() will actually ram,
|
// Orca: Set ramming depth to 0 if ramming is disabled.
|
||||||
// otherwise the unprinted reservation leaves blank bands between the purge boxes.
|
float ramming_depth = m_enable_filament_ramming ? ((int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming) : 0;
|
||||||
const bool do_ramming = tool_ramming_enabled(old_tool);
|
float first_wipe_line = - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width);
|
||||||
// Orca: with the gap wall on a multi-tool printer the ram band is quantized up to
|
|
||||||
// the whole reserved rows and the wipe restarts at the left-edge boundary on a
|
|
||||||
// fresh row below it (BBL parity: the old-tool purge is whole rows and the wipe
|
|
||||||
// always starts at the box corner, where the entry scrub runs).
|
|
||||||
const bool boundary_wipe_start = boundary_wipe_start_enabled(old_tool);
|
|
||||||
float ramming_depth = do_ramming ? ((int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming) : 0;
|
|
||||||
// first_wipe_line rides for free on the last (partially used) ramming row, which
|
|
||||||
// is already covered by ramming_depth. Without ramming that row does not exist
|
|
||||||
// (and with a boundary wipe start the ram band is quantized to whole rows), so
|
|
||||||
// the whole wipe volume needs reserved wiping depth.
|
|
||||||
float first_wipe_line = (do_ramming && !boundary_wipe_start) ? - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width) : 0.f;
|
|
||||||
|
|
||||||
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height);
|
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par);
|
||||||
|
|
||||||
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
||||||
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
||||||
@@ -2103,11 +2245,12 @@ WipeTower2::WipeTowerInfo::ToolChange WipeTower2::set_toolchange(size_t old_tool
|
|||||||
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
||||||
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
||||||
// ORCA: and first-layer purge segments do not leave visible gaps.
|
// ORCA: and first-layer purge segments do not leave visible gaps.
|
||||||
|
const bool first_layer_plan = (m_plan.size() - 1) == m_first_layer_idx;
|
||||||
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
||||||
|
|
||||||
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||||
|
|
||||||
return WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume);
|
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2145,64 +2288,49 @@ void WipeTower2::save_on_last_wipe()
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Which toolchange will finish_layer extrusions be subtracted from?
|
// Which toolchange will finish_layer extrusions be subtracted from?
|
||||||
int idx = first_toolchange_to_nonsoluble_nonsupport(m_layer_info->tool_changes);
|
int idx = first_toolchange_to_nonsoluble(m_layer_info->tool_changes);
|
||||||
|
|
||||||
if (idx == -1) {
|
if (idx == -1) {
|
||||||
// In this case, finish_layer will be called at the very beginning.
|
// In this case, finish_layer will be called at the very beginning.
|
||||||
finish_layer().total_extrusion_length_in_plane();
|
finish_layer().total_extrusion_length_in_plane();
|
||||||
}
|
}
|
||||||
|
|
||||||
const float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
|
|
||||||
auto recompute_toolchange = [this, width](WipeTowerInfo::ToolChange& toolchange, float volume_to_save) {
|
|
||||||
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
|
|
||||||
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
|
|
||||||
|
|
||||||
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
|
||||||
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
|
||||||
// ORCA: m_extra_flow * m_perimeter_width, while later layers use
|
|
||||||
// ORCA: m_extra_spacing_wipe * m_perimeter_width.
|
|
||||||
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
|
||||||
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
|
||||||
// ORCA: and first-layer purge segments do not leave visible gaps.
|
|
||||||
const bool first_layer_plan = size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx;
|
|
||||||
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
|
||||||
|
|
||||||
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
|
||||||
|
|
||||||
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
|
|
||||||
toolchange.wipe_volume = volume_left_to_wipe;
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i=0; i<int(m_layer_info->tool_changes.size()); ++i) {
|
for (int i=0; i<int(m_layer_info->tool_changes.size()); ++i) {
|
||||||
auto& toolchange = m_layer_info->tool_changes[i];
|
auto& toolchange = m_layer_info->tool_changes[i];
|
||||||
tool_change(toolchange.new_tool);
|
tool_change(toolchange.new_tool);
|
||||||
|
|
||||||
if (i == idx) {
|
if (i == idx) {
|
||||||
recompute_toolchange(toolchange, length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height));
|
float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
|
||||||
} else if (toolchange.wipe_volume < m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower) {
|
|
||||||
// Keep filament_minimal_purge_on_wipe_tower enforced for toolchanges that get
|
float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height);
|
||||||
// no finish-layer saving, e.g. a support/soluble filament skipped as the
|
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
|
||||||
// finish filament above. Recomputing only when the clamp binds leaves all
|
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
|
||||||
// other toolchanges with their planned values bit-for-bit.
|
|
||||||
recompute_toolchange(toolchange, 0.f);
|
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
||||||
|
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
||||||
|
// ORCA: m_extra_flow * m_perimeter_width, while later layers use
|
||||||
|
// ORCA: m_extra_spacing_wipe * m_perimeter_width.
|
||||||
|
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
||||||
|
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
||||||
|
// ORCA: and first-layer purge segments do not leave visible gaps.
|
||||||
|
const bool first_layer_plan = size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx;
|
||||||
|
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
||||||
|
|
||||||
|
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||||
|
|
||||||
|
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
|
||||||
|
toolchange.wipe_volume = volume_left_to_wipe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Return the index of the toolchange whose new filament should print the layer's
|
// Return index of first toolchange that switches to non-soluble extruder
|
||||||
// finish extrusions (sparse infill + wall + brim), or -1 to print them with the
|
// ot -1 if there is no such toolchange.
|
||||||
// layer's incoming filament before any toolchange happens.
|
int WipeTower2::first_toolchange_to_nonsoluble(
|
||||||
// Like WipeTower::first_toolchange_to_nonsoluble_nonsupport(): support and soluble
|
|
||||||
// filaments bond poorly to the material printed on top of them, so they must not
|
|
||||||
// print the tower's shell when another filament is available on the layer.
|
|
||||||
int WipeTower2::first_toolchange_to_nonsoluble_nonsupport(
|
|
||||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const
|
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const
|
||||||
{
|
{
|
||||||
if (tool_changes.empty())
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// If a specific wipe tower filament is forced, use it to decide where to finish the layer.
|
// If a specific wipe tower filament is forced, use it to decide where to finish the layer.
|
||||||
if (m_wipe_tower_filament > 0) {
|
if (m_wipe_tower_filament > 0) {
|
||||||
for (size_t idx = 0; idx < tool_changes.size(); ++idx) {
|
for (size_t idx = 0; idx < tool_changes.size(); ++idx) {
|
||||||
@@ -2211,19 +2339,8 @@ int WipeTower2::first_toolchange_to_nonsoluble_nonsupport(
|
|||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
// Orca: allow calculation of the required depth and wipe volume for soluble toolchanges as well.
|
||||||
auto is_wall_filament = [this](size_t tool) {
|
return tool_changes.empty() ? -1 : 0;
|
||||||
return !m_filpar[tool].is_soluble && !m_filpar[tool].is_support;
|
|
||||||
};
|
|
||||||
for (size_t idx = 0; idx < tool_changes.size(); ++idx)
|
|
||||||
if (is_wall_filament(tool_changes[idx].new_tool))
|
|
||||||
return idx;
|
|
||||||
if (is_wall_filament(tool_changes.front().old_tool))
|
|
||||||
return -1;
|
|
||||||
// Only support/soluble filaments on this layer: keep the first toolchange so the
|
|
||||||
// finish-layer saving and the minimal-purge clamp still apply to it (Orca depth
|
|
||||||
// and wipe volume accounting, see save_on_last_wipe()).
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
|
static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
|
||||||
@@ -2246,24 +2363,6 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Precompute, for every plan layer, the wall openings ("skip points") at each toolchange's
|
|
||||||
// entry position, like WipeTower::get_all_wall_skip_points(). toolchange_entry_pos()
|
|
||||||
// reproduces from the finalized plan where tool_change() will start, so each gap coincides
|
|
||||||
// with the entry travel's target (tcr.start_pos, pre-rotation frame). BBL parity: the gap
|
|
||||||
// sits at the CP_TOOLCHANGE_WIPE start row, never at the ram band.
|
|
||||||
void WipeTower2::compute_wall_skip_points()
|
|
||||||
{
|
|
||||||
m_wall_skip_points.assign(m_plan.size(), std::vector<Vec2f>());
|
|
||||||
for (size_t layer_id = 0; layer_id < m_plan.size(); ++layer_id) {
|
|
||||||
float depth_traversed = 0.f;
|
|
||||||
for (const auto& toolchange : m_plan[layer_id].tool_changes) {
|
|
||||||
m_wall_skip_points[layer_id].emplace_back(
|
|
||||||
toolchange_entry_pos(depth_traversed, toolchange.ramming_depth, layer_id == m_first_layer_idx));
|
|
||||||
depth_traversed += toolchange.required_depth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
|
// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
|
||||||
// Resulting ToolChangeResults are appended into vector "result"
|
// Resulting ToolChangeResults are appended into vector "result"
|
||||||
void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
|
void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
|
||||||
@@ -2279,41 +2378,12 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (m_wall_type == (int)wtwRib) {
|
m_rib_length = std::max({m_rib_length, sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width)});
|
||||||
// Rib wall: force a square tower like WipeTower::plan_tower_new(), ignoring the
|
|
||||||
// configured prime_tower_width (the GUI greys it out in rib mode). The planned depths
|
|
||||||
// already include the extra-spacing factors, so sqrt(depth * width) preserves the
|
|
||||||
// purge area. Replan every toolchange for the new width, then re-derive the depths.
|
|
||||||
float max_depth = 0.f;
|
|
||||||
for (const auto& current_plan : m_plan)
|
|
||||||
max_depth = std::max(max_depth, current_plan.depth);
|
|
||||||
if (max_depth > EPSILON) {
|
|
||||||
m_wipe_tower_width = align_ceil(std::sqrt(max_depth * m_wipe_tower_width), m_perimeter_width);
|
|
||||||
for (size_t idx = 0; idx < m_plan.size(); ++idx)
|
|
||||||
for (auto& toolchange : m_plan[idx].tool_changes)
|
|
||||||
toolchange = set_toolchange(toolchange.old_tool, toolchange.new_tool,
|
|
||||||
m_plan[idx].height, toolchange.wipe_volume,
|
|
||||||
idx == m_first_layer_idx);
|
|
||||||
plan_tower();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like WipeTower::plan_tower_new(): extend the ribs instead of the tower when the
|
|
||||||
// tower is smaller than the height-based stability minimum.
|
|
||||||
const float min_depth = WipeTower::get_limit_depth_by_height(m_wipe_tower_height);
|
|
||||||
if (m_wipe_tower_depth + EPSILON < min_depth)
|
|
||||||
m_rib_length = std::max(m_rib_length, min_depth * (float)std::sqrt(2.f));
|
|
||||||
}
|
|
||||||
|
|
||||||
const float diagonal = std::sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width);
|
|
||||||
m_rib_length = std::max(m_rib_length, diagonal);
|
|
||||||
m_rib_length += m_extra_rib_length;
|
m_rib_length += m_extra_rib_length;
|
||||||
m_rib_length = std::max(diagonal, m_rib_length); // a negative extra length must not shrink the ribs below the diagonal
|
m_rib_length = std::max(0.f, m_rib_length);
|
||||||
m_rib_width = std::min(m_rib_width, std::min(m_wipe_tower_depth, m_wipe_tower_width) /
|
m_rib_width = std::min(m_rib_width, std::min(m_wipe_tower_depth, m_wipe_tower_width) /
|
||||||
2.f); // Ensure that the rib wall of the wipetower are attached to the infill.
|
2.f); // Ensure that the rib wall of the wipetower are attached to the infill.
|
||||||
|
|
||||||
if (m_use_gap_wall)
|
|
||||||
compute_wall_skip_points();
|
|
||||||
|
|
||||||
m_layer_info = m_plan.begin();
|
m_layer_info = m_plan.begin();
|
||||||
m_current_height = 0.f;
|
m_current_height = 0.f;
|
||||||
|
|
||||||
@@ -2340,7 +2410,7 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
|
|||||||
if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width)
|
if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width)
|
||||||
m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f;
|
m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f;
|
||||||
|
|
||||||
int idx = first_toolchange_to_nonsoluble_nonsupport(layer.tool_changes);
|
int idx = first_toolchange_to_nonsoluble(layer.tool_changes);
|
||||||
WipeTower::ToolChangeResult finish_layer_tcr;
|
WipeTower::ToolChangeResult finish_layer_tcr;
|
||||||
|
|
||||||
if (idx == -1) {
|
if (idx == -1) {
|
||||||
@@ -2433,7 +2503,8 @@ Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2&
|
|||||||
double feedrate,
|
double feedrate,
|
||||||
bool first_layer,
|
bool first_layer,
|
||||||
bool rib_wall,
|
bool rib_wall,
|
||||||
bool extrude_perimeter)
|
bool extrude_perimeter,
|
||||||
|
bool skip_points)
|
||||||
{
|
{
|
||||||
|
|
||||||
float retract_length = m_filpar[m_current_tool].retract_length;
|
float retract_length = m_filpar[m_current_tool].retract_length;
|
||||||
@@ -2453,28 +2524,18 @@ Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2&
|
|||||||
if (!extrude_perimeter)
|
if (!extrude_perimeter)
|
||||||
return wall_polygon;
|
return wall_polygon;
|
||||||
|
|
||||||
if (m_use_gap_wall) {
|
if (skip_points) {
|
||||||
// Cut the wall open at each toolchange's entry (see compute_wall_skip_points()).
|
result_wall = contrust_gap_for_skip_points(wall_polygon, std::vector<Vec2f>(), m_wipe_tower_width, 2.5 * m_perimeter_width,
|
||||||
// The vector is empty during the save_on_last_wipe planning passes, which therefore
|
|
||||||
// measure the un-gapped wall — same approximation as the BBL tower.
|
|
||||||
static const std::vector<Vec2f> no_skip_points;
|
|
||||||
const size_t layer_id = size_t(m_layer_info - m_plan.begin());
|
|
||||||
const std::vector<Vec2f>& layer_skip_points =
|
|
||||||
layer_id < m_wall_skip_points.size() ? m_wall_skip_points[layer_id] : no_skip_points;
|
|
||||||
result_wall = contrust_gap_for_skip_points(wall_polygon, layer_skip_points, m_wipe_tower_width, 2.5 * m_perimeter_width,
|
|
||||||
insert_skip_polygon);
|
insert_skip_polygon);
|
||||||
} else {
|
} else {
|
||||||
result_wall.push_back(to_polyline(wall_polygon));
|
result_wall.push_back(to_polyline(wall_polygon));
|
||||||
insert_skip_polygon = wall_polygon;
|
insert_skip_polygon = wall_polygon;
|
||||||
}
|
}
|
||||||
writer.generate_path(result_wall, feedrate, retract_length, retract_speed, m_used_fillet);
|
writer.generate_path(result_wall, feedrate, retract_length, retract_speed, m_used_fillet);
|
||||||
// Tower-local shift that puts the rib wall's protruding first-layer min corner at the
|
//if (m_cur_layer_id == 0) {
|
||||||
// configured tower position, like WipeTower::generate_support_wall_new(). Measured on
|
// BoundingBox bbox = get_extents(result_wall);
|
||||||
// the un-gapped outline so a wall gap cannot shift the tower.
|
// m_rib_offset = Vec2f(-unscaled<float>(bbox.min.x()), -unscaled<float>(bbox.min.y()));
|
||||||
if (rib_wall && is_first_layer()) {
|
//}
|
||||||
BoundingBox bbox = get_extents(insert_skip_polygon);
|
|
||||||
m_rib_offset = Vec2f(-unscaled<float>(bbox.min.x()), -unscaled<float>(bbox.min.y()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return insert_skip_polygon;
|
return insert_skip_polygon;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,10 +34,6 @@ public:
|
|||||||
bool is_finish,
|
bool is_finish,
|
||||||
bool is_contact = false) const;
|
bool is_contact = false) const;
|
||||||
|
|
||||||
// Whether this print cuts wall openings ("skip points") at the toolchange entries.
|
|
||||||
// Shared with the entry routing in GCode.cpp so the router and the tower agree.
|
|
||||||
static bool use_gap_wall(const PrintConfig& config);
|
|
||||||
|
|
||||||
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
||||||
// y -- y coordinates of wipe tower in mm ( left bottom corner )
|
// y -- y coordinates of wipe tower in mm ( left bottom corner )
|
||||||
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
|
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
|
||||||
@@ -73,9 +69,9 @@ public:
|
|||||||
const float brim = m_wipe_tower_brim_width_real;
|
const float brim = m_wipe_tower_brim_width_real;
|
||||||
return BoundingBoxf(Vec2d(-brim, -brim), Vec2d(double(m_wipe_tower_width) + brim, double(m_wipe_tower_depth) + brim));
|
return BoundingBoxf(Vec2d(-brim, -brim), Vec2d(double(m_wipe_tower_width) + brim, double(m_wipe_tower_depth) + brim));
|
||||||
}
|
}
|
||||||
// Tower-local shift that puts the rib wall's first-layer min corner at the configured
|
// WT2 doesn't currently compute a rib-origin compensation like WipeTower (m_rib_offset),
|
||||||
// tower position, like WipeTower::get_rib_offset(). Zero unless the rib wall is used.
|
// so expose a zero offset for consistency purposes (to maintain API parity).
|
||||||
Vec2f get_rib_offset() const { return m_rib_offset; }
|
Vec2f get_rib_offset() const { return Vec2f::Zero(); }
|
||||||
float get_rib_width() const { return m_rib_width; }
|
float get_rib_width() const { return m_rib_width; }
|
||||||
float get_rib_length() const { return m_rib_length; }
|
float get_rib_length() const { return m_rib_length; }
|
||||||
|
|
||||||
@@ -153,7 +149,6 @@ public:
|
|||||||
struct FilamentParameters {
|
struct FilamentParameters {
|
||||||
std::string material = "PLA";
|
std::string material = "PLA";
|
||||||
bool is_soluble = false;
|
bool is_soluble = false;
|
||||||
bool is_support = false;
|
|
||||||
int temperature = 0;
|
int temperature = 0;
|
||||||
int first_layer_temperature = 0;
|
int first_layer_temperature = 0;
|
||||||
int interface_print_temperature = 0;
|
int interface_print_temperature = 0;
|
||||||
@@ -225,6 +220,7 @@ private:
|
|||||||
float m_perimeter_speed = 0.f;
|
float m_perimeter_speed = 0.f;
|
||||||
float m_first_layer_speed = 0.f;
|
float m_first_layer_speed = 0.f;
|
||||||
size_t m_first_layer_idx = size_t(-1);
|
size_t m_first_layer_idx = size_t(-1);
|
||||||
|
bool m_flat_ironing = false;
|
||||||
bool m_enable_tower_interface_features = false;
|
bool m_enable_tower_interface_features = false;
|
||||||
bool m_enable_tower_interface_cooldown_during_tower = false;
|
bool m_enable_tower_interface_cooldown_during_tower = false;
|
||||||
bool m_prev_layer_had_interface = false;
|
bool m_prev_layer_had_interface = false;
|
||||||
@@ -235,12 +231,6 @@ private:
|
|||||||
float m_rib_width = 10;
|
float m_rib_width = 10;
|
||||||
float m_extra_rib_length = 0;
|
float m_extra_rib_length = 0;
|
||||||
float m_rib_length = 0;
|
float m_rib_length = 0;
|
||||||
Vec2f m_rib_offset = Vec2f::Zero();
|
|
||||||
bool m_use_gap_wall = false;
|
|
||||||
// Per plan layer, each toolchange's entry position (tower-local, un-shifted frame):
|
|
||||||
// where the wall is cut open so the entry travel does not cross the printed wall.
|
|
||||||
// Filled by compute_wall_skip_points() once the plan is final.
|
|
||||||
std::vector<std::vector<Vec2f>> m_wall_skip_points;
|
|
||||||
|
|
||||||
bool m_enable_arc_fitting = false;
|
bool m_enable_arc_fitting = false;
|
||||||
|
|
||||||
@@ -288,37 +278,6 @@ private:
|
|||||||
|
|
||||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||||
|
|
||||||
// Purge row lattice of toolchange_Wipe(): row pitch and extrusion width.
|
|
||||||
float wipe_row_spacing(bool first_layer) const { return (first_layer ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; }
|
|
||||||
float wipe_line_width() const { return m_perimeter_width * m_extra_flow; }
|
|
||||||
|
|
||||||
// Whether toolchange_Unload() rams this (old) tool out.
|
|
||||||
bool tool_ramming_enabled(size_t tool) const { return (m_semm && m_enable_filament_ramming) || m_filpar[tool].multitool_ramming; }
|
|
||||||
// Whether the wipe restarts at the box boundary on a fresh row below the quantized
|
|
||||||
// ram band after ramming this (old) tool out (multi-tool gap wall; SEMM keeps the
|
|
||||||
// stock continue-from-ram-end behavior).
|
|
||||||
bool boundary_wipe_start_enabled(size_t tool) const { return tool_ramming_enabled(tool) && !m_semm && m_use_gap_wall; }
|
|
||||||
|
|
||||||
// With a boundary wipe start the wipe begins on a fresh row below the quantized ram
|
|
||||||
// band. Y offset from the box start to that first wipe row.
|
|
||||||
float wipe_start_offset_after_ram(float ramming_depth, bool first_layer) const
|
|
||||||
{
|
|
||||||
return ramming_depth + wipe_row_spacing(first_layer) - (m_perimeter_width + wipe_line_width()) / 2.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tower-local entry position of a toolchange whose box starts depth_traversed into
|
|
||||||
// the layer: the box corner, moved down to the first wipe row when the plan gives
|
|
||||||
// it a boundary wipe start (ramming_depth > 0 iff the unload rams). tool_change()
|
|
||||||
// enters here and compute_wall_skip_points() cuts the wall gap here, so the routed
|
|
||||||
// entry, the gap and the wipe scrub all share one opening.
|
|
||||||
Vec2f toolchange_entry_pos(float depth_traversed, float ramming_depth, bool first_layer) const
|
|
||||||
{
|
|
||||||
Vec2f pos(m_perimeter_width / 2.f, m_perimeter_width / 2.f + depth_traversed);
|
|
||||||
if (!m_semm && m_use_gap_wall && ramming_depth > 0.f)
|
|
||||||
pos.y() += wipe_start_offset_after_ram(ramming_depth, first_layer);
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculates extrusion flow needed to produce required line width for given layer height
|
// Calculates extrusion flow needed to produce required line width for given layer height
|
||||||
float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow
|
float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow
|
||||||
{
|
{
|
||||||
@@ -369,10 +328,9 @@ private:
|
|||||||
std::vector<float> m_used_filament_length;
|
std::vector<float> m_used_filament_length;
|
||||||
std::vector<std::pair<float, std::vector<float>>> m_used_filament_length_until_layer;
|
std::vector<std::pair<float, std::vector<float>>> m_used_filament_length_until_layer;
|
||||||
|
|
||||||
// Return the index of the toolchange whose new filament should print the layer's
|
// Return index of first toolchange that switches to non-soluble extruder
|
||||||
// finish extrusions (sparse infill + wall + brim), or -1 to print them with the
|
// ot -1 if there is no such toolchange.
|
||||||
// layer's incoming filament before any toolchange happens.
|
int first_toolchange_to_nonsoluble(
|
||||||
int first_toolchange_to_nonsoluble_nonsupport(
|
|
||||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
||||||
|
|
||||||
void toolchange_Unload(
|
void toolchange_Unload(
|
||||||
@@ -395,9 +353,7 @@ private:
|
|||||||
WipeTowerWriter2 &writer,
|
WipeTowerWriter2 &writer,
|
||||||
const WipeTower::box_coordinates &cleaning_box,
|
const WipeTower::box_coordinates &cleaning_box,
|
||||||
float wipe_volume,
|
float wipe_volume,
|
||||||
bool interface_layer,
|
bool interface_layer);
|
||||||
bool priming = false,
|
|
||||||
bool fill_box = false);
|
|
||||||
|
|
||||||
|
|
||||||
Polygon generate_support_rib_wall(WipeTowerWriter2& writer,
|
Polygon generate_support_rib_wall(WipeTowerWriter2& writer,
|
||||||
@@ -405,7 +361,8 @@ private:
|
|||||||
double feedrate,
|
double feedrate,
|
||||||
bool first_layer,
|
bool first_layer,
|
||||||
bool rib_wall,
|
bool rib_wall,
|
||||||
bool extrude_perimeter);
|
bool extrude_perimeter,
|
||||||
|
bool skip_points);
|
||||||
|
|
||||||
Polygon generate_support_cone_wall(
|
Polygon generate_support_cone_wall(
|
||||||
WipeTowerWriter2& writer,
|
WipeTowerWriter2& writer,
|
||||||
@@ -415,12 +372,6 @@ private:
|
|||||||
float spacing);
|
float spacing);
|
||||||
|
|
||||||
Polygon generate_rib_polygon(const WipeTower::box_coordinates& wt_box);
|
Polygon generate_rib_polygon(const WipeTower::box_coordinates& wt_box);
|
||||||
|
|
||||||
void compute_wall_skip_points();
|
|
||||||
|
|
||||||
// Computes the depth reserved for a toolchange (shared by plan_toolchange() and the
|
|
||||||
// rib-wall square-tower replanning in generate()).
|
|
||||||
WipeTowerInfo::ToolChange set_toolchange(size_t old_tool, size_t new_tool, float layer_height, float wipe_volume, bool first_layer_plan);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -129,13 +129,13 @@ public:
|
|||||||
std::vector<PathFittingData> fitting_result;
|
std::vector<PathFittingData> fitting_result;
|
||||||
//BBS: simplify points by arc fitting
|
//BBS: simplify points by arc fitting
|
||||||
void simplify_by_fitting_arc(double tolerance);
|
void simplify_by_fitting_arc(double tolerance);
|
||||||
void reset_to_linear_move();
|
//BBS:
|
||||||
//BBS:
|
|
||||||
Polylines equally_spaced_lines(double distance) const;
|
Polylines equally_spaced_lines(double distance) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void append_fitting_result_after_append_points();
|
void append_fitting_result_after_append_points();
|
||||||
void append_fitting_result_after_append_polyline(const Polyline& src);
|
void append_fitting_result_after_append_polyline(const Polyline& src);
|
||||||
|
void reset_to_linear_move();
|
||||||
bool split_fitting_result_before_index(const size_t index, Point &new_endpoint, std::vector<PathFittingData>& data) const;
|
bool split_fitting_result_before_index(const size_t index, Point &new_endpoint, std::vector<PathFittingData>& data) const;
|
||||||
bool split_fitting_result_after_index(const size_t index, Point &new_startpoint, std::vector<PathFittingData>& data) const;
|
bool split_fitting_result_after_index(const size_t index, Point &new_startpoint, std::vector<PathFittingData>& data) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -147,6 +147,17 @@ Semver get_version_from_json(std::string file_path)
|
|||||||
return Semver();
|
return Semver();
|
||||||
//throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file_path, err.what()));
|
//throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file_path, err.what()));
|
||||||
}
|
}
|
||||||
|
catch(...) {
|
||||||
|
return Semver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_vendor_cache_version(const std::string& json_path)
|
||||||
|
{
|
||||||
|
// The version a vendor's cache is stamped with. A profile without a parsable
|
||||||
|
// version cannot be judged for staleness, so it is simply never cached.
|
||||||
|
const Semver ver = get_version_from_json(json_path);
|
||||||
|
return ver.valid() ? ver.to_string() : std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
//BBS: add a function to load the key-values from xxx.json
|
//BBS: add a function to load the key-values from xxx.json
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
#include "Semver.hpp"
|
#include "Semver.hpp"
|
||||||
#include "ProjectTask.hpp"
|
#include "ProjectTask.hpp"
|
||||||
|
|
||||||
|
#include <cereal/access.hpp>
|
||||||
|
|
||||||
//BBS: change system directories
|
//BBS: change system directories
|
||||||
#define PRESET_SYSTEM_DIR "system"
|
#define PRESET_SYSTEM_DIR "system"
|
||||||
#define PRESET_USER_DIR "user"
|
#define PRESET_USER_DIR "user"
|
||||||
@@ -114,6 +116,10 @@ extern Semver get_version_from_json(std::string file_path);
|
|||||||
//BBS: add a function to load the key-values from xxx.json
|
//BBS: add a function to load the key-values from xxx.json
|
||||||
extern int get_values_from_json(std::string file_path, std::vector<std::string>& keys, std::map<std::string, std::string>& key_values);
|
extern int get_values_from_json(std::string file_path, std::vector<std::string>& keys, std::map<std::string, std::string>& key_values);
|
||||||
|
|
||||||
|
// Returns the version a vendor JSON's preset cache is stamped with: its Semver
|
||||||
|
// string, or an empty string when the profile carries no usable version.
|
||||||
|
extern std::string get_vendor_cache_version(const std::string& json_path);
|
||||||
|
|
||||||
extern ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree);
|
extern ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree);
|
||||||
|
|
||||||
extern void extend_default_config_length(DynamicPrintConfig& config, const bool set_nil_to_default, const DynamicPrintConfig& defaults);
|
extern void extend_default_config_length(DynamicPrintConfig& config, const bool set_nil_to_default, const DynamicPrintConfig& defaults);
|
||||||
@@ -131,6 +137,10 @@ public:
|
|||||||
PrinterVariant() {}
|
PrinterVariant() {}
|
||||||
PrinterVariant(const std::string &name) : name(name) {}
|
PrinterVariant(const std::string &name) : name(name) {}
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
|
// All fields, declaration order — keep in sync; bump CACHE_VERSION on change.
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive& ar) { ar(name); } // PrinterVariant
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrinterModel {
|
struct PrinterModel {
|
||||||
@@ -139,7 +149,7 @@ public:
|
|||||||
std::string name;
|
std::string name;
|
||||||
//BBS: this is internal id for the printer. Currently only used for searching in database
|
//BBS: this is internal id for the printer. Currently only used for searching in database
|
||||||
std::string model_id;
|
std::string model_id;
|
||||||
PrinterTechnology technology;
|
PrinterTechnology technology = ptFFF;
|
||||||
std::string family;
|
std::string family;
|
||||||
std::vector<PrinterVariant> variants;
|
std::vector<PrinterVariant> variants;
|
||||||
std::vector<std::string> default_materials;
|
std::vector<std::string> default_materials;
|
||||||
@@ -162,6 +172,17 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PrinterVariant* variant(const std::string &name) const { return const_cast<PrinterModel*>(this)->variant(name); }
|
const PrinterVariant* variant(const std::string &name) const { return const_cast<PrinterModel*>(this)->variant(name); }
|
||||||
|
|
||||||
|
// All fields, declaration order — keep in sync; bump CACHE_VERSION on change.
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive& ar) // PrinterModel
|
||||||
|
{
|
||||||
|
ar(id, name, model_id, technology, family, variants, default_materials,
|
||||||
|
not_support_bed_types, bed_model, bed_texture, image_bed_type,
|
||||||
|
bottom_texture_end_name, use_double_extruder_default_texture,
|
||||||
|
bottom_texture_rect, bottom_texture_rect_longer, middle_texture_rect,
|
||||||
|
hotend_model);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
std::vector<PrinterModel> models;
|
std::vector<PrinterModel> models;
|
||||||
|
|
||||||
@@ -173,6 +194,14 @@ public:
|
|||||||
|
|
||||||
bool valid() const { return ! name.empty() && ! id.empty() && config_version.valid(); }
|
bool valid() const { return ! name.empty() && ! id.empty() && config_version.valid(); }
|
||||||
|
|
||||||
|
// All fields, declaration order — keep in sync; bump CACHE_VERSION on change.
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive& ar) // VendorProfile
|
||||||
|
{
|
||||||
|
ar(name, id, config_version, config_update_url, changelog_url,
|
||||||
|
models, default_filaments, default_sla_materials);
|
||||||
|
}
|
||||||
|
|
||||||
// Load VendorProfile from an ini file.
|
// Load VendorProfile from an ini file.
|
||||||
// If `load_all` is false, only the header with basic info (name, version, URLs) is loaded.
|
// If `load_all` is false, only the header with basic info (name, version, URLs) is loaded.
|
||||||
static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true);
|
static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true);
|
||||||
@@ -425,12 +454,30 @@ public:
|
|||||||
|
|
||||||
// BBS: move constructor to public
|
// BBS: move constructor to public
|
||||||
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
|
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
|
||||||
|
// Default constructor is public so cereal can default-construct elements when
|
||||||
protected:
|
// deserializing std::vector<Preset> (std::allocator is not a cereal::access friend).
|
||||||
Preset() = default;
|
Preset() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
friend class PresetCollection;
|
friend class PresetCollection;
|
||||||
friend class PresetBundle;
|
friend class PresetBundle;
|
||||||
|
friend class cereal::access;
|
||||||
|
|
||||||
|
// Hand-written cereal serialization for the per-vendor binary cache.
|
||||||
|
// Lists every data member except the two raw pointers:
|
||||||
|
// - loading_substitutions: transient parse state, never cached
|
||||||
|
// - vendor: re-pointed on load from the vendor id stored alongside each preset
|
||||||
|
// Keep this list in sync with the member declarations, in declaration order;
|
||||||
|
// bump CACHE_VERSION in PresetBundle.cpp when it changes.
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive& ar)
|
||||||
|
{
|
||||||
|
ar(type, is_default, is_external, is_system, is_visible, is_dirty,
|
||||||
|
is_compatible, is_project_embedded, name, file, loaded, config,
|
||||||
|
alias, renamed_from, m_excluded_from, m_from_orca_filament_lib,
|
||||||
|
bundle_id, version, ini_str, setting_id, filament_id, user_id,
|
||||||
|
base_id, sync_info, description, updated_time, key_values);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
bool is_compatible_with_print (const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer);
|
bool is_compatible_with_print (const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer);
|
||||||
|
|||||||
+613
-48
@@ -1,7 +1,18 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <chrono>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include "PresetBundle.hpp"
|
#include "PresetBundle.hpp"
|
||||||
|
|
||||||
|
#include <boost/crc.hpp>
|
||||||
|
#include <boost/iostreams/device/array.hpp>
|
||||||
|
#include <boost/iostreams/stream.hpp>
|
||||||
|
#include <cereal/archives/binary.hpp>
|
||||||
|
#include <cereal/types/map.hpp>
|
||||||
|
#include <cereal/types/set.hpp>
|
||||||
|
#include <cereal/types/string.hpp>
|
||||||
|
#include <cereal/types/vector.hpp>
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
@@ -564,6 +575,8 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
|
|||||||
|
|
||||||
//BBS: add config related logs
|
//BBS: add config related logs
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, substitution_rule %1%, preferred printer_model_id %2%")%substitution_rule%preferred_selection.printer_model_id;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, substitution_rule %1%, preferred printer_model_id %2%")%substitution_rule%preferred_selection.printer_model_id;
|
||||||
|
const auto startup_t0 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
//BBS: change system config to json
|
//BBS: change system config to json
|
||||||
std::tie(substitutions, errors_cummulative) = this->load_system_presets_from_json(substitution_rule);
|
std::tie(substitutions, errors_cummulative) = this->load_system_presets_from_json(substitution_rule);
|
||||||
|
|
||||||
@@ -589,6 +602,12 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
|
|||||||
|
|
||||||
set_calibrate_printer("");
|
set_calibrate_printer("");
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto total_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now() - startup_t0).count();
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "PresetBundle: all presets loaded in " << total_ms << " ms";
|
||||||
|
}
|
||||||
|
|
||||||
//BBS: add config related logs
|
//BBS: add config related logs
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" finished, returned substitutions %1%")%substitutions.size();
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" finished, returned substitutions %1%")%substitutions.size();
|
||||||
return substitutions;
|
return substitutions;
|
||||||
@@ -1001,6 +1020,8 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
|||||||
bundles.m_bundles.clear();
|
bundles.m_bundles.clear();
|
||||||
bundles.WriteUnlock();
|
bundles.WriteUnlock();
|
||||||
|
|
||||||
|
const auto user_load_t0 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
// Load bundle metadata from _local directory first
|
// Load bundle metadata from _local directory first
|
||||||
fs::path local_dir(folder / PRESET_LOCAL_DIR);
|
fs::path local_dir(folder / PRESET_LOCAL_DIR);
|
||||||
if (fs::exists(local_dir)) {
|
if (fs::exists(local_dir)) {
|
||||||
@@ -1019,7 +1040,6 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
|||||||
metadata.filament_presets.clear();
|
metadata.filament_presets.clear();
|
||||||
metadata.printer_presets.clear();
|
metadata.printer_presets.clear();
|
||||||
|
|
||||||
// Add the profiles
|
|
||||||
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||||
metadata.print_presets.push_back(preset.name);
|
metadata.print_presets.push_back(preset.name);
|
||||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id));
|
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id));
|
||||||
@@ -1056,7 +1076,6 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
|||||||
metadata.printer_presets.clear();
|
metadata.printer_presets.clear();
|
||||||
metadata.is_subscribed = true;
|
metadata.is_subscribed = true;
|
||||||
|
|
||||||
// Load presets from bundle (same logic as __local__)
|
|
||||||
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||||
metadata.print_presets.push_back(preset.name);
|
metadata.print_presets.push_back(preset.name);
|
||||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id));
|
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id));
|
||||||
@@ -1077,34 +1096,41 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// BBS do not load sla_print
|
// BBS do not load sla_print
|
||||||
// BBS: change directoties by design
|
// BBS: change directories by design
|
||||||
try {
|
|
||||||
std::string print_selected_preset_name = prints.get_selected_preset().name;
|
{
|
||||||
this->prints.load_presets(dir_user_presets, PRESET_PRINT_NAME, substitutions, substitution_rule);
|
const auto json_t0 = std::chrono::steady_clock::now();
|
||||||
prints.select_preset_by_name(print_selected_preset_name, false);
|
try {
|
||||||
} catch (const std::runtime_error &err) {
|
std::string sel = prints.get_selected_preset().name;
|
||||||
errors_cummulative += err.what();
|
this->prints.load_presets(dir_user_presets, PRESET_PRINT_NAME, substitutions, substitution_rule);
|
||||||
|
prints.select_preset_by_name(sel, false);
|
||||||
|
} catch (const std::runtime_error& err) { errors_cummulative += err.what(); }
|
||||||
|
try {
|
||||||
|
std::string sel = filaments.get_selected_preset().name;
|
||||||
|
this->filaments.load_presets(dir_user_presets, PRESET_FILAMENT_NAME, substitutions, substitution_rule);
|
||||||
|
filaments.select_preset_by_name(sel, false);
|
||||||
|
} catch (const std::runtime_error& err) { errors_cummulative += err.what(); }
|
||||||
|
try {
|
||||||
|
std::string sel = printers.get_selected_preset().name;
|
||||||
|
this->printers.load_presets(dir_user_presets, PRESET_PRINTER_NAME, substitutions, substitution_rule);
|
||||||
|
printers.select_preset_by_name(sel, false);
|
||||||
|
} catch (const std::runtime_error& err) { errors_cummulative += err.what(); }
|
||||||
|
if (!errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative);
|
||||||
|
|
||||||
|
const auto json_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now() - json_t0).count();
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "PresetBundle: user presets loaded from JSON in " << json_ms << " ms";
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
std::string filament_selected_preset_name = filaments.get_selected_preset().name;
|
{
|
||||||
this->filaments.load_presets(dir_user_presets, PRESET_FILAMENT_NAME, substitutions, substitution_rule);
|
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
filaments.select_preset_by_name(filament_selected_preset_name, false);
|
std::chrono::steady_clock::now() - user_load_t0).count();
|
||||||
} catch (const std::runtime_error &err) {
|
BOOST_LOG_TRIVIAL(info) << "PresetBundle: user + bundle presets loaded in " << ms << " ms";
|
||||||
errors_cummulative += err.what();
|
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
std::string printer_selected_preset_name = printers.get_selected_preset().name;
|
|
||||||
this->printers.load_presets(dir_user_presets, PRESET_PRINTER_NAME, substitutions, substitution_rule);
|
|
||||||
printers.select_preset_by_name(printer_selected_preset_name, false);
|
|
||||||
} catch (const std::runtime_error &err) {
|
|
||||||
errors_cummulative += err.what();
|
|
||||||
}
|
|
||||||
if (!errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative);
|
|
||||||
this->update_multi_material_filament_presets();
|
this->update_multi_material_filament_presets();
|
||||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||||
|
|
||||||
set_calibrate_printer("");
|
set_calibrate_printer("");
|
||||||
|
|
||||||
return PresetsConfigSubstitutions();
|
return PresetsConfigSubstitutions();
|
||||||
@@ -1210,13 +1236,10 @@ bool PresetBundle::apply_vendor_config(
|
|||||||
: std::map<std::string, std::string>();
|
: std::map<std::string, std::string>();
|
||||||
|
|
||||||
// Find vendors that need installation
|
// Find vendors that need installation
|
||||||
const auto vendor_dir = (fs::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
|
|
||||||
|
|
||||||
std::vector<std::string> install_bundles;
|
std::vector<std::string> install_bundles;
|
||||||
for (const auto &it : new_vendors) {
|
for (const auto &it : new_vendors) {
|
||||||
if (it.second.size() > 0) {
|
if (it.second.size() > 0) {
|
||||||
auto vendor_file = vendor_dir / (it.first + ".json");
|
if (!is_vendor_installed(it.first)) {
|
||||||
if (!fs::exists(vendor_file)) {
|
|
||||||
install_bundles.emplace_back(it.first);
|
install_bundles.emplace_back(it.first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2225,6 +2248,186 @@ void PresetBundle::remove_users_preset(AppConfig &config, std::map<std::string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The version of the filament library in effect: the installed profile, the
|
||||||
|
// installed cache that stands in for one, or — with nothing installed yet — the
|
||||||
|
// profile that ships. A vendor cache embeds filaments resolved against the
|
||||||
|
// library, so it is judged against this and never left unconstrained; a cache
|
||||||
|
// built against an older library would otherwise slip through unnoticed.
|
||||||
|
static std::string effective_lib_version(const boost::filesystem::path& installed_dir)
|
||||||
|
{
|
||||||
|
const std::string lib(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
||||||
|
// The library ships as a cache like every other vendor, so neither directory
|
||||||
|
// is guaranteed to hold its profile; ask each for whichever form it has.
|
||||||
|
for (const boost::filesystem::path& dir : {installed_dir, boost::filesystem::path(resources_dir()) / "profiles"}) {
|
||||||
|
if (boost::filesystem::exists(dir / (lib + ".json")))
|
||||||
|
return get_vendor_cache_version((dir / (lib + ".json")).string());
|
||||||
|
const std::string stamped = PresetBundle::peek_vendor_cache_version((dir / (lib + ".opc")).string(), lib);
|
||||||
|
if (! stamped.empty())
|
||||||
|
return stamped;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_vendor_installed(const std::string& vendor)
|
||||||
|
{
|
||||||
|
const boost::filesystem::path dir = boost::filesystem::path(data_dir()) / PRESET_SYSTEM_DIR;
|
||||||
|
return boost::filesystem::exists(dir / (vendor + ".json")) || boost::filesystem::exists(dir / (vendor + ".opc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Semver installed_vendor_version(const std::string& vendor)
|
||||||
|
{
|
||||||
|
const boost::filesystem::path dir = boost::filesystem::path(data_dir()) / PRESET_SYSTEM_DIR;
|
||||||
|
const boost::filesystem::path json = dir / (vendor + ".json");
|
||||||
|
if (boost::filesystem::exists(json))
|
||||||
|
return get_version_from_json(json.string());
|
||||||
|
const auto ver = Semver::parse(PresetBundle::peek_vendor_cache_version((dir / (vendor + ".opc")).string(), vendor));
|
||||||
|
return ver ? *ver : Semver();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_installed_vendor(const std::string& vendor)
|
||||||
|
{
|
||||||
|
const boost::filesystem::path dir = boost::filesystem::path(data_dir()) / PRESET_SYSTEM_DIR;
|
||||||
|
boost::filesystem::remove(dir / (vendor + ".json"));
|
||||||
|
boost::filesystem::remove(dir / (vendor + ".opc"));
|
||||||
|
if (boost::filesystem::exists(dir / vendor))
|
||||||
|
boost::filesystem::remove_all(dir / vendor);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string> vendor_names_in(const boost::filesystem::path& dir)
|
||||||
|
{
|
||||||
|
std::set<std::string> names;
|
||||||
|
for (auto& dir_entry : boost::filesystem::directory_iterator(dir)) {
|
||||||
|
const auto& path = dir_entry.path();
|
||||||
|
if (Slic3r::is_json_file(path.string()) || path.extension() == ".opc")
|
||||||
|
names.insert(path.stem().string());
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A vendor's preset cache is the whole of its installation: it carries the presets,
|
||||||
|
// the vendor profile and the version they were built at, so where one ships nothing
|
||||||
|
// else needs copying. Unless the profile beside it claims a newer version — a cache
|
||||||
|
// generated before that profile was bumped is out of date, and a cache that cannot
|
||||||
|
// be read is no installation at all — and the vendor is installed the way it was
|
||||||
|
// before caches existed, as its profile and the preset JSONs it points at. Returns
|
||||||
|
// the version the cache is stamped with, invalid when it is not the form to install.
|
||||||
|
static Semver installable_cache_version(const boost::filesystem::path& dir, const std::string& vendor)
|
||||||
|
{
|
||||||
|
const auto cache_ver = Semver::parse(PresetBundle::peek_vendor_cache_version((dir / (vendor + ".opc")).string(), vendor));
|
||||||
|
if (! cache_ver)
|
||||||
|
return Semver::invalid();
|
||||||
|
const Semver profile_ver = get_version_from_json((dir / (vendor + ".json")).string());
|
||||||
|
return profile_ver.valid() && *cache_ver < profile_ver ? Semver::invalid() : *cache_ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
Semver resource_vendor_version(const std::string& vendor)
|
||||||
|
{
|
||||||
|
const boost::filesystem::path dir = boost::filesystem::path(resources_dir()) / "profiles";
|
||||||
|
const Semver ver = installable_cache_version(dir, vendor);
|
||||||
|
return ver.valid() ? ver : get_version_from_json((dir / (vendor + ".json")).string());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool install_vendor_bundles_from_resources(
|
||||||
|
const std::vector<std::string>& bundle_names,
|
||||||
|
const std::string& resource_subdir,
|
||||||
|
const std::string& data_subdir)
|
||||||
|
{
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
|
||||||
|
fs::path rsrc_path = fs::path(Slic3r::resources_dir()) / resource_subdir;
|
||||||
|
fs::path vendor_path = fs::path(Slic3r::data_dir()) / data_subdir;
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Installing " << bundle_names.size() << " bundles from resources...";
|
||||||
|
|
||||||
|
for (const auto &bundle : bundle_names) {
|
||||||
|
try {
|
||||||
|
// Install the JSON file
|
||||||
|
auto path_in_rsrc = (rsrc_path / bundle).replace_extension(".json");
|
||||||
|
auto path_in_vendors = (vendor_path / bundle).replace_extension(".json");
|
||||||
|
auto cache_in_rsrc = (rsrc_path / bundle).replace_extension(".opc");
|
||||||
|
auto cache_in_vendors = (vendor_path / bundle).replace_extension(".opc");
|
||||||
|
|
||||||
|
// Either form of the vendor will do: a build may ship it as a cache alone.
|
||||||
|
if (!fs::exists(path_in_rsrc) && !fs::exists(cache_in_rsrc)) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "Bundle not found in resources: " << bundle;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create target directory if needed
|
||||||
|
if (!fs::exists(vendor_path))
|
||||||
|
fs::create_directories(vendor_path);
|
||||||
|
|
||||||
|
std::string error_message;
|
||||||
|
bool installed_cache = false;
|
||||||
|
if (installable_cache_version(rsrc_path, bundle).valid()) {
|
||||||
|
installed_cache = copy_file(cache_in_rsrc.string(), cache_in_vendors.string(), error_message, false) == CopyFileResult::SUCCESS;
|
||||||
|
if (! installed_cache)
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "Failed to copy " << bundle << ".opc: " << error_message;
|
||||||
|
} else {
|
||||||
|
boost::system::error_code ec;
|
||||||
|
fs::remove(cache_in_vendors, ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! installed_cache) {
|
||||||
|
CopyFileResult cfr = copy_file(path_in_rsrc.string(), path_in_vendors.string(), error_message, false);
|
||||||
|
if (cfr != CopyFileResult::SUCCESS) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Failed to copy " << bundle << ".json: " << error_message;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Left in place, an earlier install's profile would shadow the cache.
|
||||||
|
boost::system::error_code ec;
|
||||||
|
fs::remove(path_in_vendors, ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the vendor directory (if it exists)
|
||||||
|
auto dir_in_rsrc = rsrc_path / bundle;
|
||||||
|
auto dir_in_vendors = vendor_path / bundle;
|
||||||
|
|
||||||
|
// Whatever is installed came from an earlier version of this vendor and
|
||||||
|
// would be parsed in place of the one being installed now.
|
||||||
|
if (fs::exists(dir_in_vendors))
|
||||||
|
fs::remove_all(dir_in_vendors);
|
||||||
|
|
||||||
|
if (! installed_cache && fs::exists(dir_in_rsrc) && fs::is_directory(dir_in_rsrc)) {
|
||||||
|
fs::create_directories(dir_in_vendors);
|
||||||
|
|
||||||
|
// Copy with file filter (same as PresetUpdater::install_bundles_rsrc)
|
||||||
|
// Filter out certain file types: .stl, .png, .svg, .jpeg, .jpg, .3mf
|
||||||
|
auto file_filter = [](const std::string name) -> bool {
|
||||||
|
return boost::iends_with(name, ".stl") ||
|
||||||
|
boost::iends_with(name, ".png") ||
|
||||||
|
boost::iends_with(name, ".svg") ||
|
||||||
|
boost::iends_with(name, ".jpeg") ||
|
||||||
|
boost::iends_with(name, ".jpg") ||
|
||||||
|
boost::iends_with(name, ".3mf");
|
||||||
|
};
|
||||||
|
|
||||||
|
copy_directory_recursively(dir_in_rsrc, dir_in_vendors, file_filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Successfully installed bundle: " << bundle;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Exception installing bundle " << bundle << ": " << e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// m_printer_hold_alias survives reset() (and a cache body that failed partway
|
||||||
|
// in), so every full-bundle rebuild clears all five collections' maps by hand.
|
||||||
|
void PresetBundle::clear_printer_hold_aliases()
|
||||||
|
{
|
||||||
|
this->prints.m_printer_hold_alias.clear();
|
||||||
|
this->sla_prints.m_printer_hold_alias.clear();
|
||||||
|
this->filaments.m_printer_hold_alias.clear();
|
||||||
|
this->sla_materials.m_printer_hold_alias.clear();
|
||||||
|
this->printers.m_printer_hold_alias.clear();
|
||||||
|
}
|
||||||
|
|
||||||
//BBS: add json related logic, load system presets from json
|
//BBS: add json related logic, load system presets from json
|
||||||
std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule)
|
std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||||
{
|
{
|
||||||
@@ -2243,22 +2446,19 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
|||||||
if (validation_mode)
|
if (validation_mode)
|
||||||
dir = (boost::filesystem::path(data_dir())).make_preferred();
|
dir = (boost::filesystem::path(data_dir())).make_preferred();
|
||||||
|
|
||||||
|
const auto load_t0 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
// The vendors below are loaded whole and against each other — the filament
|
||||||
|
// library first, then every other vendor with it as the base — so each parse
|
||||||
|
// is complete enough to be worth caching.
|
||||||
|
m_generate_vendor_caches = m_generate_vendor_caches || ! validation_mode;
|
||||||
|
|
||||||
PresetsConfigSubstitutions substitutions;
|
PresetsConfigSubstitutions substitutions;
|
||||||
std::string errors_cummulative;
|
std::string errors_cummulative;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
std::vector<std::string> vendor_names;
|
// Sorted, so any duplicate-preset warning below comes out in the same order on
|
||||||
// store all vendor names in vendor_names
|
// every run.
|
||||||
for (auto& dir_entry : boost::filesystem::directory_iterator(dir)) {
|
const std::set<std::string> vendor_names = vendor_names_in(dir);
|
||||||
std::string vendor_file = dir_entry.path().string();
|
|
||||||
if (!Slic3r::is_json_file(vendor_file))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
std::string vendor_name = dir_entry.path().filename().string();
|
|
||||||
|
|
||||||
// Remove the .json suffix.
|
|
||||||
vendor_name.erase(vendor_name.size() - 5);
|
|
||||||
vendor_names.push_back(vendor_name);
|
|
||||||
}
|
|
||||||
// Separate ORCA_FILAMENT_LIBRARY from other vendors. It must be loaded
|
// Separate ORCA_FILAMENT_LIBRARY from other vendors. It must be loaded
|
||||||
// first because other vendors' filaments may inherit from it via the
|
// first because other vendors' filaments may inherit from it via the
|
||||||
// `base_bundle` lookup in parse_subfile. The remaining vendors are
|
// `base_bundle` lookup in parse_subfile. The remaining vendors are
|
||||||
@@ -2274,8 +2474,13 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 1: Load ORCA_FILAMENT_LIBRARY into `this` synchronously.
|
// Step 1: Load ORCA_FILAMENT_LIBRARY into `this` synchronously.
|
||||||
if (!orca_lib_vendor.empty()) {
|
if (! orca_lib_vendor.empty()) {
|
||||||
try {
|
try {
|
||||||
|
// Match a fresh launch before parsing: hold aliases and the error
|
||||||
|
// counter survive reset(), and would otherwise leak prior-cycle
|
||||||
|
// state into the library cache the load below writes.
|
||||||
|
this->clear_printer_hold_aliases();
|
||||||
|
this->m_errors = 0;
|
||||||
append(substitutions, this->load_vendor_configs_from_json(dir.string(), orca_lib_vendor, PresetBundle::LoadSystem, compatibility_rule).first);
|
append(substitutions, this->load_vendor_configs_from_json(dir.string(), orca_lib_vendor, PresetBundle::LoadSystem, compatibility_rule).first);
|
||||||
first = false;
|
first = false;
|
||||||
} catch (const std::runtime_error &err) {
|
} catch (const std::runtime_error &err) {
|
||||||
@@ -2293,15 +2498,20 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
|||||||
std::vector<PresetsConfigSubstitutions> parallel_substitutions(other_vendors.size());
|
std::vector<PresetsConfigSubstitutions> parallel_substitutions(other_vendors.size());
|
||||||
std::vector<std::string> parallel_errors(other_vendors.size());
|
std::vector<std::string> parallel_errors(other_vendors.size());
|
||||||
|
|
||||||
|
// The filament library version every vendor below is judged against. Fixed
|
||||||
|
// from here on — step 1 was the last thing that could touch the library on
|
||||||
|
// disk — so resolve it once instead of once per vendor.
|
||||||
|
const std::string lib_version = effective_lib_version(dir);
|
||||||
|
|
||||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, other_vendors.size()),
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, other_vendors.size()),
|
||||||
[&](const tbb::blocked_range<size_t>& range) {
|
[&](const tbb::blocked_range<size_t>& range) {
|
||||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||||
auto bundle = std::make_unique<PresetBundle>();
|
auto bundle = std::make_unique<PresetBundle>();
|
||||||
bundle->set_is_validation_mode(validation_mode);
|
bundle->set_is_validation_mode(validation_mode);
|
||||||
|
bundle->set_generate_vendor_caches(m_generate_vendor_caches);
|
||||||
try {
|
try {
|
||||||
auto result = bundle->load_vendor_configs_from_json(
|
auto result = bundle->load_vendor_configs_from_json(
|
||||||
dir.string(), other_vendors[i], PresetBundle::LoadSystem,
|
dir.string(), other_vendors[i], PresetBundle::LoadSystem, compatibility_rule, this, lib_version);
|
||||||
compatibility_rule, this);
|
|
||||||
parallel_substitutions[i] = std::move(result.first);
|
parallel_substitutions[i] = std::move(result.first);
|
||||||
parallel_bundles[i] = std::move(bundle);
|
parallel_bundles[i] = std::move(bundle);
|
||||||
} catch (const std::runtime_error &err) {
|
} catch (const std::runtime_error &err) {
|
||||||
@@ -2346,6 +2556,11 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->update_system_maps();
|
this->update_system_maps();
|
||||||
|
|
||||||
|
const auto load_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now() - load_t0).count();
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "PresetBundle: " << vendor_names.size() << " vendor(s) loaded in " << load_ms << " ms";
|
||||||
|
|
||||||
//BBS: add config related logs
|
//BBS: add config related logs
|
||||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, errors_cummulative %1%")%errors_cummulative;
|
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, errors_cummulative %1%")%errors_cummulative;
|
||||||
return std::make_pair(std::move(substitutions), errors_cummulative);
|
return std::make_pair(std::move(substitutions), errors_cummulative);
|
||||||
@@ -4762,18 +4977,38 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
|
|
||||||
//BBS: Load a config bundle file from json
|
//BBS: Load a config bundle file from json
|
||||||
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_from_json(
|
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_from_json(
|
||||||
const std::string &path, const std::string &vendor_name, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle)
|
const std::string &dir, const std::string &vendor_name, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle,
|
||||||
|
const std::string &lib_version_hint)
|
||||||
{
|
{
|
||||||
// Enable substitutions for user config bundle, throw an exception when loading a system profile.
|
// Enable substitutions for user config bundle, throw an exception when loading a system profile.
|
||||||
ConfigSubstitutionContext substitution_context { compatibility_rule };
|
ConfigSubstitutionContext substitution_context { compatibility_rule };
|
||||||
PresetsConfigSubstitutions substitutions;
|
PresetsConfigSubstitutions substitutions;
|
||||||
|
|
||||||
//BBS: add config related logs
|
//BBS: add config related logs
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, path %1%, compatibility_rule %2%")%path.c_str()%compatibility_rule;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, path %1%, compatibility_rule %2%")%dir.c_str()%compatibility_rule;
|
||||||
if (flags.has(LoadConfigBundleAttribute::ResetUserProfile) || flags.has(LoadConfigBundleAttribute::LoadSystem))
|
if (flags.has(LoadConfigBundleAttribute::ResetUserProfile) || flags.has(LoadConfigBundleAttribute::LoadSystem))
|
||||||
// Reset this bundle, delete user profile files if SaveImported.
|
// Reset this bundle, delete user profile files if SaveImported.
|
||||||
this->reset(flags.has(LoadConfigBundleAttribute::SaveImported));
|
this->reset(flags.has(LoadConfigBundleAttribute::SaveImported));
|
||||||
|
|
||||||
|
// Orca: only a whole-vendor load has a cache — the vendor-only and filament-only
|
||||||
|
// scans want a slice of one. Validation reads the JSONs whatever is cached.
|
||||||
|
const boost::filesystem::path dir_path(dir);
|
||||||
|
const bool cacheable = flags.has(LoadConfigBundleAttribute::LoadSystem) && ! flags.has(LoadConfigBundleAttribute::LoadFilamentOnly);
|
||||||
|
if (cacheable && ! validation_mode && this->load_vendor_cache(dir_path, vendor_name, lib_version_hint)) {
|
||||||
|
size_t presets_loaded = 0;
|
||||||
|
for (const PresetCollection* coll : std::initializer_list<const PresetCollection*>{
|
||||||
|
&this->prints, &this->sla_prints, &this->filaments, &this->sla_materials, &this->printers })
|
||||||
|
presets_loaded += coll->m_presets.size() - coll->m_num_default_presets;
|
||||||
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", %1% served from its preset cache, %2% presets")%vendor_name%presets_loaded;
|
||||||
|
return std::make_pair(std::move(substitutions), presets_loaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Orca: a build that ships preset caches installs them without the preset
|
||||||
|
// JSONs, so a vendor left to be parsed is parsed from the profiles in
|
||||||
|
// resources. An update does deliver JSONs into `dir`, and those win.
|
||||||
|
const std::string path = (validation_mode || boost::filesystem::exists(dir_path / (vendor_name + ".json")))
|
||||||
|
? dir : (boost::filesystem::path(resources_dir()) / "profiles").string();
|
||||||
|
|
||||||
// 1) load the vroot json and construct the vendor profile
|
// 1) load the vroot json and construct the vendor profile
|
||||||
VendorProfile vendor_profile(vendor_name);
|
VendorProfile vendor_profile(vendor_name);
|
||||||
std::string root_file = path + "/" + vendor_name + ".json";
|
std::string root_file = path + "/" + vendor_name + ".json";
|
||||||
@@ -5331,6 +5566,22 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Orca: leave the vendor's cache in step with the profile just parsed, so the
|
||||||
|
// next run reads it instead. It is written where the vendor was looked for,
|
||||||
|
// even when the profile came from resources, and stamped with the version that
|
||||||
|
// profile claims — a profile without one cannot be judged for staleness later,
|
||||||
|
// and a cache nothing can invalidate is worse than none.
|
||||||
|
if (cacheable && m_generate_vendor_caches && vendor_profile.config_version.valid()) {
|
||||||
|
const std::string version = vendor_profile.config_version.to_string();
|
||||||
|
// The library is its own reference point; every other vendor's cache holds
|
||||||
|
// filaments resolved against it, and is stamped with the version in effect.
|
||||||
|
const std::string lib_version = vendor_name == ORCA_FILAMENT_LIBRARY ? version
|
||||||
|
: ! lib_version_hint.empty() ? lib_version_hint : effective_lib_version(dir_path);
|
||||||
|
if (! lib_version.empty() &&
|
||||||
|
! this->save_vendor_cache((dir_path / (vendor_name + ".opc")).string(), vendor_name, version, lib_version))
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "PresetBundle: failed to save vendor cache for " << vendor_name;
|
||||||
|
}
|
||||||
|
|
||||||
//BBS: add config related logs
|
//BBS: add config related logs
|
||||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", finished, presets_loaded %1%")%presets_loaded;
|
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", finished, presets_loaded %1%")%presets_loaded;
|
||||||
return std::make_pair(std::move(substitutions), presets_loaded);
|
return std::make_pair(std::move(substitutions), presets_loaded);
|
||||||
@@ -5953,4 +6204,318 @@ bool BundleMetadata::save_to_json(const std::string& path) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ---- Preset cache file format (shared by the per-vendor cache) ----------
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct CacheFileHeader {
|
||||||
|
uint32_t magic;
|
||||||
|
uint32_t version;
|
||||||
|
uint64_t data_size;
|
||||||
|
uint32_t crc32;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
static_assert(sizeof(CacheFileHeader) == 20, "CacheFileHeader must be 20 bytes");
|
||||||
|
|
||||||
|
constexpr uint32_t CACHE_MAGIC = 0x4F52435A; // "ORCZ"
|
||||||
|
// Bump when the wire format changes in a way the schema fingerprint cannot
|
||||||
|
// detect: reordering, removing, or retyping a field of a hand-written
|
||||||
|
// serialize() (Preset, VendorProfile and its nested types), or when the
|
||||||
|
// cache's own layout or the meaning of its stamps changes (e.g. the move from
|
||||||
|
// one whole-bundle cache to one cache per vendor).
|
||||||
|
constexpr uint32_t CACHE_VERSION = 4;
|
||||||
|
|
||||||
|
// A cache stays usable as long as it was built from a vendor profile — and a
|
||||||
|
// filament library — at least as new as the ones now on disk. Profiles without
|
||||||
|
// a version cannot be judged this way and are never served from cache; where no
|
||||||
|
// profile sits beside the cache at all, nothing can be newer than it.
|
||||||
|
static bool cache_covers_version(const std::string& cached, const std::string& on_disk)
|
||||||
|
{
|
||||||
|
if (on_disk == PresetBundle::CACHE_ANY_VERSION)
|
||||||
|
return true;
|
||||||
|
const auto cached_ver = Semver::parse(cached);
|
||||||
|
const auto on_disk_ver = Semver::parse(on_disk);
|
||||||
|
return cached_ver && on_disk_ver && *cached_ver >= *on_disk_ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fingerprint of everything that determines the cache wire format: the app
|
||||||
|
// version and the DynamicPrintConfig option schema (key/type/ordinal/enum
|
||||||
|
// values — serialization_key_ordinal IS the config wire format). Any mismatch
|
||||||
|
// means bytes written by another build could deserialize into the wrong
|
||||||
|
// fields, so the cache is rejected wholesale before its body is read.
|
||||||
|
const std::string& compute_cache_schema_fingerprint()
|
||||||
|
{
|
||||||
|
// Constant for the lifetime of the process (print_config_def is immutable
|
||||||
|
// after static initialization), and asked for once per cache load and save.
|
||||||
|
static const std::string fingerprint = [] {
|
||||||
|
std::string schema;
|
||||||
|
schema += SLIC3R_VERSION;
|
||||||
|
schema += ';';
|
||||||
|
for (const auto& [key, def] : print_config_def.options) { // std::map => stable order
|
||||||
|
schema += key;
|
||||||
|
schema += '#'; schema += std::to_string(int(def.type));
|
||||||
|
schema += '@'; schema += std::to_string(def.serialization_key_ordinal);
|
||||||
|
for (const std::string& ev : def.enum_values) { schema += ','; schema += ev; }
|
||||||
|
schema += ';';
|
||||||
|
}
|
||||||
|
boost::crc_32_type crc;
|
||||||
|
crc.process_bytes(schema.data(), schema.size());
|
||||||
|
return std::to_string(crc.checksum());
|
||||||
|
}();
|
||||||
|
return fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
// static
|
||||||
|
bool PresetBundle::read_cache_blob(const std::string& path, std::string& out_blob)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
boost::nowide::ifstream ifs(path, std::ios::binary);
|
||||||
|
if (!ifs.is_open())
|
||||||
|
return false;
|
||||||
|
CacheFileHeader fhdr;
|
||||||
|
if (!ifs.read(reinterpret_cast<char*>(&fhdr), sizeof(fhdr)))
|
||||||
|
return false;
|
||||||
|
if (fhdr.magic != CACHE_MAGIC)
|
||||||
|
return false;
|
||||||
|
if (fhdr.data_size == 0 || fhdr.data_size > 512u * 1024u * 1024u)
|
||||||
|
return false;
|
||||||
|
out_blob.assign(fhdr.data_size, '\0');
|
||||||
|
if (!ifs.read(&out_blob[0], static_cast<std::streamsize>(fhdr.data_size)))
|
||||||
|
return false;
|
||||||
|
boost::crc_32_type crc;
|
||||||
|
crc.process_bytes(out_blob.data(), out_blob.size());
|
||||||
|
if (crc.checksum() != fhdr.crc32) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: CRC mismatch: " << path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: read failed (" << path << "): " << e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
bool PresetBundle::write_cache_blob(const std::string& path, const std::string& blob)
|
||||||
|
{
|
||||||
|
boost::crc_32_type crc;
|
||||||
|
crc.process_bytes(blob.data(), blob.size());
|
||||||
|
try {
|
||||||
|
boost::filesystem::create_directories(boost::filesystem::path(path).parent_path());
|
||||||
|
boost::nowide::ofstream ofs(path, std::ios::binary | std::ios::trunc);
|
||||||
|
if (!ofs.is_open()) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: cannot open for writing: " << path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CacheFileHeader fhdr;
|
||||||
|
fhdr.magic = CACHE_MAGIC;
|
||||||
|
fhdr.version = CACHE_VERSION;
|
||||||
|
fhdr.data_size = static_cast<uint64_t>(blob.size());
|
||||||
|
fhdr.crc32 = crc.checksum();
|
||||||
|
ofs.write(reinterpret_cast<const char*>(&fhdr), sizeof(fhdr));
|
||||||
|
ofs.write(blob.data(), static_cast<std::streamsize>(blob.size()));
|
||||||
|
ofs.close(); // flush; close() raises failbit on error
|
||||||
|
if (! ofs.good())
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: write failed (" << path << ")";
|
||||||
|
return ofs.good();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: write failed (" << path << "): " << e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Per-vendor preset cache ---------------------------------------------
|
||||||
|
|
||||||
|
// Serializes every non-default, non-external preset of one collection plus
|
||||||
|
// its m_printer_hold_alias. Defaults are constructor-derived state and are
|
||||||
|
// skipped; load re-installs the loaded presets after the constructor's
|
||||||
|
// defaults, exactly like the JSON parse path (reset-to-defaults + sorted
|
||||||
|
// append).
|
||||||
|
void PresetBundle::save_collection(cereal::BinaryOutputArchive& ar, const PresetCollection& coll)
|
||||||
|
{
|
||||||
|
uint64_t count = 0;
|
||||||
|
for (const Preset& p : coll.m_presets)
|
||||||
|
if (! p.is_default && ! p.is_external)
|
||||||
|
++ count;
|
||||||
|
ar(count);
|
||||||
|
for (const Preset& p : coll.m_presets) {
|
||||||
|
if (p.is_default || p.is_external)
|
||||||
|
continue;
|
||||||
|
std::string vendor_id = p.vendor ? p.vendor->id : std::string();
|
||||||
|
ar(vendor_id, p);
|
||||||
|
}
|
||||||
|
// unordered containers -> sorted, for byte-deterministic output (parity test)
|
||||||
|
std::map<std::string, std::set<std::string>> hold;
|
||||||
|
for (const auto& entry : coll.m_printer_hold_alias)
|
||||||
|
hold.emplace(entry.first, std::set<std::string>(entry.second.begin(), entry.second.end()));
|
||||||
|
ar(hold);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PresetBundle::load_collection(cereal::BinaryInputArchive& ar, PresetCollection& coll, const VendorMap& vendors)
|
||||||
|
{
|
||||||
|
coll.m_printer_hold_alias.clear();
|
||||||
|
// Drop everything but the constructor-installed defaults, like PresetCollection::reset().
|
||||||
|
coll.m_presets.erase(coll.m_presets.begin() + coll.m_num_default_presets, coll.m_presets.end());
|
||||||
|
// Mirror PresetCollection::reset(), which follows the same truncation with
|
||||||
|
// select_preset(0): re-point selection/edited-preset state at the default
|
||||||
|
// preset before the loaded presets are appended below, so a cache hit onto
|
||||||
|
// an already-populated bundle (e.g. a reload) leaves selection state
|
||||||
|
// identical to the JSON-parse path instead of carrying over a stale index.
|
||||||
|
coll.select_preset(0);
|
||||||
|
uint64_t count = 0;
|
||||||
|
ar(count);
|
||||||
|
for (uint64_t i = 0; i < count; ++ i) {
|
||||||
|
std::string vendor_id;
|
||||||
|
Preset preset(coll.m_type, std::string());
|
||||||
|
ar(vendor_id, preset);
|
||||||
|
if (! vendor_id.empty()) {
|
||||||
|
auto it = vendors.find(vendor_id);
|
||||||
|
if (it == vendors.end())
|
||||||
|
throw std::runtime_error("vendor cache references unknown vendor: " + vendor_id);
|
||||||
|
preset.vendor = &it->second;
|
||||||
|
}
|
||||||
|
coll.m_presets.emplace_back(std::move(preset));
|
||||||
|
}
|
||||||
|
std::map<std::string, std::set<std::string>> hold;
|
||||||
|
ar(hold);
|
||||||
|
for (auto& entry : hold)
|
||||||
|
coll.m_printer_hold_alias.emplace(entry.first, std::unordered_set<std::string>(entry.second.begin(), entry.second.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Per-vendor preset cache implementation ------------------------------
|
||||||
|
|
||||||
|
bool PresetBundle::save_vendor_cache(const std::string& cache_path, const std::string& vendor_name,
|
||||||
|
const std::string& vendor_version, const std::string& lib_version) const
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
std::ostringstream body(std::ios::binary);
|
||||||
|
{
|
||||||
|
cereal::BinaryOutputArchive ar(body);
|
||||||
|
ar(CACHE_VERSION);
|
||||||
|
ar(compute_cache_schema_fingerprint());
|
||||||
|
ar(vendor_name, vendor_version, lib_version);
|
||||||
|
ar(this->vendors);
|
||||||
|
save_collection(ar, this->prints);
|
||||||
|
save_collection(ar, this->sla_prints);
|
||||||
|
save_collection(ar, this->filaments);
|
||||||
|
save_collection(ar, this->sla_materials);
|
||||||
|
save_collection(ar, this->printers);
|
||||||
|
ar(this->m_config_maps, this->m_filament_id_maps);
|
||||||
|
ar(this->obsolete_presets.prints, this->obsolete_presets.sla_prints,
|
||||||
|
this->obsolete_presets.filaments, this->obsolete_presets.sla_materials,
|
||||||
|
this->obsolete_presets.printers);
|
||||||
|
ar(this->m_errors);
|
||||||
|
}
|
||||||
|
return write_cache_blob(cache_path, body.str());
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "PresetBundle: failed to save vendor cache " << cache_path << ": " << e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
std::string PresetBundle::peek_vendor_cache_version(const std::string& cache_path, const std::string& expected_vendor_name)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
boost::nowide::ifstream ifs(cache_path, std::ios::binary);
|
||||||
|
CacheFileHeader fhdr;
|
||||||
|
if (! ifs.read(reinterpret_cast<char*>(&fhdr), sizeof(fhdr)) || fhdr.magic != CACHE_MAGIC)
|
||||||
|
return {};
|
||||||
|
// Only the head of the body is read, and its CRC left unverified: the stamps
|
||||||
|
// sit at the front, this answers "what version is this vendor at?" once per
|
||||||
|
// vendor on every update check, and reading tens of megabytes to do so is not
|
||||||
|
// worth it. A stamp that comes out garbled fails to parse as a version, which
|
||||||
|
// is the same answer as none.
|
||||||
|
std::string head(static_cast<size_t>(std::min<uint64_t>(fhdr.data_size, 1024)), '\0');
|
||||||
|
if (! ifs.read(&head[0], static_cast<std::streamsize>(head.size())))
|
||||||
|
return {};
|
||||||
|
std::istringstream body(head, std::ios::binary);
|
||||||
|
cereal::BinaryInputArchive ar(body);
|
||||||
|
uint32_t cache_version = 0;
|
||||||
|
ar(cache_version);
|
||||||
|
std::string fingerprint, vendor_name, vendor_version;
|
||||||
|
ar(fingerprint);
|
||||||
|
ar(vendor_name, vendor_version);
|
||||||
|
// The fingerprint is deliberately not checked: the version a cache carries
|
||||||
|
// is what this build installed, whether or not this build can still read it.
|
||||||
|
if (cache_version != CACHE_VERSION || vendor_name != expected_vendor_name)
|
||||||
|
return {};
|
||||||
|
return vendor_version;
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PresetBundle::load_vendor_cache(const boost::filesystem::path& dir, const std::string& vendor_name, const std::string& lib_version_hint)
|
||||||
|
{
|
||||||
|
// Whichever cache answers is judged against the vendor as installed in `dir`:
|
||||||
|
// the profile there, or — with none, as when the cache is the whole of the
|
||||||
|
// installation — nothing, since nothing on disk can then be newer than it.
|
||||||
|
// Plus the filament library in effect, which a vendor's filaments were
|
||||||
|
// resolved against when its cache was built.
|
||||||
|
const boost::filesystem::path profile = dir / (vendor_name + ".json");
|
||||||
|
const std::string version = boost::filesystem::exists(profile) ? get_vendor_cache_version(profile.string())
|
||||||
|
: std::string(CACHE_ANY_VERSION);
|
||||||
|
const std::string lib_version = ! lib_version_hint.empty() ? lib_version_hint : effective_lib_version(dir);
|
||||||
|
const boost::filesystem::path rsrc = boost::filesystem::path(resources_dir()) / "profiles";
|
||||||
|
return this->load_vendor_cache((dir / (vendor_name + ".opc")).string(), vendor_name, version, lib_version)
|
||||||
|
|| (dir != rsrc && this->load_vendor_cache((rsrc / (vendor_name + ".opc")).string(), vendor_name, version, lib_version));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PresetBundle::load_vendor_cache(const std::string& cache_path, const std::string& expected_vendor_name,
|
||||||
|
const std::string& expected_vendor_version, const std::string& expected_lib_version)
|
||||||
|
{
|
||||||
|
std::string blob;
|
||||||
|
if (! read_cache_blob(cache_path, blob))
|
||||||
|
return false;
|
||||||
|
try {
|
||||||
|
// Read in place: an istringstream would copy the blob (tens of MB for
|
||||||
|
// the largest vendors) once more just to stream over it.
|
||||||
|
boost::iostreams::stream<boost::iostreams::array_source> body(blob.data(), blob.size());
|
||||||
|
cereal::BinaryInputArchive ar(body);
|
||||||
|
uint32_t cache_version = 0;
|
||||||
|
ar(cache_version);
|
||||||
|
if (cache_version != CACHE_VERSION)
|
||||||
|
return false;
|
||||||
|
std::string fingerprint;
|
||||||
|
ar(fingerprint);
|
||||||
|
if (fingerprint != compute_cache_schema_fingerprint())
|
||||||
|
return false;
|
||||||
|
std::string vendor_name, vendor_version, lib_version;
|
||||||
|
ar(vendor_name, vendor_version, lib_version);
|
||||||
|
if (vendor_name != expected_vendor_name ||
|
||||||
|
! cache_covers_version(vendor_version, expected_vendor_version) ||
|
||||||
|
! cache_covers_version(lib_version, expected_lib_version))
|
||||||
|
return false;
|
||||||
|
ar(this->vendors);
|
||||||
|
load_collection(ar, this->prints, this->vendors);
|
||||||
|
load_collection(ar, this->sla_prints, this->vendors);
|
||||||
|
load_collection(ar, this->filaments, this->vendors);
|
||||||
|
load_collection(ar, this->sla_materials, this->vendors);
|
||||||
|
load_collection(ar, this->printers, this->vendors);
|
||||||
|
ar(this->m_config_maps, this->m_filament_id_maps);
|
||||||
|
ar(this->obsolete_presets.prints, this->obsolete_presets.sla_prints,
|
||||||
|
this->obsolete_presets.filaments, this->obsolete_presets.sla_materials,
|
||||||
|
this->obsolete_presets.printers);
|
||||||
|
ar(this->m_errors);
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "PresetBundle: rejecting vendor cache " << cache_path << ": " << e.what();
|
||||||
|
// Restore a clean state so the caller can fall back to the JSON parse.
|
||||||
|
this->reset(false);
|
||||||
|
this->vendors.clear();
|
||||||
|
this->m_config_maps.clear();
|
||||||
|
this->m_filament_id_maps.clear();
|
||||||
|
this->m_errors = 0;
|
||||||
|
// A mid-body failure may have left collections the deserialization never
|
||||||
|
// reached (each load_collection clears its own collection's map only when
|
||||||
|
// it runs) with stale aliases.
|
||||||
|
this->clear_printer_hold_aliases();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "enum_bitmask.hpp"
|
#include "enum_bitmask.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -13,6 +14,11 @@
|
|||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace cereal {
|
||||||
|
class BinaryInputArchive;
|
||||||
|
class BinaryOutputArchive;
|
||||||
|
}
|
||||||
|
|
||||||
#define DEFAULT_USER_FOLDER_NAME "default"
|
#define DEFAULT_USER_FOLDER_NAME "default"
|
||||||
#define BUNDLE_STRUCTURE_JSON_NAME "bundle_structure.json"
|
#define BUNDLE_STRUCTURE_JSON_NAME "bundle_structure.json"
|
||||||
|
|
||||||
@@ -170,6 +176,44 @@ struct PresetBundleMetadata
|
|||||||
class PresetBundle
|
class PresetBundle
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// ---- Per-vendor preset cache --------------------------------------------
|
||||||
|
// One cache file per vendor (plus the Orca filament library), stamped with
|
||||||
|
// the vendor's own profile version rather than a directory scan.
|
||||||
|
|
||||||
|
// The cache is not something a caller loads from: a vendor is loaded with
|
||||||
|
// load_vendor_configs_from_json, which comes from the cache whenever one covers
|
||||||
|
// it. What is public here is what the cache's own tests drive directly.
|
||||||
|
|
||||||
|
// Save this bundle's slice belonging to one vendor (vendor_name at
|
||||||
|
// vendor_version), built against the given filament library version.
|
||||||
|
bool save_vendor_cache(const std::string& cache_path, const std::string& vendor_name,
|
||||||
|
const std::string& vendor_version, const std::string& lib_version) const;
|
||||||
|
|
||||||
|
// Expected version meaning "no profile sits beside this cache", so nothing can
|
||||||
|
// be newer than it and only its own integrity decides whether it is used. Not
|
||||||
|
// the same as an empty version, which means a profile is there but unreadable.
|
||||||
|
static constexpr const char* CACHE_ANY_VERSION = "*";
|
||||||
|
|
||||||
|
// Load a validated per-vendor cache into this bundle. Rejects (returns
|
||||||
|
// false) unless the cache version, schema fingerprint and vendor name match
|
||||||
|
// and the cache was built from a vendor profile and filament library at
|
||||||
|
// least as new as the expected ones. Profiles without a version (empty
|
||||||
|
// string) are never served from cache.
|
||||||
|
bool load_vendor_cache(const std::string& cache_path, const std::string& expected_vendor_name,
|
||||||
|
const std::string& expected_vendor_version, const std::string& expected_lib_version);
|
||||||
|
|
||||||
|
// Read the profile version a cache was stamped with, without deserializing its
|
||||||
|
// presets. Empty if the file is unreadable, not a cache this build understands,
|
||||||
|
// or not this vendor's. This is how an installed vendor's version is known when
|
||||||
|
// only its cache is installed.
|
||||||
|
static std::string peek_vendor_cache_version(const std::string& cache_path, const std::string& expected_vendor_name);
|
||||||
|
|
||||||
|
// Enable writing a per-vendor cache after a JSON parse (off by default). Only
|
||||||
|
// for bundles whose parses are complete — load_system_presets_from_json loads
|
||||||
|
// the filament library first and every other vendor against it, so its parses
|
||||||
|
// qualify; a wizard's one-off parse of a single vendor does not.
|
||||||
|
void set_generate_vendor_caches(bool enable) { m_generate_vendor_caches = enable; }
|
||||||
|
|
||||||
static DynamicPrintConfig construct_full_config(Preset &in_printer_preset,
|
static DynamicPrintConfig construct_full_config(Preset &in_printer_preset,
|
||||||
Preset &in_print_preset,
|
Preset &in_print_preset,
|
||||||
const DynamicPrintConfig &project_config,
|
const DynamicPrintConfig &project_config,
|
||||||
@@ -444,8 +488,15 @@ public:
|
|||||||
/*std::pair<PresetsConfigSubstitutions, size_t> load_configbundle(
|
/*std::pair<PresetsConfigSubstitutions, size_t> load_configbundle(
|
||||||
const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule);*/
|
const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule);*/
|
||||||
//Orca: load config bundle from json, pass the base bundle to support cross vendor inheritance
|
//Orca: load config bundle from json, pass the base bundle to support cross vendor inheritance
|
||||||
|
// Orca: `dir` is where the vendor is looked for — its own directory, whether or
|
||||||
|
// not the profile JSONs are still there. A whole-vendor load comes from the
|
||||||
|
// vendor's preset cache whenever one covers the profile on disk, and is parsed
|
||||||
|
// from the JSONs (falling back to the ones in resources) only when none does.
|
||||||
|
// `lib_version_hint` is the filament library version in effect, when the caller
|
||||||
|
// loads many vendors and has already resolved it once; empty resolves it here.
|
||||||
std::pair<PresetsConfigSubstitutions, size_t> load_vendor_configs_from_json(
|
std::pair<PresetsConfigSubstitutions, size_t> load_vendor_configs_from_json(
|
||||||
const std::string &path, const std::string &vendor_name, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle = nullptr);
|
const std::string &dir, const std::string &vendor_name, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle = nullptr,
|
||||||
|
const std::string &lib_version_hint = {});
|
||||||
|
|
||||||
// Export a config bundle file containing all the presets and the names of the active presets.
|
// Export a config bundle file containing all the presets and the names of the active presets.
|
||||||
//void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false);
|
//void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false);
|
||||||
@@ -521,7 +572,38 @@ public:
|
|||||||
// compatible_prints references a deleted (unknown) or renamed (old) preset name.
|
// compatible_prints references a deleted (unknown) or renamed (old) preset name.
|
||||||
bool check_preset_references() const;
|
bool check_preset_references() const;
|
||||||
|
|
||||||
|
// Merge one vendor's presets with the other vendor's presets, report duplicates.
|
||||||
|
// Public so per-vendor-cache consumers (e.g. the setup wizard) can assemble a
|
||||||
|
// bundle out of several per-vendor caches loaded into separate PresetBundle instances.
|
||||||
|
std::vector<std::string> merge_presets(PresetBundle &&other);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Load one vendor from its preset cache: the one in `dir`, or — when that is
|
||||||
|
// missing or stale — the one shipped in resources/profiles, both judged against
|
||||||
|
// the vendor as installed in `dir` and against the filament library in effect
|
||||||
|
// (resolved here unless the caller passes the already-resolved version).
|
||||||
|
// False, with this bundle left clean, when neither is usable and the vendor has
|
||||||
|
// to be parsed. This is how load_vendor_configs_from_json reads a cache.
|
||||||
|
bool load_vendor_cache(const boost::filesystem::path& dir, const std::string& vendor_name, const std::string& lib_version_hint = {});
|
||||||
|
|
||||||
|
// Read raw cache blob: verify magic, size, CRC.
|
||||||
|
static bool read_cache_blob(const std::string& path, std::string& out_blob);
|
||||||
|
// Write a cache blob with the standard 20-byte file header. False when the
|
||||||
|
// file could not be opened or written whole.
|
||||||
|
static bool write_cache_blob(const std::string& path, const std::string& blob);
|
||||||
|
|
||||||
|
// (De)serialization of one collection's slice for the per-vendor cache:
|
||||||
|
// every non-default, non-external preset (system or user) plus
|
||||||
|
// m_printer_hold_alias. See save_vendor_cache/load_vendor_cache.
|
||||||
|
static void save_collection(cereal::BinaryOutputArchive& ar, const PresetCollection& coll);
|
||||||
|
static void load_collection(cereal::BinaryInputArchive& ar, PresetCollection& coll, const VendorMap& vendors);
|
||||||
|
|
||||||
|
// Clear every collection's m_printer_hold_alias, which reset() leaves alone.
|
||||||
|
void clear_printer_hold_aliases();
|
||||||
|
|
||||||
|
// Whether to (re)write a per-vendor cache after a JSON parse.
|
||||||
|
bool m_generate_vendor_caches { false };
|
||||||
|
|
||||||
// Orca: validation only - flag any printer with two or more compatible
|
// Orca: validation only - flag any printer with two or more compatible
|
||||||
// filament presets sharing one filament_id (ambiguous AMS subtype match).
|
// filament presets sharing one filament_id (ambiguous AMS subtype match).
|
||||||
bool check_duplicate_filament_subtypes() const;
|
bool check_duplicate_filament_subtypes() const;
|
||||||
@@ -529,8 +611,6 @@ private:
|
|||||||
//std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
|
//std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||||
//BBS: add json related logic
|
//BBS: add json related logic
|
||||||
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule);
|
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||||
// Merge one vendor's presets with the other vendor's presets, report duplicates.
|
|
||||||
std::vector<std::string> merge_presets(PresetBundle &&other);
|
|
||||||
// Update the multicolor information for filaments.
|
// Update the multicolor information for filaments.
|
||||||
void update_filament_multi_color();
|
void update_filament_multi_color();
|
||||||
// Update renamed_from and alias maps of system profiles.
|
// Update renamed_from and alias maps of system profiles.
|
||||||
@@ -565,6 +645,38 @@ private:
|
|||||||
|
|
||||||
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
|
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
|
||||||
|
|
||||||
|
// True if `vendor` is installed in data_dir()/system. A build that ships preset
|
||||||
|
// caches installs the cache alone, so it — not the profile — marks a vendor
|
||||||
|
// installed, and either one on its own counts.
|
||||||
|
extern bool is_vendor_installed(const std::string& vendor);
|
||||||
|
|
||||||
|
// The version of the installed vendor: what its profile claims, or what its cache
|
||||||
|
// was stamped with where only the cache is installed. Invalid Semver if neither is.
|
||||||
|
extern Semver installed_vendor_version(const std::string& vendor);
|
||||||
|
|
||||||
|
// Remove every form `vendor` can be installed as from data_dir()/system: its
|
||||||
|
// profile, its preset cache, and its preset directory.
|
||||||
|
extern void remove_installed_vendor(const std::string& vendor);
|
||||||
|
|
||||||
|
// The vendors `dir` holds, sorted: one is named by its profile or, in a build that
|
||||||
|
// ships preset caches instead of the raw profile JSONs, by its cache alone.
|
||||||
|
extern std::set<std::string> vendor_names_in(const boost::filesystem::path& dir);
|
||||||
|
|
||||||
|
// The version a build ships `vendor` at: whichever of its preset cache and its
|
||||||
|
// profile is newer, that being the one installing lays down. Invalid Semver if the
|
||||||
|
// build ships neither.
|
||||||
|
extern Semver resource_vendor_version(const std::string& vendor);
|
||||||
|
|
||||||
|
// Install vendors from the resources directory into the data directory, each as
|
||||||
|
// its preset cache or as its profile and preset JSONs — whichever of the two the
|
||||||
|
// build ships at the newer version. Anything the previous install of that vendor
|
||||||
|
// left behind goes, so only the form just installed is there to be loaded.
|
||||||
|
// bundle_names: vendor names, without extension.
|
||||||
|
// Returns false on the first vendor that cannot be installed.
|
||||||
|
extern bool install_vendor_bundles_from_resources(const std::vector<std::string>& bundle_names,
|
||||||
|
const std::string& resource_subdir = "profiles",
|
||||||
|
const std::string& data_subdir = "system");
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif /* slic3r_PresetBundle_hpp_ */
|
#endif /* slic3r_PresetBundle_hpp_ */
|
||||||
|
|||||||
+48
-48
@@ -3409,11 +3409,7 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps, std::vector<
|
|||||||
}
|
}
|
||||||
else if ((extruder_volume_type_count > extruder_count) && (m_config.filament_volume_map.values.size() > index))
|
else if ((extruder_volume_type_count > extruder_count) && (m_config.filament_volume_map.values.size() > index))
|
||||||
nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]);
|
nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]);
|
||||||
// Orca: when the process variant columns cannot be matched (degenerate
|
m_config.filament_map_2.values[index] = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||||
// print_extruder_id), key the override by plain extruder index like the seeding
|
|
||||||
// above instead of poisoning the map with -1.
|
|
||||||
int slot_index = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
|
||||||
m_config.filament_map_2.values[index] = slot_index >= 0 ? slot_index : f_maps[index] - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_full_print_config = m_ori_full_print_config;
|
m_full_print_config = m_ori_full_print_config;
|
||||||
@@ -4021,33 +4017,10 @@ void Print::_make_wipe_tower()
|
|||||||
// in BBL machine, wipe tower is only use to prime extruder. So just use a global wipe volume.
|
// in BBL machine, wipe tower is only use to prime extruder. So just use a global wipe volume.
|
||||||
WipeTower wipe_tower(m_config, m_plate_index, m_origin, m_wipe_tower_data.tool_ordering.first_extruder(),
|
WipeTower wipe_tower(m_config, m_plate_index, m_origin, m_wipe_tower_data.tool_ordering.first_extruder(),
|
||||||
m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z, m_wipe_tower_data.tool_ordering.all_extruders());
|
m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z, m_wipe_tower_data.tool_ordering.all_extruders());
|
||||||
// Orca: the tower's first-layer flow follows the user's first-layer flow ratio (BBS reads
|
|
||||||
// its initial_layer_flow_ratio here — STUDIO-14254; first_layer_flow_ratio is Orca's analog,
|
|
||||||
// default 1.0 in both). Honor the set_other_flow_ratios gate that governs the option
|
|
||||||
// everywhere else.
|
|
||||||
wipe_tower.set_first_layer_flow_ratio(m_default_object_config.set_other_flow_ratios
|
|
||||||
? float(m_default_region_config.first_layer_flow_ratio)
|
|
||||||
: 1.f);
|
|
||||||
wipe_tower.set_has_tpu_filament(this->has_tpu_filament());
|
wipe_tower.set_has_tpu_filament(this->has_tpu_filament());
|
||||||
// Per-layer filament->nozzle grouping. sort_and_build_data() above publishes it on the Print
|
wipe_tower.set_filament_map(this->get_filament_maps());
|
||||||
// for by-layer prints; by-object prints publish only later (psSkirtBrim), so fall back to the
|
// Vortek H2C: pass nozzle-level map for carousel rotation detection in tool_change_new()
|
||||||
// ToolOrdering's own copy there. set_extruder() below dereferences it, so it must be set first.
|
wipe_tower.set_filament_nozzle_map(this->get_filament_nozzle_maps());
|
||||||
auto print_group_result = get_layered_nozzle_group_result();
|
|
||||||
const MultiNozzleUtils::LayeredNozzleGroupResult &nozzle_group_result =
|
|
||||||
print_group_result ? *print_group_result : m_wipe_tower_data.tool_ordering.get_layered_nozzle_group_result();
|
|
||||||
wipe_tower.set_nozzle_group_result(nozzle_group_result);
|
|
||||||
{
|
|
||||||
// Orca: acceleration options are object-scope (PrintConfig members in BBS), so resolve
|
|
||||||
// the per-variant columns here; initial_layer_travel_acceleration is FloatOrPercent
|
|
||||||
// over travel_acceleration and needs the full config to resolve.
|
|
||||||
std::vector<double> first_layer_travel_accels;
|
|
||||||
for (size_t i = 0; i < m_config.initial_layer_travel_acceleration.values.size(); ++i)
|
|
||||||
first_layer_travel_accels.emplace_back(m_full_print_config.get_abs_value_at("initial_layer_travel_acceleration", i));
|
|
||||||
wipe_tower.set_accelerations(m_default_object_config.default_acceleration.values,
|
|
||||||
m_default_object_config.initial_layer_acceleration.values,
|
|
||||||
m_default_object_config.travel_acceleration.values,
|
|
||||||
first_layer_travel_accels);
|
|
||||||
}
|
|
||||||
// Feed the has_filament_switcher device flag (develop-only dynamic key, read defensively from
|
// Feed the has_filament_switcher device flag (develop-only dynamic key, read defensively from
|
||||||
// the full config — no shipping profile sets it) and the shared printable bed used by the PETG
|
// the full config — no shipping profile sets it) and the shared printable bed used by the PETG
|
||||||
// pre-extrusion offset clamp. Both are inert unless has_filament_switcher is set.
|
// pre-extrusion offset clamp. Both are inert unless has_filament_switcher is set.
|
||||||
@@ -4083,19 +4056,27 @@ void Print::_make_wipe_tower()
|
|||||||
multi_extruder_flush.emplace_back(wipe_volumes);
|
multi_extruder_flush.emplace_back(wipe_volumes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-carousel-slot purge tracking via NozzleStatusRecorder (BBS pattern); the layered
|
// Use NozzleStatusRecorder for per-carousel-slot tracking (BBS pattern).
|
||||||
// group result set on the tower above resolves each filament to its nozzle slot per layer.
|
// The original Orca code tracked per-extruder (2 slots), which collapsed all
|
||||||
|
// carousel filaments into one slot and caused massive redundant AMS flushing.
|
||||||
|
auto group_result = get_layered_nozzle_group_result();
|
||||||
MultiNozzleUtils::NozzleStatusRecorder nozzle_recorder;
|
MultiNozzleUtils::NozzleStatusRecorder nozzle_recorder;
|
||||||
|
// Fallback (group_result == null) per-physical-nozzle tracking, matching the original
|
||||||
|
// pre-port behavior: remembers the last filament loaded in each physical nozzle slot.
|
||||||
|
std::vector<unsigned int> nozzle_cur_filament_ids(nozzle_nums, (unsigned int) -1);
|
||||||
|
|
||||||
std::vector<int>filament_maps = get_filament_maps();
|
std::vector<int>filament_maps = get_filament_maps();
|
||||||
int layer_idx = -1;
|
int layer_idx = -1;
|
||||||
|
|
||||||
unsigned int current_filament_id = m_wipe_tower_data.tool_ordering.first_extruder();
|
unsigned int current_filament_id = m_wipe_tower_data.tool_ordering.first_extruder();
|
||||||
// Initialize NozzleStatusRecorder with the first filament's carousel slot
|
// Initialize NozzleStatusRecorder with the first filament's carousel slot
|
||||||
{
|
if (group_result) {
|
||||||
auto nozzle = nozzle_group_result.get_nozzle_for_filament(current_filament_id, layer_idx);
|
auto nozzle = group_result->get_nozzle_for_filament(current_filament_id, layer_idx);
|
||||||
if (nozzle)
|
if (nozzle)
|
||||||
nozzle_recorder.set_nozzle_status(nozzle->group_id, current_filament_id, nozzle->extruder_id);
|
nozzle_recorder.set_nozzle_status(nozzle->group_id, current_filament_id, nozzle->extruder_id);
|
||||||
|
} else {
|
||||||
|
size_t cur_nozzle_id = filament_maps[current_filament_id] - 1;
|
||||||
|
nozzle_cur_filament_ids[cur_nozzle_id] = current_filament_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers
|
for (auto& layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers
|
||||||
@@ -4114,8 +4095,8 @@ void Print::_make_wipe_tower()
|
|||||||
float volume_to_purge = 0;
|
float volume_to_purge = 0;
|
||||||
|
|
||||||
// Per-carousel-slot purge tracking via NozzleStatusRecorder
|
// Per-carousel-slot purge tracking via NozzleStatusRecorder
|
||||||
{
|
if (group_result) {
|
||||||
auto nozzle_info = nozzle_group_result.get_nozzle_for_filament(filament_id, layer_idx);
|
auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_idx);
|
||||||
if (nozzle_info) {
|
if (nozzle_info) {
|
||||||
int extruder_id = nozzle_info->extruder_id;
|
int extruder_id = nozzle_info->extruder_id;
|
||||||
int nozzle_id = nozzle_info->group_id;
|
int nozzle_id = nozzle_info->group_id;
|
||||||
@@ -4134,6 +4115,22 @@ void Print::_make_wipe_tower()
|
|||||||
}
|
}
|
||||||
nozzle_recorder.set_nozzle_status(nozzle_id, filament_id, extruder_id);
|
nozzle_recorder.set_nozzle_status(nozzle_id, filament_id, extruder_id);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: original Orca per-physical-nozzle path (non-carousel printers).
|
||||||
|
// Flush source is the last filament that occupied THIS nozzle, guarded so the
|
||||||
|
// first use of a nozzle incurs no flush.
|
||||||
|
int nozzle_id = filament_maps[filament_id] - 1;
|
||||||
|
unsigned int pre_filament_id = nozzle_cur_filament_ids[nozzle_id];
|
||||||
|
if (pre_filament_id != (unsigned int) -1 && pre_filament_id != filament_id) {
|
||||||
|
volume_to_purge = multi_extruder_flush[nozzle_id][pre_filament_id][filament_id];
|
||||||
|
float flush_multiplier = (m_config.prime_volume_mode == PrimeVolumeMode::pvmFast)
|
||||||
|
? m_config.flush_multiplier_fast.get_at(nozzle_id)
|
||||||
|
: m_config.flush_multiplier.get_at(nozzle_id);
|
||||||
|
volume_to_purge *= flush_multiplier;
|
||||||
|
volume_to_purge = layer_tools.wiping_extrusions().mark_wiping_extrusions(
|
||||||
|
*this, current_filament_id, filament_id, volume_to_purge);
|
||||||
|
}
|
||||||
|
nozzle_cur_filament_ids[nozzle_id] = filament_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
//During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length.
|
//During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length.
|
||||||
@@ -4141,21 +4138,29 @@ void Print::_make_wipe_tower()
|
|||||||
float grab_purge_volume = m_config.grab_length.get_at(grab_extruder_id) * 2.4; //(diameter/2)^2*PI=2.4
|
float grab_purge_volume = m_config.grab_length.get_at(grab_extruder_id) * 2.4; //(diameter/2)^2*PI=2.4
|
||||||
volume_to_purge = std::max(0.f, volume_to_purge - grab_purge_volume);
|
volume_to_purge = std::max(0.f, volume_to_purge - grab_purge_volume);
|
||||||
|
|
||||||
// Prime volume per-filament: the tower now picks extruder-change vs nozzle-change
|
// Select prime volume per-filament: nozzle change (carousel rotation) uses
|
||||||
// (carousel) internally per plan layer, so pass both candidates (BBS pattern).
|
// filament_prime_volume_nc, filament change (same nozzle slot) uses filament_prime_volume.
|
||||||
float wipe_volume_ec = filament_id < m_config.filament_prime_volume.values.size()
|
float wipe_volume_ec = filament_id < m_config.filament_prime_volume.values.size()
|
||||||
? m_config.filament_prime_volume.values[filament_id]
|
? m_config.filament_prime_volume.values[filament_id]
|
||||||
: (float) m_config.prime_volume;
|
: (float) m_config.prime_volume;
|
||||||
float wipe_volume_nc = filament_id < m_config.filament_prime_volume_nc.values.size()
|
float wipe_volume_nc = filament_id < m_config.filament_prime_volume_nc.values.size()
|
||||||
? m_config.filament_prime_volume_nc.values[filament_id]
|
? m_config.filament_prime_volume_nc.values[filament_id]
|
||||||
: (float) m_config.prime_volume;
|
: (float) m_config.prime_volume;
|
||||||
|
|
||||||
|
float prime_volume = wipe_volume_ec;
|
||||||
|
if (group_result) {
|
||||||
|
bool is_nozzle_change = group_result->are_filaments_same_extruder(current_filament_id, filament_id, layer_idx) &&
|
||||||
|
!group_result->are_filaments_same_nozzle(current_filament_id, filament_id, layer_idx);
|
||||||
|
if (is_nozzle_change) {
|
||||||
|
prime_volume = wipe_volume_nc;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (m_config.prime_volume_mode == PrimeVolumeMode::pvmSaving) {
|
if (m_config.prime_volume_mode == PrimeVolumeMode::pvmSaving) {
|
||||||
wipe_volume_ec = 15.f;
|
prime_volume = 15.f;
|
||||||
wipe_volume_nc = 15.f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, filament_id,
|
wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, filament_id,
|
||||||
wipe_volume_ec, wipe_volume_nc, volume_to_purge);
|
prime_volume, volume_to_purge);
|
||||||
current_filament_id = filament_id;
|
current_filament_id = filament_id;
|
||||||
}
|
}
|
||||||
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
|
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
|
||||||
@@ -4333,12 +4338,7 @@ void Print::_make_wipe_tower()
|
|||||||
wipe_tower.get_rib_width(), wipe_tower.get_rib_length(),
|
wipe_tower.get_rib_width(), wipe_tower.get_rib_length(),
|
||||||
config().wipe_tower_fillet_wall.value);
|
config().wipe_tower_fillet_wall.value);
|
||||||
const Vec3d origin = Vec3d::Zero();
|
const Vec3d origin = Vec3d::Zero();
|
||||||
// FakeWipeTower::pos is a bed-frame translation applied after rotation
|
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_wipe_tower_height(),
|
||||||
// (getFakeExtrusionPathsFromWipeTower2 rotates about the local origin), so the
|
|
||||||
// tower-local rib offset must be rotated into the bed frame first.
|
|
||||||
m_fake_wipe_tower.rib_offset = Eigen::Rotation2Df(Geometry::deg2rad((float)config().wipe_tower_rotation_angle.value)) *
|
|
||||||
wipe_tower.get_rib_offset();
|
|
||||||
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position() + m_fake_wipe_tower.rib_offset, wipe_tower.width(), wipe_tower.get_wipe_tower_height(),
|
|
||||||
config().initial_layer_print_height, m_wipe_tower_data.depth,
|
config().initial_layer_print_height, m_wipe_tower_data.depth,
|
||||||
m_wipe_tower_data.z_and_depth_pairs, m_wipe_tower_data.brim_width,
|
m_wipe_tower_data.z_and_depth_pairs, m_wipe_tower_data.brim_width,
|
||||||
config().wipe_tower_rotation_angle, config().wipe_tower_cone_angle,
|
config().wipe_tower_rotation_angle, config().wipe_tower_cone_angle,
|
||||||
|
|||||||
@@ -1355,11 +1355,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps
|
if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps
|
||||||
&& opt_filament_volume_maps->values.size() == filament_maps.size())
|
&& opt_filament_volume_maps->values.size() == filament_maps.size())
|
||||||
nozzle_volume_type = (NozzleVolumeType)(opt_filament_volume_maps->values[index]);
|
nozzle_volume_type = (NozzleVolumeType)(opt_filament_volume_maps->values[index]);
|
||||||
// Orca: when the process variant columns cannot be matched (degenerate
|
m_config.filament_map_2.values[index] = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||||
// print_extruder_id), key the override by plain extruder index like the seeding
|
|
||||||
// above instead of poisoning the map with -1.
|
|
||||||
int slot_index = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
|
||||||
m_config.filament_map_2.values[index] = slot_index >= 0 ? slot_index : filament_maps[index] - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
|
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
|
||||||
@@ -1415,16 +1411,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
num_extruders_changed = true;
|
num_extruders_changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (! print_diff.empty()) {
|
|
||||||
// Orca: m_config can diverge from an unchanged full config (e.g. the in-slice retract
|
|
||||||
// override recompute writing different values than the apply-time computation). The
|
|
||||||
// invalidation above already fired for print_diff, so repair m_config here as well;
|
|
||||||
// otherwise the divergence is never corrected and every subsequent apply of the same
|
|
||||||
// config invalidates the result again, forever.
|
|
||||||
m_placeholder_parser.apply_config(filament_overrides);
|
|
||||||
m_config.apply_only(new_full_config, print_diff, true);
|
|
||||||
m_config.apply(filament_overrides);
|
|
||||||
}
|
|
||||||
|
|
||||||
ModelObjectStatusDB model_object_status_db;
|
ModelObjectStatusDB model_object_status_db;
|
||||||
|
|
||||||
|
|||||||
@@ -331,6 +331,8 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintSequence)
|
|||||||
static t_config_enum_values s_keys_map_PrintOrder{
|
static t_config_enum_values s_keys_map_PrintOrder{
|
||||||
{ "default", int(PrintOrder::Default) },
|
{ "default", int(PrintOrder::Default) },
|
||||||
{ "as_obj_list", int(PrintOrder::AsObjectList)},
|
{ "as_obj_list", int(PrintOrder::AsObjectList)},
|
||||||
|
{ "best_of", int(PrintOrder::BestOfStrategies)},
|
||||||
|
{ "snake", int(PrintOrder::Snake)},
|
||||||
};
|
};
|
||||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintOrder)
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintOrder)
|
||||||
|
|
||||||
@@ -1999,12 +2001,30 @@ void PrintConfigDef::init_fff_params()
|
|||||||
|
|
||||||
def = this->add("print_order", coEnum);
|
def = this->add("print_order", coEnum);
|
||||||
def->label = L("Intra-layer order");
|
def->label = L("Intra-layer order");
|
||||||
def->tooltip = L("Print order within a single layer.");
|
def->tooltip = L("Order in which object instances are visited within a single layer, which controls how much "
|
||||||
|
"travel is spent moving between them.\n\n"
|
||||||
|
"Default: nearest-neighbor chaining, refined with 2-opt and crossing removal. A good general "
|
||||||
|
"choice.\n"
|
||||||
|
"As object list: instances are printed in the same order as the object list, without any path "
|
||||||
|
"optimization. Use it when you need a predictable, manually controlled order.\n"
|
||||||
|
"Best of all (shortest path): every strategy is evaluated and the shortest one is used. The "
|
||||||
|
"object instance order is decided once for the whole print, while the ordering of individual "
|
||||||
|
"islands is decided per layer, so different layers may end up using different strategies. "
|
||||||
|
"Slightly slower to slice.\n"
|
||||||
|
"Snake: serpentine row-by-row traversal, refined with 2-opt. Well suited to regular grids of "
|
||||||
|
"many small parts.\n\n"
|
||||||
|
"With multiple filaments or tools in the same layer, minimizing tool changes takes priority: "
|
||||||
|
"objects are grouped by filament first and this setting only orders the instances within each "
|
||||||
|
"filament group, so the overall sequence may not look like the shortest path across the plate.");
|
||||||
def->enum_keys_map = &ConfigOptionEnum<PrintOrder>::get_enum_values();
|
def->enum_keys_map = &ConfigOptionEnum<PrintOrder>::get_enum_values();
|
||||||
def->enum_values.push_back("default");
|
def->enum_values.push_back("default");
|
||||||
def->enum_values.push_back("as_obj_list");
|
def->enum_values.push_back("as_obj_list");
|
||||||
|
def->enum_values.push_back("best_of");
|
||||||
|
def->enum_values.push_back("snake");
|
||||||
def->enum_labels.push_back(L("Default"));
|
def->enum_labels.push_back(L("Default"));
|
||||||
def->enum_labels.push_back(L("As object list"));
|
def->enum_labels.push_back(L("As object list"));
|
||||||
|
def->enum_labels.push_back(L("Best of all (shortest path)"));
|
||||||
|
def->enum_labels.push_back(L("Snake"));
|
||||||
def->mode = comAdvanced;
|
def->mode = comAdvanced;
|
||||||
def->set_default_value(new ConfigOptionEnum<PrintOrder>(PrintOrder::Default));
|
def->set_default_value(new ConfigOptionEnum<PrintOrder>(PrintOrder::Default));
|
||||||
|
|
||||||
@@ -5629,6 +5649,7 @@ void PrintConfigDef::init_fff_params()
|
|||||||
// Orca:
|
// Orca:
|
||||||
def = this->add("retract_after_wipe", coPercents);
|
def = this->add("retract_after_wipe", coPercents);
|
||||||
def->label = L("Retract amount after wipe");
|
def->label = L("Retract amount after wipe");
|
||||||
|
// xgettext:no-c-format, no-boost-format
|
||||||
def->tooltip = L("The length of fast retraction after wipe, relative to retraction length.\n"
|
def->tooltip = L("The length of fast retraction after wipe, relative to retraction length.\n"
|
||||||
"The value will be clamped by 100% minus the retract amount before the wipe value.");
|
"The value will be clamped by 100% minus the retract amount before the wipe value.");
|
||||||
def->sidetext = "%";
|
def->sidetext = "%";
|
||||||
@@ -10495,44 +10516,6 @@ int DynamicPrintConfig::get_extruder_nozzle_volume_count(int extruder_count, std
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Orca: BBL system profiles ship full-width print_extruder_id/print_extruder_variant columns, but
|
|
||||||
// custom multi-extruder printers only ever get the machine-scope columns synthesized for them (see
|
|
||||||
// extend_extruder_variant); the process scope keeps the length-1 defaults, both in presets and in
|
|
||||||
// 3mf project configs. Expanding with that degenerate map makes every per-extruder lookup fail, and
|
|
||||||
// because both keys are themselves in print_options_with_variant, the expansion then latches a
|
|
||||||
// full-width-but-wrong [1,1,...] map that also defeats the generated_extruder_id fallback in
|
|
||||||
// get_index_for_extruder. Synthesize the process columns from the printer's extruder_variant_list
|
|
||||||
// (same token walk as extend_extruder_variant) before expanding.
|
|
||||||
static void ensure_process_variant_columns(DynamicPrintConfig &config, const DynamicPrintConfig &printer_config)
|
|
||||||
{
|
|
||||||
auto id_opt = dynamic_cast<ConfigOptionInts *>(config.option("print_extruder_id"));
|
|
||||||
auto variant_opt = dynamic_cast<ConfigOptionStrings *>(config.option("print_extruder_variant"));
|
|
||||||
auto list_opt = dynamic_cast<const ConfigOptionStrings *>(printer_config.option("extruder_variant_list"));
|
|
||||||
if (!id_opt || !variant_opt || !list_opt)
|
|
||||||
return;
|
|
||||||
if (id_opt->values.size() != 1 || variant_opt->values.size() != 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::vector<int> ids;
|
|
||||||
std::vector<std::string> variants;
|
|
||||||
for (int i = 0; i < int(list_opt->values.size()); ++i) {
|
|
||||||
std::vector<std::string> tokens;
|
|
||||||
boost::split(tokens, list_opt->get_at(i), boost::is_any_of(","), boost::token_compress_on);
|
|
||||||
for (std::string &token : tokens) {
|
|
||||||
boost::trim(token);
|
|
||||||
if (token.empty())
|
|
||||||
continue;
|
|
||||||
ids.push_back(i + 1);
|
|
||||||
variants.push_back(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// A single column is the legitimate single-extruder layout, not a degenerate one.
|
|
||||||
if (ids.size() <= 1)
|
|
||||||
return;
|
|
||||||
id_opt->values = std::move(ids);
|
|
||||||
variant_opt->values = std::move(variants);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<int> DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector<std::vector<NozzleVolumeType>>& nv_types,
|
std::vector<int> DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector<std::vector<NozzleVolumeType>>& nv_types,
|
||||||
std::set<std::string>& key_set, std::string id_name, std::string variant_name, unsigned int stride, unsigned int extruder_id, NozzleVolumeType filament_nvt)
|
std::set<std::string>& key_set, std::string id_name, std::string variant_name, unsigned int stride, unsigned int extruder_id, NozzleVolumeType filament_nvt)
|
||||||
{
|
{
|
||||||
@@ -10574,8 +10557,6 @@ std::vector<int> DynamicPrintConfig::update_values_to_printer_extruders(DynamicP
|
|||||||
variant_count = 1;
|
variant_count = 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (id_name == "print_extruder_id")
|
|
||||||
ensure_process_variant_columns(*this, printer_config);
|
|
||||||
// Orca: emit the slots first, then size variant_count from what was actually
|
// Orca: emit the slots first, then size variant_count from what was actually
|
||||||
// emitted. extruder_nozzle_volume_count only equals the emitted total when every
|
// emitted. extruder_nozzle_volume_count only equals the emitted total when every
|
||||||
// extruder carries per-type stats; an extruder with an empty stats entry combined
|
// extruder carries per-type stats; an extruder with an empty stats entry combined
|
||||||
|
|||||||
@@ -214,6 +214,8 @@ enum class PrintOrder
|
|||||||
{
|
{
|
||||||
Default,
|
Default,
|
||||||
AsObjectList,
|
AsObjectList,
|
||||||
|
BestOfStrategies, // run all custom strategies, pick the shortest total path
|
||||||
|
Snake, // snake-like row traversal (back-and-forth) + 2-opt
|
||||||
Count,
|
Count,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2408,7 +2410,8 @@ namespace cereal {
|
|||||||
archive(serialization_key_ordinal);
|
archive(serialization_key_ordinal);
|
||||||
assert(serialization_key_ordinal > 0);
|
assert(serialization_key_ordinal > 0);
|
||||||
auto it = Slic3r::print_config_def.by_serialization_key_ordinal.find(serialization_key_ordinal);
|
auto it = Slic3r::print_config_def.by_serialization_key_ordinal.find(serialization_key_ordinal);
|
||||||
assert(it != Slic3r::print_config_def.by_serialization_key_ordinal.end());
|
if (it == Slic3r::print_config_def.by_serialization_key_ordinal.end())
|
||||||
|
throw std::runtime_error("VendorCache: unknown serialization_key_ordinal " + std::to_string(serialization_key_ordinal) + " - cache is stale");
|
||||||
config.set_key_value(it->second->opt_key, it->second->load_option_from_archive(archive));
|
config.set_key_value(it->second->opt_key, it->second->load_option_from_archive(archive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,6 +190,19 @@ public:
|
|||||||
os << self.to_string();
|
os << self.to_string();
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cereal: round-trip through the standard 3-part string (major.minor.patch).
|
||||||
|
// to_string() uses a BBS 4-part format that semver_parse() cannot read back.
|
||||||
|
template<class Archive>
|
||||||
|
std::string save_minimal(const Archive&) const { return to_string_sf(); }
|
||||||
|
template<class Archive>
|
||||||
|
void load_minimal(const Archive&, const std::string& s) {
|
||||||
|
auto v = Semver::parse(s);
|
||||||
|
if (! v)
|
||||||
|
throw std::runtime_error("Semver: cannot parse serialized version: " + s);
|
||||||
|
*this = std::move(*v);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
semver_t ver;
|
semver_t ver;
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "KDTreeIndirect.hpp"
|
#include "KDTreeIndirect.hpp"
|
||||||
#include "MutablePriorityQueue.hpp"
|
#include "MutablePriorityQueue.hpp"
|
||||||
#include "Print.hpp"
|
#include "Print.hpp"
|
||||||
|
#include "GCode/OrderingStrategies.hpp"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
@@ -1103,7 +1104,7 @@ std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy) {
|
|||||||
return chain_points(points);
|
return chain_points(points);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<size_t> chain_points(const Points &points, Point *start_near)
|
std::vector<size_t> chain_points(const Points &points, const Point *start_near)
|
||||||
{
|
{
|
||||||
auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; };
|
auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; };
|
||||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, points.size(), start_near);
|
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, points.size(), start_near);
|
||||||
@@ -1111,9 +1112,26 @@ std::vector<size_t> chain_points(const Points &points, Point *start_near)
|
|||||||
out.reserve(ordered.size());
|
out.reserve(ordered.size());
|
||||||
for (auto &segment_and_reversal : ordered)
|
for (auto &segment_and_reversal : ordered)
|
||||||
out.emplace_back(segment_and_reversal.first);
|
out.emplace_back(segment_and_reversal.first);
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<size_t> chain_points_with_postprocessing(const Points &points, const Point *start_near)
|
||||||
|
{
|
||||||
|
std::vector<size_t> path = chain_points(points, start_near);
|
||||||
|
// Alternate 2-opt and crossing removal until convergence.
|
||||||
|
// 2-opt can create new crossings, and crossing removal can create new
|
||||||
|
// opportunities for 2-opt improvement. Break early if neither improves.
|
||||||
|
for (int iter = 0; iter < 3; ++iter) {
|
||||||
|
bool improved = tsp_2opt_improve(path, points);
|
||||||
|
improved |= tsp_remove_crossings(path, points);
|
||||||
|
if (!improved) break;
|
||||||
|
}
|
||||||
|
if (start_near == nullptr)
|
||||||
|
tsp_rotate_minimize_closing(path, points);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
// #define DEBUG_SVG_OUTPUT
|
// #define DEBUG_SVG_OUTPUT
|
||||||
#endif /* NDEBUG */
|
#endif /* NDEBUG */
|
||||||
@@ -2025,12 +2043,13 @@ std::vector<const PrintInstance*> chain_print_object_instances(const std::vector
|
|||||||
instances.emplace_back(i, j);
|
instances.emplace_back(i, j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto segment_end_point = [&object_reference_points](size_t idx, bool /* first_point */) -> const Point& { return object_reference_points[idx]; };
|
// Order objects using nearest neighbor + post-processing (crossing removal + 2-opt).
|
||||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, instances.size(), start_near);
|
std::vector<size_t> path = chain_points_with_postprocessing(object_reference_points, start_near);
|
||||||
|
|
||||||
std::vector<const PrintInstance*> out;
|
std::vector<const PrintInstance*> out;
|
||||||
out.reserve(instances.size());
|
out.reserve(path.size());
|
||||||
for (auto& segment_and_reversal : ordered) {
|
for (size_t idx : path) {
|
||||||
const std::pair<size_t, size_t>& inst = instances[segment_and_reversal.first];
|
const std::pair<size_t, size_t>& inst = instances[idx];
|
||||||
out.emplace_back(&print_objects[inst.first]->instances()[inst.second]);
|
out.emplace_back(&print_objects[inst.first]->instances()[inst.second]);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ namespace Slic3r {
|
|||||||
using PolyNodes = std::vector<PolyNode*, PointsAllocator<PolyNode*>>;
|
using PolyNodes = std::vector<PolyNode*, PointsAllocator<PolyNode*>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<size_t> chain_points(const Points &points, Point *start_near = nullptr);
|
std::vector<size_t> chain_points(const Points &points, const Point *start_near = nullptr);
|
||||||
|
// Variant with post-processing (crossing removal + 2-opt) for object ordering.
|
||||||
|
std::vector<size_t> chain_points_with_postprocessing(const Points &points, const Point *start_near = nullptr);
|
||||||
std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy);
|
std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy);
|
||||||
|
|
||||||
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
|
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
|
||||||
|
|||||||
@@ -65,6 +65,15 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
|||||||
const bool smooth_supports = support_params.support_style != smsGrid;
|
const bool smooth_supports = support_params.support_style != smsGrid;
|
||||||
SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first;
|
SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first;
|
||||||
SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second;
|
SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second;
|
||||||
|
// The user-facing interface layer counts include the contact layer. Internally,
|
||||||
|
// contact layers are generated separately, so only the remaining layers are
|
||||||
|
// projected into intermediate interface/base-interface layers here.
|
||||||
|
const size_t num_top_interface_layers = support_params.has_top_contacts ? support_params.num_top_interface_layers - 1 : 0;
|
||||||
|
const size_t num_bottom_interface_layers = support_params.has_bottom_contacts ? support_params.num_bottom_interface_layers - 1 : 0;
|
||||||
|
const size_t num_top_base_interface_layers = std::min(support_params.num_top_base_interface_layers, num_top_interface_layers);
|
||||||
|
const size_t num_bottom_base_interface_layers = std::min(support_params.num_bottom_base_interface_layers, num_bottom_interface_layers);
|
||||||
|
const size_t num_top_interface_layers_only = num_top_interface_layers - num_top_base_interface_layers;
|
||||||
|
const size_t num_bottom_interface_layers_only = num_bottom_interface_layers - num_bottom_base_interface_layers;
|
||||||
|
|
||||||
interface_layers.assign(intermediate_layers.size(), nullptr);
|
interface_layers.assign(intermediate_layers.size(), nullptr);
|
||||||
if (support_params.has_base_interfaces())
|
if (support_params.has_base_interfaces())
|
||||||
@@ -124,6 +133,8 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
|||||||
};
|
};
|
||||||
tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())),
|
tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())),
|
||||||
[&bottom_contacts, &top_contacts, &top_interface_layers, &top_base_interface_layers, &intermediate_layers, &insert_layer, &support_params,
|
[&bottom_contacts, &top_contacts, &top_interface_layers, &top_base_interface_layers, &intermediate_layers, &insert_layer, &support_params,
|
||||||
|
num_top_interface_layers, num_bottom_interface_layers, num_top_base_interface_layers, num_bottom_base_interface_layers,
|
||||||
|
num_top_interface_layers_only, num_bottom_interface_layers_only,
|
||||||
snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range<int>& range) {
|
snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range<int>& range) {
|
||||||
// Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below
|
// Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below
|
||||||
// this intermediate layer.
|
// this intermediate layer.
|
||||||
@@ -142,16 +153,16 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
|||||||
Polygons polygons_top_contact_projected_base;
|
Polygons polygons_top_contact_projected_base;
|
||||||
Polygons polygons_bottom_contact_projected_interface;
|
Polygons polygons_bottom_contact_projected_interface;
|
||||||
Polygons polygons_bottom_contact_projected_base;
|
Polygons polygons_bottom_contact_projected_base;
|
||||||
if (support_params.num_top_interface_layers > 0) {
|
if (num_top_interface_layers > 0) {
|
||||||
// Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
|
// Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
|
||||||
coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers) - 1)]->print_z;
|
coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(num_top_interface_layers) - 1)]->print_z;
|
||||||
coordf_t top_inteface_z = std::numeric_limits<coordf_t>::max();
|
coordf_t top_interface_z = std::numeric_limits<coordf_t>::max();
|
||||||
if (support_params.num_top_base_interface_layers > 0)
|
if (num_top_base_interface_layers > 0)
|
||||||
// Some top base interface layers will be generated.
|
// Some top base interface layers will be generated.
|
||||||
top_inteface_z = support_params.num_top_interface_layers_only() == 0 ?
|
top_interface_z = num_top_interface_layers_only == 0 ?
|
||||||
// Only base interface layers to generate.
|
// Only base interface layers to generate.
|
||||||
- std::numeric_limits<coordf_t>::max() :
|
- std::numeric_limits<coordf_t>::max() :
|
||||||
intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers_only()) - 1)]->print_z;
|
intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(num_top_interface_layers_only) - 1)]->print_z;
|
||||||
// Move idx_top_contact_first up until above the current print_z.
|
// Move idx_top_contact_first up until above the current print_z.
|
||||||
idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON
|
idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON
|
||||||
// Collect the top contact areas above this intermediate layer, below top_z.
|
// Collect the top contact areas above this intermediate layer, below top_z.
|
||||||
@@ -160,22 +171,22 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
|||||||
//FIXME maybe this adds one interface layer in excess?
|
//FIXME maybe this adds one interface layer in excess?
|
||||||
if (top_contact_layer.bottom_z - EPSILON > top_z)
|
if (top_contact_layer.bottom_z - EPSILON > top_z)
|
||||||
break;
|
break;
|
||||||
polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface,
|
polygons_append(top_contact_layer.bottom_z - EPSILON > top_interface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface,
|
||||||
// For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers.
|
// For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers.
|
||||||
// For grid supports, merging of support regions will be performed by the projection into grid.
|
// For grid supports, merging of support regions will be performed by the projection into grid.
|
||||||
snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons);
|
snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (support_params.num_bottom_interface_layers > 0) {
|
if (num_bottom_interface_layers > 0) {
|
||||||
// Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
|
// Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
|
||||||
coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers) + 1)]->bottom_z;
|
coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - int(num_bottom_interface_layers) + 1)]->bottom_z;
|
||||||
coordf_t bottom_interface_z = - std::numeric_limits<coordf_t>::max();
|
coordf_t bottom_interface_z = - std::numeric_limits<coordf_t>::max();
|
||||||
if (support_params.num_bottom_base_interface_layers > 0)
|
if (num_bottom_base_interface_layers > 0)
|
||||||
// Some bottom base interface layers will be generated.
|
// Some bottom base interface layers will be generated.
|
||||||
bottom_interface_z = support_params.num_bottom_interface_layers_only() == 0 ?
|
bottom_interface_z = num_bottom_interface_layers_only == 0 ?
|
||||||
// Only base interface layers to generate.
|
// Only base interface layers to generate.
|
||||||
std::numeric_limits<coordf_t>::max() :
|
std::numeric_limits<coordf_t>::max() :
|
||||||
intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers_only()))]->bottom_z;
|
intermediate_layers[std::max(0, idx_intermediate_layer - int(num_bottom_interface_layers_only))]->bottom_z;
|
||||||
// Move idx_bottom_contact_first up until touching bottom_z.
|
// Move idx_bottom_contact_first up until touching bottom_z.
|
||||||
idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const SupportGeneratorLayer *layer){ return layer->print_z >= bottom_z - EPSILON; });
|
idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const SupportGeneratorLayer *layer){ return layer->print_z >= bottom_z - EPSILON; });
|
||||||
// Collect the top contact areas above this intermediate layer, below top_z.
|
// Collect the top contact areas above this intermediate layer, below top_z.
|
||||||
@@ -1563,13 +1574,17 @@ void generate_support_toolpaths(
|
|||||||
// Pointer to the 1st layer interface filler.
|
// Pointer to the 1st layer interface filler.
|
||||||
auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get();
|
auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get();
|
||||||
// Filler for the 1st layer interface, if different from filler_interface.
|
// Filler for the 1st layer interface, if different from filler_interface.
|
||||||
auto filler_raft_contact_ptr = std::unique_ptr<Fill>(range.begin() == n_raft_layers && config.support_interface_top_layers.value == 0 ?
|
const bool top_interfaces_enabled = support_params.num_top_interface_layers > 0;
|
||||||
|
const bool bottom_interfaces_enabled = support_params.num_bottom_interface_layers > 0;
|
||||||
|
const coordf_t base_interface_density = top_interfaces_enabled || !bottom_interfaces_enabled ?
|
||||||
|
support_params.top_interface_density : support_params.bottom_interface_density;
|
||||||
|
auto filler_raft_contact_ptr = std::unique_ptr<Fill>(range.begin() == n_raft_layers && !top_interfaces_enabled ?
|
||||||
Fill::new_from_type(support_params.raft_interface_fill_pattern) : nullptr);
|
Fill::new_from_type(support_params.raft_interface_fill_pattern) : nullptr);
|
||||||
// Pointer to the 1st layer interface filler.
|
// Pointer to the 1st layer interface filler.
|
||||||
auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get();
|
auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get();
|
||||||
// Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer).
|
// Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer).
|
||||||
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr :
|
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr :
|
||||||
Fill::new_from_type(support_params.top_interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
|
Fill::new_from_type(base_interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
|
||||||
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(support_params.base_fill_pattern));
|
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(support_params.base_fill_pattern));
|
||||||
filler_interface->set_bounding_box(bbox_object);
|
filler_interface->set_bounding_box(bbox_object);
|
||||||
if (filler_first_layer_ptr)
|
if (filler_first_layer_ptr)
|
||||||
@@ -1583,10 +1598,7 @@ void generate_support_toolpaths(
|
|||||||
{
|
{
|
||||||
SupportLayer &support_layer = *support_layers[support_layer_id];
|
SupportLayer &support_layer = *support_layers[support_layer_id];
|
||||||
LayerCache &layer_cache = layer_caches[support_layer_id];
|
LayerCache &layer_cache = layer_caches[support_layer_id];
|
||||||
const float support_interface_angle = (config.support_interface_pattern == smipRectilinearInterlaced) ?
|
const float support_interface_angle = support_params.support_interface_angle(support_layer.interface_id());
|
||||||
support_params.raft_interface_angle(support_layer.interface_id()) :
|
|
||||||
((support_params.support_style == smsGrid || config.support_interface_pattern == smipRectilinear) ?
|
|
||||||
support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()));
|
|
||||||
|
|
||||||
// Find polygons with the same print_z.
|
// Find polygons with the same print_z.
|
||||||
SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer;
|
SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer;
|
||||||
@@ -1619,7 +1631,9 @@ void generate_support_toolpaths(
|
|||||||
bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z);
|
bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z);
|
||||||
// ORCA: Organic tree uses projected contacts to build the interface stack; avoid extra bottom-contact extrusion.
|
// ORCA: Organic tree uses projected contacts to build the interface stack; avoid extra bottom-contact extrusion.
|
||||||
const bool organic_tree = support_params.support_style == SupportMaterialStyle::smsTreeOrganic;
|
const bool organic_tree = support_params.support_style == SupportMaterialStyle::smsTreeOrganic;
|
||||||
if (config.support_interface_top_layers == 0) {
|
const bool top_interfaces = support_params.num_top_interface_layers > 0;
|
||||||
|
const bool bottom_interfaces = support_params.num_bottom_interface_layers > 0;
|
||||||
|
if (!top_interfaces) {
|
||||||
// If no top interface layers were requested, we treat the contact layer exactly as a generic base layer.
|
// If no top interface layers were requested, we treat the contact layer exactly as a generic base layer.
|
||||||
// Don't merge the raft contact layer though.
|
// Don't merge the raft contact layer though.
|
||||||
if (support_params.can_merge_support_regions && ! raft_layer) {
|
if (support_params.can_merge_support_regions && ! raft_layer) {
|
||||||
@@ -1642,15 +1656,29 @@ void generate_support_toolpaths(
|
|||||||
if (top_contact_layer.could_merge(interface_layer) && ! raft_layer)
|
if (top_contact_layer.could_merge(interface_layer) && ! raft_layer)
|
||||||
top_contact_layer.merge(std::move(interface_layer));
|
top_contact_layer.merge(std::move(interface_layer));
|
||||||
}
|
}
|
||||||
if ((config.support_interface_top_layers == 0 || config.support_interface_bottom_layers == 0) && support_params.can_merge_support_regions) {
|
if (!bottom_interfaces && support_params.can_merge_support_regions) {
|
||||||
if (base_layer.could_merge(bottom_contact_layer))
|
if (base_layer.could_merge(bottom_contact_layer))
|
||||||
base_layer.merge(std::move(bottom_contact_layer));
|
base_layer.merge(std::move(bottom_contact_layer));
|
||||||
else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging)
|
else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging)
|
||||||
base_layer = std::move(bottom_contact_layer);
|
base_layer = std::move(bottom_contact_layer);
|
||||||
} else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) {
|
} else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) {
|
||||||
top_contact_layer.merge(std::move(bottom_contact_layer));
|
if (top_interfaces && bottom_interfaces) {
|
||||||
|
top_contact_layer.merge(std::move(bottom_contact_layer));
|
||||||
|
} else if (bottom_interfaces) {
|
||||||
|
top_contact_layer.set_polygons_to_extrude(
|
||||||
|
diff(top_contact_layer.polygons_to_extrude(), bottom_contact_layer.polygons_to_extrude()));
|
||||||
|
} else {
|
||||||
|
bottom_contact_layer.set_polygons_to_extrude(
|
||||||
|
diff(bottom_contact_layer.polygons_to_extrude(), top_contact_layer.polygons_to_extrude()));
|
||||||
|
}
|
||||||
} else if (bottom_contact_layer.could_merge(interface_layer) && ! organic_tree) {
|
} else if (bottom_contact_layer.could_merge(interface_layer) && ! organic_tree) {
|
||||||
bottom_contact_layer.merge(std::move(interface_layer));
|
const bool interface_layer_is_bottom = interface_layer.layer->layer_type == SupporLayerType::BottomInterface;
|
||||||
|
if (bottom_interfaces && interface_layer_is_bottom) {
|
||||||
|
bottom_contact_layer.merge(std::move(interface_layer));
|
||||||
|
} else {
|
||||||
|
bottom_contact_layer.set_polygons_to_extrude(
|
||||||
|
diff(bottom_contact_layer.polygons_to_extrude(), interface_layer.polygons_to_extrude()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Orca: For organic trees the support-material regions are generated from
|
// Orca: For organic trees the support-material regions are generated from
|
||||||
@@ -1730,12 +1758,12 @@ void generate_support_toolpaths(
|
|||||||
interface_as_base ? ExtrusionRole::erSupportMaterial : ExtrusionRole::erSupportMaterialInterface, interface_flow);
|
interface_as_base ? ExtrusionRole::erSupportMaterial : ExtrusionRole::erSupportMaterialInterface, interface_flow);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const bool top_interfaces = support_params.num_top_interface_layers > 0;
|
|
||||||
const bool bottom_interfaces = top_interfaces && support_params.num_bottom_interface_layers > 0;
|
|
||||||
extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase);
|
extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase);
|
||||||
if (!organic_tree)
|
if (!organic_tree)
|
||||||
extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase);
|
extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase);
|
||||||
extrude_interface(interface_layer, top_interfaces ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase);
|
const bool interface_layer_enabled = !interface_layer.empty() &&
|
||||||
|
(interface_layer.layer->layer_type == SupporLayerType::BottomInterface ? bottom_interfaces : top_interfaces);
|
||||||
|
extrude_interface(interface_layer, interface_layer_enabled ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase);
|
||||||
// Base interface layers under soluble interfaces
|
// Base interface layers under soluble interfaces
|
||||||
if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) {
|
if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) {
|
||||||
Fill *filler = filler_base_interface.get();
|
Fill *filler = filler_base_interface.get();
|
||||||
@@ -1745,7 +1773,7 @@ void generate_support_toolpaths(
|
|||||||
Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
|
Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
|
||||||
filler->angle = support_interface_angle;
|
filler->angle = support_interface_angle;
|
||||||
filler->spacing = support_params.support_material_interface_flow.spacing();
|
filler->spacing = support_params.support_material_interface_flow.spacing();
|
||||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.top_interface_density));
|
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / base_interface_density));
|
||||||
fill_expolygons_generate_paths(
|
fill_expolygons_generate_paths(
|
||||||
// Destination
|
// Destination
|
||||||
base_interface_layer.extrusions,
|
base_interface_layer.extrusions,
|
||||||
@@ -1753,7 +1781,7 @@ void generate_support_toolpaths(
|
|||||||
// Regions to fill
|
// Regions to fill
|
||||||
union_safety_offset_ex(base_interface_layer.polygons_to_extrude()),
|
union_safety_offset_ex(base_interface_layer.polygons_to_extrude()),
|
||||||
// Filler and its parameters
|
// Filler and its parameters
|
||||||
filler, float(support_params.top_interface_density),
|
filler, float(base_interface_density),
|
||||||
// Extrusion parameters
|
// Extrusion parameters
|
||||||
ExtrusionRole::erSupportMaterial, interface_flow);
|
ExtrusionRole::erSupportMaterial, interface_flow);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ struct SupportParameters {
|
|||||||
|
|
||||||
{
|
{
|
||||||
this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value);
|
this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value);
|
||||||
this->num_bottom_interface_layers = number_of_support_interface_bottom_layers(object_config);
|
this->num_bottom_interface_layers = std::max(0, number_of_support_interface_bottom_layers(object_config));
|
||||||
this->has_top_contacts = num_top_interface_layers > 0;
|
this->has_top_contacts = num_top_interface_layers > 0;
|
||||||
this->has_bottom_contacts = num_bottom_interface_layers > 0;
|
this->has_bottom_contacts = num_bottom_interface_layers > 0;
|
||||||
// BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion
|
// BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion
|
||||||
@@ -46,15 +46,15 @@ struct SupportParameters {
|
|||||||
if (non_soluble_base_top) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
|
if (non_soluble_base_top) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
|
||||||
this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2));
|
this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2));
|
||||||
} else {
|
} else {
|
||||||
this->num_top_base_interface_layers =
|
// Keep at least one configured layer on the interface filament.
|
||||||
(different_support_interface_filament && this->zero_gap_interface_top) ? 1 : 0;
|
this->num_top_base_interface_layers = different_support_interface_filament && num_top_interface_layers > 1 ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (non_soluble_base_bottom) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
|
if (non_soluble_base_bottom) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
|
||||||
this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2));
|
this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2));
|
||||||
} else {
|
} else {
|
||||||
this->num_bottom_base_interface_layers =
|
// Keep at least one configured layer on the interface filament.
|
||||||
(different_support_interface_filament && this->zero_gap_interface_bottom) ? 1 : 0;
|
this->num_bottom_base_interface_layers = different_support_interface_filament && num_bottom_interface_layers > 1 ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height));
|
this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height));
|
||||||
@@ -74,7 +74,7 @@ struct SupportParameters {
|
|||||||
for (auto layer : object.layers())
|
for (auto layer : object.layers())
|
||||||
this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, layer->height));
|
this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, layer->height));
|
||||||
|
|
||||||
if (object_config.support_interface_top_layers.value == 0) {
|
if (this->num_top_interface_layers == 0 && this->num_bottom_interface_layers == 0) {
|
||||||
// No interface layers allowed, print everything with the base support pattern.
|
// No interface layers allowed, print everything with the base support pattern.
|
||||||
this->support_material_interface_flow = this->support_material_flow;
|
this->support_material_interface_flow = this->support_material_flow;
|
||||||
}
|
}
|
||||||
@@ -120,8 +120,8 @@ struct SupportParameters {
|
|||||||
this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing);
|
this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing);
|
||||||
this->support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing();
|
this->support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing();
|
||||||
this->support_density = std::min(1., this->support_material_flow.spacing() / this->support_spacing);
|
this->support_density = std::min(1., this->support_material_flow.spacing() / this->support_spacing);
|
||||||
if (object_config.support_interface_top_layers.value == 0) {
|
if (this->num_top_interface_layers == 0) {
|
||||||
// No interface layers allowed, print everything with the base support pattern.
|
// No top interface layers allowed; keep unused top interface parameters aligned with base support.
|
||||||
this->top_interface_spacing = this->support_spacing;
|
this->top_interface_spacing = this->support_spacing;
|
||||||
this->top_interface_density = this->support_density;
|
this->top_interface_density = this->support_density;
|
||||||
}
|
}
|
||||||
@@ -133,16 +133,20 @@ struct SupportParameters {
|
|||||||
this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase;
|
this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase;
|
||||||
this->interface_fill_pattern = (this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
this->interface_fill_pattern = (this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||||
this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase;
|
this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase;
|
||||||
|
const coordf_t contact_interface_density = this->num_top_interface_layers > 0 ?
|
||||||
|
this->top_interface_density : this->bottom_interface_density;
|
||||||
|
const bool zero_gap_contact_interface = this->num_top_interface_layers > 0 ?
|
||||||
|
this->zero_gap_interface_top : this->zero_gap_interface_bottom;
|
||||||
if (object_config.support_interface_pattern == smipGrid)
|
if (object_config.support_interface_pattern == smipGrid)
|
||||||
this->contact_fill_pattern = ipGrid;
|
this->contact_fill_pattern = ipGrid;
|
||||||
else if (object_config.support_interface_pattern == smipRectilinearInterlaced)
|
else if (object_config.support_interface_pattern == smipRectilinearInterlaced)
|
||||||
this->contact_fill_pattern = ipRectilinear;
|
this->contact_fill_pattern = ipRectilinear;
|
||||||
else
|
else
|
||||||
this->contact_fill_pattern =
|
this->contact_fill_pattern =
|
||||||
(object_config.support_interface_pattern == smipAuto && this->zero_gap_interface_top) ||
|
(object_config.support_interface_pattern == smipAuto && zero_gap_contact_interface) ||
|
||||||
object_config.support_interface_pattern == smipConcentric ?
|
object_config.support_interface_pattern == smipConcentric ?
|
||||||
ipConcentric :
|
ipConcentric :
|
||||||
(this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
(contact_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||||
|
|
||||||
this->raft_angle_1st_layer = 0.f;
|
this->raft_angle_1st_layer = 0.f;
|
||||||
this->raft_angle_base = 0.f;
|
this->raft_angle_base = 0.f;
|
||||||
@@ -188,6 +192,7 @@ struct SupportParameters {
|
|||||||
std::numeric_limits<double>::max();
|
std::numeric_limits<double>::max();
|
||||||
|
|
||||||
support_style = object_config.support_style;
|
support_style = object_config.support_style;
|
||||||
|
support_interface_pattern = object_config.support_interface_pattern;
|
||||||
if (support_style != smsDefault) {
|
if (support_style != smsDefault) {
|
||||||
if ((support_style == smsSnug || support_style == smsGrid) && is_tree(object_config.support_type)) support_style = smsDefault;
|
if ((support_style == smsSnug || support_style == smsGrid) && is_tree(object_config.support_type)) support_style = smsDefault;
|
||||||
if ((support_style == smsTreeSlim || support_style == smsTreeStrong || support_style == smsTreeHybrid || support_style == smsTreeOrganic) &&
|
if ((support_style == smsTreeSlim || support_style == smsTreeStrong || support_style == smsTreeHybrid || support_style == smsTreeOrganic) &&
|
||||||
@@ -211,9 +216,9 @@ struct SupportParameters {
|
|||||||
bool has_top_contacts;
|
bool has_top_contacts;
|
||||||
// Is there at least a bottom contact layer extruded below support base?
|
// Is there at least a bottom contact layer extruded below support base?
|
||||||
bool has_bottom_contacts;
|
bool has_bottom_contacts;
|
||||||
// Number of top interface layers without counting the contact layer.
|
// User-configured number of top interface layers, including the contact layer.
|
||||||
size_t num_top_interface_layers;
|
size_t num_top_interface_layers;
|
||||||
// Number of bottom interface layers without counting the contact layer.
|
// User-configured number of bottom interface layers, including the contact layer.
|
||||||
size_t num_bottom_interface_layers;
|
size_t num_bottom_interface_layers;
|
||||||
// Number of top base interface layers.
|
// Number of top base interface layers.
|
||||||
size_t num_top_base_interface_layers;
|
size_t num_top_base_interface_layers;
|
||||||
@@ -235,7 +240,7 @@ struct SupportParameters {
|
|||||||
Flow support_material_interface_flow;
|
Flow support_material_interface_flow;
|
||||||
// Flow at the bottom interfaces and contacts.
|
// Flow at the bottom interfaces and contacts.
|
||||||
Flow support_material_bottom_interface_flow;
|
Flow support_material_bottom_interface_flow;
|
||||||
// Flow at raft inteface & contact layers.
|
// Flow at raft interface & contact layers.
|
||||||
Flow raft_interface_flow;
|
Flow raft_interface_flow;
|
||||||
coordf_t support_extrusion_width;
|
coordf_t support_extrusion_width;
|
||||||
// Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
|
// Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
|
||||||
@@ -262,6 +267,7 @@ struct SupportParameters {
|
|||||||
// Density of the base support layers.
|
// Density of the base support layers.
|
||||||
coordf_t support_density;
|
coordf_t support_density;
|
||||||
SupportMaterialStyle support_style = smsDefault;
|
SupportMaterialStyle support_style = smsDefault;
|
||||||
|
SupportMaterialInterfacePattern support_interface_pattern = smipAuto;
|
||||||
|
|
||||||
// Pattern of the sparse infill including sparse raft layers.
|
// Pattern of the sparse infill including sparse raft layers.
|
||||||
InfillPattern base_fill_pattern;
|
InfillPattern base_fill_pattern;
|
||||||
@@ -280,9 +286,33 @@ struct SupportParameters {
|
|||||||
float raft_angle_base;
|
float raft_angle_base;
|
||||||
float raft_angle_interface;
|
float raft_angle_interface;
|
||||||
|
|
||||||
// Produce a raft interface angle for a given SupportLayer::interface_id()
|
// Produce a +/-45deg alternating raft interface angle for a given SupportLayer::interface_id().
|
||||||
float raft_interface_angle(size_t interface_id) const
|
float raft_interface_angle(size_t interface_id) const
|
||||||
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); }
|
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI_4) : float(+ M_PI_4)); }
|
||||||
|
|
||||||
|
// Produce support interface angle for a given SupportLayer::interface_id().
|
||||||
|
// Angle will be shifted/rotated based on interface pattern.
|
||||||
|
float support_interface_angle(size_t interface_id) const
|
||||||
|
{
|
||||||
|
float angle;
|
||||||
|
|
||||||
|
switch (this->support_interface_pattern) {
|
||||||
|
case SupportMaterialInterfacePattern::smipRectilinear:
|
||||||
|
angle = support_style == SupportMaterialStyle::smsSnug ? this->interface_angle - float(M_PI_4) : this->interface_angle;
|
||||||
|
break;
|
||||||
|
case SupportMaterialInterfacePattern::smipRectilinearInterlaced:
|
||||||
|
angle = this->interface_angle + ((interface_id & 1) ? float(M_PI_4) : float(-M_PI_4));
|
||||||
|
break;
|
||||||
|
case SupportMaterialInterfacePattern::smipGrid:
|
||||||
|
angle = this->base_angle;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
angle = this->interface_angle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
bool independent_layer_height = false;
|
bool independent_layer_height = false;
|
||||||
const double thresh_big_overhang = Slic3r::sqr(scale_(10));
|
const double thresh_big_overhang = Slic3r::sqr(scale_(10));
|
||||||
|
|||||||
@@ -469,7 +469,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 2) Sum over top / bottom ranges.
|
// 2) Sum over top / bottom ranges.
|
||||||
const bool processing_last_mesh = outline_idx == layer_outline_indices.size();
|
const bool processing_last_mesh = outline_idx == layer_outline_indices.back();
|
||||||
tbb::parallel_for(tbb::blocked_range<LayerIndex>(data.begin(), data.end()),
|
tbb::parallel_for(tbb::blocked_range<LayerIndex>(data.begin(), data.end()),
|
||||||
[&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, radius,
|
[&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, radius,
|
||||||
xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, processing_last_mesh, &throw_on_cancel]
|
xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, processing_last_mesh, &throw_on_cancel]
|
||||||
|
|||||||
@@ -1511,7 +1511,9 @@ void TreeSupport::generate_toolpaths()
|
|||||||
// ORCA: reset interface Fill state per area group to keep angles deterministic.
|
// ORCA: reset interface Fill state per area group to keep angles deterministic.
|
||||||
filler_interface->fixed_angle = false;
|
filler_interface->fixed_angle = false;
|
||||||
filler_interface->layer_id = size_t(-1);
|
filler_interface->layer_id = size_t(-1);
|
||||||
filler_interface->angle = base_support_angle + M_PI_2; // default interface angle is perpendicular to support angle
|
filler_Roof1stLayer->fixed_angle = false;
|
||||||
|
filler_Roof1stLayer->layer_id = size_t(-1);
|
||||||
|
filler_interface->angle = m_support_params.support_interface_angle(area_group.interface_id);
|
||||||
if (area_group.type != SupportLayer::BaseType) {
|
if (area_group.type != SupportLayer::BaseType) {
|
||||||
// interface
|
// interface
|
||||||
if (layer_id == 0) {
|
if (layer_id == 0) {
|
||||||
@@ -1537,8 +1539,10 @@ void TreeSupport::generate_toolpaths()
|
|||||||
fill_params.density = interface_density;
|
fill_params.density = interface_density;
|
||||||
// Note: spacing means the separation between two lines as if they are tightly extruded
|
// Note: spacing means the separation between two lines as if they are tightly extruded
|
||||||
filler_Roof1stLayer->spacing = interface_flow.spacing();
|
filler_Roof1stLayer->spacing = interface_flow.spacing();
|
||||||
filler_Roof1stLayer->angle = base_support_angle;
|
filler_Roof1stLayer->angle = m_support_params.support_interface_angle(area_group.interface_id);
|
||||||
fill_params.dont_sort = true;
|
fill_params.dont_sort = true;
|
||||||
|
filler_Roof1stLayer->fixed_angle = (m_object_config->support_interface_pattern == smipRectilinearInterlaced ||
|
||||||
|
m_object_config->support_interface_pattern == smipRectilinear);
|
||||||
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
||||||
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
||||||
// generate a perimeter first to support interface better
|
// generate a perimeter first to support interface better
|
||||||
@@ -1556,18 +1560,11 @@ void TreeSupport::generate_toolpaths()
|
|||||||
fill_params.density = bottom_interface_density;
|
fill_params.density = bottom_interface_density;
|
||||||
filler_interface->spacing = interface_flow.spacing();
|
filler_interface->spacing = interface_flow.spacing();
|
||||||
|
|
||||||
if (m_object_config->support_interface_pattern == smipGrid) {
|
fill_params.dont_sort = (m_object_config->support_interface_pattern == smipGrid ||
|
||||||
filler_interface->angle = base_support_angle;
|
m_object_config->support_interface_pattern == smipRectilinearInterlaced);
|
||||||
fill_params.dont_sort = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
|
|
||||||
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
|
|
||||||
filler_interface->fixed_angle = true;
|
|
||||||
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
|
|
||||||
fill_params.dont_sort = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
filler_interface->fixed_angle = (m_object_config->support_interface_pattern == smipRectilinearInterlaced ||
|
||||||
|
m_object_config->support_interface_pattern == smipRectilinear);
|
||||||
|
|
||||||
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
||||||
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
||||||
@@ -1579,17 +1576,11 @@ void TreeSupport::generate_toolpaths()
|
|||||||
fill_params.density = interface_density;
|
fill_params.density = interface_density;
|
||||||
filler_interface->spacing = interface_flow.spacing();
|
filler_interface->spacing = interface_flow.spacing();
|
||||||
|
|
||||||
if (m_object_config->support_interface_pattern == smipGrid) {
|
fill_params.dont_sort = (m_object_config->support_interface_pattern == smipGrid ||
|
||||||
filler_interface->angle = base_support_angle;
|
m_object_config->support_interface_pattern == smipRectilinearInterlaced);
|
||||||
fill_params.dont_sort = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
|
filler_interface->fixed_angle = (m_object_config->support_interface_pattern == smipRectilinearInterlaced ||
|
||||||
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
|
m_object_config->support_interface_pattern == smipRectilinear);
|
||||||
filler_interface->fixed_angle = true;
|
|
||||||
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
|
|
||||||
fill_params.dont_sort = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
||||||
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
||||||
@@ -2014,6 +2005,9 @@ void TreeSupport::draw_circles()
|
|||||||
// generate areas
|
// generate areas
|
||||||
const coordf_t layer_height = config.layer_height.value;
|
const coordf_t layer_height = config.layer_height.value;
|
||||||
const size_t top_interface_layers = m_support_params.num_top_interface_layers;
|
const size_t top_interface_layers = m_support_params.num_top_interface_layers;
|
||||||
|
const int top_base_interface_layers = std::min<int>(
|
||||||
|
int(m_support_params.num_top_base_interface_layers),
|
||||||
|
top_interface_layers > 0 ? int(top_interface_layers) - 1 : 0);
|
||||||
const size_t bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
const size_t bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
||||||
const double nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(0);
|
const double nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(0);
|
||||||
const coordf_t line_width = config.get_abs_value("support_line_width", nozzle_diameter);
|
const coordf_t line_width = config.get_abs_value("support_line_width", nozzle_diameter);
|
||||||
@@ -2054,12 +2048,14 @@ void TreeSupport::draw_circles()
|
|||||||
|
|
||||||
ExPolygons& base_areas = ts_layer->base_areas;
|
ExPolygons& base_areas = ts_layer->base_areas;
|
||||||
ExPolygons& roof_areas = ts_layer->roof_areas;
|
ExPolygons& roof_areas = ts_layer->roof_areas;
|
||||||
|
ExPolygons roof_base_areas;
|
||||||
ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
|
ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
|
||||||
ExPolygons& floor_areas = ts_layer->floor_areas;
|
ExPolygons& floor_areas = ts_layer->floor_areas;
|
||||||
ExPolygons& roof_gap_areas = ts_layer->roof_gap_areas;
|
ExPolygons& roof_gap_areas = ts_layer->roof_gap_areas;
|
||||||
coordf_t max_layers_above_base = 0;
|
coordf_t max_layers_above_base = 0;
|
||||||
coordf_t max_layers_above_roof = 0;
|
coordf_t max_layers_above_roof = 0;
|
||||||
coordf_t max_layers_above_roof1 = 0;
|
coordf_t max_layers_above_roof1 = 0;
|
||||||
|
size_t first_base_roof_area = 0;
|
||||||
bool floor_interface_as_base = false;
|
bool floor_interface_as_base = false;
|
||||||
bool has_circle_node = false;
|
bool has_circle_node = false;
|
||||||
bool need_extra_wall = false;
|
bool need_extra_wall = false;
|
||||||
@@ -2094,8 +2090,6 @@ void TreeSupport::draw_circles()
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
const SupportNode& node = *p_node;
|
const SupportNode& node = *p_node;
|
||||||
// ORCA: Cap top interface height in mm based on per-node support layer height.
|
|
||||||
const coordf_t top_interface_height = coordf_t(top_interface_layers) * node.height;
|
|
||||||
ExPolygons area;
|
ExPolygons area;
|
||||||
// Generate directly from overhang polygon if one of the following is true:
|
// Generate directly from overhang polygon if one of the following is true:
|
||||||
// 1) node is a normal part of hybrid support
|
// 1) node is a normal part of hybrid support
|
||||||
@@ -2159,18 +2153,16 @@ void TreeSupport::draw_circles()
|
|||||||
|
|
||||||
if (obj_layer_nr>0 && node.distance_to_top < 0)
|
if (obj_layer_nr>0 && node.distance_to_top < 0)
|
||||||
append(roof_gap_areas, area);
|
append(roof_gap_areas, area);
|
||||||
// ORCA: Roof1stLayer must also fit inside the mm cap.
|
|
||||||
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 &&
|
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 &&
|
||||||
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail==false)
|
node.is_sharp_tail == false)
|
||||||
{
|
{
|
||||||
append(roof_1st_layer, area);
|
append(roof_1st_layer, area);
|
||||||
max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top);
|
max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top);
|
||||||
}
|
}
|
||||||
// ORCA: Roof layers must also fit inside the mm cap.
|
|
||||||
else if (obj_layer_nr > 0 && node.support_roof_layers_below > 1 &&
|
else if (obj_layer_nr > 0 && node.support_roof_layers_below > 1 &&
|
||||||
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail == false)
|
node.is_sharp_tail == false)
|
||||||
{
|
{
|
||||||
append(roof_areas, area);
|
append(node.support_roof_layers_below <= top_base_interface_layers ? roof_base_areas : roof_areas, area);
|
||||||
max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top);
|
max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -2184,9 +2176,17 @@ void TreeSupport::draw_circles()
|
|||||||
//m_object->print()->set_status(65, (boost::format( _u8L("Support: generate polygons at layer %d")) % layer_nr).str());
|
//m_object->print()->set_status(65, (boost::format( _u8L("Support: generate polygons at layer %d")) % layer_nr).str());
|
||||||
|
|
||||||
// join roof segments
|
// join roof segments
|
||||||
roof_areas = diff_clipped(offset2_ex(roof_areas, line_width_scaled, -line_width_scaled), get_collision(false));
|
roof_areas = diff_clipped(closing_ex(roof_areas, line_width_scaled), get_collision(false));
|
||||||
roof_areas = intersection_ex(roof_areas, m_machine_border);
|
roof_areas = intersection_ex(roof_areas, m_machine_border);
|
||||||
roof_1st_layer = diff_clipped(offset2_ex(roof_1st_layer, line_width_scaled, -line_width_scaled), get_collision(false));
|
roof_base_areas = diff_clipped(closing_ex(roof_base_areas, line_width_scaled), get_collision(false));
|
||||||
|
roof_base_areas = intersection_ex(roof_base_areas, m_machine_border);
|
||||||
|
if (!roof_base_areas.empty() && !roof_areas.empty())
|
||||||
|
roof_base_areas = diff_ex(roof_base_areas,
|
||||||
|
ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas, get_extents(roof_base_areas)));
|
||||||
|
|
||||||
|
first_base_roof_area = roof_areas.size();
|
||||||
|
append(roof_areas, std::move(roof_base_areas));
|
||||||
|
roof_1st_layer = diff_clipped(closing_ex(roof_1st_layer, line_width_scaled), get_collision(false));
|
||||||
|
|
||||||
// roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer
|
// roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer
|
||||||
roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer)));
|
roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer)));
|
||||||
@@ -2366,9 +2366,11 @@ void TreeSupport::draw_circles()
|
|||||||
area_groups.back().need_infill = overlaps({ expoly }, area_poly);
|
area_groups.back().need_infill = overlaps({ expoly }, area_poly);
|
||||||
area_groups.back().need_extra_wall = need_extra_wall && !area_groups.back().need_infill;
|
area_groups.back().need_extra_wall = need_extra_wall && !area_groups.back().need_infill;
|
||||||
}
|
}
|
||||||
for (auto& expoly : ts_layer->roof_areas) {
|
for (size_t roof_idx = 0; roof_idx < ts_layer->roof_areas.size(); ++roof_idx) {
|
||||||
|
auto &expoly = ts_layer->roof_areas[roof_idx];
|
||||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||||
area_groups.emplace_back(&expoly, SupportLayer::RoofType, max_layers_above_roof);
|
area_groups.emplace_back(&expoly, SupportLayer::RoofType, max_layers_above_roof);
|
||||||
|
area_groups.back().interface_as_base = roof_idx >= first_base_roof_area;
|
||||||
}
|
}
|
||||||
for (auto &expoly : ts_layer->floor_areas) {
|
for (auto &expoly : ts_layer->floor_areas) {
|
||||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||||
@@ -2378,6 +2380,7 @@ void TreeSupport::draw_circles()
|
|||||||
for (auto &expoly : ts_layer->roof_1st_layer) {
|
for (auto &expoly : ts_layer->roof_1st_layer) {
|
||||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||||
area_groups.emplace_back(&expoly, SupportLayer::Roof1stLayer, max_layers_above_roof1);
|
area_groups.emplace_back(&expoly, SupportLayer::Roof1stLayer, max_layers_above_roof1);
|
||||||
|
area_groups.back().interface_as_base = top_base_interface_layers > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &area_group : area_groups) {
|
for (auto &area_group : area_groups) {
|
||||||
@@ -2406,7 +2409,6 @@ void TreeSupport::draw_circles()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
// ORCA: normalize interface_id sequencing to follow printed interface layers only.
|
// ORCA: normalize interface_id sequencing to follow printed interface layers only.
|
||||||
const int top_base_layers = int(m_support_params.num_top_base_interface_layers);
|
|
||||||
const bool interlaced = m_object_config->support_interface_pattern == smipRectilinearInterlaced;
|
const bool interlaced = m_object_config->support_interface_pattern == smipRectilinearInterlaced;
|
||||||
int roof_interface_id = 0;
|
int roof_interface_id = 0;
|
||||||
int floor_interface_id = 0;
|
int floor_interface_id = 0;
|
||||||
@@ -2425,7 +2427,6 @@ void TreeSupport::draw_circles()
|
|||||||
if (area_group.type == SupportLayer::RoofType || area_group.type == SupportLayer::Roof1stLayer) {
|
if (area_group.type == SupportLayer::RoofType || area_group.type == SupportLayer::Roof1stLayer) {
|
||||||
if (interlaced)
|
if (interlaced)
|
||||||
area_group.interface_id = roof_interface_id;
|
area_group.interface_id = roof_interface_id;
|
||||||
area_group.interface_as_base = top_base_layers > 0 && roof_interface_id < top_base_layers;
|
|
||||||
has_roof_interface = true;
|
has_roof_interface = true;
|
||||||
} else if (area_group.type == SupportLayer::FloorType) {
|
} else if (area_group.type == SupportLayer::FloorType) {
|
||||||
if (interlaced)
|
if (interlaced)
|
||||||
@@ -2897,7 +2898,7 @@ void TreeSupport::drop_nodes()
|
|||||||
node_parent->merged_neighbours.push_front(node_parent == p_node ? neighbour : p_node);
|
node_parent->merged_neighbours.push_front(node_parent == p_node ? neighbour : p_node);
|
||||||
const bool to_buildplate = !is_inside_ex(get_collision(0, obj_layer_nr_next), next_position);
|
const bool to_buildplate = !is_inside_ex(get_collision(0, obj_layer_nr_next), next_position);
|
||||||
SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next,
|
SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next,
|
||||||
node_parent->support_roof_layers_below - (node_parent->distance_to_top > 0 ? 1 : 0),
|
node_parent->support_roof_layers_below - (node_parent->distance_to_top >= 0 ? 1 : 0),
|
||||||
to_buildplate, node_parent, print_z_next, height_next);
|
to_buildplate, node_parent, print_z_next, height_next);
|
||||||
get_max_move_dist(next_node);
|
get_max_move_dist(next_node);
|
||||||
m_ts_data->m_mutex.lock();
|
m_ts_data->m_mutex.lock();
|
||||||
@@ -2949,7 +2950,7 @@ void TreeSupport::drop_nodes()
|
|||||||
for(auto& overhang:overhangs_next) {
|
for(auto& overhang:overhangs_next) {
|
||||||
Point next_pt = overhang.contour.centroid();
|
Point next_pt = overhang.contour.centroid();
|
||||||
SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next,
|
SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next,
|
||||||
p_node->support_roof_layers_below - (p_node->distance_to_top > 0 ? 1 : 0),
|
p_node->support_roof_layers_below - (p_node->distance_to_top >= 0 ? 1 : 0),
|
||||||
to_buildplate, p_node, print_z_next, height_next);
|
to_buildplate, p_node, print_z_next, height_next);
|
||||||
next_node->max_move_dist = 0;
|
next_node->max_move_dist = 0;
|
||||||
next_node->overhang = std::move(overhang);
|
next_node->overhang = std::move(overhang);
|
||||||
@@ -3096,7 +3097,7 @@ void TreeSupport::drop_nodes()
|
|||||||
auto next_collision = get_collision(0, obj_layer_nr_next);
|
auto next_collision = get_collision(0, obj_layer_nr_next);
|
||||||
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex);
|
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex);
|
||||||
SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next,
|
SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next,
|
||||||
node.support_roof_layers_below - (node.distance_to_top > 0 ? 1 : 0),
|
node.support_roof_layers_below - (node.distance_to_top >= 0 ? 1 : 0),
|
||||||
to_buildplate, p_node, print_z_next, height_next);
|
to_buildplate, p_node, print_z_next, height_next);
|
||||||
// don't increase radius if next node will collide partially with the object (STUDIO-7883)
|
// don't increase radius if next node will collide partially with the object (STUDIO-7883)
|
||||||
to_outside = projection_onto(next_collision, next_node->position);
|
to_outside = projection_onto(next_collision, next_node->position);
|
||||||
@@ -3376,21 +3377,6 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ORCA: Recompute support_roof_layers_below from remaining interface height (independent heights).
|
|
||||||
const int top_layers = m_object->config().support_interface_top_layers.value;
|
|
||||||
if (m_support_params.independent_layer_height && top_layers > 0) {
|
|
||||||
const coordf_t interface_height_mm = coordf_t(top_layers) * m_slicing_params.layer_height;
|
|
||||||
for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) {
|
|
||||||
if (contact_nodes[layer_nr].empty()) continue;
|
|
||||||
for (SupportNode *node : contact_nodes[layer_nr]) {
|
|
||||||
if (node->height <= EPSILON) continue;
|
|
||||||
const coordf_t remaining_mm = interface_height_mm - (node->dist_mm_to_top - this->top_z_distance);
|
|
||||||
const int layers_fit = remaining_mm < -EPSILON ? 0 : int(std::floor((remaining_mm + EPSILON) / node->height));
|
|
||||||
node->support_roof_layers_below = std::min(layers_fit, top_layers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// log layer_heights
|
// log layer_heights
|
||||||
for (size_t i = 0; i < layer_heights.size(); i++) {
|
for (size_t i = 0; i < layer_heights.size(); i++) {
|
||||||
//if (layer_heights[i].height > EPSILON)
|
//if (layer_heights[i].height > EPSILON)
|
||||||
@@ -3498,7 +3484,7 @@ void TreeSupport::generate_contact_points()
|
|||||||
if (force_add || !already_inserted.count(hash_pos)) {
|
if (force_add || !already_inserted.count(hash_pos)) {
|
||||||
already_inserted.emplace(hash_pos);
|
already_inserted.emplace(hash_pos);
|
||||||
bool to_buildplate = true;
|
bool to_buildplate = true;
|
||||||
size_t roof_layers = add_interface ? (support_roof_layers > 0 ? support_roof_layers - 1 : 0) : 0; // subtract 1 because the contact node itself counts as one layer
|
size_t roof_layers = add_interface ? support_roof_layers : 0;
|
||||||
// add a new node as a virtual node which acts as the invisible gap between support and object
|
// add a new node as a virtual node which acts as the invisible gap between support and object
|
||||||
// distance_to_top=-1: it's virtual
|
// distance_to_top=-1: it's virtual
|
||||||
// print_z=object_layer->bottom_z: it directly contacts the bottom
|
// print_z=object_layer->bottom_z: it directly contacts the bottom
|
||||||
|
|||||||
@@ -706,7 +706,7 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
|
|||||||
filler->spacing = flow.spacing();
|
filler->spacing = flow.spacing();
|
||||||
filler->angle = roof ?
|
filler->angle = roof ?
|
||||||
//fixme support_layer.interface_id() instead of layer_idx
|
//fixme support_layer.interface_id() instead of layer_idx
|
||||||
(support_params.interface_angle + (layer_idx & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)) :
|
(support_params.interface_angle + ((layer_idx & 1) ? float(- M_PI_4) : float(+ M_PI_4))) :
|
||||||
support_params.base_angle;
|
support_params.base_angle;
|
||||||
|
|
||||||
// ORCA: use top-specific interface density after separating top/bottom settings.
|
// ORCA: use top-specific interface density after separating top/bottom settings.
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ struct TreeSupportMeshGroupSettings {
|
|||||||
this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width();
|
this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width();
|
||||||
this->support_roof_line_width = support_material_interface_flow(&print_object, config.layer_height).scaled_width();
|
this->support_roof_line_width = support_material_interface_flow(&print_object, config.layer_height).scaled_width();
|
||||||
const int bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
const int bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
||||||
this->support_bottom_enable = config.support_interface_top_layers.value > 0 && bottom_interface_layers > 0;
|
this->support_bottom_enable = bottom_interface_layers > 0;
|
||||||
this->support_bottom_height = this->support_bottom_enable ?
|
this->support_bottom_height = this->support_bottom_enable ?
|
||||||
bottom_interface_layers * this->layer_height :
|
bottom_interface_layers * this->layer_height :
|
||||||
0;
|
0;
|
||||||
@@ -705,7 +705,7 @@ public:
|
|||||||
SupportGeneratorLayersPtr& top_contacts_mutable() { return this->top_contacts; }
|
SupportGeneratorLayersPtr& top_contacts_mutable() { return this->top_contacts; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Insert the contact layer and some of the inteface and base interface layers below.
|
// Insert the contact layer and some of the interface and base interface layers below.
|
||||||
void add_roofs(std::vector<Polygons> &&new_roofs, const size_t insert_layer_idx)
|
void add_roofs(std::vector<Polygons> &&new_roofs, const size_t insert_layer_idx)
|
||||||
{
|
{
|
||||||
if (! new_roofs.empty()) {
|
if (! new_roofs.empty()) {
|
||||||
|
|||||||
@@ -722,15 +722,6 @@ void copy_directory_recursively(const boost::filesystem::path& source,
|
|||||||
std::function<bool(const std::string)> filter = nullptr,
|
std::function<bool(const std::string)> filter = nullptr,
|
||||||
bool merge_mode = false);
|
bool merge_mode = false);
|
||||||
|
|
||||||
// Install vendor bundles from resources directory to data directory
|
|
||||||
// bundle_names: vector of vendor bundle names (without .json extension)
|
|
||||||
// resource_subdir: subdirectory under resources_dir() (default: "profiles")
|
|
||||||
// data_subdir: subdirectory under data_dir() (default: "system")
|
|
||||||
// Returns: true if all bundles installed successfully, false otherwise
|
|
||||||
bool install_vendor_bundles_from_resources(const std::vector<std::string>& bundle_names,
|
|
||||||
const std::string& resource_subdir = "profiles",
|
|
||||||
const std::string& data_subdir = "system");
|
|
||||||
|
|
||||||
// Orca: Since 1.7.9 Boost deprecated save_string_file and load_string_file, copy and modified from boost 1.7.8
|
// Orca: Since 1.7.9 Boost deprecated save_string_file and load_string_file, copy and modified from boost 1.7.8
|
||||||
void save_string_file(const boost::filesystem::path& p, const std::string& str);
|
void save_string_file(const boost::filesystem::path& p, const std::string& str);
|
||||||
void load_string_file(const boost::filesystem::path& p, std::string& str);
|
void load_string_file(const boost::filesystem::path& p, std::string& str);
|
||||||
|
|||||||
@@ -42,10 +42,22 @@ struct Calib_Params
|
|||||||
std::string shaper_type;
|
std::string shaper_type;
|
||||||
std::vector<double> accelerations;
|
std::vector<double> accelerations;
|
||||||
std::vector<double> speeds;
|
std::vector<double> speeds;
|
||||||
|
// Resolved layer height for the VFA tower (0 = auto: nozzle_diameter / 2). Each speed block is a
|
||||||
|
// fixed number of layers tall, so this also determines the physical block height / tower height.
|
||||||
|
double vfa_layer_height = 0.0;
|
||||||
|
// Scale the calibration model to the nozzle diameter and set the layer height accordingly (temp tower / VFA).
|
||||||
|
// When false the 0.4 mm / 0.2 mm reference model is printed as-is.
|
||||||
|
bool nozzle_based_resize = true;
|
||||||
|
|
||||||
CalibMode mode;
|
CalibMode mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Number of printed layers per speed block in the VFA tower. The base model has 5 mm blocks designed
|
||||||
|
// for a 0.2 mm layer height (0.4 mm nozzle), i.e. 25 layers per block.
|
||||||
|
static constexpr int vfa_layers_per_block = 25;
|
||||||
|
static constexpr double vfa_base_block_height = 5.0;
|
||||||
|
static constexpr double vfa_base_nozzle_diameter = 0.4;
|
||||||
|
|
||||||
enum FlowRatioCalibrationType {
|
enum FlowRatioCalibrationType {
|
||||||
COMPLETE_CALIBRATION = 0,
|
COMPLETE_CALIBRATION = 0,
|
||||||
FINE_CALIBRATION,
|
FINE_CALIBRATION,
|
||||||
|
|||||||
@@ -1724,76 +1724,6 @@ void copy_directory_recursively(const boost::filesystem::path& source,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool install_vendor_bundles_from_resources(
|
|
||||||
const std::vector<std::string>& bundle_names,
|
|
||||||
const std::string& resource_subdir,
|
|
||||||
const std::string& data_subdir)
|
|
||||||
{
|
|
||||||
namespace fs = boost::filesystem;
|
|
||||||
|
|
||||||
fs::path rsrc_path = fs::path(Slic3r::resources_dir()) / resource_subdir;
|
|
||||||
fs::path vendor_path = fs::path(Slic3r::data_dir()) / data_subdir;
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "Installing " << bundle_names.size() << " bundles from resources...";
|
|
||||||
|
|
||||||
for (const auto &bundle : bundle_names) {
|
|
||||||
try {
|
|
||||||
// Install the JSON file
|
|
||||||
auto path_in_rsrc = (rsrc_path / bundle).replace_extension(".json");
|
|
||||||
auto path_in_vendors = (vendor_path / bundle).replace_extension(".json");
|
|
||||||
|
|
||||||
if (!fs::exists(path_in_rsrc)) {
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "Bundle not found in resources: " << bundle;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create target directory if needed
|
|
||||||
if (!fs::exists(vendor_path))
|
|
||||||
fs::create_directories(vendor_path);
|
|
||||||
|
|
||||||
// Copy JSON file
|
|
||||||
std::string error_message;
|
|
||||||
CopyFileResult cfr = copy_file(path_in_rsrc.string(), path_in_vendors.string(), error_message, false);
|
|
||||||
if (cfr != CopyFileResult::SUCCESS) {
|
|
||||||
BOOST_LOG_TRIVIAL(error) << "Failed to copy " << bundle << ".json: " << error_message;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the vendor directory (if it exists)
|
|
||||||
auto dir_in_rsrc = rsrc_path / bundle;
|
|
||||||
auto dir_in_vendors = vendor_path / bundle;
|
|
||||||
|
|
||||||
if (fs::exists(dir_in_rsrc) && fs::is_directory(dir_in_rsrc)) {
|
|
||||||
// Remove existing directory
|
|
||||||
if (fs::exists(dir_in_vendors))
|
|
||||||
fs::remove_all(dir_in_vendors);
|
|
||||||
fs::create_directories(dir_in_vendors);
|
|
||||||
|
|
||||||
// Copy with file filter (same as PresetUpdater::install_bundles_rsrc)
|
|
||||||
// Filter out certain file types: .stl, .png, .svg, .jpeg, .jpg, .3mf
|
|
||||||
auto file_filter = [](const std::string name) -> bool {
|
|
||||||
return boost::iends_with(name, ".stl") ||
|
|
||||||
boost::iends_with(name, ".png") ||
|
|
||||||
boost::iends_with(name, ".svg") ||
|
|
||||||
boost::iends_with(name, ".jpeg") ||
|
|
||||||
boost::iends_with(name, ".jpg") ||
|
|
||||||
boost::iends_with(name, ".3mf");
|
|
||||||
};
|
|
||||||
|
|
||||||
copy_directory_recursively(dir_in_rsrc, dir_in_vendors, file_filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "Successfully installed bundle: " << bundle;
|
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
BOOST_LOG_TRIVIAL(error) << "Exception installing bundle " << bundle << ": " << e.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void save_string_file(const boost::filesystem::path& p, const std::string& str)
|
void save_string_file(const boost::filesystem::path& p, const std::string& str)
|
||||||
{
|
{
|
||||||
boost::nowide::ofstream file;
|
boost::nowide::ofstream file;
|
||||||
|
|||||||
@@ -432,14 +432,9 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
|
|||||||
cfg.models_variants_installed.erase(it ++);
|
cfg.models_variants_installed.erase(it ++);
|
||||||
else
|
else
|
||||||
++ it;
|
++ it;
|
||||||
// Read the active config bundle, parse the config version.
|
// Orca: the version the vendor is installed at, read from its profile or —
|
||||||
PresetBundle bundle;
|
// where the cache is the whole installation — from the cache's own stamp.
|
||||||
//BBS: change directoties by design
|
cfg.version.config_version = installed_vendor_version(cfg.name);
|
||||||
//bundle.load_configbundle((data_dir / PRESET_SYSTEM_DIR / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
|
||||||
bundle.load_vendor_configs_from_json((data_dir/PRESET_SYSTEM_DIR).string(), cfg.name, PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
|
||||||
for (const auto &vp : bundle.vendors)
|
|
||||||
if (vp.second.id == cfg.name)
|
|
||||||
cfg.version.config_version = vp.second.config_version;
|
|
||||||
snapshot.vendor_configs.emplace_back(std::move(cfg));
|
snapshot.vendor_configs.emplace_back(std::move(cfg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ PingCodeBindDialog::PingCodeBindDialog(Plater* plater /*= nullptr*/)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
SetSizer(sizer_main);
|
SetSizerAndFit(sizer_main);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
|
|
||||||
@@ -670,7 +670,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
|||||||
m_sizer_main->Add(m_sw_bind_failed_info, 0, wxALIGN_CENTER, 0);
|
m_sizer_main->Add(m_sw_bind_failed_info, 0, wxALIGN_CENTER, 0);
|
||||||
m_sizer_main->Add(m_simplebook, 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, ButtonProps::ChoiceButtonGap());
|
m_sizer_main->Add(m_simplebook, 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, ButtonProps::ChoiceButtonGap());
|
||||||
|
|
||||||
SetSizer(m_sizer_main);
|
SetSizerAndFit(m_sizer_main);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
Centre(wxBOTH);
|
Centre(wxBOTH);
|
||||||
@@ -992,7 +992,7 @@ UnBindMachineDialog::UnBindMachineDialog(Plater *plater /*= nullptr*/)
|
|||||||
m_sizer_main->Add(m_sizer_button, 0, wxALIGN_RIGHT | wxRIGHT, ButtonProps::ChoiceButtonGap());
|
m_sizer_main->Add(m_sizer_button, 0, wxALIGN_RIGHT | wxRIGHT, ButtonProps::ChoiceButtonGap());
|
||||||
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(20));
|
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(20));
|
||||||
|
|
||||||
SetSizer(m_sizer_main);
|
SetSizerAndFit(m_sizer_main);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
Centre(wxBOTH);
|
Centre(wxBOTH);
|
||||||
|
|||||||
@@ -632,9 +632,8 @@ EditCalibrationHistoryDialog::EditCalibrationHistoryDialog(wxWindow
|
|||||||
|
|
||||||
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
|
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
|
||||||
|
|
||||||
SetSizer(main_sizer);
|
SetSizerAndFit(main_sizer);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
|
||||||
CenterOnParent();
|
CenterOnParent();
|
||||||
|
|
||||||
wxGetApp().UpdateDlgDarkUI(this);
|
wxGetApp().UpdateDlgDarkUI(this);
|
||||||
@@ -910,9 +909,8 @@ NewCalibrationHistoryDialog::NewCalibrationHistoryDialog(wxWindow *parent, const
|
|||||||
|
|
||||||
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
|
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
|
||||||
|
|
||||||
SetSizer(main_sizer);
|
SetSizerAndFit(main_sizer);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
|
||||||
CenterOnParent();
|
CenterOnParent();
|
||||||
|
|
||||||
wxGetApp().UpdateDlgDarkUI(this);
|
wxGetApp().UpdateDlgDarkUI(this);
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ CalibrationDialog::CalibrationDialog(Plater *plater)
|
|||||||
body_panel->Layout();
|
body_panel->Layout();
|
||||||
|
|
||||||
m_sizer_main->Add(body_panel, 0, wxEXPAND | wxALL, FromDIP(25));
|
m_sizer_main->Add(body_panel, 0, wxEXPAND | wxALL, FromDIP(25));
|
||||||
SetSizer(m_sizer_main);
|
SetSizerAndFit(m_sizer_main);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
|
|
||||||
|
|||||||
@@ -112,9 +112,8 @@ CloneDialog::CloneDialog(wxWindow *parent)
|
|||||||
|
|
||||||
v_sizer->Add(bottom_sizer, 0, wxEXPAND);
|
v_sizer->Add(bottom_sizer, 0, wxEXPAND);
|
||||||
|
|
||||||
this->SetSizer(v_sizer);
|
this->SetSizerAndFit(v_sizer);
|
||||||
this->Layout();
|
this->Layout();
|
||||||
v_sizer->Fit(this);
|
|
||||||
|
|
||||||
wxGetApp().UpdateDlgDarkUI(this);
|
wxGetApp().UpdateDlgDarkUI(this);
|
||||||
|
|
||||||
|
|||||||
@@ -66,41 +66,41 @@ using Config::SnapshotDB;
|
|||||||
|
|
||||||
// Configuration data structures extensions needed for the wizard
|
// Configuration data structures extensions needed for the wizard
|
||||||
//BBS: set BBL as default
|
//BBS: set BBL as default
|
||||||
bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_bbl_bundle)
|
bool Bundle::load(fs::path dir, const std::string &vendor_name, bool ais_in_resources, bool ais_bbl_bundle)
|
||||||
{
|
{
|
||||||
this->preset_bundle = std::make_unique<PresetBundle>();
|
this->preset_bundle = std::make_unique<PresetBundle>();
|
||||||
this->is_in_resources = ais_in_resources;
|
this->is_in_resources = ais_in_resources;
|
||||||
this->is_bbl_bundle = ais_bbl_bundle;
|
this->is_bbl_bundle = ais_bbl_bundle;
|
||||||
|
|
||||||
std::string path_string = source_path.string();
|
|
||||||
std::string parent_path = source_path.parent_path().string();
|
|
||||||
//BBS: add json logic for vendor bundles
|
//BBS: add json logic for vendor bundles
|
||||||
std::string vendor_name = source_path.filename().string();
|
// Orca: served from the vendor's preset cache where one covers it — which is
|
||||||
if (Slic3r::is_json_file(path_string)) {
|
// how a shipped build carries its vendors — and parsed from the JSONs otherwise.
|
||||||
// Remove the .json suffix.
|
// A vendor that can be neither read nor parsed — a cache the build cannot use
|
||||||
vendor_name.erase(vendor_name.size() - 5);
|
// with the preset JSONs behind it pruned, say — is one the wizard cannot offer.
|
||||||
}
|
// Every other vendor still can be, so it is left out rather than thrown over.
|
||||||
else
|
size_t presets_loaded = 0;
|
||||||
|
try {
|
||||||
|
auto [config_substitutions, loaded] = preset_bundle->load_vendor_configs_from_json(
|
||||||
|
dir.string(), vendor_name, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
|
||||||
|
UNUSED(config_substitutions);
|
||||||
|
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
|
||||||
|
assert(config_substitutions.empty());
|
||||||
|
presets_loaded = loaded;
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
BOOST_LOG_TRIVIAL(fatal) << boost::format("Vendor bundle: `%1%`: cannot be loaded, leaving it out: %2%") % vendor_name % e.what();
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
// Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
|
|
||||||
//BBS: add json logic for vendor bundles
|
|
||||||
auto [config_substitutions, presets_loaded] = preset_bundle->load_vendor_configs_from_json(
|
|
||||||
parent_path, vendor_name, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
|
|
||||||
UNUSED(config_substitutions);
|
|
||||||
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
|
|
||||||
assert(config_substitutions.empty());
|
|
||||||
auto first_vendor = preset_bundle->vendors.begin();
|
auto first_vendor = preset_bundle->vendors.begin();
|
||||||
if (first_vendor == preset_bundle->vendors.end()) {
|
if (first_vendor == preset_bundle->vendors.end()) {
|
||||||
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
|
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % vendor_name;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (presets_loaded == 0) {
|
if (presets_loaded == 0) {
|
||||||
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string;
|
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % vendor_name;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded;
|
BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % vendor_name % presets_loaded;
|
||||||
this->vendor_profile = &first_vendor->second;
|
this->vendor_profile = &first_vendor->second;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -125,15 +125,10 @@ BundleMap BundleMap::load()
|
|||||||
|
|
||||||
//Orca: add custom as default
|
//Orca: add custom as default
|
||||||
//Orca: add json logic for vendor bundle
|
//Orca: add json logic for vendor bundle
|
||||||
auto orca_bundle_path = (vendor_dir / PresetBundle::ORCA_DEFAULT_BUNDLE).replace_extension(".json");
|
|
||||||
auto orca_bundle_rsrc = false;
|
|
||||||
if (!boost::filesystem::exists(orca_bundle_path)) {
|
|
||||||
orca_bundle_path = (rsrc_vendor_dir / PresetBundle::ORCA_DEFAULT_BUNDLE).replace_extension(".json");
|
|
||||||
orca_bundle_rsrc = true;
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
|
const bool from_rsrc = ! is_vendor_installed(PresetBundle::ORCA_DEFAULT_BUNDLE);
|
||||||
Bundle bbl_bundle;
|
Bundle bbl_bundle;
|
||||||
if (bbl_bundle.load(std::move(orca_bundle_path), orca_bundle_rsrc, true))
|
if (bbl_bundle.load(from_rsrc ? rsrc_vendor_dir : vendor_dir, PresetBundle::ORCA_DEFAULT_BUNDLE, from_rsrc, true))
|
||||||
res.emplace(PresetBundle::ORCA_DEFAULT_BUNDLE, std::move(bbl_bundle));
|
res.emplace(PresetBundle::ORCA_DEFAULT_BUNDLE, std::move(bbl_bundle));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,18 +136,13 @@ BundleMap BundleMap::load()
|
|||||||
// and then additionally from resources/profiles.
|
// and then additionally from resources/profiles.
|
||||||
bool is_in_resources = false;
|
bool is_in_resources = false;
|
||||||
for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) {
|
for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) {
|
||||||
for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) {
|
for (const std::string &id : vendor_names_in(*dir)) {
|
||||||
//BBS: add json logic for vendor bundle
|
// Don't load this bundle if we've already loaded it.
|
||||||
if (Slic3r::is_json_file(dir_entry.path().string())) {
|
if (res.find(id) != res.end()) { continue; }
|
||||||
std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".json" part
|
|
||||||
|
|
||||||
// Don't load this bundle if we've already loaded it.
|
Bundle bundle;
|
||||||
if (res.find(id) != res.end()) { continue; }
|
if (bundle.load(*dir, id, is_in_resources))
|
||||||
|
res.emplace(id, std::move(bundle));
|
||||||
Bundle bundle;
|
|
||||||
if (bundle.load(dir_entry.path(), is_in_resources))
|
|
||||||
res.emplace(std::move(id), std::move(bundle));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is_in_resources = true;
|
is_in_resources = true;
|
||||||
|
|||||||
@@ -71,9 +71,11 @@ struct Bundle
|
|||||||
Bundle() = default;
|
Bundle() = default;
|
||||||
Bundle(Bundle&& other);
|
Bundle(Bundle&& other);
|
||||||
|
|
||||||
|
// Load the vendor `vendor_name` as it is installed in `dir`, from its preset
|
||||||
|
// cache or its profile JSONs, whichever is usable.
|
||||||
// Returns false if not loaded. Reason for that is logged as boost::log error.
|
// Returns false if not loaded. Reason for that is logged as boost::log error.
|
||||||
//BBS: set BBL as default
|
//BBS: set BBL as default
|
||||||
bool load(fs::path source_path, bool is_in_resources, bool is_bbl_bundle = false);
|
bool load(fs::path dir, const std::string &vendor_name, bool is_in_resources, bool is_bbl_bundle = false);
|
||||||
|
|
||||||
const std::string& vendor_id() const { return vendor_profile->id; }
|
const std::string& vendor_id() const { return vendor_profile->id; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -80,9 +80,8 @@ ConnectPrinterDialog::ConnectPrinterDialog(wxWindow *parent, wxWindowID id, cons
|
|||||||
|
|
||||||
main_sizer->Add(sizer_top);
|
main_sizer->Add(sizer_top);
|
||||||
|
|
||||||
this->SetSizer(main_sizer);
|
this->SetSizerAndFit(main_sizer);
|
||||||
this->Layout();
|
this->Layout();
|
||||||
this->Fit();
|
|
||||||
CentreOnParent();
|
CentreOnParent();
|
||||||
|
|
||||||
m_textCtrl_code->Bind(wxEVT_TEXT, &ConnectPrinterDialog::on_input_enter, this);
|
m_textCtrl_code->Bind(wxEVT_TEXT, &ConnectPrinterDialog::on_input_enter, this);
|
||||||
|
|||||||
@@ -2201,25 +2201,14 @@ bool CreatePrinterPresetDialog::load_system_and_user_presets_with_curr_model(Pre
|
|||||||
} else {
|
} else {
|
||||||
selected_vendor_id = m_printer_preset_vendor_selected.id;
|
selected_vendor_id = m_printer_preset_vendor_selected.id;
|
||||||
|
|
||||||
if (boost::filesystem::exists(boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR / selected_vendor_id)) {
|
|
||||||
preset_path = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).string();
|
|
||||||
} else if (boost::filesystem::exists(boost::filesystem::path(Slic3r::resources_dir()) / "profiles" / selected_vendor_id)) {
|
|
||||||
preset_path = (boost::filesystem::path(Slic3r::resources_dir()) / "profiles").string();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preset_path.empty()) {
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "Preset path was not found";
|
|
||||||
MessageDialog dlg(this, _L("Preset path was not found; please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
|
||||||
wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
|
||||||
dlg.ShowModal();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Pass the app's preset bundle (which already holds OrcaFilamentLibrary) as the base
|
// Pass the app's preset bundle (which already holds OrcaFilamentLibrary) as the base
|
||||||
// bundle so vendor filaments that inherit OFL bases resolve via the existing
|
// bundle so vendor filaments that inherit OFL bases resolve via the existing
|
||||||
// cross-vendor inheritance path.
|
// cross-vendor inheritance path.
|
||||||
temp_preset_bundle.load_vendor_configs_from_json(preset_path, selected_vendor_id,
|
// Orca: served from the vendor's preset cache where one covers it — a shipped
|
||||||
|
// build carries that instead of the raw preset JSONs — and parsed otherwise.
|
||||||
|
temp_preset_bundle.load_vendor_configs_from_json((boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).string(),
|
||||||
|
selected_vendor_id,
|
||||||
PresetBundle::LoadConfigBundleAttribute::LoadSystem,
|
PresetBundle::LoadConfigBundleAttribute::LoadSystem,
|
||||||
ForwardCompatibilitySubstitutionRule::EnableSilent,
|
ForwardCompatibilitySubstitutionRule::EnableSilent,
|
||||||
wxGetApp().preset_bundle);
|
wxGetApp().preset_bundle);
|
||||||
|
|||||||
@@ -112,9 +112,8 @@ DownloadProgressDialog::DownloadProgressDialog(wxString title)
|
|||||||
m_simplebook_status->AddPage(m_panel_download_failed, wxEmptyString, false);
|
m_simplebook_status->AddPage(m_panel_download_failed, wxEmptyString, false);
|
||||||
m_simplebook_status->AddPage(m_panel_install_failed, wxEmptyString, false);
|
m_simplebook_status->AddPage(m_panel_install_failed, wxEmptyString, false);
|
||||||
|
|
||||||
SetSizer(m_sizer_main);
|
SetSizerAndFit(m_sizer_main);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
|
||||||
CentreOnParent();
|
CentreOnParent();
|
||||||
|
|
||||||
Bind(wxEVT_CLOSE_WINDOW, &DownloadProgressDialog::on_close, this);
|
Bind(wxEVT_CLOSE_WINDOW, &DownloadProgressDialog::on_close, this);
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ void ExtrusionCalibration::create()
|
|||||||
top_sizer->Add(FromDIP(24), 0);
|
top_sizer->Add(FromDIP(24), 0);
|
||||||
top_sizer->Add(sizer_main, 1, wxEXPAND);
|
top_sizer->Add(sizer_main, 1, wxEXPAND);
|
||||||
top_sizer->Add(FromDIP(24), 0);
|
top_sizer->Add(FromDIP(24), 0);
|
||||||
SetSizer(top_sizer);
|
SetSizerAndFit(top_sizer);
|
||||||
|
|
||||||
// set default nozzle
|
// set default nozzle
|
||||||
m_comboBox_nozzle_dia->SetSelection(1);
|
m_comboBox_nozzle_dia->SetSelection(1);
|
||||||
@@ -271,7 +271,6 @@ void ExtrusionCalibration::create()
|
|||||||
set_step(1);
|
set_step(1);
|
||||||
|
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
|
||||||
|
|
||||||
m_k_val->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent& e) {
|
m_k_val->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent& e) {
|
||||||
input_value_finish();
|
input_value_finish();
|
||||||
|
|||||||
@@ -105,9 +105,8 @@ FilamentPickerDialog::FilamentPickerDialog(wxWindow *parent, const wxString& fil
|
|||||||
container_sizer->Add(main_sizer, 1, wxEXPAND | wxALL, FromDIP(10));
|
container_sizer->Add(main_sizer, 1, wxEXPAND | wxALL, FromDIP(10));
|
||||||
container_sizer->Add(dlg_btns, 0, wxEXPAND);
|
container_sizer->Add(dlg_btns, 0, wxEXPAND);
|
||||||
|
|
||||||
SetSizer(container_sizer);
|
SetSizerAndFit(container_sizer);
|
||||||
Layout();
|
Layout();
|
||||||
container_sizer->Fit(this);
|
|
||||||
|
|
||||||
// Position the dialog relative to the parent window
|
// Position the dialog relative to the parent window
|
||||||
if (GetParent()) {
|
if (GetParent()) {
|
||||||
|
|||||||
@@ -6047,10 +6047,10 @@ void GLCanvas3D::_render_3d_navigator()
|
|||||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_X], "Y"); // ORCA use uppercase to match text on tranform widgets
|
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_X], "Y"); // ORCA use uppercase to match text on tranform widgets
|
||||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Y], "Z"); // ORCA use uppercase to match text on tranform widgets
|
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Y], "Z"); // ORCA use uppercase to match text on tranform widgets
|
||||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Z], "X"); // ORCA use uppercase to match text on tranform widgets
|
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Z], "X"); // ORCA use uppercase to match text on tranform widgets
|
||||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_FRONT], _utf8("Front").c_str());
|
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_FRONT], _u8L_CONTEXT("Front", "Camera View").c_str());
|
||||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BACK], _u8L_CONTEXT("Back", "Camera View").c_str());
|
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BACK], _u8L_CONTEXT("Back", "Camera View").c_str());
|
||||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_TOP], _utf8("Top").c_str());
|
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_TOP], _u8L_CONTEXT("Top", "Camera View").c_str());
|
||||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BOTTOM], _utf8("Bottom").c_str());
|
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BOTTOM], _u8L_CONTEXT("Bottom", "Camera View").c_str());
|
||||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_LEFT], _u8L_CONTEXT("Left", "Camera View").c_str());
|
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_LEFT], _u8L_CONTEXT("Left", "Camera View").c_str());
|
||||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_RIGHT], _u8L_CONTEXT("Right", "Camera View").c_str());
|
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_RIGHT], _u8L_CONTEXT("Right", "Camera View").c_str());
|
||||||
|
|
||||||
|
|||||||
@@ -3502,7 +3502,7 @@ static void check_objects_after_cut(const ModelObjectPtrs& objects)
|
|||||||
names += ", " + from_u8(err_objects_names[i]);
|
names += ", " + from_u8(err_objects_names[i]);
|
||||||
WarningDialog(wxGetApp().plater(), format_wxstr(_L("Objects(%1%) have duplicated connectors. "
|
WarningDialog(wxGetApp().plater(), format_wxstr(_L("Objects(%1%) have duplicated connectors. "
|
||||||
"Some connectors may be missing in slicing result.\n"
|
"Some connectors may be missing in slicing result.\n"
|
||||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||||
"Thank you."), names)).ShowModal();
|
"Thank you."), names)).ShowModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2654,14 +2654,14 @@ static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame,
|
|||||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||||
//view_menu->AppendSeparator();
|
//view_menu->AppendSeparator();
|
||||||
//TRN To be shown in the main menu View->Top
|
//TRN To be shown in the main menu View->Top
|
||||||
append_menu_item(view_menu, wxID_ANY, _L("Top") + "\t" + ctrl + "1", _L("Top View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("top"); },
|
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Top", "Camera View") + "\t" + ctrl + "1", _L("Top View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("top"); },
|
||||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||||
//TRN To be shown in the main menu View->Bottom
|
//TRN To be shown in the main menu View->Bottom
|
||||||
append_menu_item(view_menu, wxID_ANY, _L("Bottom") + "\t" + ctrl + "2", _L("Bottom View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("bottom"); },
|
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Bottom", "Camera View") + "\t" + ctrl + "2", _L("Bottom View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("bottom"); },
|
||||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||||
append_menu_item(view_menu, wxID_ANY, _L("Front") + "\t" + ctrl + "3", _L("Front View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("front"); },
|
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Front", "Camera View") + "\t" + ctrl + "3", _L("Front View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("front"); },
|
||||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||||
append_menu_item(view_menu, wxID_ANY, _L("Rear") + "\t" + ctrl + "4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); },
|
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Rear", "Camera View") + "\t" + ctrl + "4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); },
|
||||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Left", "Camera View") + "\t" + ctrl + "5", _L("Left View"),[mainFrame](wxCommandEvent &) {mainFrame->select_view("left"); },
|
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Left", "Camera View") + "\t" + ctrl + "5", _L("Left View"),[mainFrame](wxCommandEvent &) {mainFrame->select_view("left"); },
|
||||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
|
|||||||
main_sizer->Add(btn_sizer, 0, wxBOTTOM | wxRIGHT | wxEXPAND | wxTOP, FromDIP(10));
|
main_sizer->Add(btn_sizer, 0, wxBOTTOM | wxRIGHT | wxEXPAND | wxTOP, FromDIP(10));
|
||||||
|
|
||||||
apply_style(style);
|
apply_style(style);
|
||||||
SetSizerAndFit(main_sizer);
|
SetSizer(main_sizer);
|
||||||
wxGetApp().UpdateDlgDarkUI(this);
|
wxGetApp().UpdateDlgDarkUI(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +221,7 @@ void MsgDialog::apply_style(long style)
|
|||||||
|
|
||||||
void MsgDialog::finalize()
|
void MsgDialog::finalize()
|
||||||
{
|
{
|
||||||
|
GetSizer()->SetSizeHints(this);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
CenterOnParent();
|
CenterOnParent();
|
||||||
@@ -547,7 +548,7 @@ DeleteConfirmDialog::DeleteConfirmDialog(wxWindow *parent, const wxString &title
|
|||||||
m_del_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_OK); });
|
m_del_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_OK); });
|
||||||
m_cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_CANCEL); });
|
m_cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_CANCEL); });
|
||||||
|
|
||||||
SetSizer(m_main_sizer);
|
SetSizerAndFit(m_main_sizer);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
wxGetApp().UpdateDlgDarkUI(this);
|
wxGetApp().UpdateDlgDarkUI(this);
|
||||||
@@ -582,7 +583,7 @@ Newer3mfVersionDialog::Newer3mfVersionDialog(wxWindow *parent, const Semver *fil
|
|||||||
main_sizer->Add(content_sizer, 0, wxEXPAND | wxALL, FromDIP(5));
|
main_sizer->Add(content_sizer, 0, wxEXPAND | wxALL, FromDIP(5));
|
||||||
main_sizer->Add(get_btn_sizer(), 0, wxEXPAND | wxALL, FromDIP(5));
|
main_sizer->Add(get_btn_sizer(), 0, wxEXPAND | wxALL, FromDIP(5));
|
||||||
|
|
||||||
this->SetSizer(main_sizer);
|
this->SetSizerAndFit(main_sizer);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
wxGetApp().UpdateDlgDarkUI(this);
|
wxGetApp().UpdateDlgDarkUI(this);
|
||||||
@@ -745,9 +746,8 @@ NetworkErrorDialog::NetworkErrorDialog(wxWindow* parent)
|
|||||||
sizer_main->Add(sizer_button, 1, wxEXPAND | wxLEFT | wxRIGHT, 15);
|
sizer_main->Add(sizer_button, 1, wxEXPAND | wxLEFT | wxRIGHT, 15);
|
||||||
sizer_main->Add(0, 0, 0, wxTOP, 18);
|
sizer_main->Add(0, 0, 0, wxTOP, 18);
|
||||||
|
|
||||||
SetSizer(sizer_main);
|
SetSizerAndFit(sizer_main);
|
||||||
Layout();
|
Layout();
|
||||||
sizer_main->Fit(this);
|
|
||||||
Centre(wxBOTH);
|
Centre(wxBOTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ NetworkPluginDownloadDialog::NetworkPluginDownloadDialog(wxWindow* parent, Mode
|
|||||||
} else {
|
} else {
|
||||||
create_missing_plugin_ui();
|
create_missing_plugin_ui();
|
||||||
}
|
}
|
||||||
|
main_sizer->SetSizeHints(this);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
CentreOnParent();
|
CentreOnParent();
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ NetworkTestDialog::NetworkTestDialog(wxWindow* parent, wxWindowID id, const wxSt
|
|||||||
|
|
||||||
init_bind();
|
init_bind();
|
||||||
|
|
||||||
this->SetSizer(main_sizer);
|
this->SetSizerAndFit(main_sizer);
|
||||||
this->Layout();
|
this->Layout();
|
||||||
|
|
||||||
this->Centre(wxBOTH);
|
this->Centre(wxBOTH);
|
||||||
|
|||||||
@@ -2298,7 +2298,6 @@ arrangement::ArrangePolygon PartPlate::estimate_wipe_tower_polygon(const Dynamic
|
|||||||
bool enable_wrapping = (wrapping_opt != nullptr) && wrapping_opt->value;
|
bool enable_wrapping = (wrapping_opt != nullptr) && wrapping_opt->value;
|
||||||
wt_size = estimate_wipe_tower_size(config, w, v, extruder_count, plate_extruder_size, use_global_objects, enable_wrapping);
|
wt_size = estimate_wipe_tower_size(config, w, v, extruder_count, plate_extruder_size, use_global_objects, enable_wrapping);
|
||||||
int plate_width=m_width, plate_depth=m_depth;
|
int plate_width=m_width, plate_depth=m_depth;
|
||||||
w = wt_size(0); // effective width; differs from prime_tower_width when the rib wall squares the tower
|
|
||||||
float depth = wt_size(1);
|
float depth = wt_size(1);
|
||||||
float margin = WIPE_TOWER_MARGIN + tower_brim_width, wp_brim_width = 0.f;
|
float margin = WIPE_TOWER_MARGIN + tower_brim_width, wp_brim_width = 0.f;
|
||||||
const ConfigOption* wipe_tower_brim_width_opt = config.option("prime_tower_brim_width");
|
const ConfigOption* wipe_tower_brim_width_opt = config.option("prime_tower_brim_width");
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ PartSkipDialog::PartSkipDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _
|
|||||||
m_simplebook->AddPage(m_book_third_panel, _("dialog page"), false);
|
m_simplebook->AddPage(m_book_third_panel, _("dialog page"), false);
|
||||||
m_sizer->Add(m_simplebook, 1, wxEXPAND | wxALL, 5);
|
m_sizer->Add(m_simplebook, 1, wxEXPAND | wxALL, 5);
|
||||||
|
|
||||||
SetSizer(m_sizer);
|
SetSizerAndFit(m_sizer);
|
||||||
m_zoom_in_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomIn, this);
|
m_zoom_in_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomIn, this);
|
||||||
m_zoom_out_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomOut, this);
|
m_zoom_out_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomOut, this);
|
||||||
m_switch_drag_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnSwitchDrag, this);
|
m_switch_drag_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnSwitchDrag, this);
|
||||||
@@ -281,7 +281,6 @@ PartSkipDialog::PartSkipDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _
|
|||||||
m_all_checkbox->Bind(wxEVT_TOGGLEBUTTON, &PartSkipDialog::OnAllCheckbox, this);
|
m_all_checkbox->Bind(wxEVT_TOGGLEBUTTON, &PartSkipDialog::OnAllCheckbox, this);
|
||||||
|
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
|
||||||
CentreOnParent();
|
CentreOnParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+52
-12
@@ -14183,7 +14183,7 @@ void Plater::calib_temp(const Calib_Params& params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std::abs(nozzle_scale - 1.0) > EPSILON)
|
if (params.nozzle_based_resize && std::abs(nozzle_scale - 1.0) > EPSILON)
|
||||||
model().objects[0]->scale(nozzle_scale, nozzle_scale, nozzle_scale);
|
model().objects[0]->scale(nozzle_scale, nozzle_scale, nozzle_scale);
|
||||||
|
|
||||||
model().objects[0]->ensure_on_bed();
|
model().objects[0]->ensure_on_bed();
|
||||||
@@ -14191,7 +14191,9 @@ void Plater::calib_temp(const Calib_Params& params) {
|
|||||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||||
set_config_values<int, ConfigOptionInts>(filament_config, "nozzle_temperature_initial_layer", (int) start_temp);
|
set_config_values<int, ConfigOptionInts>(filament_config, "nozzle_temperature_initial_layer", (int) start_temp);
|
||||||
set_config_values<int, ConfigOptionInts>(filament_config, "nozzle_temperature", (int) start_temp);
|
set_config_values<int, ConfigOptionInts>(filament_config, "nozzle_temperature", (int) start_temp);
|
||||||
model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter/2));
|
// When resizing is disabled the 0.4 mm / 0.2 mm reference model is printed as-is (preset layer height kept).
|
||||||
|
if (params.nozzle_based_resize)
|
||||||
|
model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||||
model().objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btOuterOnly));
|
model().objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btOuterOnly));
|
||||||
model().objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(5.0));
|
model().objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(5.0));
|
||||||
model().objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0));
|
model().objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0));
|
||||||
@@ -14202,7 +14204,8 @@ void Plater::calib_temp(const Calib_Params& params) {
|
|||||||
|
|
||||||
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||||
print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
|
print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
|
||||||
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(nozzle_diameter/2));
|
if (params.nozzle_based_resize)
|
||||||
|
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||||
|
|
||||||
|
|
||||||
changed_objects({ 0 });
|
changed_objects({ 0 });
|
||||||
@@ -14366,6 +14369,42 @@ void Plater::calib_VFA(const Calib_Params& params)
|
|||||||
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||||
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
|
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
|
||||||
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||||
|
|
||||||
|
const ConfigOptionFloats* nozzle_diameter_config = printer_config->option<ConfigOptionFloats>("nozzle_diameter");
|
||||||
|
size_t nozzle_id = static_cast<size_t>(std::max(params.extruder_id, 0));
|
||||||
|
double nozzle_diameter = vfa_base_nozzle_diameter;
|
||||||
|
if (nozzle_diameter_config && !nozzle_diameter_config->values.empty()) {
|
||||||
|
nozzle_id = std::min(nozzle_id, nozzle_diameter_config->values.size() - 1);
|
||||||
|
nozzle_diameter = nozzle_diameter_config->values[nozzle_id];
|
||||||
|
}
|
||||||
|
if (nozzle_diameter <= 0.0)
|
||||||
|
nozzle_diameter = vfa_base_nozzle_diameter;
|
||||||
|
|
||||||
|
// Resolved layer height: use the (possibly auto-adjusted) value from the dialog, else default to nozzle/2.
|
||||||
|
double layer_height = params.vfa_layer_height > 0.0 ? params.vfa_layer_height : nozzle_diameter / 2.0;
|
||||||
|
|
||||||
|
// cut upper (on the unscaled model, using the base block height); the scaling below keeps the physical
|
||||||
|
// block height (vfa_layers_per_block * layer_height) in sync with the speed stepping in GCode::process_layer.
|
||||||
|
// Subtract EPSILON (as the temperature tower does) so the cut lands just below the flat block surface instead
|
||||||
|
// of exactly on it, which would otherwise add a degenerate extra layer.
|
||||||
|
auto obj_bb = model().objects[0]->bounding_box_exact();
|
||||||
|
auto height = vfa_base_block_height * ((params.end - params.start) / params.step + 1) - EPSILON;
|
||||||
|
if (height < obj_bb.size().z()) {
|
||||||
|
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When resizing is enabled, XY scales with the nozzle (footprint / line width) and Z scales so each base
|
||||||
|
// block becomes vfa_layers_per_block layers of the resolved layer height. When disabled the 0.4 mm / 0.2 mm
|
||||||
|
// reference model is printed as-is (preset layer height kept).
|
||||||
|
if (params.nozzle_based_resize) {
|
||||||
|
const double xy_scale = nozzle_diameter / vfa_base_nozzle_diameter;
|
||||||
|
const double z_scale = (vfa_layers_per_block * layer_height) / vfa_base_block_height;
|
||||||
|
if (std::abs(xy_scale - 1.0) > EPSILON || std::abs(z_scale - 1.0) > EPSILON)
|
||||||
|
model().objects[0]->scale(xy_scale, xy_scale, z_scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
model().objects[0]->ensure_on_bed();
|
||||||
|
|
||||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||||
filament_config->set_key_value("slow_down_layer_time", new ConfigOptionFloats { 0.0 });
|
filament_config->set_key_value("slow_down_layer_time", new ConfigOptionFloats { 0.0 });
|
||||||
set_config_values<bool, ConfigOptionBoolsNullable>(print_config, "enable_overhang_speed", false);
|
set_config_values<bool, ConfigOptionBoolsNullable>(print_config, "enable_overhang_speed", false);
|
||||||
@@ -14379,6 +14418,10 @@ void Plater::calib_VFA(const Calib_Params& params)
|
|||||||
print_config->set_key_value("spiral_mode", new ConfigOptionBool(true));
|
print_config->set_key_value("spiral_mode", new ConfigOptionBool(true));
|
||||||
print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
|
print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
|
||||||
print_config->set_key_value("precise_z_height", new ConfigOptionBool(false));
|
print_config->set_key_value("precise_z_height", new ConfigOptionBool(false));
|
||||||
|
if (params.nozzle_based_resize) {
|
||||||
|
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(layer_height));
|
||||||
|
model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(layer_height));
|
||||||
|
}
|
||||||
model().objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btOuterOnly));
|
model().objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btOuterOnly));
|
||||||
model().objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(3.0));
|
model().objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(3.0));
|
||||||
model().objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0));
|
model().objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0));
|
||||||
@@ -14389,14 +14432,11 @@ void Plater::calib_VFA(const Calib_Params& params)
|
|||||||
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_ui_from_settings();
|
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_ui_from_settings();
|
||||||
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->update_ui_from_settings();
|
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->update_ui_from_settings();
|
||||||
|
|
||||||
// cut upper
|
// Pass the resolved layer height on (only meaningful when resized). GCode's VFA stepping is layer-based, so
|
||||||
auto obj_bb = model().objects[0]->bounding_box_exact();
|
// it does not require it, but keep it consistent with the geometry.
|
||||||
auto height = 5 * ((params.end - params.start) / params.step + 1);
|
Calib_Params calib_params = params;
|
||||||
if (height < obj_bb.size().z()) {
|
calib_params.vfa_layer_height = params.nozzle_based_resize ? layer_height : 0.0;
|
||||||
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
|
p->background_process.fff_print()->set_calib_params(calib_params);
|
||||||
}
|
|
||||||
|
|
||||||
p->background_process.fff_print()->set_calib_params(params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plater::calib_input_shaping_freq(const Calib_Params& params)
|
void Plater::calib_input_shaping_freq(const Calib_Params& params)
|
||||||
@@ -15094,7 +15134,7 @@ ProjectDropDialog::ProjectDropDialog(const std::string &filename)
|
|||||||
|
|
||||||
m_sizer_main->Add(dlg_btns, 0, wxEXPAND);
|
m_sizer_main->Add(dlg_btns, 0, wxEXPAND);
|
||||||
|
|
||||||
SetSizer(m_sizer_main);
|
SetSizerAndFit(m_sizer_main);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
Centre(wxBOTH);
|
Centre(wxBOTH);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ PrintOptionsDialog::PrintOptionsDialog(wxWindow* parent)
|
|||||||
{
|
{
|
||||||
this->SetDoubleBuffered(true);
|
this->SetDoubleBuffered(true);
|
||||||
SetBackgroundColour(*wxWHITE);
|
SetBackgroundColour(*wxWHITE);
|
||||||
SetSize(FromDIP(480),FromDIP(520));
|
// SetMinSize(FromDIP(wxSize{wxDefaultCoord,520}));
|
||||||
|
|
||||||
|
|
||||||
m_scrollwindow = new wxScrolledWindow(this, wxID_ANY);
|
m_scrollwindow = new wxScrolledWindow(this, wxID_ANY);
|
||||||
@@ -50,7 +50,8 @@ PrintOptionsDialog::PrintOptionsDialog(wxWindow* parent)
|
|||||||
m_scrollwindow->FitInside();
|
m_scrollwindow->FitInside();
|
||||||
|
|
||||||
this->Layout();
|
this->Layout();
|
||||||
// mainSizer->Fit(this);
|
mainSizer->SetMinSize(wxDefaultCoord, FromDIP(520));
|
||||||
|
mainSizer->Fit(this);
|
||||||
//this->Fit();
|
//this->Fit();
|
||||||
|
|
||||||
m_cb_ai_monitoring->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &evt) {
|
m_cb_ai_monitoring->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &evt) {
|
||||||
@@ -1670,12 +1671,9 @@ PrinterPartsDialog::PrinterPartsDialog(wxWindow* parent)
|
|||||||
/*inset data*/
|
/*inset data*/
|
||||||
sizer->Add(single_panel, 0, wxEXPAND, 0);
|
sizer->Add(single_panel, 0, wxEXPAND, 0);
|
||||||
sizer->Add(multiple_panel, 0, wxEXPAND, 0);
|
sizer->Add(multiple_panel, 0, wxEXPAND, 0);
|
||||||
SetSizer(sizer);
|
|
||||||
Layout();
|
|
||||||
Fit();
|
|
||||||
|
|
||||||
single_panel->Hide();
|
single_panel->Hide();
|
||||||
|
SetSizerAndFit(sizer);
|
||||||
|
Layout();
|
||||||
wxGetApp().UpdateDlgDarkUI(this);
|
wxGetApp().UpdateDlgDarkUI(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1752,6 +1750,7 @@ bool PrinterPartsDialog::Show(bool show)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GetSizer()->SetSizeHints(this);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ PublishDialog::PublishDialog(Plater *plater)
|
|||||||
top_sizer->Add(m_main_sizer, 1, wxALL | wxEXPAND, 0);
|
top_sizer->Add(m_main_sizer, 1, wxALL | wxEXPAND, 0);
|
||||||
top_sizer->Add(FromDIP(30), 0, 0, wxEXPAND, 0);
|
top_sizer->Add(FromDIP(30), 0, 0, wxEXPAND, 0);
|
||||||
|
|
||||||
this->SetSizer(top_sizer);
|
this->SetSizerAndFit(top_sizer);
|
||||||
this->Layout();
|
this->Layout();
|
||||||
|
|
||||||
this->Centre(wxBOTH);
|
this->Centre(wxBOTH);
|
||||||
|
|||||||
@@ -310,10 +310,9 @@ StepMeshDialog::StepMeshDialog(wxWindow* parent, Slic3r::Step& file, double line
|
|||||||
|
|
||||||
bSizer->Add(bSizer_button, 1, wxEXPAND);
|
bSizer->Add(bSizer_button, 1, wxEXPAND);
|
||||||
|
|
||||||
this->SetSizer(bSizer);
|
this->SetSizerAndFit(bSizer);
|
||||||
update_mesh_number_text();
|
update_mesh_number_text();
|
||||||
this->Layout();
|
this->Layout();
|
||||||
bSizer->Fit(this);
|
|
||||||
|
|
||||||
this->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {
|
this->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {
|
||||||
SetFocusIgnoringChildren();
|
SetFocusIgnoringChildren();
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ TroubleshootDialog::TroubleshootDialog()
|
|||||||
m_sizer->AddSpacer(FromDIP(20));
|
m_sizer->AddSpacer(FromDIP(20));
|
||||||
m_sizer->Add(right_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM | wxRIGHT, FromDIP(15));
|
m_sizer->Add(right_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM | wxRIGHT, FromDIP(15));
|
||||||
|
|
||||||
SetSizer(m_sizer);
|
SetSizerAndFit(m_sizer);
|
||||||
Layout();
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
CenterOnParent();
|
CenterOnParent();
|
||||||
|
|||||||
@@ -168,6 +168,9 @@ private:
|
|||||||
}
|
}
|
||||||
wxClientDC dc(this);
|
wxClientDC dc(this);
|
||||||
int cWidth = GetClientSize().GetWidth();
|
int cWidth = GetClientSize().GetWidth();
|
||||||
|
// Don't compute/commit a size based on a not-yet-laid-out width
|
||||||
|
// Mirrors the guard in OnPaint() so both use the same wrap results
|
||||||
|
if (cWidth < 50) return;
|
||||||
|
|
||||||
int y = 0;
|
int y = 0;
|
||||||
for (size_t i = 0; i < m_lines.size(); ++i) {
|
for (size_t i = 0; i < m_lines.size(); ++i) {
|
||||||
|
|||||||
@@ -773,7 +773,7 @@ std::vector<std::string> DiffViewCtrl::selected_options()
|
|||||||
|
|
||||||
static std::string none{"none"};
|
static std::string none{"none"};
|
||||||
#define UNSAVE_CHANGE_DIALOG_SCROLL_WINDOW_SIZE wxSize(FromDIP(490), FromDIP(374))
|
#define UNSAVE_CHANGE_DIALOG_SCROLL_WINDOW_SIZE wxSize(FromDIP(490), FromDIP(374))
|
||||||
#define UNSAVE_CHANGE_DIALOG_ACTION_LINE_SIZE wxSize(FromDIP(490), FromDIP(60))
|
#define UNSAVE_CHANGE_DIALOG_ACTION_LINE_SIZE wxSize(FromDIP(490), -1)
|
||||||
#define UNSAVE_CHANGE_DIALOG_FIRST_VALUE_WIDTH FromDIP(190)
|
#define UNSAVE_CHANGE_DIALOG_FIRST_VALUE_WIDTH FromDIP(190)
|
||||||
#define UNSAVE_CHANGE_DIALOG_VALUE_WIDTH FromDIP(150)
|
#define UNSAVE_CHANGE_DIALOG_VALUE_WIDTH FromDIP(150)
|
||||||
#define UNSAVE_CHANGE_DIALOG_ITEM_HEIGHT FromDIP(24)
|
#define UNSAVE_CHANGE_DIALOG_ITEM_HEIGHT FromDIP(24)
|
||||||
@@ -1075,11 +1075,6 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection *dependent_
|
|||||||
m_sizer_main->Add(m_sizer_button, 0, wxEXPAND | wxTOP, 6);
|
m_sizer_main->Add(m_sizer_button, 0, wxEXPAND | wxTOP, 6);
|
||||||
m_sizer_main->Add(0, 0, 1, wxTOP, 18);
|
m_sizer_main->Add(0, 0, 1, wxTOP, 18);
|
||||||
|
|
||||||
SetSizer(m_sizer_main);
|
|
||||||
Layout();
|
|
||||||
Fit();
|
|
||||||
Centre(wxBOTH);
|
|
||||||
|
|
||||||
if (params) {
|
if (params) {
|
||||||
if (params->left_to_right)
|
if (params->left_to_right)
|
||||||
update_tree(type, params->config, params->from, params->to);
|
update_tree(type, params->config, params->from, params->to);
|
||||||
@@ -1095,6 +1090,11 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection *dependent_
|
|||||||
//topSizer->SetSizeHints(this);
|
//topSizer->SetSizeHints(this);
|
||||||
|
|
||||||
show_info_line(Action::Undef);
|
show_info_line(Action::Undef);
|
||||||
|
|
||||||
|
SetSizerAndFit(m_sizer_main);
|
||||||
|
Layout();
|
||||||
|
Fit();
|
||||||
|
// Centre(wxBOTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name)
|
void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name)
|
||||||
@@ -1499,6 +1499,7 @@ void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_action_line->SetLabel(action_msg);
|
m_action_line->SetLabel(action_msg);
|
||||||
|
m_action_line->Wrap(UNSAVE_CHANGE_DIALOG_SCROLL_WINDOW_SIZE.x);
|
||||||
|
|
||||||
update_tree(type, presets);
|
update_tree(type, presets);
|
||||||
update_list();
|
update_list();
|
||||||
|
|||||||
@@ -213,9 +213,8 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_
|
|||||||
m_scrollwindw_release_note->Layout();
|
m_scrollwindw_release_note->Layout();
|
||||||
|
|
||||||
|
|
||||||
SetSizer(m_sizer_main);
|
SetSizerAndFit(m_sizer_main);
|
||||||
Layout();
|
Layout();
|
||||||
m_sizer_main->Fit(this);
|
|
||||||
|
|
||||||
Centre(wxBOTH);
|
Centre(wxBOTH);
|
||||||
wxGetApp().UpdateDlgDarkUI(this);
|
wxGetApp().UpdateDlgDarkUI(this);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#include "WebGuideDialog.hpp"
|
#include "WebGuideDialog.hpp"
|
||||||
#include "ConfigWizard.hpp"
|
#include "ConfigWizard.hpp"
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/join.hpp>
|
||||||
#include <boost/filesystem/operations.hpp>
|
#include <boost/filesystem/operations.hpp>
|
||||||
|
#include <boost/nowide/fstream.hpp>
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
#include <boost/iostreams/detail/select.hpp>
|
#include <boost/iostreams/detail/select.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
@@ -9,6 +11,7 @@
|
|||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
#include "libslic3r/AppConfig.hpp"
|
#include "libslic3r/AppConfig.hpp"
|
||||||
#include "libslic3r/Config.hpp"
|
#include "libslic3r/Config.hpp"
|
||||||
|
#include "libslic3r/Preset.hpp"
|
||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
#include "slic3r/GUI/wxExtensions.hpp"
|
#include "slic3r/GUI/wxExtensions.hpp"
|
||||||
#include "slic3r/GUI/GUI_App.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
@@ -41,8 +44,6 @@ using namespace nlohmann;
|
|||||||
|
|
||||||
namespace Slic3r { namespace GUI {
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
json m_ProfileJson;
|
|
||||||
|
|
||||||
static wxString update_custom_filaments()
|
static wxString update_custom_filaments()
|
||||||
{
|
{
|
||||||
json m_Res = json::object();
|
json m_Res = json::object();
|
||||||
@@ -190,12 +191,10 @@ GuideFrame::GuideFrame(GUI_App *pGUI, long style)
|
|||||||
|
|
||||||
GuideFrame::~GuideFrame()
|
GuideFrame::~GuideFrame()
|
||||||
{
|
{
|
||||||
m_destroy = true;
|
*m_cancel_token = true; // stop the loading thread and any queued CallAfter lambdas before join
|
||||||
if (m_load_task && m_load_task->joinable()) {
|
if (m_load_task && m_load_task->joinable())
|
||||||
m_load_task->join();
|
m_load_task->join();
|
||||||
delete m_load_task;
|
m_load_task.reset();
|
||||||
m_load_task = nullptr;
|
|
||||||
}
|
|
||||||
if (m_browser) {
|
if (m_browser) {
|
||||||
delete m_browser;
|
delete m_browser;
|
||||||
m_browser = nullptr;
|
m_browser = nullptr;
|
||||||
@@ -301,15 +300,71 @@ void GuideFrame::OnNavigationRequest(wxWebViewEvent &evt)
|
|||||||
/**
|
/**
|
||||||
* Callback invoked when a navigation request was accepted
|
* Callback invoked when a navigation request was accepted
|
||||||
*/
|
*/
|
||||||
|
// The empty shape every profile-loading path starts from or falls back to.
|
||||||
|
void GuideFrame::reset_profile_json()
|
||||||
|
{
|
||||||
|
m_ProfileJson["model"] = json::array();
|
||||||
|
m_ProfileJson["machine"] = json::object();
|
||||||
|
m_ProfileJson["filament"] = json::object();
|
||||||
|
m_ProfileJson["process"] = json::array();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuideFrame::init_guide_paths()
|
||||||
|
{
|
||||||
|
m_ProfileJson = json::parse("{}");
|
||||||
|
reset_profile_json();
|
||||||
|
|
||||||
|
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
|
||||||
|
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
|
||||||
|
orca_bundle_rsrc = true;
|
||||||
|
|
||||||
|
if (boost::filesystem::exists(vendor_dir)) {
|
||||||
|
for (const auto& entry : boost::filesystem::directory_iterator(vendor_dir)) {
|
||||||
|
if (!boost::filesystem::is_directory(entry) &&
|
||||||
|
boost::iequals(entry.path().extension().string(), ".json") &&
|
||||||
|
!boost::iequals(entry.path().stem().string(), PresetBundle::ORCA_FILAMENT_LIBRARY)) {
|
||||||
|
orca_bundle_rsrc = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lib_json = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
||||||
|
m_OrcaFilaLibPath = boost::filesystem::exists(vendor_dir / lib_json)
|
||||||
|
? (vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string()
|
||||||
|
: (rsrc_vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuideFrame::on_profile_loaded()
|
||||||
|
{
|
||||||
|
// Must be called on the main thread.
|
||||||
|
SaveProfileData();
|
||||||
|
const std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||||
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents:\n" << strAll;
|
||||||
|
json res;
|
||||||
|
res["command"] = "userguide_profile_load_finish";
|
||||||
|
res["sequence_id"] = "10001";
|
||||||
|
RunScript(wxString::Format("HandleStudio(%s)", res.dump(-1, ' ', true)));
|
||||||
|
}
|
||||||
|
|
||||||
void GuideFrame::OnNavigationComplete(wxWebViewEvent &evt)
|
void GuideFrame::OnNavigationComplete(wxWebViewEvent &evt)
|
||||||
{
|
{
|
||||||
//wxLogMessage("%s", "Navigation complete; url='" + evt.GetURL() + "'");
|
//wxLogMessage("%s", "Navigation complete; url='" + evt.GetURL() + "'");
|
||||||
if (!bFirstComplete) {
|
if (!bFirstComplete) {
|
||||||
m_load_task = new boost::thread(boost::bind(&GuideFrame::LoadProfileData, this));
|
|
||||||
// boost::thread LoadProfileThread(boost::bind(&GuideFrame::LoadProfileData, this));
|
|
||||||
//LoadProfileThread.detach();
|
|
||||||
|
|
||||||
bFirstComplete = true;
|
bFirstComplete = true;
|
||||||
|
try {
|
||||||
|
init_guide_paths();
|
||||||
|
if (BuildProfileDataFromPresetBundle()) {
|
||||||
|
if (!*m_cancel_token)
|
||||||
|
on_profile_loaded();
|
||||||
|
} else {
|
||||||
|
// Presets not yet in memory — delegate to background thread.
|
||||||
|
m_load_task = std::make_unique<boost::thread>(boost::bind(&GuideFrame::LoadProfileData, this));
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", init error: " << e.what();
|
||||||
|
m_load_task = std::make_unique<boost::thread>(boost::bind(&GuideFrame::LoadProfileData, this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_browser->Show();
|
m_browser->Show();
|
||||||
@@ -762,11 +817,9 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle
|
|||||||
bool check_unsaved_preset_changes = false;
|
bool check_unsaved_preset_changes = false;
|
||||||
std::vector<std::string> install_bundles;
|
std::vector<std::string> install_bundles;
|
||||||
std::vector<std::string> remove_bundles;
|
std::vector<std::string> remove_bundles;
|
||||||
const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
|
|
||||||
for (const auto &it : enabled_vendors) {
|
for (const auto &it : enabled_vendors) {
|
||||||
if (it.second.size() > 0) {
|
if (it.second.size() > 0) {
|
||||||
auto vendor_file = vendor_dir/(it.first + ".json");
|
if (!is_vendor_installed(it.first)) {
|
||||||
if (!fs::exists(vendor_file)) {
|
|
||||||
install_bundles.emplace_back(it.first);
|
install_bundles.emplace_back(it.first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -777,8 +830,7 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle
|
|||||||
if (it.second.size() > 0) {
|
if (it.second.size() > 0) {
|
||||||
if (enabled_vendors.find(it.first) != enabled_vendors.end())
|
if (enabled_vendors.find(it.first) != enabled_vendors.end())
|
||||||
continue;
|
continue;
|
||||||
auto vendor_file = vendor_dir/(it.first + ".json");
|
if (is_vendor_installed(it.first)) {
|
||||||
if (fs::exists(vendor_file)) {
|
|
||||||
remove_bundles.emplace_back(it.first);
|
remove_bundles.emplace_back(it.first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1127,99 +1179,244 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GuideFrame::LoadProfileData()
|
bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_resource_vendors)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
m_ProfileJson = json::parse("{}");
|
// Models from vendor profiles
|
||||||
m_ProfileJson["model"] = json::array();
|
for (const auto& [vendor_id, vp] : bundle.vendors) {
|
||||||
m_ProfileJson["machine"] = json::object();
|
for (const auto& model : vp.models) {
|
||||||
m_ProfileJson["filament"] = json::object();
|
std::string nozzle_str;
|
||||||
m_ProfileJson["process"] = json::array();
|
for (const auto& v : model.variants) {
|
||||||
|
if (!nozzle_str.empty()) nozzle_str += ";";
|
||||||
|
nozzle_str += v.name;
|
||||||
|
}
|
||||||
|
const std::string materials_str = boost::algorithm::join(model.default_materials, ";");
|
||||||
|
boost::filesystem::path cover_path =
|
||||||
|
(boost::filesystem::path(resources_dir()) / "profiles" / vp.id / (model.id + "_cover.png"))
|
||||||
|
.make_preferred();
|
||||||
|
if (!boost::filesystem::exists(cover_path))
|
||||||
|
cover_path =
|
||||||
|
(boost::filesystem::path(resources_dir()) / "web/image/printer" / (model.id + "_cover.png"))
|
||||||
|
.make_preferred();
|
||||||
|
|
||||||
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
|
json entry;
|
||||||
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
|
entry["model"] = model.id;
|
||||||
|
entry["name"] = model.name;
|
||||||
// Orca: add custom as default
|
entry["vendor"] = vp.id;
|
||||||
// Orca: add json logic for vendor bundle
|
entry["nozzle_diameter"] = nozzle_str;
|
||||||
orca_bundle_rsrc = true;
|
entry["materials"] = materials_str;
|
||||||
|
entry["cover"] = cover_path.string();
|
||||||
// search if there exists a .json file in vendor_dir folder, if exists, set orca_bundle_rsrc to false
|
entry["nozzle_selected"] = "";
|
||||||
for (const auto& entry : boost::filesystem::directory_iterator(vendor_dir)) {
|
entry["sub_path"] = "";
|
||||||
if (!boost::filesystem::is_directory(entry) && boost::iequals(entry.path().extension().string(), ".json") && !boost::iequals(entry.path().stem().string(), PresetBundle::ORCA_FILAMENT_LIBRARY)) {
|
m_ProfileJson["model"].push_back(entry);
|
||||||
orca_bundle_rsrc = false;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// load the default filament library first
|
// Machine map: preset name -> {model, nozzle variant}
|
||||||
std::set<std::string> loaded_vendors;
|
for (const Preset& p : bundle.printers()) {
|
||||||
auto filament_library_name = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
if (!p.is_system || !p.vendor) continue;
|
||||||
if (boost::filesystem::exists(vendor_dir / filament_library_name)) {
|
const auto* printer_model = p.config.option<ConfigOptionString>("printer_model");
|
||||||
m_OrcaFilaLibPath = (vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
|
const auto* printer_variant = p.config.option<ConfigOptionString>("printer_variant");
|
||||||
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (vendor_dir / filament_library_name).string());
|
if (!printer_model || printer_model->value.empty() || !printer_variant) continue;
|
||||||
} else {
|
|
||||||
m_OrcaFilaLibPath = (rsrc_vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
|
|
||||||
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (rsrc_vendor_dir / filament_library_name).string());
|
|
||||||
}
|
|
||||||
loaded_vendors.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
|
||||||
|
|
||||||
//load custom bundle from user data path
|
json mach;
|
||||||
boost::filesystem::directory_iterator endIter;
|
mach["model"] = printer_model->value;
|
||||||
for (boost::filesystem::directory_iterator iter(vendor_dir); iter != endIter; iter++) {
|
mach["nozzle"] = printer_variant->value;
|
||||||
if (!boost::filesystem::is_directory(*iter)) {
|
m_ProfileJson["machine"][p.name] = mach;
|
||||||
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
|
||||||
strVendor = strVendor.AfterLast('\\');
|
|
||||||
strVendor = strVendor.AfterLast('/');
|
|
||||||
|
|
||||||
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
|
||||||
if(strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
|
||||||
loaded_vendors.insert(w2s(strVendor));
|
|
||||||
}
|
|
||||||
if (m_destroy)
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::filesystem::directory_iterator others_endIter;
|
// Filament map from system filament presets (vendor/type already resolved in config)
|
||||||
for (boost::filesystem::directory_iterator iter(rsrc_vendor_dir); iter != others_endIter; iter++) {
|
const json& machines = m_ProfileJson["machine"];
|
||||||
if (!boost::filesystem::is_directory(*iter)) {
|
for (const Preset& p : bundle.filaments()) {
|
||||||
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
if (!p.is_system || !p.vendor) continue;
|
||||||
strVendor = strVendor.AfterLast('\\');
|
const auto* fila_vendor = p.config.option<ConfigOptionStrings>("filament_vendor");
|
||||||
strVendor = strVendor.AfterLast('/');
|
const auto* fila_type = p.config.option<ConfigOptionStrings>("filament_type");
|
||||||
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
const auto* compat_printers = p.config.option<ConfigOptionStrings>("compatible_printers");
|
||||||
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
std::string vendor = (fila_vendor && !fila_vendor->values.empty()) ? fila_vendor->values[0] : "";
|
||||||
loaded_vendors.insert(w2s(strVendor));
|
std::string type = (fila_type && !fila_type->values.empty()) ? fila_type->values[0] : "";
|
||||||
|
|
||||||
|
std::string model_list;
|
||||||
|
if (compat_printers) {
|
||||||
|
for (const std::string& pname : compat_printers->values) {
|
||||||
|
auto it = machines.find(pname);
|
||||||
|
if (it != machines.end()) {
|
||||||
|
const std::string m = (*it)["model"];
|
||||||
|
const std::string n = (*it)["nozzle"];
|
||||||
|
model_list += "[" + m + "++" + n + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (m_destroy)
|
|
||||||
return 0;
|
json ff;
|
||||||
|
ff["name"] = p.name;
|
||||||
|
ff["sub_path"] = p.file;
|
||||||
|
ff["vendor"] = vendor;
|
||||||
|
ff["type"] = type;
|
||||||
|
ff["models"] = model_list;
|
||||||
|
ff["selected"] = 0;
|
||||||
|
m_ProfileJson["filament"][p.name] = ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
wxGetApp().CallAfter([this] {
|
// Process list from visible system print presets
|
||||||
if (!m_destroy) {
|
for (const Preset& p : bundle.prints()) {
|
||||||
//sync to appconfig first to populate current selections
|
if (!p.is_system || !p.vendor || !p.is_visible) continue;
|
||||||
SaveProfileData();
|
json entry;
|
||||||
|
entry["name"] = p.name;
|
||||||
|
entry["sub_path"] = p.file;
|
||||||
|
m_ProfileJson["process"].push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
//sync to web after selections are populated
|
if (require_all_resource_vendors) {
|
||||||
std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
|
// If rsrc_vendor_dir has vendors (profile JSONs, or the preset caches a
|
||||||
|
// packaged build ships instead) not covered by the current bundle, the
|
||||||
|
// bundle is incomplete (e.g. dev env where data_dir/system only has
|
||||||
|
// OrcaFilamentLibrary+Custom). Fall back so the slow path reads both dirs.
|
||||||
|
try {
|
||||||
|
for (const std::string& name : vendor_names_in(rsrc_vendor_dir)) {
|
||||||
|
if (bundle.vendors.find(name) == bundle.vendors.end()) {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "GuideFrame: vendor '" << name
|
||||||
|
<< "' in resources but not in preset_bundle — falling back to JSON loading";
|
||||||
|
reset_profile_json();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const std::exception&) {}
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents: " << std::endl << strAll;
|
BOOST_LOG_TRIVIAL(info) << "GuideFrame: built profile data ("
|
||||||
json m_Res = json::object();
|
<< m_ProfileJson["model"].size() << " models, "
|
||||||
m_Res["command"] = "userguide_profile_load_finish";
|
<< m_ProfileJson["machine"].size() << " machines, "
|
||||||
m_Res["sequence_id"] = "10001";
|
<< m_ProfileJson["filament"].size() << " filaments)";
|
||||||
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', true));
|
return !m_ProfileJson["machine"].empty();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "GuideFrame::BuildProfileJson failed: " << e.what()
|
||||||
|
<< " — falling back to JSON loading";
|
||||||
|
reset_profile_json();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RunScript(strJS);
|
bool GuideFrame::BuildProfileDataFromPresetBundle()
|
||||||
|
{
|
||||||
|
PresetBundle* pb = wxGetApp().preset_bundle;
|
||||||
|
if (!pb || pb->vendors.empty())
|
||||||
|
return false;
|
||||||
|
return BuildProfileJson(*pb, /*require_all_resource_vendors=*/true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuideFrame::BuildProfileDataFromVendors()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Same vendor set and precedence as the JSON scan in LoadProfileData: a
|
||||||
|
// vendor in the user's system dir shadows the bundled one of that name.
|
||||||
|
// A vendor is named by its profile or, where a build ships preset caches
|
||||||
|
// instead, by its cache alone — so both forms name one here.
|
||||||
|
std::map<std::string, boost::filesystem::path> vendor_files;
|
||||||
|
auto collect = [&vendor_files](const boost::filesystem::path& dir) {
|
||||||
|
boost::system::error_code ec;
|
||||||
|
if (!boost::filesystem::exists(dir, ec))
|
||||||
|
return;
|
||||||
|
for (const auto& e : boost::filesystem::directory_iterator(dir, ec))
|
||||||
|
if (Slic3r::is_json_file(e.path().string()) || e.path().extension() == ".opc")
|
||||||
|
vendor_files.emplace(e.path().stem().string(), e.path()); // first wins
|
||||||
|
};
|
||||||
|
collect(vendor_dir);
|
||||||
|
collect(rsrc_vendor_dir);
|
||||||
|
|
||||||
|
// Each vendor comes from its preset cache where one covers it, which is what
|
||||||
|
// makes this worth doing instead of the scan below; the filament library goes
|
||||||
|
// first because the others' filaments inherit from it, and resolving those on
|
||||||
|
// the vendors the cache does not cover needs it already loaded.
|
||||||
|
PresetBundle bundle;
|
||||||
|
auto load_vendor = [this](PresetBundle& into, const std::string& vendor, const PresetBundle* base) {
|
||||||
|
into.load_vendor_configs_from_json(vendor_dir.string(), vendor, PresetBundle::LoadSystem,
|
||||||
|
ForwardCompatibilitySubstitutionRule::EnableSilent, base);
|
||||||
|
};
|
||||||
|
const std::string filament_library(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
||||||
|
if (vendor_files.count(filament_library))
|
||||||
|
load_vendor(bundle, filament_library, nullptr);
|
||||||
|
for (const auto& entry : vendor_files) {
|
||||||
|
if (*m_cancel_token)
|
||||||
|
return false; // as in the scan below: a vendor without a cache is parsed, and that takes time
|
||||||
|
const std::string& vendor = entry.first;
|
||||||
|
// A cache is only ever written for a versioned vendor; a JSON has to be
|
||||||
|
// asked, so that an unversioned one (blacklist.json) carrying no presets
|
||||||
|
// is passed over.
|
||||||
|
if (vendor == filament_library ||
|
||||||
|
(entry.second.extension() != ".opc" && get_vendor_cache_version(entry.second.string()).empty()))
|
||||||
|
continue;
|
||||||
|
PresetBundle tmp;
|
||||||
|
load_vendor(tmp, vendor, &bundle);
|
||||||
|
bundle.merge_presets(std::move(tmp));
|
||||||
|
}
|
||||||
|
if (bundle.vendors.empty())
|
||||||
|
return false;
|
||||||
|
return BuildProfileJson(bundle, /*require_all_resource_vendors=*/false);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed: " << e.what();
|
||||||
|
reset_profile_json();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int GuideFrame::LoadProfileData()
|
||||||
|
{
|
||||||
|
// Background thread: the fast path in OnNavigationComplete failed (presets not yet loaded).
|
||||||
|
// Loading order (fastest to slowest):
|
||||||
|
// 1. Load every vendor, from its preset cache wherever one covers it
|
||||||
|
// 2. Read all vendor JSONs by hand
|
||||||
|
try {
|
||||||
|
if (!BuildProfileDataFromVendors()) {
|
||||||
|
// Last resort — read all vendor JSONs
|
||||||
|
std::set<std::string> loaded_vendors;
|
||||||
|
auto filament_library_name = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
||||||
|
if (boost::filesystem::exists(vendor_dir / filament_library_name))
|
||||||
|
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (vendor_dir / filament_library_name).string());
|
||||||
|
else
|
||||||
|
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (rsrc_vendor_dir / filament_library_name).string());
|
||||||
|
loaded_vendors.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
||||||
|
|
||||||
|
boost::filesystem::directory_iterator endIter;
|
||||||
|
for (boost::filesystem::directory_iterator iter(vendor_dir); iter != endIter; iter++) {
|
||||||
|
if (!boost::filesystem::is_directory(*iter)) {
|
||||||
|
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
||||||
|
strVendor = strVendor.AfterLast('\\');
|
||||||
|
strVendor = strVendor.AfterLast('/');
|
||||||
|
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
||||||
|
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
||||||
|
continue;
|
||||||
|
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
||||||
|
loaded_vendors.insert(w2s(strVendor));
|
||||||
|
}
|
||||||
|
if (*m_cancel_token) return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::filesystem::directory_iterator others_endIter;
|
||||||
|
for (boost::filesystem::directory_iterator iter(rsrc_vendor_dir); iter != others_endIter; iter++) {
|
||||||
|
if (!boost::filesystem::is_directory(*iter)) {
|
||||||
|
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
||||||
|
strVendor = strVendor.AfterLast('\\');
|
||||||
|
strVendor = strVendor.AfterLast('/');
|
||||||
|
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
||||||
|
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
||||||
|
continue;
|
||||||
|
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
||||||
|
loaded_vendors.insert(w2s(strVendor));
|
||||||
|
}
|
||||||
|
if (*m_cancel_token) return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture the cancel token by value (shared_ptr) so the lambda doesn't
|
||||||
|
// touch `this` if GuideFrame is destroyed before the event fires.
|
||||||
|
auto tok = m_cancel_token;
|
||||||
|
wxGetApp().CallAfter([this, tok] {
|
||||||
|
if (!*tok)
|
||||||
|
on_profile_loaded();
|
||||||
});
|
});
|
||||||
} catch (std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
// wxLogMessage("GUIDE: load_profile_error %s ", e.what());
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what();
|
||||||
// wxMessageBox(e.what(), "", MB_OK);
|
|
||||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what() << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filament_info_cache.clear();
|
filament_info_cache.clear();
|
||||||
|
|||||||
@@ -30,10 +30,14 @@
|
|||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <boost/thread.hpp>
|
||||||
|
|
||||||
namespace Slic3r { namespace GUI {
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
class GuideFrame : public DPIDialog
|
class GuideFrame : public DPIDialog
|
||||||
@@ -78,6 +82,12 @@ public:
|
|||||||
int LoadProfileData();
|
int LoadProfileData();
|
||||||
int SaveProfileData();
|
int SaveProfileData();
|
||||||
int LoadProfileFamily(std::string strVendor, std::string strFilePath);
|
int LoadProfileFamily(std::string strVendor, std::string strFilePath);
|
||||||
|
void init_guide_paths();
|
||||||
|
void on_profile_loaded();
|
||||||
|
bool BuildProfileJson(const PresetBundle& bundle, bool require_all_resource_vendors);
|
||||||
|
bool BuildProfileDataFromPresetBundle();
|
||||||
|
bool BuildProfileDataFromVendors();
|
||||||
|
void reset_profile_json();
|
||||||
int SaveProfile();
|
int SaveProfile();
|
||||||
int GetFilamentInfo( std::string VendorDirectory,json & pFilaList, std::string filepath, std::string &sVendor, std::string &sType);
|
int GetFilamentInfo( std::string VendorDirectory,json & pFilaList, std::string filepath, std::string &sVendor, std::string &sType);
|
||||||
|
|
||||||
@@ -112,8 +122,11 @@ private:
|
|||||||
|
|
||||||
//First Load
|
//First Load
|
||||||
bool bFirstComplete{false};
|
bool bFirstComplete{false};
|
||||||
bool m_destroy{false};
|
// Set once in the destructor. Read through `this` by the loading thread
|
||||||
boost::thread* m_load_task{ nullptr };
|
// (joined before `this` dies) and captured as the shared_ptr by CallAfter
|
||||||
|
// lambdas so they don't touch `this` after the object is freed.
|
||||||
|
std::shared_ptr<std::atomic<bool>> m_cancel_token{std::make_shared<std::atomic<bool>>(false)};
|
||||||
|
std::unique_ptr<boost::thread> m_load_task;
|
||||||
|
|
||||||
// User Config
|
// User Config
|
||||||
bool PrivacyUse;
|
bool PrivacyUse;
|
||||||
@@ -123,6 +136,7 @@ private:
|
|||||||
bool InstallNetplugin;
|
bool InstallNetplugin;
|
||||||
bool network_plugin_ready {false};
|
bool network_plugin_ready {false};
|
||||||
|
|
||||||
|
json m_ProfileJson;
|
||||||
json m_OrcaFilaList;
|
json m_OrcaFilaList;
|
||||||
std::string m_OrcaFilaLibPath;
|
std::string m_OrcaFilaLibPath;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
#include "Widgets/HyperLink.hpp"
|
#include "Widgets/HyperLink.hpp"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <cmath>
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
#include "libslic3r/Flow.hpp"
|
||||||
#include "libslic3r/Utils.hpp"
|
#include "libslic3r/Utils.hpp"
|
||||||
|
|
||||||
namespace Slic3r { namespace GUI {
|
namespace Slic3r { namespace GUI {
|
||||||
@@ -34,6 +36,23 @@ int GetTextMax(wxWindow* parent, const std::vector<wxString>& labels)
|
|||||||
return text_size.x + parent->FromDIP(10);
|
return text_size.x + parent->FromDIP(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CheckBox* add_scale_checkbox(wxWindow* parent, wxSizer* settings_sizer)
|
||||||
|
{
|
||||||
|
auto row = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
auto cb = new CheckBox(parent);
|
||||||
|
cb->SetValue(true);
|
||||||
|
auto text = new wxStaticText(parent, wxID_ANY, _L("Auto-scale for nozzle"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||||
|
cb->SetToolTip(_L("This model is designed around a 0.4 mm nozzle with a 0.2 mm layer height. \n"
|
||||||
|
"When the scaling option is enabled (recommended), it dynamically resizes to match your current nozzle diameter"
|
||||||
|
" and an appropriate layer height, making the test both accurate and easy to read.\n"
|
||||||
|
"Turn scaling off only if you wish to print the reference model exactly as-is."));
|
||||||
|
text->SetToolTip(cb->GetToolTipText());
|
||||||
|
row->Add(cb , 0, wxALL | wxALIGN_CENTER_VERTICAL, parent->FromDIP(2));
|
||||||
|
row->Add(text, 0, wxALL | wxALIGN_CENTER_VERTICAL, parent->FromDIP(2));
|
||||||
|
settings_sizer->Add(row, 0, wxLEFT | wxTOP, parent->FromDIP(3));
|
||||||
|
return cb;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string> get_shaper_type_values()
|
std::vector<std::string> get_shaper_type_values()
|
||||||
{
|
{
|
||||||
if (auto* preset_bundle = wxGetApp().preset_bundle) {
|
if (auto* preset_bundle = wxGetApp().preset_bundle) {
|
||||||
@@ -402,6 +421,9 @@ Temp_Calibration_Dlg::Temp_Calibration_Dlg(wxWindow* parent, wxWindowID id, Plat
|
|||||||
temp_step_sizer->Add(m_tiStep , 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2));
|
temp_step_sizer->Add(m_tiStep , 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2));
|
||||||
settings_sizer->Add(temp_step_sizer, 0, wxLEFT, FromDIP(3));
|
settings_sizer->Add(temp_step_sizer, 0, wxLEFT, FromDIP(3));
|
||||||
|
|
||||||
|
// Resize the model to the nozzle diameter (recommended)
|
||||||
|
m_cbResize = add_scale_checkbox(this, settings_sizer);
|
||||||
|
|
||||||
settings_sizer->AddSpacer(FromDIP(5));
|
settings_sizer->AddSpacer(FromDIP(5));
|
||||||
|
|
||||||
v_sizer->Add(settings_sizer, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(10));
|
v_sizer->Add(settings_sizer, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(10));
|
||||||
@@ -475,6 +497,7 @@ void Temp_Calibration_Dlg::on_start(wxCommandEvent& event) {
|
|||||||
}
|
}
|
||||||
m_params.start = start;
|
m_params.start = start;
|
||||||
m_params.end = end;
|
m_params.end = end;
|
||||||
|
m_params.nozzle_based_resize = m_cbResize->GetValue();
|
||||||
m_params.mode = CalibMode::Calib_Temp_Tower;
|
m_params.mode = CalibMode::Calib_Temp_Tower;
|
||||||
m_plater->calib_temp(m_params);
|
m_plater->calib_temp(m_params);
|
||||||
EndModal(wxID_OK);
|
EndModal(wxID_OK);
|
||||||
@@ -691,6 +714,22 @@ VFA_Test_Dlg::VFA_Test_Dlg(wxWindow* parent, wxWindowID id, Plater* plater)
|
|||||||
vol_step_sizer->Add(m_tiStep , 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2));
|
vol_step_sizer->Add(m_tiStep , 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2));
|
||||||
settings_sizer->Add(vol_step_sizer, 0, wxLEFT, FromDIP(3));
|
settings_sizer->Add(vol_step_sizer, 0, wxLEFT, FromDIP(3));
|
||||||
|
|
||||||
|
// Resize the model to the nozzle diameter (recommended)
|
||||||
|
m_cbResize = add_scale_checkbox(this, settings_sizer);
|
||||||
|
|
||||||
|
// Auto-adjust parameters to the filament's max volumetric speed
|
||||||
|
auto auto_adjust_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
m_cbAutoAdjust = new CheckBox(this);
|
||||||
|
m_cbAutoAdjust->SetValue(true);
|
||||||
|
auto auto_adjust_text = new wxStaticText(this, wxID_ANY, _L("Auto-adjust to max volumetric speed"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||||
|
m_cbAutoAdjust->SetToolTip(_L("If the end speed would exceed the filament's maximum volumetric speed, automatically lower the layer "
|
||||||
|
"height (keeping standard values and staying within the machine's limits) to reach it. If even the "
|
||||||
|
"minimum layer height is not enough, lower the end speed instead."));
|
||||||
|
auto_adjust_text->SetToolTip(m_cbAutoAdjust->GetToolTipText());
|
||||||
|
auto_adjust_sizer->Add(m_cbAutoAdjust , 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2));
|
||||||
|
auto_adjust_sizer->Add(auto_adjust_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2));
|
||||||
|
settings_sizer->Add(auto_adjust_sizer, 0, wxLEFT | wxTOP, FromDIP(3));
|
||||||
|
|
||||||
settings_sizer->AddSpacer(FromDIP(5));
|
settings_sizer->AddSpacer(FromDIP(5));
|
||||||
|
|
||||||
v_sizer->Add(settings_sizer, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(10));
|
v_sizer->Add(settings_sizer, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(10));
|
||||||
@@ -732,6 +771,136 @@ void VFA_Test_Dlg::on_start(wxCommandEvent& event)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the requested end speed would exceed the filament's maximum volumetric speed, the slicer clamps the
|
||||||
|
// outer wall speed, so the upper blocks of the tower would all print at the same (clamped) speed instead of
|
||||||
|
// the requested one. Depending on the "Auto-adjust" option, either fix it automatically or just warn.
|
||||||
|
m_params.vfa_layer_height = 0.0; // 0 = auto (nozzle/2); overridden below when auto-adjusting
|
||||||
|
m_params.nozzle_based_resize = m_cbResize->GetValue();
|
||||||
|
if (const auto* preset_bundle = wxGetApp().preset_bundle) {
|
||||||
|
const auto& printer_config = preset_bundle->printers.get_edited_preset().config;
|
||||||
|
const auto& print_config = preset_bundle->prints.get_edited_preset().config;
|
||||||
|
const auto& filament_config = preset_bundle->filaments.get_edited_preset().config;
|
||||||
|
|
||||||
|
const int extruder_id = std::max(m_params.extruder_id, 0);
|
||||||
|
auto get_at = [extruder_id](const ConfigOptionFloats* opt, double fallback) {
|
||||||
|
if (opt == nullptr || opt->values.empty())
|
||||||
|
return fallback;
|
||||||
|
return opt->values[std::min(static_cast<size_t>(extruder_id), opt->values.size() - 1)];
|
||||||
|
};
|
||||||
|
|
||||||
|
const double nozzle_diameter = get_at(printer_config.option<ConfigOptionFloats>("nozzle_diameter"), vfa_base_nozzle_diameter);
|
||||||
|
double preset_lh = nozzle_diameter / 2.0;
|
||||||
|
if (const auto* lh_opt = print_config.option<ConfigOptionFloat>("layer_height"))
|
||||||
|
if (lh_opt->value > 0.0)
|
||||||
|
preset_lh = lh_opt->value;
|
||||||
|
// Layer height the tower will actually print at: nozzle/2 when resizing, else the preset value.
|
||||||
|
const double default_lh = m_params.nozzle_based_resize ? nozzle_diameter / 2.0 : preset_lh;
|
||||||
|
const double max_vol_speed = get_at(filament_config.option<ConfigOptionFloats>("filament_max_volumetric_speed"), 0.0);
|
||||||
|
const double machine_min_lh = get_at(printer_config.option<ConfigOptionFloats>("min_layer_height"), 0.0);
|
||||||
|
const double machine_max_lh = get_at(printer_config.option<ConfigOptionFloats>("max_layer_height"), 0.0);
|
||||||
|
|
||||||
|
double line_width = print_config.get_abs_value("outer_wall_line_width", nozzle_diameter);
|
||||||
|
if (line_width <= 0.0)
|
||||||
|
line_width = print_config.get_abs_value("line_width", nozzle_diameter);
|
||||||
|
if (line_width <= 0.0)
|
||||||
|
line_width = nozzle_diameter;
|
||||||
|
|
||||||
|
// Max outer-wall speed printable at a given layer height without exceeding the volumetric limit.
|
||||||
|
auto speed_limit_for_lh = [&](double lh) -> double {
|
||||||
|
const double mm3_per_mm = Flow(line_width, lh, nozzle_diameter).mm3_per_mm();
|
||||||
|
return mm3_per_mm > 0.0 ? max_vol_speed / mm3_per_mm : 1e9;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto confirm_clamp = [&](const wxString& question) -> bool {
|
||||||
|
MessageDialog msg_dlg(nullptr,
|
||||||
|
wxString::Format(_L("The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed "
|
||||||
|
"(%.1f mm³/s), which limits the outer wall to about %.0f mm/s at this line width and "
|
||||||
|
"layer height.\n Speeds above this will be clamped, so the upper blocks of the tower "
|
||||||
|
"will not print at the requested speed.\n\n%s"),
|
||||||
|
m_params.end, max_vol_speed, speed_limit_for_lh(default_lh), question),
|
||||||
|
_L("VFA test"), wxICON_WARNING | wxYES_NO | wxNO_DEFAULT);
|
||||||
|
return msg_dlg.ShowModal() == wxID_YES;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (max_vol_speed > 0.0 && nozzle_diameter > 0.0 && m_params.end > speed_limit_for_lh(default_lh)) {
|
||||||
|
// The layer-height auto-adjust only applies when resizing is enabled (it changes the layer height).
|
||||||
|
if (m_cbAutoAdjust->GetValue() && m_params.nozzle_based_resize) {
|
||||||
|
// Candidate layer heights are the ones actually used by the process profiles compatible with the
|
||||||
|
// current printer (clamped to the machine's layer-height limits, when set). A smaller layer height
|
||||||
|
// means a smaller cross-section, hence a higher printable speed under the volumetric limit; pick the
|
||||||
|
// largest candidate that still reaches the end speed to keep the change from the default minimal.
|
||||||
|
std::vector<double> candidates;
|
||||||
|
for (const auto& preset : preset_bundle->prints.get_presets()) {
|
||||||
|
if (!preset.is_compatible || preset.is_default)
|
||||||
|
continue;
|
||||||
|
const auto* lh_opt = preset.config.option<ConfigOptionFloat>("layer_height");
|
||||||
|
if (lh_opt == nullptr || lh_opt->value <= 0.0)
|
||||||
|
continue;
|
||||||
|
const double lh = lh_opt->value;
|
||||||
|
if ((machine_min_lh > 0.0 && lh < machine_min_lh - 1e-6) ||
|
||||||
|
(machine_max_lh > 0.0 && lh > machine_max_lh + 1e-6))
|
||||||
|
continue;
|
||||||
|
candidates.push_back(lh);
|
||||||
|
}
|
||||||
|
std::sort(candidates.begin(), candidates.end());
|
||||||
|
candidates.erase(std::unique(candidates.begin(), candidates.end(),
|
||||||
|
[](double a, double b) { return std::abs(a - b) < 1e-6; }),
|
||||||
|
candidates.end());
|
||||||
|
|
||||||
|
// Largest candidate <= the default layer height that still reaches the end speed (smallest change).
|
||||||
|
double chosen_lh = 0.0;
|
||||||
|
for (auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
|
||||||
|
if (*it > default_lh + 1e-6)
|
||||||
|
continue; // never increase the layer height above the default
|
||||||
|
if (speed_limit_for_lh(*it) >= m_params.end) { chosen_lh = *it; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chosen_lh > 0.0) {
|
||||||
|
// Reducing the layer height is enough to reach the requested end speed.
|
||||||
|
m_params.vfa_layer_height = chosen_lh;
|
||||||
|
MessageDialog msg_dlg(nullptr,
|
||||||
|
wxString::Format(_L("The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed "
|
||||||
|
"(%.1f mm³/s) at the default layer height (%.2f mm).\n\n"
|
||||||
|
"The layer height has been reduced to %.2f mm (a value used by this printer's "
|
||||||
|
"profiles) so the tower can reach the requested speed."),
|
||||||
|
m_params.end, max_vol_speed, default_lh, chosen_lh),
|
||||||
|
_L("VFA test"), wxICON_INFORMATION | wxOK);
|
||||||
|
msg_dlg.ShowModal();
|
||||||
|
} else if (!candidates.empty()) {
|
||||||
|
// Even the smallest available layer height cannot reach the end speed; propose a lower end speed
|
||||||
|
// based on that layer height, the line width and the maximum volumetric speed.
|
||||||
|
const double min_lh = candidates.front();
|
||||||
|
const double reachable = speed_limit_for_lh(min_lh);
|
||||||
|
double new_end = std::floor(reachable / m_params.step) * m_params.step; // snap down to a step multiple
|
||||||
|
if (new_end < m_params.start + m_params.step)
|
||||||
|
new_end = m_params.start + m_params.step;
|
||||||
|
MessageDialog msg_dlg(nullptr,
|
||||||
|
wxString::Format(_L("Even at the smallest layer height used by this printer's profiles (%.2f mm) the "
|
||||||
|
"end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed "
|
||||||
|
"(%.1f mm³/s).\n\n"
|
||||||
|
"The layer height will be set to %.2f mm and the end speed lowered to %.0f mm/s.\n\n"
|
||||||
|
"Continue?"),
|
||||||
|
min_lh, m_params.end, max_vol_speed, min_lh, new_end),
|
||||||
|
_L("VFA test"), wxICON_WARNING | wxYES_NO | wxNO_DEFAULT);
|
||||||
|
if (msg_dlg.ShowModal() != wxID_YES)
|
||||||
|
return;
|
||||||
|
m_params.end = new_end;
|
||||||
|
m_params.vfa_layer_height = min_lh;
|
||||||
|
} else {
|
||||||
|
// No compatible process profiles to draw layer heights from: warn and let the user decide.
|
||||||
|
if (!confirm_clamp(_L("Continue anyway?")))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Auto-adjust off, or resizing disabled (which forbids changing the layer height): just warn.
|
||||||
|
if (!confirm_clamp(m_params.nozzle_based_resize
|
||||||
|
? _L("Enable \"Auto-adjust\" to fix this automatically, or continue anyway?")
|
||||||
|
: _L("Enable \"Auto-scale for nozzle\" and \"Auto-adjust\" to fix this automatically, or continue anyway?")))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_params.mode = CalibMode::Calib_VFA_Tower;
|
m_params.mode = CalibMode::Calib_VFA_Tower;
|
||||||
m_plater->calib_VFA(m_params);
|
m_plater->calib_VFA(m_params);
|
||||||
EndModal(wxID_OK);
|
EndModal(wxID_OK);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user