Compare commits

..
Author SHA1 Message Date
SoftFever ba5ecdfea9 Cut redundant work and duplication from the preset cache paths
The schema fingerprint, filament library version and cache blob are no
longer recomputed, re-read or copied once per vendor on the startup path,
and the setup wizard's completeness check now sees vendors shipped as
caches alone. Duplicated reset/join/slurp blocks are folded into helpers.
2026-07-29 21:56:17 +08:00
SoftFever e5aa9a8cfc Speed up profile loading with per-vendor preset caches
Each vendor's system presets are serialized into a <vendor>.opc cache
that the app loads instead of parsing the profile JSONs, falling back
to the parse whenever no cache covers what is installed. Shipped builds
carry the caches instead of the raw profiles, installing and updating
treat a vendor's cache as its installation, and the setup wizard loads
through the same path. Per-platform scripts and CI generate the caches
at build time; tests and a design doc cover the format and its
validation.
2026-07-29 21:56:17 +08:00
Kiss Lorand 29d4513694 Fix overlapping brims (#14991) 2026-07-28 17:46:14 -03:00
5ede9711f5 Fix GTK3 dialog min size: SetSizer → SetSizerAndFit for dialogs without explicit SetMinSize (#14948)
* For dialog without explicitly `SetMinSize`, we should use `SetSizerAndFit` instead, otherwise the dialog will not show correctly on GTK3. (OrcaSlicer/OrcaSlicer#14561)
- and if `SetSizer` is called before the full layout has been built, then an extra `SetSizeHints` should be called before layout/fit so the min size can be properly set automatically based on children's min sizes accordingly.

* Fix GTK3 dialog min size: SetSizer → SetSizerAndFit for dialogs without explicit SetMinSize

Replace SetSizer() with SetSizerAndFit() in 11 dialog constructors that
neither call SetMinSize() nor SetSizeHints(), ensuring proper minimum
size propagation from child widgets on GTK3.

SetSizerAndFit internally calls sizer->SetSizeHints(window), which
sets the window's minimum size based on children — the same fix
applied to ProjectDropDialog in 8a7662083e.

Also drop sizer->Fit(this) calls where present, since they only
resize but don't set the min size hint needed by GTK3.

Co-Authored-By: Claude <noreply@anthropic.com>

* Update code style

* Update TroubleshootDialog.hpp

* Fix unsaved preset dialog layout

* Fix MsgDialog layout

* Fix other 3 instances in MsgDialog.cpp

* Fix a few more instances

* Fix printer option dialog too big on Windows

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-07-28 14:32:55 +08:00
76 changed files with 11703 additions and 14657 deletions
+5
View File
@@ -1,2 +1,7 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* 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
-4
View File
@@ -282,10 +282,6 @@ jobs:
sed -i "/name: OrcaSlicer/{n;s|buildsystem: simple|buildsystem: simple\n build-options:\n env:\n git_commit_hash: \"$git_commit_hash\"|}" \
scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
shell: bash
- name: Generate config sources
# Generated on the host: the flatpak build itself runs offline.
run: python3 tools/run_codegen.py
shell: bash
- uses: flatpak/flatpak-github-actions/flatpak-builder@master
with:
bundle: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
+29 -13
View File
@@ -57,19 +57,6 @@ jobs:
useLocalCache: true # <--= Use the local cache (default is 'false').
useCloudCache: true
# run_codegen.py resolves its own toolchain (a pinned protoc plus protobuf/pyyaml in a
# cached virtualenv), so nothing has to be pip-installed around it. That is what makes it
# work on windows-11-arm, where `pip install grpcio-tools` has no wheel and fails building
# the grpcio C extension from source.
- name: Generate config sources
if: runner.os == 'Windows'
run: python tools/run_codegen.py
shell: pwsh
- name: Generate config sources
if: runner.os != 'Windows'
run: python3 tools/run_codegen.py
shell: bash
- name: Install CMake 3.31.x (Windows ARM64)
# windows-11-arm ships CMake 4.x, which removed pre-3.5 policy
# compatibility AND has incomplete ASM_ARMASM linker modules
@@ -175,6 +162,14 @@ jobs:
retention-days: 5
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 }}
if: runner.os == 'macOS' && !inputs.macos-combine-only
working-directory: ${{ github.workspace }}
@@ -403,6 +398,13 @@ jobs:
if ($arch -eq "arm64") { .\build_release_vs.bat slicer arm64 tests } else { .\build_release_vs.bat slicer tests }
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
if: runner.os == 'Windows'
working-directory: ${{ github.workspace }}
@@ -552,6 +554,20 @@ jobs:
retention-days: 5
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)
# 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
+1 -5
View File
@@ -46,11 +46,7 @@ test.js
internal_docs/
*.flatpak
/flatpak-repo/
# Config codegen: descriptor set, generated C++ and the downloaded protoc /
# bootstrap virtualenv. All of it is produced by tools/run_codegen.py.
config.desc
src/slic3r/GUI/generated/
.codegen-tools/
# Python bytecode
__pycache__/
*.pyc
*.opc
+1
View File
@@ -30,6 +30,7 @@ ctest --test-dir ./tests/fff_print
- C++17, selective C++20. PascalCase classes, snake_case functions/variables
- `#pragma once` for headers. Smart pointers and RAII preferred
- 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
-6
View File
@@ -1082,12 +1082,6 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
endfunction()
# Config codegen — generates src/slic3r/GUI/generated/*.cpp from src/PrintConfigs/*.proto.
# Must run before compiling libslic3r (PrintConfig.cpp #includes the generated files).
# The toolchain (protoc, protobuf, pyyaml) is resolved by tools/run_codegen.py itself;
# set PROTOC=<path> to use an installed protoc instead of the pinned download.
include(cmake/modules/ConfigCodegen.cmake)
# libslic3r, OrcaSlicer GUI and the OrcaSlicer executable.
add_subdirectory(deps_src)
add_subdirectory(src)
+2 -5
View File
@@ -556,11 +556,6 @@ if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
BUILD_ARGS+=(-DORCA_UPDATER_SIG_KEY="${ORCA_UPDATER_SIG_KEY}")
fi
echo "Generating config sources from proto..."
# Resolves protoc and its Python packages itself; nothing is installed into the
# system interpreter (distro Pythons refuse that under PEP 668 anyway).
python3 tools/run_codegen.py || { echo "ERROR: config codegen failed"; exit 1; }
print_and_run cmake -S . -B $BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" "${CMAKE_CCACHE_ARGS[@]}" -G "Ninja Multi-Config" \
-DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} \
-DORCA_TOOLS=ON \
@@ -572,6 +567,8 @@ if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target OrcaSlicer
echo "Building 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
fi
if [[ -n "${BUILD_TESTS}" ]] ; then
-5
View File
@@ -240,11 +240,6 @@ function verify_python_runtime() {
}
function build_slicer() {
echo "Generating config sources from proto..."
# Resolves protoc and its Python packages itself (into .codegen-tools/), so nothing
# is installed into the system interpreter.
python3 tools/run_codegen.py || { echo "ERROR: config codegen failed"; exit 1; }
# iterate over two architectures: x86_64 and arm64
for _ARCH in x86_64 arm64; do
# if ARCH is universal or equal to _ARCH
-10
View File
@@ -145,16 +145,6 @@ if "%1"=="deps" goto :done
:slicer
echo "building Orca Slicer..."
cd %WP%
echo "generating config sources from proto..."
REM run_codegen.py resolves protoc and its python packages itself (grpcio-tools has
REM no wheel on ARM64 and fails to build there).
python tools/run_codegen.py
if errorlevel 1 (
echo "ERROR: config codegen failed"
exit /b 1
)
mkdir %build_dir%
cd %build_dir%
-10
View File
@@ -66,16 +66,6 @@ if "%1"=="deps" exit /b 0
:slicer
echo "building Orca Slicer..."
cd %WP%
echo "generating config sources from proto..."
REM run_codegen.py resolves protoc and its python packages itself (grpcio-tools has
REM no wheel on ARM64 and fails to build there).
python tools/run_codegen.py
if errorlevel 1 (
echo "ERROR: config codegen failed"
exit /b 1
)
mkdir %build_dir%
cd %build_dir%
-144
View File
@@ -1,144 +0,0 @@
# OrcaSlicer Config Codegen CMake Module
#
# Generates C++ source files from protobuf schema definitions.
# Generated files live in src/slic3r/GUI/generated/ and are gitignored.
# Run 'python tools/run_codegen.py' to regenerate; it resolves protoc and the Python
# packages it needs itself (see tools/codegen_toolchain.py).
#
# Targets:
# codegen_config - Custom target to regenerate C++ from .proto files
# validate_config - Custom target to validate generated vs original
#
# Usage in parent CMakeLists.txt:
# include(cmake/modules/ConfigCodegen.cmake)
find_program(PROTOC_EXECUTABLE protoc)
find_package(Python3 COMPONENTS Interpreter QUIET)
set(_generated_marker "${CMAKE_SOURCE_DIR}/src/slic3r/GUI/generated/PrintConfigDef_generated.cpp")
# Decide which interpreter can actually run the codegen.
#
# The main CMakeLists forces Python3_EXECUTABLE to the *bundled embed* interpreter (used for
# the in-app Python plugin runtime). That interpreter has neither protoc nor the protobuf /
# pyyaml packages, and for cross-compiled targets it may not even be the host architecture — so
# it cannot run tools/run_codegen.py. Prefer a host interpreter, which tools/run_codegen.py can
# bootstrap the toolchain into.
#
# ORCA_CODEGEN_PYTHON lets a build explicitly point at the interpreter to use.
if(NOT ORCA_CODEGEN_PYTHON)
find_program(_orca_host_python NAMES python3 python)
if(_orca_host_python)
set(ORCA_CODEGEN_PYTHON "${_orca_host_python}")
else()
set(ORCA_CODEGEN_PYTHON "${Python3_EXECUTABLE}")
endif()
endif()
# --check answers "can this interpreter regenerate right now?" (protobuf + pyyaml importable
# and a protoc already resolvable). It never downloads or installs, so wiring up the build-time
# regeneration below never turns a build into a network operation.
set(_codegen_usable FALSE)
if(ORCA_CODEGEN_PYTHON)
execute_process(
COMMAND ${ORCA_CODEGEN_PYTHON} "${CMAKE_SOURCE_DIR}/tools/codegen_toolchain.py" --check
RESULT_VARIABLE _codegen_probe
OUTPUT_QUIET ERROR_QUIET
)
if(_codegen_probe EQUAL 0)
set(_codegen_usable TRUE)
endif()
endif()
# If generated files are missing (fresh clone), run the codegen now at configure time so a plain
# cmake configure + build works without a separate pre-build step. Unlike the build-time
# regeneration above this is allowed to fetch the toolchain — there is nothing to build without it.
if(NOT EXISTS "${_generated_marker}")
set(_codegen_result 1)
if(ORCA_CODEGEN_PYTHON)
message(STATUS "Config codegen: generated files missing — running codegen now...")
execute_process(
COMMAND ${ORCA_CODEGEN_PYTHON} "${CMAKE_SOURCE_DIR}/tools/run_codegen.py" --no-validate
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
RESULT_VARIABLE _codegen_result
)
endif()
if(NOT _codegen_result EQUAL 0)
message(FATAL_ERROR
"Config codegen: generated files are missing and could not be generated.\n"
"Run it manually with a host Python:\n"
" python tools/run_codegen.py\n"
"or pass -DORCA_CODEGEN_PYTHON=<python>. Offline builds can point at an installed\n"
"protoc with PROTOC=<path>.")
endif()
message(STATUS "Config codegen: generated files created successfully")
endif()
set(CONFIG_PROTO_DIR "${CMAKE_SOURCE_DIR}/src/PrintConfigs")
set(CONFIG_CODEGEN_DIR "${CMAKE_SOURCE_DIR}/src/slic3r/GUI/generated")
set(CONFIG_LAYOUT_YAML "${CMAKE_SOURCE_DIR}/src/PrintConfigs/layout.yaml")
set(CONFIG_DESC_FILE "${CMAKE_BINARY_DIR}/config.desc")
set(CODEGEN_TOOL "${CMAKE_SOURCE_DIR}/tools/config_codegen.py")
set(VALIDATE_TOOL "${CMAKE_SOURCE_DIR}/tools/validate_codegen.py")
set(RUN_CODEGEN_TOOL "${CMAKE_SOURCE_DIR}/tools/run_codegen.py")
# Generated output files (TabLayout_generated.cpp is also generated from layout.yaml)
set(CONFIG_GENERATED_SOURCES
"${CONFIG_CODEGEN_DIR}/PrintConfigDef_generated.cpp"
"${CONFIG_CODEGEN_DIR}/Preset_options_generated.cpp"
"${CONFIG_CODEGEN_DIR}/Invalidation_generated.cpp"
"${CONFIG_CODEGEN_DIR}/OptionKeys_generated.cpp"
"${CONFIG_CODEGEN_DIR}/TabLayout_generated.cpp"
)
set(CONFIG_GENERATED_SOURCES "${CONFIG_GENERATED_SOURCES}" CACHE INTERNAL "Generated config cpp files")
# Collect all .proto source files (flat in src/PrintConfigs/, excluding config_metadata.proto)
file(GLOB CONFIG_PROTO_FILES
"${CONFIG_PROTO_DIR}/filament.proto"
"${CONFIG_PROTO_DIR}/print.proto"
"${CONFIG_PROTO_DIR}/printer.proto"
)
set(CONFIG_PROTO_FILES
"${CONFIG_PROTO_DIR}/config_metadata.proto"
${CONFIG_PROTO_FILES}
)
if(_codegen_usable)
# Single command: run_codegen.py resolves protoc itself.
# Proto files → generated .cpp files. Runs automatically when any .proto changes.
add_custom_command(
OUTPUT ${CONFIG_GENERATED_SOURCES}
COMMAND ${ORCA_CODEGEN_PYTHON} ${RUN_CODEGEN_TOOL} --no-validate
DEPENDS ${CONFIG_PROTO_FILES} ${CONFIG_LAYOUT_YAML} ${CODEGEN_TOOL}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Re-generating config C++ from changed .proto files"
VERBATIM
)
# codegen_config is part of ALL — runs before every build, checks if protos changed.
add_custom_target(codegen_config ALL
DEPENDS ${CONFIG_GENERATED_SOURCES}
COMMENT "Config codegen up to date"
)
# Validation target: cmake --build . --target validate_config
add_custom_target(validate_config
COMMAND ${ORCA_CODEGEN_PYTHON} ${VALIDATE_TOOL}
DEPENDS ${CONFIG_GENERATED_SOURCES}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Validating generated config code against PrintConfig.cpp"
VERBATIM
)
message(STATUS "Config codegen: enabled — proto changes auto-regenerate on next build")
else()
# No usable codegen toolchain in this interpreter (e.g. the bundled embed Python in CI).
# The generated files already exist (checked/produced above); use them as-is and provide a
# no-op codegen_config target so dependents that reference it still resolve.
add_custom_target(codegen_config ALL
COMMENT "Config codegen: using pre-generated files (no codegen toolchain in this interpreter)")
add_custom_target(validate_config
COMMENT "Config codegen: validation skipped (no codegen toolchain in this interpreter)")
message(STATUS "Config codegen: no codegen toolchain in '${ORCA_CODEGEN_PYTHON}' — using pre-generated files")
endif()
+218
View File
@@ -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` |
-495
View File
@@ -1,495 +0,0 @@
# PrintConfig Codegen — Design Document
## 1. Problem Statement
Every config setting in OrcaSlicer (e.g. `travel_speed`, `wipe_distance`) is independently maintained as string literals across ~12 locations in the codebase with zero compile-time validation linking them.
A single setting like `wipe_distance` appears in:
| # | Location | What's duplicated |
|---|----------|-------------------|
| 1 | `PrintConfig.cpp` `init_fff_params()` | Key, type, label, tooltip, default, constraints |
| 2 | `PrintConfig.hpp` struct members | Type + member name (must match key string) |
| 3 | `Preset.cpp` option lists | Key string in serialization lists |
| 4 | `PrintConfig.cpp` extruder/filament lists | Key string in 4 sub-lists |
| 5 | `PrintConfig.cpp` variant option sets | Key string in variant sets |
| 6 | `Print.cpp` invalidation chains | `opt_key == "..."` checks |
| 7 | `Tab.cpp` GUI layout | `append_single_option_line("key")` |
| 8 | GUI files (`Field.cpp`, `OptionsGroup.cpp`) | `opt_key == "..."` special-case handling |
| 9 | `PrintConfig.cpp` `handle_legacy()` | Old-to-new key name mapping |
| 10 | `PrintConfig.cpp` `new_def` G-code placeholders | Re-declared type, label, tooltip |
| 11 | `resources/profiles/*.json` | Key strings as JSON keys |
### Consequences
- Adding a new setting requires editing ~12 files manually
- A typo in any one location causes a silent bug (no compile-time validation)
- Print providers cannot add/customize settings without forking the C++ codebase
- No cross-language tooling (Python scripts, web editors) can consume the schema
- No build-time validation of profile JSONs against the canonical option list
---
## 2. Goals
- **Single source of truth** for all setting definitions
- **Compile-time safety** against key mismatches across the codebase
- **Adding a new setting = editing 1 file** (instead of ~12)
- **Enable print providers** to customize settings (defaults, constraints, visibility) without C++ changes
- **Cross-language API generation** (Python, TypeScript, JSON Schema)
- **Build-time validation** of profile JSONs
- **Hidden mode** — setting exists in config/serialization but is not shown in UI
- **Disabled mode** — setting shown in UI but greyed out / non-editable
- **Automated UI layout** — new settings with GUI annotations appear in UI without manual `Tab.cpp` edits
### Non-goals
- Runtime performance changes (slicing engine untouched)
- Changing the `.3mf` wire format (cereal/JSON serialization preserved)
---
## 3. Current Architecture
### 3.1 Type System
`Config.hpp` defines ~15 `ConfigOptionType` variants:
```cpp
coFloat, coInt, coBool, coString, coPercent, coFloatOrPercent,
coPoint, coPoint3, coEnum,
coFloats, coInts, coBools, coStrings, coPercents, coFloatsOrPercents,
coPoints, coEnums
```
Each has a corresponding C++ class (`ConfigOptionFloat`, `ConfigOptionBools`, etc.) with virtual `serialize()`/`deserialize()` methods:
```cpp
class ConfigOption {
virtual ConfigOptionType type() const = 0;
virtual std::string serialize() const = 0;
virtual bool deserialize(const std::string &str, bool append = false) = 0;
virtual ConfigOption* clone() const = 0;
// ...
};
class ConfigOptionFloat : public ConfigOptionSingle<double> {
ConfigOptionType type() const override { return coFloat; }
std::string serialize() const override { /* double -> string */ }
bool deserialize(const std::string &str, bool append) override { /* string -> double */ }
};
```
### 3.2 Definition Layer
`ConfigOptionDef` holds all metadata for one setting:
```
opt_key, type, nullable, default_value,
label, full_label, category, tooltip, sidetext,
mode (Simple/Advanced/Develop),
min, max, max_literal, ratio_over,
gui_type, multiline, full_width, height,
enum_values, enum_labels, enum_keys_map,
aliases, shortcut
```
All ~500 settings are registered in `PrintConfigDef::init_fff_params()` (~6000 lines) into the global `print_config_def` singleton. Each registration block:
```cpp
def = this->add("bridge_flow", coFloat); // register key + type
def->label = L("Bridge flow ratio"); // UI label
def->category = L("Quality"); // tab category
def->tooltip = L("..."); // tooltip
def->min = 0; // constraint
def->max = 2.0; // constraint
def->mode = comAdvanced; // visibility mode
def->set_default_value(new ConfigOptionFloat(1)); // default
```
### 3.3 Storage Layer
Two parallel systems:
**StaticPrintConfig** — compiled-in struct fields via `PRINT_CONFIG_CLASS_DEFINE` macro. Name-to-byte-offset cache. Used in the slicing engine for direct member access (performance-critical).
**DynamicPrintConfig**`std::map<string, ConfigOptionUniquePtr>`. Used in GUI and for diff/apply operations.
Class hierarchy:
```
FullPrintConfig
├── PrintObjectConfig (~120 fields)
├── PrintRegionConfig (~80 fields)
└── PrintConfig
├── MachineEnvelopeConfig (~25 fields)
└── GCodeConfig (~80 fields)
```
### 3.4 Serialization
- **JSON presets**: `ConfigBase::save_to_json()` / `load_from_json()` — iterates option keys, calls `opt->serialize()` to string, writes JSON key-value pairs
- **Binary .3mf**: cereal archives via `load_option_from_archive()` / `save_option_to_archive()`
- Both formats use the same string keys as identifiers
### 3.5 Invalidation
`Print::invalidate_state_by_config_options()` contains large `opt_key == "..."` chains that classify each changed option key into pipeline steps to invalidate:
```
posSlice, posPerimeters, posInfill, posSupportMaterial,
psGCodeExport, psSkirtBrim, psWipeTower
```
### 3.6 GUI Binding
- `Tab.cpp` builds UI via `append_single_option_line("key_name")` — looks up `ConfigOptionDef` from `print_config_def`, auto-creates the appropriate widget
- `ConfigManipulation.cpp` contains `toggle_print_fff_options()` — imperative logic that reads config values and toggles field visibility
## 4. Proposed Solution
### 4.1 Architecture
Protobuf as schema + codegen, NOT a runtime replacement.
```
┌─────────────────────┐ ┌──────────────────────────────┐
│ src/PrintConfigs/ │ codegen │ PrintConfigDef_generated.cpp │ done
│ *.proto │ ──────────────> │ Preset_options_generated.cpp │ done
│ layout.yaml │ │ Invalidation_generated.cpp │ done
│ │ │ OptionKeys_generated.cpp │ done
└─────────────────────┘ │ PrintConfig_generated.hpp │ future
│ TabLayout_generated.cpp │ future
└──────────────────────────────┘
```
### 4.2 Proto Schema Design
#### 4.2.1 Custom Field Options
`src/PrintConfigs/config_metadata.proto` defines custom extensions covering all `ConfigOptionDef` metadata:
```protobuf
syntax = "proto3";
import "google/protobuf/descriptor.proto";
package orca;
enum ConfigMode {
MODE_SIMPLE = 0;
MODE_ADVANCED = 1;
MODE_DEVELOP = 2;
}
enum PresetType {
PRESET_PRINT = 0;
PRESET_FILAMENT = 1;
PRESET_PRINTER = 2;
}
enum InvalidationStep {
STEP_GCODE_EXPORT = 0;
STEP_SKIRT_BRIM = 1;
STEP_WIPE_TOWER = 2;
STEP_SLICE = 3;
STEP_PERIMETERS = 4;
STEP_INFILL = 5;
STEP_SUPPORT = 6;
STEP_NONE = 7;
}
enum OptionListMembership {
LIST_NONE = 0;
LIST_EXTRUDER_OPTION_KEYS = 1;
LIST_FILAMENT_OPTION_KEYS = 2;
LIST_VARIANT_OPTION_KEYS = 3;
}
extend google.protobuf.FieldOptions {
// Display metadata
string label = 50001;
string full_label = 50002;
string tooltip = 50003;
string category = 50004;
string sidetext = 50005;
// Numeric constraints
double min_value = 50006;
double max_value = 50007;
double max_literal = 50008;
// UI behavior
ConfigMode mode = 50009;
string ratio_over = 50010;
bool multiline = 50013;
bool full_width = 50014;
int32 height = 50015;
// Classification
PresetType preset = 50011;
repeated InvalidationStep invalidates = 50012;
repeated OptionListMembership list_membership = 50018;
// Migration
string legacy_name = 50016;
// Nullable support (for ConfigOptionFloatsNullable, etc.)
bool is_nullable = 50017;
// GUI type override (e.g. "i_enum_open", "color", "f_enum_open")
string gui_type = 50019;
string gui_flags = 50020;
// Enum metadata
string enum_keys_map_ref = 50021;
bool no_cli = 50022;
bool readonly = 50023;
// C++ codegen hints
string co_type_hint = 50024;
// Default value — constructor args only (e.g. "1.0", "5000.0, 5000.0")
// Codegen reconstructs full C++ from co_type + this value
string default_value = 50025;
bool has_default = 50028; // proto3 can't distinguish empty string from unset
// Enum values and labels
repeated string enum_value_entries = 50026;
repeated string enum_label_entries = 50027;
}
extend google.protobuf.MessageOptions {
// Virtual preset keys: keys that belong to this preset type in Preset.cpp
// option lists but have no ConfigOptionDef entry (printer identity fields,
// host/connectivity settings, filament retraction overrides, compatibility
// flags, cross-preset keys). The codegen emits these directly into the
// s_Preset_*_options array alongside the field-derived keys.
// To add a virtual key: add one option line here and re-run codegen.
repeated string virtual_preset_keys = 60001;
}
```
#### 4.2.2 Setting Files
Settings are split into three `.proto` files by preset type. Each setting becomes a proto field with annotations:
| File | Contents |
|------|----------|
| `src/PrintConfigs/print.proto` | ~477 print/process settings |
| `src/PrintConfigs/filament.proto` | ~103 filament settings |
| `src/PrintConfigs/printer.proto` | ~42 printer settings |
Each file also carries message-level `virtual_preset_keys` declarations (see §5.2.3).
Example field:
```protobuf
float travel_speed = 42 [
(label) = "Travel",
(tooltip) = "Speed of travel which is faster and without extrusion.",
(sidetext) = "mm/s",
(min_value) = 1,
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINT,
(has_default) = true,
(default_value) = "200",
(invalidates) = STEP_GCODE_EXPORT
];
```
#### 4.2.3 Virtual Preset Keys
The `s_Preset_*_options` vectors in `Preset.cpp` need to include keys beyond those with `ConfigOptionDef` entries — for example, printer identity fields (`printer_technology`, `printable_area`), connectivity settings (`host_type`, `print_host`), filament retraction overrides (`filament_retraction_length`, `filament_z_hop`, …), and cross-preset keys that belong to multiple preset types.
These are declared directly in the `.proto` message body using the `virtual_preset_keys` message option:
```protobuf
message PrinterSettings {
// Virtual keys (not in PrintConfigDef)
option (virtual_preset_keys) = "printer_technology";
option (virtual_preset_keys) = "printable_area";
option (virtual_preset_keys) = "host_type";
option (virtual_preset_keys) = "print_host";
// ... etc
// Cross-preset keys (defined in print.proto, also saved in printer presets)
option (virtual_preset_keys) = "single_extruder_multi_material";
option (virtual_preset_keys) = "wipe_tower_type";
// ... etc
float extruder_clearance_height_to_rod = 1 [ ... ];
// ...
}
```
The codegen reads these and merges them (deduplicated, sorted) with the field-derived keys into the generated `s_Preset_printer_options` vector. No hand-written extender struct in `Preset.cpp` is needed.
#### 4.2.4 Type Mapping
| C++ Type | Proto Representation | Notes |
|---|---|---|
| `ConfigOptionFloat` | `float field = N` | |
| `ConfigOptionInt` | `int32 field = N` | |
| `ConfigOptionBool` | `bool field = N` | |
| `ConfigOptionString` | `string field = N` | |
| `ConfigOptionFloats` | `repeated float field = N` | Per-extruder vectors |
| `ConfigOptionInts` | `repeated int32 field = N` | |
| `ConfigOptionBools` | `repeated bool field = N` | |
| `ConfigOptionStrings` | `repeated string field = N` | |
| `ConfigOptionPercent` | `float field = N` | `(co_type_hint) = "coPercent"` |
| `ConfigOptionPercents` | `repeated float field = N` | `(co_type_hint) = "coPercents"` |
| `ConfigOptionFloatOrPercent` | `FloatOrPercent field = N` | Custom wrapper message |
| `ConfigOptionEnum<T>` | `int32 field = N` | `(co_type_hint) = "coEnum"` + `(enum_keys_map_ref)` |
| `ConfigOptionPoint` | `Point2D field = N` | Custom wrapper message |
| `ConfigOptionFloatsNullable` | `repeated float field = N` | `(is_nullable) = true` |
#### 4.2.5 UI Layout File
`src/PrintConfigs/layout.yaml` declares the UI tab/page/group structure used by `Tab.cpp`. It lists field names in display order under their respective groups. The codegen will eventually use this to generate `TabLayout_generated.cpp` (future phase).
### 4.3 Code Generator Outputs
| Output | Replaces | Status |
|---|---|---|
| `PrintConfigDef_generated.cpp` | `init_fff_params()` body (~6000 lines) | Done |
| `Preset_options_generated.cpp` | `s_Preset_*_options` string vectors | Done |
| `Invalidation_generated.cpp` | `opt_key ==` chains in `Print.cpp` | Done |
| `OptionKeys_generated.cpp` | Extruder/filament key lists | Done |
| `PrintConfig_generated.hpp` | `PRINT_CONFIG_CLASS_DEFINE` macro blocks | Future |
| `TabLayout_generated.cpp` | `append_single_option_line()` calls in `Tab.cpp` | Future |
### 4.4 CMake Integration
```cmake
add_custom_command(
OUTPUT ${GENERATED_SOURCES}
COMMAND ${ORCA_CODEGEN_PYTHON} tools/run_codegen.py --no-validate
DEPENDS src/PrintConfigs/*.proto src/PrintConfigs/layout.yaml tools/config_codegen.py
)
```
Generated files are **not** checked into the repo — `src/slic3r/GUI/generated/` and `config.desc` are gitignored, and every build produces them. Each build script and CI job runs `tools/run_codegen.py` before configuring; a fresh clone that goes straight to `cmake` gets them generated at configure time.
`tools/run_codegen.py` resolves its own toolchain (`tools/codegen_toolchain.py`), so no caller has to install anything:
- **protoc** — `$PROTOC`, then `PATH`, then a cached copy, then a pinned checksum-verified release downloaded into `.codegen-tools/` (also gitignored)
- **protobuf + pyyaml** — the calling interpreter if it has them, otherwise a virtualenv under `.codegen-tools/` that the script re-execs into
Set `PROTOC=<path>` to build offline or against a distro protoc.
### 4.5 Provider Customization
Providers ship an overlay file alongside their existing JSON profiles:
```yaml
# resources/profiles/Creality/settings_overlay.yaml
overrides:
travel_speed:
max_value: 600
default: 300
travel_speed_z:
mode: hidden # not relevant for this printer
firmware_retraction:
mode: disabled # shown but locked — firmware handles this
custom_options:
- key: creality_vibration_compensation
type: bool
label: "Vibration Compensation"
default: true
category: "Quality"
mode: advanced
gui_page: "Quality"
gui_group: "Other"
```
Custom options get field numbers > 1000 to avoid conflicts.
---
## 5. What Changes vs. What Stays
### Changes (generated from proto)
| Artifact | Current | After | Status |
|---|---|---|---|
| `init_fff_params()` body (~6000 lines) | Hand-written C++ | `#include` of generated file | Done |
| `s_Preset_*_options` lists | Hand-written string vectors | Generated from `(preset)` + `virtual_preset_keys` | Done |
| `invalidate_state_by_config_options()` | Hand-written `opt_key ==` chains | Generated map lookup | Done |
| Extruder/filament key lists | Hand-written string vectors | Generated from `(list_membership)` | Done |
| `PRINT_CONFIG_CLASS_DEFINE` blocks in `.hpp` | Hand-written macros | Generated from `.proto` | Future |
| `Tab.cpp` `append_single_option_line()` layout | Hand-written per-setting calls | Generated from `layout.yaml` + `(tab_*)` annotations | Future |
### Stays manual (NOT generated)
| Component | Reason |
|---|---|
| Conditional visibility (`toggle_print_fff_options`) | Complex runtime logic depending on config values; cannot be declaratively expressed |
| Custom GUI rendering (`Field.cpp`, `OptionsGroup.cpp`) | Case-specific widget behavior (color pickers, special enums) |
| `handle_legacy()` | Migration logic; partially automatable via `(legacy_name)` but complex transforms stay manual |
| Enum C++ maps (top of `PrintConfig.cpp`) | Could eventually generate from proto enums |
---
## 6. Developer Workflow
### Adding a new setting
1. Add a field to the appropriate `.proto` file (`print.proto`, `filament.proto`, or `printer.proto`) with all relevant annotations
2. Run `python tools/run_codegen.py`
3. Commit the `.proto` change — the generated files are gitignored build output, never committed
### Adding a virtual preset key
Virtual keys are preset option keys that have no `ConfigOptionDef` (printer identity fields, connectivity settings, etc.) or that exist in one preset type's proto but also need to appear in another preset's options list.
1. Add `option (virtual_preset_keys) = "key_name";` in the appropriate `.proto` message body
2. Run `python tools/run_codegen.py`
### Running the codegen pipeline manually
```bash
# Full pipeline: compile protos → generate C++ → validate
python tools/run_codegen.py
# Validate only (check generated files are up to date)
python tools/run_codegen.py --validate-only
# Inject invalidation/list-membership annotations from Print.cpp / PrintConfig.cpp
python tools/annotate_protos.py [--dry-run]
```
---
## 7. File Layout
```
src/PrintConfigs/
├── config_metadata.proto # Custom field/message option extensions
├── layout.yaml # UI tab/page/group structure (Tab.cpp layout)
├── print.proto # ~477 print/process settings
├── filament.proto # ~103 filament settings
└── printer.proto # ~42 printer/machine settings
tools/
├── run_codegen.py # Full pipeline script — the entry point everything calls
├── codegen_toolchain.py # Resolves protoc / protobuf / pyyaml
├── config_codegen.py # Proto descriptor → C++ codegen
├── config_metadata.py # Reads the orca.* option extensions from the descriptor set
└── validate_codegen.py # Generated vs original validation
src/slic3r/GUI/generated/ # gitignored — regenerated by every build
├── PrintConfigDef_generated.cpp # init_fff_params() body — #included by PrintConfig.cpp
├── Preset_options_generated.cpp # s_Preset_*_options — #included by Preset.cpp
├── Invalidation_generated.cpp # s_print_steps_map + s_object_steps_map — #included by Print.cpp
├── OptionKeys_generated.cpp # s_extruder_option_keys, s_filament_option_keys
└── TabLayout_generated.cpp # Tab page/group layout from layout.yaml
.codegen-tools/ # gitignored — downloaded protoc + bootstrap virtualenv
cmake/modules/
└── ConfigCodegen.cmake # CMake integration (build-time regeneration)
docs/
└── PrintConfig_Codegen_Design.md # This design document
```
+122
View File
@@ -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
+143
View File
@@ -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
@@ -393,9 +393,6 @@ modules:
- type: dir
path: ../../localization
dest: localization
- type: dir
path: ../../tools
dest: tools
- type: file
path: ../../CMakeLists.txt
-174
View File
@@ -1,174 +0,0 @@
// OrcaSlicer Print Config - Custom Field Options
// This file defines the metadata annotations used on all config setting fields.
// These annotations are read by the codegen tool to produce:
// - PrintConfigDef_generated.cpp (init_fff_params replacement)
// - PrintConfig_generated.hpp (struct definitions)
// - Preset_options_generated.cpp (preset option lists)
// - Invalidation_generated.cpp (invalidation map)
// - OptionKeys_generated.cpp (extruder/filament/variant key lists)
syntax = "proto3";
import "google/protobuf/descriptor.proto";
package orca;
// --- Enums for annotations ---
enum ConfigMode {
MODE_SIMPLE = 0;
MODE_ADVANCED = 1;
MODE_DEVELOP = 2;
MODE_EXPERT = 3;
}
enum PresetType {
PRESET_PRINT = 0;
PRESET_FILAMENT = 1;
PRESET_PRINTER = 2;
}
enum InvalidationStep {
STEP_GCODE_EXPORT = 0;
STEP_SKIRT_BRIM = 1;
STEP_WIPE_TOWER = 2;
STEP_SLICE = 3;
STEP_PERIMETERS = 4;
STEP_INFILL = 5;
STEP_SUPPORT = 6;
STEP_NONE = 7;
}
enum OptionListMembership {
LIST_NONE = 0;
LIST_EXTRUDER_OPTION_KEYS = 1;
LIST_FILAMENT_OPTION_KEYS = 2;
LIST_VARIANT_OPTION_KEYS = 3;
}
// C++ co_type override values, used when the proto wire type (float/int32) is
// ambiguous about the real ConfigOption class. Each value name is the coXXX
// identifier the codegen emits, and must have a matching entry in
// hint_class_map (config_codegen.py). Making this an enum lets protoc reject a
// typo'd hint (e.g. coFloar) at compile time instead of silently mis-typing a
// setting. To add a new override: add a value here AND its hint_class_map entry.
enum CoTypeHint {
CO_TYPE_HINT_UNSET = 0;
coPercent = 1;
coPercents = 2;
coEnum = 3;
coEnums = 4;
}
// Mirrors ConfigOptionDef::GUIType in src/libslic3r/Config.hpp. The codegen
// emits ConfigOptionDef::GUIType::<name>, so each value name must match a C++
// enumerator there. As an enum, a typo'd gui_type fails at protoc time.
enum GuiType {
GUI_TYPE_UNSET = 0;
i_enum_open = 1;
f_enum_open = 2;
color = 3;
select_open = 4;
slider = 5;
legend = 6;
one_string = 7;
plugin_picker = 8;
}
// --- Custom field options ---
extend google.protobuf.FieldOptions {
// Display metadata
string label = 50001;
string full_label = 50002;
string tooltip = 50003;
string category = 50004;
string sidetext = 50005;
// Numeric constraints
double min_value = 50006;
double max_value = 50007;
double max_literal = 50008;
// UI behavior
ConfigMode mode = 50009;
string ratio_over = 50010;
bool multiline = 50013;
bool full_width = 50014;
int32 height = 50015;
// Classification
PresetType preset = 50011;
repeated InvalidationStep invalidates = 50012;
repeated OptionListMembership list_membership = 50018;
// Migration
string legacy_name = 50016;
// Nullable support (for ConfigOptionFloatsNullable, etc.)
bool is_nullable = 50017;
// GUI type override (e.g. i_enum_open, color, f_enum_open)
GuiType gui_type = 50019;
string gui_flags = 50020;
// Enum metadata (key map reference for C++ enum binding)
string enum_keys_map_ref = 50021;
// Whether this option should not appear in CLI
bool no_cli = 50022;
// ConfigOptionDef::readonly
bool readonly = 50023;
// Override the auto-detected co_type (e.g. coPercent, coEnum)
// Used when proto type (float, int32) is ambiguous
CoTypeHint co_type_hint = 50024;
// Default value — constructor args only (e.g. "1.0", "5000.0, 5000.0")
// Codegen reconstructs full C++ from co_type + this value
string default_value = 50025;
// Whether a default value is present (needed because proto3 can't
// distinguish "set to empty string" from "not set")
bool has_default = 50028;
// Enum values (string keys for coEnum options)
repeated string enum_value_entries = 50026;
// Enum labels (display names for enum values)
repeated string enum_label_entries = 50027;
// UI layout: where this option appears in Tab.cpp
// tab_type = "Print" | "Filament" | "Printer"
// tab_page = page title as shown in the UI (e.g. "Quality")
// tab_optgroup = optgroup title within that page (e.g. "Layer height")
string tab_type = 50029;
string tab_page = 50030;
string tab_optgroup = 50031;
}
// --- Message-level options ---
extend google.protobuf.MessageOptions {
// Virtual keys: preset option keys that belong to this preset type but have
// no ConfigOptionDef entry (printer metadata, host settings, compatibility
// flags, filament retraction overrides, cross-preset keys, etc.).
// The codegen emits these directly into s_Preset_*_options alongside the
// field-derived keys, so no hand-written extender is needed in Preset.cpp.
repeated string virtual_preset_keys = 60001;
}
// --- Wrapper messages for complex types ---
// Represents ConfigOptionFloatOrPercent
message FloatOrPercent {
double value = 1;
bool percent = 2;
}
// Represents ConfigOptionPoint (Vec2d)
message Point2D {
double x = 1;
double y = 2;
}
File diff suppressed because it is too large Load Diff
-687
View File
@@ -1,687 +0,0 @@
tabs:
- name: TabPrint
pages:
- name: "Quality"
icon: "custom-gcode_quality"
groups:
- name: "Layer height"
fields:
- layer_height
- initial_layer_print_height
- name: "Line width"
fields:
- line_width
- initial_layer_line_width
- outer_wall_line_width
- inner_wall_line_width
- top_surface_line_width
- sparse_infill_line_width
- internal_solid_infill_line_width
- bridge_line_width: quality_settings_line_width#bridge
- support_line_width
- name: "Seam"
fields:
- seam_position
- staggered_inner_seams
- seam_gap
- seam_slope_type
- seam_slope_conditional
- scarf_angle_threshold
- scarf_overhang_threshold
- scarf_joint_speed
- seam_slope_start_height
- seam_slope_entire_loop
- seam_slope_min_length
- seam_slope_steps
- scarf_joint_flow_ratio
- seam_slope_inner_walls
- role_based_wipe_speed
- wipe_speed
- wipe_on_loops
- wipe_before_external_loop
- name: "Precision"
fields:
- slice_closing_radius
- resolution
- enable_arc_fitting
- xy_hole_compensation
- xy_contour_compensation
- elefant_foot_compensation
- elefant_foot_layers_density
- elefant_foot_compensation_layers
- precise_outer_wall
- precise_z_height
- hole_to_polyhole
- hole_to_polyhole_threshold
- hole_to_polyhole_twisted
- hole_to_polyhole_max_edges
- name: "Ironing"
fields:
- ironing_type
- ironing_pattern
- ironing_flow
- ironing_spacing
- ironing_inset
- ironing_angle
- ironing_angle_fixed
- name: "Wall generator"
fields:
- wall_generator
- wall_transition_angle
- wall_transition_filter_deviation
- wall_transition_length
- wall_distribution_count
- initial_layer_min_bead_width
- min_bead_width
- min_feature_size
- min_length_factor
- wall_maximum_resolution
- wall_maximum_deviation
- name: "Walls and surfaces"
fields:
- wall_sequence
- is_infill_first
- wall_direction
- print_flow_ratio
- top_solid_infill_flow_ratio
- bottom_solid_infill_flow_ratio
- set_other_flow_ratios
- first_layer_flow_ratio
- outer_wall_flow_ratio
- inner_wall_flow_ratio
- overhang_flow_ratio
- sparse_infill_flow_ratio
- internal_solid_infill_flow_ratio
- gap_fill_flow_ratio
- support_flow_ratio
- support_interface_flow_ratio
- only_one_wall_first_layer
- only_one_wall_top
- min_width_top_surface
- reduce_crossing_wall
- max_travel_detour_distance
- small_area_infill_flow_compensation
- small_area_infill_flow_compensation_model
- name: "Bridging"
fields:
- bridge_flow
- internal_bridge_flow
- bridge_density
- internal_bridge_density
- thick_bridges
- thick_internal_bridges
- enable_extra_bridge_layer
- dont_filter_internal_bridges
- counterbore_hole_bridging
- name: "Overhangs"
fields:
- detect_overhang_wall
- make_overhang_printable
- make_overhang_printable_angle
- make_overhang_printable_hole_size
- extra_perimeters_on_overhangs
- overhang_reverse
- overhang_reverse_internal_only
- overhang_reverse_threshold
- name: "Strength"
icon: "custom-gcode_strength"
groups:
- name: "Walls"
fields:
- wall_loops
- alternate_extra_wall
- detect_thin_wall
- name: "Top/bottom shells"
fields:
- top_shell_layers
- top_shell_thickness
- top_surface_density
- top_surface_pattern
- top_layer_direction
- top_surface_expansion: strength_settings_top_bottom_shells#surface-expansion
- top_surface_expansion_margin: strength_settings_top_bottom_shells#surface-expansion-margin
- top_surface_expansion_direction: strength_settings_top_bottom_shells#surface-expansion-direction
- bottom_shell_layers
- bottom_shell_thickness
- bottom_surface_density
- bottom_surface_pattern
- bottom_layer_direction
- center_of_surface_pattern: strength_settings_top_bottom_shells#center-surface-pattern-on
- anisotropic_surfaces: strength_settings_top_bottom_shells#anisotropic-surfaces
- top_bottom_infill_wall_overlap
- name: "Infill"
fields:
- sparse_infill_density
- fill_multiline
- sparse_infill_pattern
- infill_direction
- sparse_infill_rotate_template
- skin_infill_density
- skeleton_infill_density
- infill_lock_depth
- skin_infill_depth
- skin_infill_line_width
- skeleton_infill_line_width
- symmetric_infill_y_axis
- infill_shift_step
- lateral_lattice_angle_1
- lateral_lattice_angle_2
- infill_overhang_angle
- infill_anchor_max
- infill_anchor
- internal_solid_infill_pattern
- solid_infill_direction
- solid_infill_rotate_template
- gap_fill_target
- filter_out_gap_fill
- separated_infills: strength_settings_infill#separated-infills
- infill_wall_overlap
- lightning_overhang_angle: strength_settings_patterns#lightning
- lightning_prune_angle: strength_settings_patterns#lightning
- lightning_straightening_angle: strength_settings_patterns#lightning
- name: "Advanced"
fields:
- align_infill_direction_to_model
- extra_solid_infills
- bridge_angle
- internal_bridge_angle
- relative_bridge_angle: strength_settings_advanced#relative-bridge-angle
- minimum_sparse_infill_area
- infill_combination
- infill_combination_max_layer_height
- detect_narrow_internal_solid_infill
- ensure_vertical_shell_thickness
- name: "Speed"
icon: "custom-gcode_speed"
groups:
- name: "First layer speed"
fields:
- initial_layer_speed
- initial_layer_infill_speed
- initial_layer_travel_speed
- slow_down_layers
- name: "Other layers speed"
fields:
- outer_wall_speed
- inner_wall_speed
- small_perimeter_speed
- small_perimeter_threshold
- sparse_infill_speed
- internal_solid_infill_speed
- top_surface_speed
- gap_infill_speed
- ironing_speed
- support_speed
- support_interface_speed
- small_support_perimeter_speed
- small_support_perimeter_threshold
- name: "Overhang speed"
fields:
- enable_overhang_speed
- slowdown_for_curled_perimeters
- [overhang_1_4_speed, overhang_2_4_speed, overhang_3_4_speed, overhang_4_4_speed]
- [bridge_speed, internal_bridge_speed]
- name: "Travel speed"
fields:
- travel_speed
- name: "Acceleration"
fields:
- default_acceleration
- outer_wall_acceleration
- inner_wall_acceleration
- bridge_acceleration
- sparse_infill_acceleration
- internal_solid_infill_acceleration
- initial_layer_acceleration
- initial_layer_travel_acceleration
- top_surface_acceleration
- travel_acceleration
- accel_to_decel_enable
- accel_to_decel_factor
- name: "Jerk(XY)"
fields:
- default_junction_deviation
- default_jerk
- outer_wall_jerk
- inner_wall_jerk
- infill_jerk
- top_surface_jerk
- initial_layer_jerk
- initial_layer_travel_jerk
- travel_jerk
- name: "Advanced"
fields:
- max_volumetric_extrusion_rate_slope
- max_volumetric_extrusion_rate_slope_segment_length
- extrusion_rate_smoothing_external_perimeter_only
- name: "Support"
icon: "custom-gcode_support"
groups:
- name: "Support"
fields:
- enable_support
- support_type
- support_style
- support_threshold_angle
- support_threshold_overlap
- raft_first_layer_density
- raft_first_layer_expansion
- support_on_build_plate_only
- support_critical_regions_only
- support_remove_small_overhang
- name: "Raft"
fields:
- raft_layers
- raft_contact_distance
- name: "Support filament"
fields:
- support_filament
- support_interface_filament
- support_interface_not_for_body
- name: "Support ironing"
fields:
- support_ironing
- support_ironing_pattern
- support_ironing_flow
- support_ironing_spacing
- name: "Advanced"
fields:
- support_top_z_distance
- support_bottom_z_distance
- tree_support_wall_count
- support_base_pattern
- support_base_pattern_spacing
- support_angle
- support_interface_top_layers
- support_interface_bottom_layers
- support_interface_pattern
- support_interface_spacing
- support_bottom_interface_spacing
- support_expansion
- support_object_xy_distance
- support_object_first_layer_gap
- bridge_no_support
- max_bridge_length
- independent_support_layer_height
- name: "Tree supports"
fields:
- tree_support_tip_diameter
- tree_support_branch_distance
- tree_support_branch_distance_organic
- tree_support_top_rate
- tree_support_branch_diameter
- tree_support_branch_diameter_organic
- tree_support_branch_diameter_angle
- tree_support_branch_angle
- tree_support_branch_angle_organic
- tree_support_angle_slow
- tree_support_auto_brim
- tree_support_brim_width
- name: "Multimaterial"
icon: "custom-gcode_multi_material"
groups:
- name: "Prime tower"
fields:
- enable_prime_tower
- prime_tower_skip_points
- enable_tower_interface_features
- enable_tower_interface_cooldown_during_tower
- prime_tower_enable_framework
- prime_tower_width
- prime_volume
- prime_tower_brim_width
- prime_tower_infill_gap
- wipe_tower_rotation_angle
- wipe_tower_bridging
- wipe_tower_extra_spacing
- wipe_tower_extra_flow
- wipe_tower_max_purge_speed
- wipe_tower_wall_type
- wipe_tower_cone_angle
- wipe_tower_extra_rib_length
- wipe_tower_rib_width
- wipe_tower_fillet_wall
- wipe_tower_no_sparse_layers
- single_extruder_multi_material_priming
- name: "Filament for Features"
fields:
- wall_filament
- sparse_infill_filament
- solid_infill_filament
- wipe_tower_filament
- name: "Ooze prevention"
fields:
- ooze_prevention
- standby_temperature_delta
- preheat_time
- preheat_steps
- name: "Flush options"
fields:
- flush_into_infill
- flush_into_objects
- flush_into_support
- name: "Advanced"
fields:
- interlocking_beam
- toolchange_ordering: multimaterial_settings_advanced#toolchange-ordering
- interface_shells
- mmu_segmented_region_max_width
- mmu_segmented_region_interlocking_depth
- interlocking_beam_width
- interlocking_orientation
- interlocking_beam_layer_count
- interlocking_depth
- interlocking_boundary_avoidance
- name: "Others"
icon: "custom-gcode_other"
groups:
- name: "Skirt"
fields:
- skirt_loops
- skirt_type
- min_skirt_length
- skirt_distance
- skirt_start_angle
- skirt_speed
- skirt_height
- draft_shield
- single_loop_draft_shield
- name: "Brim"
fields:
- brim_type
- brim_width
- brim_object_gap
- brim_flow_ratio
- brim_use_efc_outline
- combine_brims
- brim_ears_max_angle
- brim_ears_detection_length
- name: "Special mode"
fields:
- slicing_mode
- print_sequence
- print_order
- spiral_mode
- spiral_mode_smooth
- spiral_mode_max_xy_smoothing
- spiral_starting_flow_ratio
- spiral_finishing_flow_ratio
- timelapse_type
- enable_wrapping_detection
- name: "Fuzzy Skin"
fields:
- fuzzy_skin
- fuzzy_skin_mode
- fuzzy_skin_noise_type
- fuzzy_skin_point_distance
- fuzzy_skin_thickness
- fuzzy_skin_scale
- fuzzy_skin_octaves
- fuzzy_skin_persistence
- fuzzy_skin_first_layer
- name: "G-code output"
fields:
- reduce_infill_retraction
- gcode_add_line_number
- gcode_comments
- gcode_label_objects
- exclude_object
- filename_format
- name: "Change extrusion role G-code" # HOOK
fields:
- process_change_extrusion_role_gcode
- name: "Post-processing Scripts"
fields:
- post_process
- name: "Notes"
fields:
- notes
- name: TabFilament
pages:
- name: "Filament"
icon: "custom-gcode_filament"
groups:
- name: "Basic information" # HOOK
fields:
- filament_type
- filament_vendor
- filament_soluble
- filament_is_support
- filament_change_length
- required_nozzle_HRC
- default_filament_colour
- filament_diameter
- filament_adhesiveness_category
- filament_density
- filament_shrink
- filament_shrinkage_compensation_z
- filament_cost
- temperature_vitrification
- idle_temperature
- [nozzle_temperature_range_low, nozzle_temperature_range_high]
- name: "Flow ratio and Pressure Advance"
fields:
- pellet_flow_coefficient
- filament_flow_ratio
- enable_pressure_advance
- pressure_advance
- adaptive_pressure_advance
- adaptive_pressure_advance_overhangs
- adaptive_pressure_advance_bridges
- adaptive_pressure_advance_model
- name: "Print chamber temperature"
fields:
- activate_chamber_temp_control
- chamber_temperature
- name: "Print temperature"
fields:
- [nozzle_temperature_initial_layer, nozzle_temperature]
- name: "Bed temperature"
fields:
- [supertack_plate_temp_initial_layer, supertack_plate_temp]
- [cool_plate_temp_initial_layer, cool_plate_temp]
- [textured_cool_plate_temp_initial_layer, textured_cool_plate_temp]
- [eng_plate_temp_initial_layer, eng_plate_temp]
- [hot_plate_temp_initial_layer, hot_plate_temp]
- [textured_plate_temp_initial_layer, textured_plate_temp]
- name: "Volumetric speed limitation"
fields:
- filament_adaptive_volumetric_speed
- filament_max_volumetric_speed
- name: "Cooling"
icon: "custom-gcode_cooling_fan"
groups:
- name: "Cooling for specific layer"
fields:
- close_fan_the_first_x_layers
- full_fan_speed_layer
- initial_layer_fan_speed: material_cooling#first-layer-fan-speed
- name: "Part cooling fan"
fields:
- [fan_min_speed, fan_cooling_layer_time]
- [fan_max_speed, slow_down_layer_time]
- reduce_fan_stop_start_freq
- slow_down_for_layer_cooling
- dont_slow_down_outer_wall
- slow_down_min_speed
- enable_overhang_bridge_fan
- overhang_fan_threshold
- overhang_fan_speed
- internal_bridge_fan_speed
- support_material_interface_fan_speed
- ironing_fan_speed
- name: "Auxiliary part cooling fan"
fields:
- additional_cooling_fan_speed
- name: "Exhaust fan"
fields:
- activate_air_filtration
- [activate_air_filtration_during_print, during_print_exhaust_fan_speed]
- [activate_air_filtration_on_completion, complete_print_exhaust_fan_speed]
- name: "Advanced"
icon: "custom-gcode_advanced"
groups:
- name: "Filament start G-code" # HOOK
fields:
- filament_start_gcode
- name: "Change extrusion role G-code" # HOOK
fields:
- filament_change_extrusion_role_gcode
- name: "Filament end G-code" # HOOK
fields:
- filament_end_gcode
- name: "Multimaterial"
icon: "custom-gcode_multi_material"
groups:
- name: "Wipe tower parameters"
fields:
- filament_minimal_purge_on_wipe_tower
- filament_tower_interface_pre_extrusion_dist
- filament_tower_interface_pre_extrusion_length
- filament_tower_ironing_area
- filament_tower_interface_purge_volume
- filament_tower_interface_print_temp
- name: "Multi Filament"
fields:
- long_retractions_when_ec
- retraction_distances_when_ec
- name: "Tool change parameters with single extruder MM printers"
fields:
- filament_loading_speed_start
- filament_loading_speed
- filament_unloading_speed_start
- filament_unloading_speed
- filament_toolchange_delay
- filament_cooling_moves
- filament_cooling_initial_speed
- filament_cooling_final_speed
- filament_stamping_loading_speed
- filament_stamping_distance
- filament_ramming_parameters # HOOK
- name: "Tool change parameters with multi extruder MM printers"
fields:
- filament_multitool_ramming
- filament_multitool_ramming_volume
- filament_multitool_ramming_flow
- name: "Dependencies"
icon: "advanced"
groups:
- name: "Compatible printers"
fields:
- compatible_printers # HOOK
- compatible_printers_condition
- name: "Compatible process profiles"
fields:
- compatible_prints # HOOK
- compatible_prints_condition
- name: "Notes"
icon: "custom-gcode_note"
groups:
- name: "Notes"
fields:
- filament_notes
- name: TabPrinter
pages:
- name: "Basic information"
icon: "custom-gcode_object-info"
groups:
- name: "Printable space"
icon: "param_printable_space"
hook: true
- name: "Advanced"
icon: "param_advanced"
hook: true
- name: "Cooling Fan"
icon: "param_cooling_fan"
hook: true
- name: "Extruder Clearance"
icon: "param_extruder_clearance"
fields:
- extruder_clearance_radius: printer_basic_information_extruder_clearance#radius
- extruder_clearance_height_to_rod: printer_basic_information_extruder_clearance#height-to-rod
- extruder_clearance_height_to_lid: printer_basic_information_extruder_clearance#height-to-lid
- name: "Adaptive bed mesh"
icon: "param_adaptive_mesh"
fields:
- bed_mesh_min: printer_basic_information_adaptive_bed_mesh#bed-mesh
- bed_mesh_max: printer_basic_information_adaptive_bed_mesh#bed-mesh
- bed_mesh_probe_distance: printer_basic_information_adaptive_bed_mesh#probe-point-distance
- adaptive_bed_mesh_margin: printer_basic_information_adaptive_bed_mesh#mesh-margin
- name: "Accessory"
icon: "param_accessory"
fields:
- nozzle_type: printer_basic_information_accessory#nozzle-type
- nozzle_hrc: printer_basic_information_accessory#nozzle-hrc
- auxiliary_fan: printer_basic_information_accessory#auxiliary-part-cooling-fan
- support_chamber_temp_control: printer_basic_information_accessory#support-controlling-chamber-temperature
- support_air_filtration: printer_basic_information_accessory#support-air-filtration
- name: "Machine G-code"
icon: "custom-gcode_gcode"
groups:
- name: "File header G-code"
icon: "param_gcode"
gcode: true
fields:
- file_start_gcode: ""
- name: "Machine start G-code"
icon: "param_gcode"
gcode: true
fields:
- machine_start_gcode: printer_machine_gcode#machine-start-g-code
- name: "Machine end G-code"
icon: "param_gcode"
gcode: true
fields:
- machine_end_gcode: printer_machine_gcode#machine-end-g-code
- name: "Printing by object G-code"
icon: "param_gcode"
gcode: true
fields:
- printing_by_object_gcode: printer_machine_gcode#between-object-g-code
- name: "Before layer change G-code"
icon: "param_gcode"
gcode: true
fields:
- before_layer_change_gcode: printer_machine_gcode#before-layer-change-g-code
- name: "Layer change G-code"
icon: "param_gcode"
gcode: true
fields:
- layer_change_gcode: printer_machine_gcode#after-layer-change-g-code
- name: "Timelapse G-code"
icon: "param_gcode"
gcode: true
fields:
- time_lapse_gcode: printer_machine_gcode#time-lapse-g-code
- name: "Clumping Detection G-code"
icon: "param_gcode"
gcode: true
fields:
- wrapping_detection_gcode: printer_machine_gcode#clumping-detection-g-code
- name: "Change filament G-code"
icon: "param_gcode"
gcode: true
fields:
- change_filament_gcode: printer_machine_gcode#change-filament-g-code
- name: "Change extrusion role G-code"
icon: "param_gcode"
gcode: true
fields:
- change_extrusion_role_gcode: printer_machine_gcode#change-extrusion-role-g-code
- name: "Pause G-code"
icon: "param_gcode"
gcode: true
fields:
- machine_pause_gcode: printer_machine_gcode#pause-g-code
- name: "Template Custom G-code"
icon: "param_gcode"
gcode: true
fields:
- template_custom_gcode: printer_machine_gcode#template-custom-g-code
- name: "Notes"
icon: "custom-gcode_note"
groups:
- name: "Notes"
icon: "note"
fields:
- printer_notes: ""
File diff suppressed because it is too large Load Diff
-967
View File
@@ -1,967 +0,0 @@
// OrcaSlicer - PrinterSettings
// Edit this file and re-run: python tools/run_codegen.py
syntax = "proto3";
import "config_metadata.proto";
package orca;
message PrinterSettings {
// ── Virtual keys: not in PrintConfigDef, but required in printer presets ──
// Printer identity / bed shape (handled by Preset/PrinterConfig directly)
option (virtual_preset_keys) = "bed_custom_model";
option (virtual_preset_keys) = "bed_custom_texture";
option (virtual_preset_keys) = "bed_exclude_area";
option (virtual_preset_keys) = "bbl_use_printhost";
option (virtual_preset_keys) = "default_print_profile";
option (virtual_preset_keys) = "extruder_printable_area";
option (virtual_preset_keys) = "extruder_variant_list";
option (virtual_preset_keys) = "grab_length";
option (virtual_preset_keys) = "head_wrap_detect_zone";
option (virtual_preset_keys) = "inherits";
option (virtual_preset_keys) = "physical_extruder_map";
option (virtual_preset_keys) = "printable_area";
option (virtual_preset_keys) = "printable_height";
option (virtual_preset_keys) = "extruder_printable_height";
option (virtual_preset_keys) = "printer_agent";
option (virtual_preset_keys) = "printer_extruder_id";
option (virtual_preset_keys) = "printer_extruder_variant";
option (virtual_preset_keys) = "printer_model";
option (virtual_preset_keys) = "printer_technology";
option (virtual_preset_keys) = "printer_variant";
option (virtual_preset_keys) = "silent_mode";
option (virtual_preset_keys) = "support_object_skip_flush";
// Print-host / connectivity
option (virtual_preset_keys) = "host_type";
option (virtual_preset_keys) = "print_host";
option (virtual_preset_keys) = "print_host_webui";
option (virtual_preset_keys) = "printhost_apikey";
option (virtual_preset_keys) = "printhost_authorization_type";
option (virtual_preset_keys) = "printhost_cafile";
option (virtual_preset_keys) = "printhost_port";
option (virtual_preset_keys) = "printhost_password";
option (virtual_preset_keys) = "printhost_ssl_ignore_revoke";
option (virtual_preset_keys) = "printhost_user";
// Cross-preset: defined in print.proto but also saved in printer presets
option (virtual_preset_keys) = "bed_temperature_formula";
option (virtual_preset_keys) = "cooling_tube_length";
option (virtual_preset_keys) = "cooling_tube_retraction";
option (virtual_preset_keys) = "default_bed_type";
option (virtual_preset_keys) = "default_nozzle_volume_type";
option (virtual_preset_keys) = "emit_machine_limits_to_gcode";
option (virtual_preset_keys) = "enable_filament_ramming";
option (virtual_preset_keys) = "enable_long_retraction_when_cut";
option (virtual_preset_keys) = "extra_loading_move";
option (virtual_preset_keys) = "extruder_type";
option (virtual_preset_keys) = "high_current_on_filament_swap";
option (virtual_preset_keys) = "long_retractions_when_cut";
option (virtual_preset_keys) = "machine_load_filament_time";
option (virtual_preset_keys) = "machine_tool_change_time";
option (virtual_preset_keys) = "machine_unload_filament_time";
option (virtual_preset_keys) = "manual_filament_change";
option (virtual_preset_keys) = "master_extruder_id";
option (virtual_preset_keys) = "nozzle_flush_dataset";
option (virtual_preset_keys) = "nozzle_height";
option (virtual_preset_keys) = "nozzle_volume";
option (virtual_preset_keys) = "parking_pos_retraction";
option (virtual_preset_keys) = "preferred_orientation";
option (virtual_preset_keys) = "purge_in_prime_tower";
option (virtual_preset_keys) = "retract_lift_enforce";
option (virtual_preset_keys) = "retraction_distances_when_cut";
option (virtual_preset_keys) = "single_extruder_multi_material";
option (virtual_preset_keys) = "thumbnails_format";
option (virtual_preset_keys) = "tool_change_on_wipe_tower";
option (virtual_preset_keys) = "travel_slope";
option (virtual_preset_keys) = "upward_compatible_machine";
option (virtual_preset_keys) = "wipe_tower_type";
option (virtual_preset_keys) = "wrapping_detection_layers";
option (virtual_preset_keys) = "wrapping_exclude_area";
option (virtual_preset_keys) = "z_hop_types";
// Printer keys with hand-written defs in init_common_params (not proto fields);
// declared here so they land in s_Preset_printer_options and vendor machine
// profiles that set them are not stripped on load.
option (virtual_preset_keys) = "use_3mf";
option (virtual_preset_keys) = "support_parallel_printheads";
option (virtual_preset_keys) = "parallel_printheads_count";
option (virtual_preset_keys) = "parallel_printheads_bed_exclude_areas";
float extruder_clearance_height_to_rod = 1 [
(label) = "Height to rod",
(tooltip) = "Distance of the nozzle tip to the lower rod. Used for collision avoidance in by-object printing.",
(category) = "Printer/Basic information",
(sidetext) = "mm",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Extruder Clearance",
(min_value) = 0,
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "40",
(invalidates) = STEP_GCODE_EXPORT
];
float extruder_clearance_height_to_lid = 2 [
(label) = "Height to lid",
(tooltip) = "Distance of the nozzle tip to the lid. Used for collision avoidance in by-object printing.",
(category) = "Printer/Basic information",
(sidetext) = "mm",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Extruder Clearance",
(min_value) = 0,
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "120",
(invalidates) = STEP_GCODE_EXPORT
];
float extruder_clearance_radius = 3 [
(label) = "Radius",
(tooltip) = "Clearance radius around extruder. Used for collision avoidance in by-object printing.",
(category) = "Printer/Basic information",
(sidetext) = "mm",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Extruder Clearance",
(min_value) = 0,
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "40",
(invalidates) = STEP_GCODE_EXPORT
];
Point2D bed_mesh_min = 4 [
(label) = "Bed mesh min",
(tooltip) = "This option sets the min point for the allowed bed mesh area. Due to the probe's XY offset, most printers are unable to probe the entire bed. To ensure the probe point does not go outside the bed area, the minimum and maximum points of the bed mesh should be set appropriately. OrcaSlicer ensures that adaptive_bed_mesh_min/adaptive_bed_mesh_max values do not exceed these min/max points. This information can usually be obtained from your printer manufacturer. The default setting is (-99999, -99999), which means there are no limits, thus allowing probing across the entire bed.",
(category) = "Printer/Basic information",
(sidetext) = "mm",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Adaptive bed mesh",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "Vec2d(-99999, -99999)"
];
Point2D bed_mesh_max = 5 [
(label) = "Bed mesh max",
(tooltip) = "This option sets the max point for the allowed bed mesh area. Due to the probe's XY offset, most printers are unable to probe the entire bed. To ensure the probe point does not go outside the bed area, the minimum and maximum points of the bed mesh should be set appropriately. OrcaSlicer ensures that adaptive_bed_mesh_min/adaptive_bed_mesh_max values do not exceed these min/max points. This information can usually be obtained from your printer manufacturer. The default setting is (99999, 99999), which means there are no limits, thus allowing probing across the entire bed.",
(category) = "Printer/Basic information",
(sidetext) = "mm",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Adaptive bed mesh",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "Vec2d(99999, 99999)"
];
Point2D bed_mesh_probe_distance = 6 [
(label) = "Probe point distance",
(tooltip) = "This option sets the preferred distance between probe points (grid size) for the X and Y directions, with the default being 50mm for both X and Y.",
(category) = "Printer/Basic information",
(sidetext) = "mm",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Adaptive bed mesh",
(min_value) = 0,
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "Vec2d(50, 50)"
];
float adaptive_bed_mesh_margin = 7 [
(label) = "Mesh margin",
(tooltip) = "This option determines the additional distance by which the adaptive bed mesh area should be expanded in the XY directions.",
(category) = "Printer/Basic information",
(sidetext) = "mm",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Adaptive bed mesh",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "0"
];
bool scan_first_layer = 8 [
(label) = "Scan first layer",
(tooltip) = "Enable this to enable the camera on printer to check the quality of first layer.",
(category) = "Printer/Basic information",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Advanced",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false"
];
int32 enable_power_loss_recovery = 9 [
(label) = "Power Loss Recovery",
(tooltip) = "Choose how to control power loss recovery. When set to Printer configuration, the slicer will not emit power loss recovery G-code and will leave the printer's configuration unchanged. Applicable to Bambu Lab or Marlin 2 firmware based printers.",
(category) = "Printer/Basic information",
(enum_keys_map_ref) = "ConfigOptionEnum<PowerLossRecoveryMode>::get_enum_values()",
(co_type_hint) = coEnum,
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Advanced",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "PowerLossRecoveryMode::PrinterConfiguration",
(enum_value_entries) = "printer_configuration",
(enum_value_entries) = "enable",
(enum_value_entries) = "disable",
(enum_label_entries) = "Printer configuration",
(enum_label_entries) = "Enable",
(enum_label_entries) = "Disable"
];
repeated int32 nozzle_type = 10 [
(label) = "Nozzle type",
(tooltip) = "The metallic material of nozzle. This determines the abrasive resistance of nozzle, and what kind of filament can be printed.",
(category) = "Printer/Basic information",
(enum_keys_map_ref) = "ConfigOptionEnum<NozzleType>::get_enum_values()",
(co_type_hint) = coEnums,
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Accessory",
(mode) = MODE_ADVANCED,
(is_nullable) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "ntUndefine",
(enum_value_entries) = "undefine",
(enum_value_entries) = "hardened_steel",
(enum_value_entries) = "stainless_steel",
(enum_value_entries) = "tungsten_carbide",
(enum_value_entries) = "brass",
(enum_label_entries) = "Undefine",
(enum_label_entries) = "Hardened steel",
(enum_label_entries) = "Stainless steel",
(enum_label_entries) = "Tungsten carbide",
(enum_label_entries) = "Brass",
(list_membership) = LIST_EXTRUDER_OPTION_KEYS
];
int32 nozzle_hrc = 11 [
(label) = "Nozzle HRC",
(tooltip) = "The nozzle's hardness. Zero means no checking for nozzle's hardness during slicing.",
(category) = "Printer/Basic information",
(sidetext) = "HRC",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Accessory",
(min_value) = 0,
(max_value) = 500,
(mode) = MODE_DEVELOP,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "0",
(invalidates) = STEP_GCODE_EXPORT
];
int32 printer_structure = 12 [
(label) = "Printer structure",
(tooltip) = "The physical arrangement and components of a printing device.",
(category) = "Printer/Basic information",
(enum_keys_map_ref) = "ConfigOptionEnum<PrinterStructure>::get_enum_values()",
(co_type_hint) = coEnum,
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Advanced",
(mode) = MODE_DEVELOP,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "psUndefine",
(enum_value_entries) = "undefine",
(enum_value_entries) = "corexy",
(enum_value_entries) = "i3",
(enum_value_entries) = "hbot",
(enum_value_entries) = "delta",
(enum_label_entries) = "Undefine",
(enum_label_entries) = "CoreXY",
(enum_label_entries) = "I3",
(enum_label_entries) = "Hbot",
(enum_label_entries) = "Delta"
];
Point2D best_object_pos = 13 [
(label) = "Best object position",
(tooltip) = "Best auto arranging position in range [0,1] w.r.t. bed shape.",
(category) = "Printer/Basic information",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Printable space",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "Vec2d(0.5, 0.5)"
];
bool auxiliary_fan = 14 [
(label) = "Auxiliary part cooling fan",
(tooltip) = "Enable this option if machine has auxiliary part cooling fan. G-code command: M106 P2 S(0-255).",
(category) = "Printer/Basic information",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Accessory",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false"
];
float fan_speedup_time = 15 [
(tooltip) = "Start the fan this number of seconds earlier than its target start time (you can use fractional seconds). It assumes infinite acceleration for this time estimation, and will only take into account G1 and G0 moves (arc fitting is unsupported).\\nIt won't move fan commands from custom G-code (they act as a sort of 'barrier').\\nIt won't move fan commands into the start G-code if the 'only custom start G-code' is activated.\\nUse 0 to deactivate.",
(category) = "Printer/Basic information",
(sidetext) = "s",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Cooling Fan",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "0",
(invalidates) = STEP_GCODE_EXPORT
];
bool fan_speedup_overhangs = 16 [
(label) = "Only overhangs",
(tooltip) = "Will only take into account the delay for the cooling of overhangs.",
(category) = "Printer/Basic information",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Cooling Fan",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "true",
(invalidates) = STEP_GCODE_EXPORT
];
float fan_kickstart = 17 [
(label) = "Fan kick-start time",
(tooltip) = "Emit a max fan speed command for this amount of seconds before reducing to target speed to kick-start the cooling fan.\\nThis is useful for fans where a low PWM/power may be insufficient to get the fan started spinning from a stop, or to get the fan up to speed faster.\\nSet to 0 to deactivate.",
(category) = "Printer/Basic information",
(sidetext) = "s",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Cooling Fan",
(min_value) = 0,
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "0",
(invalidates) = STEP_GCODE_EXPORT
];
int32 part_cooling_fan_min_pwm = 18 [
(label) = "Minimum non-zero part cooling fan speed",
(tooltip) = "Some part-cooling fans cannot start spinning when commanded below a certain PWM duty cycle. When set above 0, any non-zero part-cooling fan command will be raised to at least this percentage so the fan reliably starts. A fan command of 0 (fan off) is always honoured exactly. This clamp is applied after every other fan calculation (first-layer ramp, layer-time interpolation, overhang/bridge/support-interface/ironing overrides), so scaling still operates within the range [this value, 100%].\\nIf your firmware already disables the fan below a threshold (for example Klipper's [fan] off_below: 0.10 shuts the fan off whenever the commanded duty cycle is below 10%), this option and the firmware threshold should ideally be set to the same value. Matching them (e.g. off_below: 0.10 in Klipper and 10% here) guarantees the slicer never emits a non-zero value that the firmware would silently drop, and the fan never receives a value below the one you know it can actually spool at.\\nSet to 0 to deactivate.",
(category) = "Printer/Basic information",
(sidetext) = "%",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Cooling Fan",
(min_value) = 0,
(max_value) = 100,
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "0",
(invalidates) = STEP_GCODE_EXPORT
];
float time_cost = 19 [
(label) = "Time cost",
(tooltip) = "The printer cost per hour.",
(category) = "Printer/Basic information",
(sidetext) = "money/h",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Advanced",
(min_value) = 0,
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "0"
];
bool support_chamber_temp_control = 20 [
(label) = "Support control chamber temperature",
(tooltip) = "This option is enabled if machine support controlling chamber temperature\\nG-code command: M141 S(0-255)",
(category) = "Printer/Basic information",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Accessory",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "true"
];
bool support_air_filtration = 21 [
(label) = "Support air filtration",
(tooltip) = "Enable this if printer support air filtration\\nG-code command: M106 P3 S(0-255)",
(category) = "Printer/Basic information",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Accessory",
(mode) = MODE_DEVELOP,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "true"
];
int32 gcode_flavor = 22 [
(label) = "G-code flavor",
(tooltip) = "What kind of G-code the printer is compatible with.",
(category) = "Printer/Basic information",
(enum_keys_map_ref) = "ConfigOptionEnum<GCodeFlavor>::get_enum_values()",
(co_type_hint) = coEnum,
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Advanced",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "gcfMarlinLegacy",
(enum_value_entries) = "marlin",
(enum_value_entries) = "klipper",
(enum_value_entries) = "reprapfirmware",
(enum_value_entries) = "repetier",
(enum_value_entries) = "marlin2",
(enum_value_entries) = "reprap",
(enum_value_entries) = "teacup",
(enum_value_entries) = "makerware",
(enum_value_entries) = "sailfish",
(enum_value_entries) = "mach3",
(enum_value_entries) = "machinekit",
(enum_value_entries) = "smoothie",
(enum_value_entries) = "no-extrusion",
(enum_label_entries) = "Marlin(legacy)",
(enum_label_entries) = "Klipper",
(enum_label_entries) = "RepRapFirmware",
(enum_label_entries) = "Repetier",
(enum_label_entries) = "Marlin 2",
(enum_label_entries) = "RepRap/Sprinter",
(enum_label_entries) = "Teacup",
(enum_label_entries) = "MakerWare (MakerBot)",
(enum_label_entries) = "Sailfish (MakerBot)",
(enum_label_entries) = "Mach3/LinuxCNC",
(enum_label_entries) = "Machinekit",
(enum_label_entries) = "Smoothie",
(enum_label_entries) = "No extrusion",
(invalidates) = STEP_SKIRT_BRIM,
(invalidates) = STEP_WIPE_TOWER
];
bool pellet_modded_printer = 23 [
(label) = "Pellet Modded Printer",
(tooltip) = "Enable this option if your printer uses pellets instead of filaments.",
(category) = "Printer/Basic information",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Advanced",
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false"
];
bool support_multi_bed_types = 24 [
(label) = "Support multi bed types",
(tooltip) = "Enable this option if you want to use multiple bed types.",
(category) = "Printer/Basic information",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Printable space",
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false",
(invalidates) = STEP_SKIRT_BRIM,
(invalidates) = STEP_WIPE_TOWER
];
bool use_firmware_retraction = 25 [
(label) = "Use firmware retraction",
(tooltip) = "This experimental setting uses G10 and G11 commands to have the firmware handle the retraction. This is only supported in recent Marlin.",
(category) = "Printer/Basic information",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Advanced",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false",
(invalidates) = STEP_GCODE_EXPORT
];
bool disable_m73 = 26 [
(label) = "Disable set remaining print time",
(tooltip) = "Disable generating of the M73: Set remaining print time in the final G-code.",
(category) = "Printer/Basic information",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Advanced",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false",
(invalidates) = STEP_GCODE_EXPORT
];
float z_offset = 27 [
(label) = "Z offset",
(tooltip) = "This value will be added (or subtracted) from all the Z coordinates in the output G-code. It is used to compensate for bad Z endstop position: for example, if your endstop zero actually leaves the nozzle 0.3mm far from the print bed, set this to -0.3 (or fix your endstop).",
(category) = "Printer/Basic information",
(sidetext) = "mm",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Printable space",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "0",
(invalidates) = STEP_SKIRT_BRIM,
(invalidates) = STEP_WIPE_TOWER
];
string thumbnails = 28 [
(label) = "G-code thumbnails",
(tooltip) = "Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the following format: \\\"XxY, XxY, ...\\\"",
(category) = "Printer/Basic information",
(gui_type) = one_string,
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Advanced",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "\"48x48/PNG,300x300/PNG\"",
(invalidates) = STEP_GCODE_EXPORT
];
bool use_relative_e_distances = 29 [
(label) = "Use relative E distances",
(tooltip) = "Relative extrusion is recommended when using \\\"label_objects\\\" option. Some extruders work better with this option unchecked (absolute extrusion mode). Wipe tower is only compatible with relative mode. It is recommended on most printers. Default is checked.",
(category) = "Printer/Basic information",
(tab_type) = "Printer",
(tab_page) = "Basic information",
(tab_optgroup) = "Advanced",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "true",
(invalidates) = STEP_GCODE_EXPORT
];
string before_layer_change_gcode = 30 [
(label) = "Before layer change G-code",
(tooltip) = "This G-code is inserted at every layer change before the Z lift.",
(category) = "Printer/Machine G-code",
(tab_type) = "Printer",
(tab_page) = "Machine G-code",
(tab_optgroup) = "Before layer change G-code",
(height) = 5,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "",
(invalidates) = STEP_GCODE_EXPORT
];
string machine_end_gcode = 31 [
(label) = "End G-code",
(tooltip) = "End G-code when finishing the entire print.",
(category) = "Printer/Machine G-code",
(tab_type) = "Printer",
(tab_page) = "Machine G-code",
(tab_optgroup) = "Machine end G-code",
(height) = 12,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "\"M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n\"",
(invalidates) = STEP_GCODE_EXPORT
];
string printing_by_object_gcode = 32 [
(label) = "Between Object G-code",
(tooltip) = "Insert G-code between objects. This parameter will only come into effect when you print your models object by object.",
(category) = "Printer/Machine G-code",
(tab_type) = "Printer",
(tab_page) = "Machine G-code",
(tab_optgroup) = "Printing by object G-code",
(height) = 12,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "",
(invalidates) = STEP_GCODE_EXPORT
];
string layer_change_gcode = 33 [
(label) = "Layer change G-code",
(tooltip) = "This G-code is inserted at every layer change after the Z lift.",
(category) = "Printer/Machine G-code",
(tab_type) = "Printer",
(tab_page) = "Machine G-code",
(tab_optgroup) = "Layer change G-code",
(height) = 5,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "",
(invalidates) = STEP_GCODE_EXPORT
];
string time_lapse_gcode = 34 [
(label) = "Timelapse G-code",
(category) = "Printer/Machine G-code",
(tab_type) = "Printer",
(tab_page) = "Machine G-code",
(tab_optgroup) = "Timelapse G-code",
(height) = 5,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "",
(invalidates) = STEP_GCODE_EXPORT
];
string wrapping_detection_gcode = 35 [
(label) = "Clumping detection G-code",
(category) = "Printer/Machine G-code",
(tab_type) = "Printer",
(tab_page) = "Machine G-code",
(tab_optgroup) = "Clumping Detection G-code",
(height) = 5,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "\"\"",
(invalidates) = STEP_GCODE_EXPORT
];
string machine_pause_gcode = 36 [
(label) = "Pause G-code",
(tooltip) = "This G-code will be used as a code for the pause print. Users can insert pause G-code in the G-code viewer.",
(category) = "Printer/Machine G-code",
(tab_type) = "Printer",
(tab_page) = "Machine G-code",
(tab_optgroup) = "Pause G-code",
(height) = 12,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = ""
];
string template_custom_gcode = 37 [
(label) = "Custom G-code",
(tooltip) = "This G-code will be used as a custom code.",
(category) = "Printer/Machine G-code",
(tab_type) = "Printer",
(tab_page) = "Machine G-code",
(tab_optgroup) = "Template Custom G-code",
(height) = 12,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = ""
];
string file_start_gcode = 38 [
(label) = "File header G-code",
(tooltip) = "G-code written at the very top of the output file, before any other content. Useful for adding metadata that printer firmware reads from the first lines of the file (e.g. estimated print time, filament usage). Supports placeholders like {print_time_sec} and {used_filament_length}.",
(category) = "Printer/Machine G-code",
(tab_type) = "Printer",
(tab_page) = "Machine G-code",
(tab_optgroup) = "File header G-code",
(height) = 8,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "\"\""
];
string machine_start_gcode = 39 [
(label) = "Start G-code",
(tooltip) = "Start G-code when starting the entire print.",
(category) = "Printer/Machine G-code",
(tab_type) = "Printer",
(tab_page) = "Machine G-code",
(tab_optgroup) = "Machine start G-code",
(height) = 12,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "\"G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n\"",
(invalidates) = STEP_GCODE_EXPORT
];
string change_filament_gcode = 40 [
(label) = "Change filament G-code",
(tooltip) = "This G-code is inserted when filament is changed, including T commands to trigger tool change.",
(category) = "Printer/Machine G-code",
(tab_type) = "Printer",
(tab_page) = "Machine G-code",
(tab_optgroup) = "Change filament G-code",
(height) = 5,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "",
(invalidates) = STEP_GCODE_EXPORT
];
string change_extrusion_role_gcode = 41 [
(label) = "Change extrusion role G-code",
(tooltip) = "This G-code is inserted when the extrusion role is changed.",
(category) = "Printer/Machine G-code",
(tab_type) = "Printer",
(tab_page) = "Machine G-code",
(tab_optgroup) = "Change extrusion role G-code",
(height) = 5,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = ""
];
string printer_notes = 42 [
(label) = "Printer notes",
(tooltip) = "You can put your notes regarding the printer here.",
(category) = "Printer/Notes",
(tab_type) = "Printer",
(tab_page) = "Notes",
(tab_optgroup) = "Notes",
(height) = 13,
(mode) = MODE_ADVANCED,
(multiline) = true,
(full_width) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "",
(invalidates) = STEP_GCODE_EXPORT
];
bool cooling_filter_enabled = 43 [
(label) = "Use cooling filter",
(tooltip) = "Enable this if printer support cooling filter",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false"
];
repeated float deretract_speed_extruder_change = 44 [
(label) = "Deretraction speed (extruder change)",
(tooltip) = "Speed for reloading filament into the nozzle when switching extruder.",
(sidetext) = "mm/s",
(is_nullable) = true,
(mode) = MODE_DEVELOP,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "0."
];
bool enable_pre_heating = 45 [
(label) = "enable_pre_heating",
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false"
];
repeated int32 extruder_max_nozzle_count = 46 [
(label) = "extruder_max_nozzle_count",
(is_nullable) = true,
(mode) = MODE_DEVELOP,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "1"
];
repeated int32 extruder_nozzle_count = 47 [
(label) = "extruder nozzle count",
(tooltip) = "extruder nozzle count",
(mode) = MODE_DEVELOP,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "1"
];
repeated string extruder_nozzle_stats = 48 [
(label) = "Extruder nozzle stats",
(tooltip) = "Physical nozzle counts per extruder, keyed by nozzle volume type.",
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = ""
];
repeated int32 extruder_nozzle_volume_type = 49 [
(label) = "Extruder nozzle volume type",
(tooltip) = "Nozzle volume type per physical nozzle of an extruder.",
(co_type_hint) = coEnums,
(enum_keys_map_ref) = "ConfigOptionEnum<NozzleVolumeType>::get_enum_values()",
(mode) = MODE_DEVELOP,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "NozzleVolumeType::nvtStandard",
(enum_value_entries) = "Standard",
(enum_value_entries) = "High Flow",
(enum_value_entries) = "TPU High Flow",
(enum_label_entries) = "Standard",
(enum_label_entries) = "High Flow",
(enum_label_entries) = "TPU High Flow"
];
int32 fan_direction = 50 [
(label) = "Fan direction",
(tooltip) = "Cooling fan direction of the printer",
(co_type_hint) = coEnum,
(enum_keys_map_ref) = "ConfigOptionEnum<FanDirection>::get_enum_values()",
(mode) = MODE_DEVELOP,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "fdUndefine",
(enum_value_entries) = "undefine",
(enum_value_entries) = "left",
(enum_value_entries) = "right",
(enum_value_entries) = "both",
(enum_label_entries) = "Undefined",
(enum_label_entries) = "Left",
(enum_label_entries) = "Right",
(enum_label_entries) = "Both"
];
bool farthest_point_timelapse = 51 [
(label) = "Farthest point timelapse",
(tooltip) = "When enabled, the timelapse snapshot is taken at the farthest point from camera instead of traveling to the wipe tower or excess chute. Only effective in traditional timelapse mode on non-I3 printers.",
(mode) = MODE_SIMPLE,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false"
];
bool group_algo_with_time = 52 [
(label) = "group_algo_with_time",
(mode) = MODE_DEVELOP,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false"
];
bool handle_hotend_as_extruder = 53 [
(label) = "handle_hotend_as_extruder",
(mode) = MODE_DEVELOP,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false"
];
repeated float hotend_cooling_rate = 54 [
(label) = "hotend_cooling_rate",
(is_nullable) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "2"
];
repeated float hotend_heating_rate = 55 [
(label) = "hotend_heating_rate",
(is_nullable) = true,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "2"
];
float machine_hotend_change_time = 56 [
(label) = "Hotend change time",
(tooltip) = "Time to change hotend.",
(sidetext) = "s",
(min_value) = 0,
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "0.0"
];
float machine_prepare_compensation_time = 57 [
(label) = "machine_prepare_compensation_time",
(mode) = MODE_DEVELOP,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "260"
];
bool support_cooling_filter = 58 [
(label) = "support_cooling_filter",
(mode) = MODE_ADVANCED,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false"
];
bool support_fast_purge_mode = 59 [
(label) = "Support fast purge mode",
(tooltip) = "Whether this printer supports fast purge mode with optimized temperature and multiplier.",
(mode) = MODE_DEVELOP,
(preset) = PRESET_PRINTER,
(has_default) = true,
(default_value) = "false"
];
}
+10
View File
@@ -20,6 +20,16 @@ if (SLIC3R_ENC_CHECK)
)
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
# using the above encoding-check binary
+84
View File
@@ -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
View File
@@ -32,15 +32,13 @@ static void append_and_translate(ExPolygons &dst, const ExPolygons &src, const P
for (; dst_idx < dst.size(); ++dst_idx)
dst[dst_idx].translate(instance_shift);
}
// BBS: generate brim area by objs
static void append_and_translate(ExPolygons& dst, const ExPolygons& src,
const PrintInstance& instance, size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
// Orca: Translate the brim area into print coordinates and store it per instance.
static void append_and_translate(const ExPolygons& src, const PrintInstance& instance,
size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
ExPolygons srcShifted = src;
Point instance_shift = instance.shift_without_plate_offset();
for (size_t src_idx = 0; src_idx < srcShifted.size(); ++src_idx)
srcShifted[src_idx].translate(instance_shift);
srcShifted = diff_ex(srcShifted, dst);
//expolygons_append(dst, temp2);
for (ExPolygon& expoly : srcShifted)
expoly.translate(instance_shift);
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) {
const PrintInstance& instance = object->instances()[instance_idx];
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(holes, holes_object, 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,
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
for (const ObjectID printObjID : print.print_object_ids()) {
BoundingBox bbx;
-15
View File
@@ -493,21 +493,6 @@ add_library(libslic3r STATIC ${lisbslic3r_sources}
${OpenVDBUtils_SOURCES})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${lisbslic3r_sources})
# Ensure codegen runs before compiling libslic3r (PrintConfig.cpp, Preset.cpp, Print.cpp
# all #include generated files — MSVC tracks the #include deps automatically after first build,
# but this dependency ensures the generated files exist and are up-to-date before compilation).
if(TARGET codegen_config)
add_dependencies(libslic3r codegen_config)
# Tell cmake that the three source files that #include generated code depend on them,
# so an incremental build recompiles them when protos change.
set_source_files_properties(
"${CMAKE_CURRENT_SOURCE_DIR}/PrintConfig.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Preset.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Print.cpp"
PROPERTIES OBJECT_DEPENDS "${CONFIG_GENERATED_SOURCES}"
)
endif()
if (SLIC3R_STATIC)
set(CGAL_Boost_USE_STATIC_LIBS ON CACHE BOOL "" FORCE)
endif ()
+3
View File
@@ -27,6 +27,9 @@
#include <cereal/access.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 {
struct FloatOrPercent
+424 -1
View File
@@ -147,6 +147,17 @@ Semver get_version_from_json(std::string file_path)
return Semver();
//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
@@ -1002,8 +1013,388 @@ bool Preset::has_cali_lines(PresetBundle* preset_bundle)
return false;
}
#include "../slic3r/GUI/generated/Preset_options_generated.cpp"
static std::vector<std::string> s_Preset_print_options{
"layer_height",
"initial_layer_print_height",
"wall_loops",
"alternate_extra_wall",
"slice_closing_radius",
"spiral_mode",
"spiral_mode_smooth",
"spiral_mode_max_xy_smoothing",
"spiral_starting_flow_ratio",
"spiral_finishing_flow_ratio",
"slicing_mode",
"top_shell_layers",
"top_shell_thickness",
"top_surface_density",
"bottom_surface_density",
"bottom_shell_layers",
"bottom_shell_thickness",
"extra_perimeters_on_overhangs",
"ensure_vertical_shell_thickness",
"reduce_crossing_wall",
"detect_thin_wall",
"detect_overhang_wall",
"overhang_reverse",
"overhang_reverse_threshold",
"overhang_reverse_internal_only",
"wall_direction",
"seam_position",
"staggered_inner_seams",
"wall_sequence",
"is_infill_first",
"sparse_infill_density",
"fill_multiline",
"gyroid_optimized",
"sparse_infill_pattern",
"lateral_lattice_angle_1",
"lateral_lattice_angle_2",
"infill_overhang_angle",
"lightning_overhang_angle",
"lightning_prune_angle",
"lightning_straightening_angle",
"top_surface_pattern",
"top_surface_expansion",
"top_surface_expansion_margin",
"top_surface_expansion_direction",
"bottom_surface_pattern",
"top_surface_fill_order",
"bottom_surface_fill_order",
"infill_direction",
"solid_infill_direction",
"top_layer_direction",
"bottom_layer_direction",
"counterbore_hole_bridging",
"infill_shift_step",
"sparse_infill_rotate_template",
"solid_infill_rotate_template",
"symmetric_infill_y_axis",
"skeleton_infill_density",
"infill_lock_depth",
"skin_infill_depth",
"skin_infill_density",
"align_infill_direction_to_model",
"extra_solid_infills",
"center_of_surface_pattern",
"separated_infills",
"minimum_sparse_infill_area",
"reduce_infill_retraction",
"internal_solid_infill_pattern",
"gap_fill_target",
"ironing_type",
"ironing_pattern",
"ironing_flow",
"ironing_speed",
"ironing_spacing",
"ironing_angle",
"ironing_angle_fixed",
"ironing_inset",
"support_ironing",
"support_ironing_pattern",
"support_ironing_flow",
"support_ironing_spacing",
"max_travel_detour_distance",
"fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_distance", "fuzzy_skin_first_layer", "fuzzy_skin_noise_type", "fuzzy_skin_mode", "fuzzy_skin_scale", "fuzzy_skin_octaves", "fuzzy_skin_persistence", "fuzzy_skin_ripples_per_layer", "fuzzy_skin_ripple_offset", "fuzzy_skin_layers_between_ripple_offset",
"max_volumetric_extrusion_rate_slope", "max_volumetric_extrusion_rate_slope_segment_length","extrusion_rate_smoothing_external_perimeter_only",
"inner_wall_speed", "outer_wall_speed", "sparse_infill_speed", "internal_solid_infill_speed",
"top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed",
"bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed",
"outer_wall_acceleration", "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_height","single_loop_draft_shield", "draft_shield",
"brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
"support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style",
// BBS
"print_extruder_id",
"print_extruder_variant",
"independent_support_layer_height",
"support_angle",
"support_interface_top_layers",
"support_interface_bottom_layers",
"support_interface_pattern",
"support_interface_spacing",
"support_interface_loop_pattern",
"support_top_z_distance",
"support_on_build_plate_only",
"support_critical_regions_only",
"bridge_no_support",
"thick_bridges",
"thick_internal_bridges",
"dont_filter_internal_bridges",
"enable_extra_bridge_layer",
"max_bridge_length",
"print_sequence",
"print_order",
"support_remove_small_overhang",
"filename_format",
"outer_wall_filament_id",
"inner_wall_filament_id",
"support_bottom_z_distance",
"sparse_infill_filament_id",
"internal_solid_filament_id",
"top_surface_filament_id",
"bottom_surface_filament_id",
"support_filament",
"support_interface_filament",
"support_interface_not_for_body",
"ooze_prevention",
"standby_temperature_delta",
"preheat_time",
"preheat_steps",
"interface_shells",
"line_width",
"initial_layer_line_width",
"inner_wall_line_width",
"outer_wall_line_width",
"sparse_infill_line_width",
"internal_solid_infill_line_width",
"skin_infill_line_width",
"skeleton_infill_line_width",
"top_surface_line_width",
"support_line_width",
"infill_wall_overlap",
"top_bottom_infill_wall_overlap",
"bridge_flow",
"bridge_line_width",
"internal_bridge_flow",
"elefant_foot_compensation",
"elefant_foot_compensation_layers",
"elefant_foot_layers_density",
"xy_contour_compensation",
"xy_hole_compensation",
"resolution",
"enable_prime_tower",
"prime_tower_enable_framework",
"prime_tower_width",
"prime_tower_brim_width",
"prime_tower_skip_points",
"prime_volume",
"prime_tower_infill_gap",
"prime_tower_flat_ironing",
"enable_tower_interface_features",
"enable_tower_interface_cooldown_during_tower",
"wipe_tower_no_sparse_layers",
"compatible_printers",
"compatible_printers_condition",
"inherits",
"flush_into_infill",
"flush_into_objects",
"flush_into_support",
"tree_support_branch_angle",
"tree_support_angle_slow",
"tree_support_wall_count",
"tree_support_top_rate",
"tree_support_branch_distance",
"tree_support_tip_diameter",
"tree_support_branch_diameter",
"tree_support_branch_diameter_angle",
"detect_narrow_internal_solid_infill",
"gcode_add_line_number",
"enable_arc_fitting",
"precise_z_height",
"infill_combination",
"infill_combination_max_layer_height", /*"adaptive_layer_height",*/
"support_bottom_interface_spacing",
"enable_overhang_speed",
"slowdown_for_curled_perimeters",
"overhang_1_4_speed",
"overhang_2_4_speed",
"overhang_3_4_speed",
"overhang_4_4_speed",
"initial_layer_infill_speed",
"only_one_wall_top",
"timelapse_type",
"wall_generator",
"wall_transition_length",
"wall_transition_filter_deviation",
"wall_transition_angle",
"wall_distribution_count",
"min_feature_size",
"min_bead_width",
"post_process",
"slicing_pipeline_plugin",
"plugins",
"print_plugin_config_overrides",
"process_change_extrusion_role_gcode",
"min_length_factor",
"wall_maximum_resolution",
"wall_maximum_deviation",
"small_perimeter_speed",
"small_perimeter_threshold",
"small_support_perimeter_speed",
"small_support_perimeter_threshold",
"bridge_angle",
"internal_bridge_angle",
"relative_bridge_angle",
"filter_out_gap_fill",
"travel_acceleration",
"inner_wall_acceleration",
"min_width_top_surface",
"default_jerk",
"outer_wall_jerk",
"inner_wall_jerk",
"infill_jerk",
"top_surface_jerk",
"initial_layer_jerk",
"travel_jerk",
"default_junction_deviation",
"top_solid_infill_flow_ratio",
"bottom_solid_infill_flow_ratio",
"only_one_wall_first_layer",
"print_flow_ratio",
"seam_gap",
"set_other_flow_ratios",
"first_layer_flow_ratio",
"outer_wall_flow_ratio",
"inner_wall_flow_ratio",
"overhang_flow_ratio",
"sparse_infill_flow_ratio",
"internal_solid_infill_flow_ratio",
"gap_fill_flow_ratio",
"support_flow_ratio",
"support_interface_flow_ratio",
"role_based_wipe_speed",
"wipe_speed",
"accel_to_decel_enable",
"accel_to_decel_factor",
"wipe_on_loops",
"wipe_before_external_loop",
"bridge_density",
"internal_bridge_density",
"precise_outer_wall",
"bridge_acceleration",
"sparse_infill_acceleration",
"internal_solid_infill_acceleration",
"tree_support_auto_brim",
"tree_support_brim_width",
"gcode_comments",
"gcode_label_objects",
"initial_layer_travel_speed",
"initial_layer_travel_acceleration",
"initial_layer_travel_jerk",
"exclude_object",
"slow_down_layers",
"infill_anchor",
"infill_anchor_max",
"initial_layer_min_bead_width",
"make_overhang_printable",
"make_overhang_printable_angle",
"make_overhang_printable_hole_size",
"notes",
"wipe_tower_cone_angle",
"wipe_tower_extra_spacing",
"wipe_tower_max_purge_speed",
"wipe_tower_wall_type",
"wipe_tower_extra_rib_length",
"wipe_tower_rib_width",
"wipe_tower_fillet_wall",
"wipe_tower_filament",
"wiping_volumes_extruders",
"wipe_tower_bridging",
"wipe_tower_extra_flow",
"single_extruder_multi_material_priming",
"toolchange_ordering",
"wipe_tower_rotation_angle",
"tree_support_branch_distance_organic",
"tree_support_branch_diameter_organic",
"tree_support_branch_angle_organic",
"hole_to_polyhole",
"hole_to_polyhole_threshold",
"hole_to_polyhole_twisted",
"hole_to_polyhole_max_edges",
"mmu_segmented_region_max_width",
"mmu_segmented_region_interlocking_depth",
"small_area_infill_flow_compensation",
"small_area_infill_flow_compensation_model",
"enable_wrapping_detection",
"seam_slope_type",
"seam_slope_conditional",
"scarf_angle_threshold",
"scarf_joint_speed",
"scarf_joint_flow_ratio",
"seam_slope_start_height",
"seam_slope_entire_loop",
"seam_slope_min_length",
"seam_slope_steps",
"seam_slope_inner_walls",
"scarf_overhang_threshold",
"interlocking_beam",
"interlocking_orientation",
"interlocking_beam_layer_count",
"interlocking_depth",
"interlocking_boundary_avoidance",
"interlocking_beam_width",
"calib_flowrate_topinfill_special_order",
// Z Anti-Aliasing (ZAA)
"zaa_enabled",
"zaa_minimize_perimeter_height",
"zaa_dont_alternate_fill_direction",
"zaa_min_z",
"ironing_expansion",
};
static std::vector<std::string> s_Preset_filament_options {/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "pellet_flow_coefficient", "volumetric_speed_coefficients", "filament_type",
"filament_soluble", "filament_is_support", "filament_printable", "filament_extruder_compatibility",
"filament_max_volumetric_speed", "filament_adaptive_volumetric_speed",
"filament_flow_ratio", "filament_density", "filament_adhesiveness_category", "filament_cost", "filament_minimal_purge_on_wipe_tower",
"filament_tower_interface_pre_extrusion_dist", "filament_tower_interface_pre_extrusion_length", "filament_tower_ironing_area", "filament_tower_interface_purge_volume",
"filament_tower_interface_print_temp",
"nozzle_temperature", "nozzle_temperature_initial_layer",
// BBS
"cool_plate_temp", "textured_cool_plate_temp", "eng_plate_temp", "hot_plate_temp", "textured_plate_temp", "cool_plate_temp_initial_layer", "textured_cool_plate_temp_initial_layer", "eng_plate_temp_initial_layer", "hot_plate_temp_initial_layer", "textured_plate_temp_initial_layer", "supertack_plate_temp_initial_layer", "supertack_plate_temp",
// "bed_type",
//BBS:temperature_vitrification
"temperature_vitrification", "reduce_fan_stop_start_freq","dont_slow_down_outer_wall", "slow_down_for_layer_cooling", "fan_min_speed",
"fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "close_additional_fan_first_x_layers", "first_x_layer_fan_speed", "full_fan_speed_layer", "initial_layer_fan_speed", "additional_fan_full_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed",
"filament_start_gcode", "filament_end_gcode", "filament_change_extrusion_role_gcode",
//exhaust fan control
"activate_air_filtration","activate_air_filtration_during_print","activate_air_filtration_on_completion","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed",
// Retract overrides
"filament_deretraction_speed",
"filament_retract_after_wipe", // Orca
"filament_retract_before_wipe",
"filament_retract_lift_above",
"filament_retract_lift_below",
"filament_retract_lift_enforce",
"filament_retract_restart_extra",
"filament_retract_when_changing_layer",
"filament_retraction_length",
"filament_retraction_minimum_travel",
"filament_retraction_speed",
"filament_wipe",
"filament_z_hop",
"filament_z_hop_types",
// Profile compatibility
"filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits",
//BBS
"filament_wipe_distance", "additional_cooling_fan_speed",
"nozzle_temperature_range_low", "nozzle_temperature_range_high",
"filament_extruder_variant",
//SoftFever
"enable_pressure_advance", "pressure_advance","adaptive_pressure_advance","adaptive_pressure_advance_model","adaptive_pressure_advance_overhangs", "adaptive_pressure_advance_bridges","chamber_temperature", "filament_shrink","filament_shrinkage_compensation_z", "support_material_interface_fan_speed","internal_bridge_fan_speed", "filament_notes" /*,"filament_seam_gap"*/,
"ironing_fan_speed",
// Filament ironing overrides
"filament_ironing_flow", "filament_ironing_spacing", "filament_ironing_inset", "filament_ironing_speed",
"filament_loading_speed", "filament_loading_speed_start",
"filament_unloading_speed", "filament_unloading_speed_start", "filament_toolchange_delay", "filament_cooling_moves", "filament_stamping_loading_speed", "filament_stamping_distance",
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters",
"filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", "activate_chamber_temp_control", "chamber_minimal_temperature",
"filament_long_retractions_when_cut","filament_retraction_distances_when_cut", "idle_temperature",
//BBS filament change length while the extruder color
"filament_change_length","filament_flush_volumetric_speed","filament_flush_temp","filament_flush_temp_fast", "filament_cooling_before_tower",
// Multi-nozzle pre-cooling / ramming / nozzle-change (nc) filament overrides
"filament_ramming_volumetric_speed", "filament_ramming_volumetric_speed_nc",
"filament_ramming_travel_time", "filament_ramming_travel_time_nc",
"filament_pre_cooling_temperature", "filament_pre_cooling_temperature_nc",
"filament_preheat_temperature_delta", "filament_retract_length_nc",
"filament_change_length_nc", "filament_prime_volume", "filament_prime_volume_nc",
"long_retractions_when_ec", "retraction_distances_when_ec",
"filament_plugin_config_overrides",
//ams chamber
"filament_dev_ams_drying_ams_limitations", "filament_dev_ams_drying_temperature", "filament_dev_ams_drying_time", "filament_dev_ams_drying_heat_distortion_temperature",
"filament_dev_chamber_drying_bed_temperature", "filament_dev_chamber_drying_time",
"filament_dev_drying_softening_temperature", "filament_dev_drying_cooling_temperature"
};
static std::vector<std::string> s_Preset_machine_limits_options {
"machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_travel",
@@ -1020,6 +1411,38 @@ static std::vector<std::string> s_Preset_machine_limits_options {
"input_shaping_emit", "input_shaping_type", "input_shaping_freq_x", "input_shaping_freq_y", "input_shaping_damp_x", "input_shaping_damp_y",
};
static std::vector<std::string> s_Preset_printer_options {
"printer_technology",
"printable_area", "extruder_printable_area", "support_parallel_printheads", "parallel_printheads_count", "parallel_printheads_bed_exclude_areas", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor",
"fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
"printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod",
"nozzle_height", "master_extruder_id",
"default_print_profile", "inherits",
"silent_mode",
"scan_first_layer", "enable_power_loss_recovery", "wrapping_detection_layers", "wrapping_exclude_area", "machine_load_filament_time", "machine_unload_filament_time", "machine_tool_change_time", "time_cost", "machine_pause_gcode", "template_custom_gcode",
"nozzle_type", "nozzle_hrc","auxiliary_fan", "fan_direction", "nozzle_volume","upward_compatible_machine", "z_hop_types", "travel_slope", "retract_lift_enforce","support_chamber_temp_control","support_air_filtration","support_cooling_filter","cooling_filter_enabled","printer_structure","farthest_point_timelapse",
"best_object_pos", "head_wrap_detect_zone",
"host_type", "print_host", "printhost_apikey", "flashforge_serial_number", "bbl_use_printhost", "printer_agent",
"print_host_webui",
"printhost_cafile","printhost_port","printhost_authorization_type",
"printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "thumbnails", "thumbnails_format",
"use_relative_e_distances", "extruder_type", "use_firmware_retraction", "printer_notes",
"grab_length", "support_object_skip_flush", "physical_extruder_map",
"cooling_tube_retraction",
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower",
"z_offset",
"disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "use_3mf", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut",
"bed_temperature_formula", "nozzle_flush_dataset",
// Multi-nozzle count + pre-heat model printer options
"extruder_max_nozzle_count", "group_algo_with_time", "enable_pre_heating", "hotend_heating_rate", "hotend_cooling_rate",
"machine_hotend_change_time", "machine_prepare_compensation_time",
// Fast-purge printer flag + device/firmware-facing per-variant extruder-change
// deretraction speed (unconsumed by the slicer; carried by H2D/A2L/X2D/P2S machine profiles).
"support_fast_purge_mode", "deretract_speed_extruder_change",
"printer_plugin_config_overrides"
};
static std::vector<std::string> s_Preset_sla_print_options {
"layer_height",
+50 -3
View File
@@ -16,6 +16,8 @@
#include "Semver.hpp"
#include "ProjectTask.hpp"
#include <cereal/access.hpp>
//BBS: change system directories
#define PRESET_SYSTEM_DIR "system"
#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
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 void extend_default_config_length(DynamicPrintConfig& config, const bool set_nil_to_default, const DynamicPrintConfig& defaults);
@@ -131,6 +137,10 @@ public:
PrinterVariant() {}
PrinterVariant(const std::string &name) : name(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 {
@@ -139,7 +149,7 @@ public:
std::string name;
//BBS: this is internal id for the printer. Currently only used for searching in database
std::string model_id;
PrinterTechnology technology;
PrinterTechnology technology = ptFFF;
std::string family;
std::vector<PrinterVariant> variants;
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); }
// 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;
@@ -173,6 +194,14 @@ public:
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.
// 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);
@@ -425,12 +454,30 @@ 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) {}
protected:
// Default constructor is public so cereal can default-construct elements when
// deserializing std::vector<Preset> (std::allocator is not a cereal::access friend).
Preset() = default;
protected:
friend class PresetCollection;
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);
+613 -48
View File
@@ -1,7 +1,18 @@
#include <cassert>
#include <chrono>
#include <ctime>
#include <sstream>
#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 "libslic3r.h"
#include "I18N.hpp"
@@ -564,6 +575,8 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
//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;
const auto startup_t0 = std::chrono::steady_clock::now();
//BBS: change system config to json
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("");
{
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
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" finished, returned substitutions %1%")%substitutions.size();
return substitutions;
@@ -1001,6 +1020,8 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
bundles.m_bundles.clear();
bundles.WriteUnlock();
const auto user_load_t0 = std::chrono::steady_clock::now();
// Load bundle metadata from _local directory first
fs::path local_dir(folder / PRESET_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.printer_presets.clear();
// Add the profiles
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
metadata.print_presets.push_back(preset.name);
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id));
@@ -1056,7 +1076,6 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
metadata.printer_presets.clear();
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) {
metadata.print_presets.push_back(preset.name);
}, 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: change directoties 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);
prints.select_preset_by_name(print_selected_preset_name, false);
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
// BBS: change directories by design
{
const auto json_t0 = std::chrono::steady_clock::now();
try {
std::string sel = prints.get_selected_preset().name;
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);
filaments.select_preset_by_name(filament_selected_preset_name, false);
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
{
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - user_load_t0).count();
BOOST_LOG_TRIVIAL(info) << "PresetBundle: user + bundle presets loaded in " << ms << " ms";
}
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_compatible(PresetSelectCompatibleType::Never);
set_calibrate_printer("");
return PresetsConfigSubstitutions();
@@ -1210,13 +1236,10 @@ bool PresetBundle::apply_vendor_config(
: std::map<std::string, std::string>();
// 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;
for (const auto &it : new_vendors) {
if (it.second.size() > 0) {
auto vendor_file = vendor_dir / (it.first + ".json");
if (!fs::exists(vendor_file)) {
if (!is_vendor_installed(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
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)
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;
std::string errors_cummulative;
bool first = true;
std::vector<std::string> vendor_names;
// store all vendor names in vendor_names
for (auto& dir_entry : boost::filesystem::directory_iterator(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);
}
bool first = true;
// Sorted, so any duplicate-preset warning below comes out in the same order on
// every run.
const std::set<std::string> vendor_names = vendor_names_in(dir);
// Separate ORCA_FILAMENT_LIBRARY from other vendors. It must be loaded
// first because other vendors' filaments may inherit from it via the
// `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.
if (!orca_lib_vendor.empty()) {
if (! orca_lib_vendor.empty()) {
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);
first = false;
} 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<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()),
[&](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
auto bundle = std::make_unique<PresetBundle>();
bundle->set_is_validation_mode(validation_mode);
bundle->set_generate_vendor_caches(m_generate_vendor_caches);
try {
auto result = bundle->load_vendor_configs_from_json(
dir.string(), other_vendors[i], PresetBundle::LoadSystem,
compatibility_rule, this);
dir.string(), other_vendors[i], PresetBundle::LoadSystem, compatibility_rule, this, lib_version);
parallel_substitutions[i] = std::move(result.first);
parallel_bundles[i] = std::move(bundle);
} catch (const std::runtime_error &err) {
@@ -2346,6 +2556,11 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
}
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
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, errors_cummulative %1%")%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
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.
ConfigSubstitutionContext substitution_context { compatibility_rule };
PresetsConfigSubstitutions substitutions;
//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))
// Reset this bundle, delete user profile files if 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
VendorProfile vendor_profile(vendor_name);
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
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", finished, presets_loaded %1%")%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;
}
}
// ---- 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
+115 -3
View File
@@ -6,6 +6,7 @@
#include "enum_bitmask.hpp"
#include <memory>
#include <set>
#include <shared_mutex>
#include <unordered_map>
#include <optional>
@@ -13,6 +14,11 @@
#include <boost/filesystem/path.hpp>
#include <unordered_set>
namespace cereal {
class BinaryInputArchive;
class BinaryOutputArchive;
}
#define DEFAULT_USER_FOLDER_NAME "default"
#define BUNDLE_STRUCTURE_JSON_NAME "bundle_structure.json"
@@ -170,6 +176,44 @@ struct PresetBundleMetadata
class PresetBundle
{
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,
Preset &in_print_preset,
const DynamicPrintConfig &project_config,
@@ -444,8 +488,15 @@ public:
/*std::pair<PresetsConfigSubstitutions, size_t> load_configbundle(
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: `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(
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.
//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.
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:
// 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
// filament presets sharing one filament_id (ambiguous AMS subtype match).
bool check_duplicate_filament_subtypes() const;
@@ -529,8 +611,6 @@ private:
//std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
//BBS: add json related logic
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.
void update_filament_multi_color();
// Update renamed_from and alias maps of system profiles.
@@ -565,6 +645,38 @@ private:
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
#endif /* slic3r_PresetBundle_hpp_ */
+323 -17
View File
@@ -101,23 +101,329 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
if (opt_keys.empty())
return false;
#include "../slic3r/GUI/generated/Invalidation_generated.cpp"
// Cache the plenty of parameters, which influence the G-code generator only,
// or they are only notes not influencing the generated G-code.
static std::unordered_set<std::string> steps_gcode = {
//BBS
"additional_cooling_fan_speed",
"reduce_crossing_wall",
"max_travel_detour_distance",
"printable_area",
//BBS: add bed_exclude_area
"bed_exclude_area",
"thumbnail_size",
"before_layer_change_gcode",
"enable_pressure_advance",
"pressure_advance",
"enable_overhang_bridge_fan",
"overhang_fan_speed",
"overhang_fan_threshold",
"slow_down_for_layer_cooling",
"default_acceleration",
"deretraction_speed",
"close_fan_the_first_x_layers",
"machine_end_gcode",
"printing_by_object_gcode",
"filament_end_gcode",
"post_process",
// "plugins" is the derived manifest backing the plugin-picker options; on its own it only
// affects G-code export. The specific option (e.g. slicing_pipeline_plugin) drives any re-slice.
"plugins",
"extruder_clearance_height_to_rod",
"extruder_clearance_height_to_lid",
"extruder_clearance_radius",
"nozzle_height",
"extruder_colour",
"extruder_offset",
"filament_flow_ratio",
"reduce_fan_stop_start_freq",
"dont_slow_down_outer_wall",
"fan_cooling_layer_time",
"full_fan_speed_layer",
"initial_layer_fan_speed",
"fan_kickstart",
"part_cooling_fan_min_pwm",
"fan_speedup_overhangs",
"fan_speedup_time",
"filament_colour",
"default_filament_colour",
"filament_diameter",
"volumetric_speed_coefficients",
"filament_density",
"filament_cost",
"filament_notes",
"outer_wall_acceleration",
"inner_wall_acceleration",
"initial_layer_acceleration",
"top_surface_acceleration",
"bridge_acceleration",
"travel_acceleration",
"sparse_infill_acceleration",
"internal_solid_infill_acceleration",
// BBS
"supertack_plate_temp_initial_layer",
"cool_plate_temp_initial_layer",
"textured_cool_plate_temp_initial_layer",
"eng_plate_temp_initial_layer",
"hot_plate_temp_initial_layer",
"textured_plate_temp_initial_layer",
"gcode_add_line_number",
"layer_change_gcode",
"time_lapse_gcode",
"wrapping_detection_gcode",
"fan_min_speed",
"fan_max_speed",
"printable_height",
"slow_down_min_speed",
"max_volumetric_extrusion_rate_slope",
"max_volumetric_extrusion_rate_slope_segment_length",
"extrusion_rate_smoothing_external_perimeter_only",
"reduce_infill_retraction",
"filename_format",
"retraction_minimum_travel",
"retract_before_wipe",
// Orca:
"retract_after_wipe",
"retract_when_changing_layer",
"retraction_length",
"retract_length_toolchange",
"z_hop",
"travel_slope",
"retract_lift_above",
"retract_lift_below",
"retract_lift_enforce",
"retract_restart_extra",
"retract_restart_extra_toolchange",
"retraction_speed",
"use_firmware_retraction",
"slow_down_layer_time",
"standby_temperature_delta",
"preheat_time",
"preheat_steps",
"machine_start_gcode",
"filament_start_gcode",
"change_filament_gcode",
"wipe",
// BBS
"wipe_distance",
"curr_bed_type",
"nozzle_volume",
"nozzle_hrc",
"required_nozzle_HRC",
"upward_compatible_machine",
"is_infill_first",
// Orca
"chamber_temperature",
"chamber_minimal_temperature",
"thumbnails",
"thumbnails_format",
"center_of_surface_pattern",
"separated_infills",
"seam_gap",
"role_based_wipe_speed",
"wipe_speed",
"use_relative_e_distances",
"accel_to_decel_enable",
"accel_to_decel_factor",
"wipe_on_loops",
"gcode_comments",
"gcode_label_objects",
"exclude_object",
"support_material_interface_fan_speed",
"internal_bridge_fan_speed", // ORCA: Add support for separate internal bridge fan speed control
"ironing_fan_speed",
"single_extruder_multi_material_priming",
"activate_air_filtration",
"activate_air_filtration_during_print",
"activate_air_filtration_on_completion",
"during_print_exhaust_fan_speed",
"complete_print_exhaust_fan_speed",
"activate_chamber_temp_control",
"manual_filament_change",
"disable_m73",
"use_firmware_retraction",
"enable_long_retraction_when_cut",
"long_retractions_when_cut",
"retraction_distances_when_cut",
"filament_long_retractions_when_cut",
"filament_retraction_distances_when_cut",
"grab_length",
"bed_temperature_formula",
"filament_notes",
"process_notes",
"printer_notes",
"use_3mf"
};
std::vector<PrintStep> steps;
static std::unordered_set<std::string> steps_ignore;
std::vector<PrintStep> steps;
std::vector<PrintObjectStep> osteps;
bool invalidated = false;
for (const t_config_option_key &opt_key : opt_keys) {
auto it_ps = s_print_steps_map.find(opt_key);
auto it_os = s_object_steps_map.find(opt_key);
if (it_ps != s_print_steps_map.end() || it_os != s_object_steps_map.end()) {
if (it_ps != s_print_steps_map.end())
steps.insert(steps.end(), it_ps->second.begin(), it_ps->second.end());
if (it_os != s_object_steps_map.end())
osteps.insert(osteps.end(), it_os->second.begin(), it_os->second.end());
if (steps_gcode.find(opt_key) != steps_gcode.end()) {
// These options only affect G-code export or they are just notes without influence on the generated G-code,
// so there is nothing to invalidate.
steps.emplace_back(psGCodeExport);
} else if (steps_ignore.find(opt_key) != steps_ignore.end()) {
// These steps have no influence on the G-code whatsoever. Just ignore them.
} else if (
opt_key == "skirt_type"
|| opt_key == "skirt_loops"
|| opt_key == "skirt_speed"
|| opt_key == "skirt_height"
|| opt_key == "min_skirt_length"
|| opt_key == "single_loop_draft_shield"
|| opt_key == "draft_shield"
|| opt_key == "skirt_distance"
|| opt_key == "skirt_start_angle"
|| opt_key == "ooze_prevention"
|| opt_key == "wipe_tower_x"
|| opt_key == "wipe_tower_y"
|| opt_key == "wipe_tower_rotation_angle") {
steps.emplace_back(psSkirtBrim);
} else if (
opt_key == "slicing_pipeline_plugin"
|| opt_key == "print_plugin_config_overrides"
|| opt_key == "initial_layer_print_height"
|| opt_key == "nozzle_diameter"
|| opt_key == "filament_shrink"
|| opt_key == "filament_shrinkage_compensation_z"
|| opt_key == "resolution"
|| opt_key == "precise_z_height"
// Spiral Vase forces different kind of slicing than the normal model:
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
// Therefore toggling the Spiral Vase on / off requires complete reslicing.
|| opt_key == "spiral_mode") {
osteps.emplace_back(posSlice);
} else if (
opt_key == "print_sequence"
|| opt_key == "filament_type"
|| opt_key == "chamber_temperature"
|| opt_key == "nozzle_temperature_initial_layer"
|| opt_key == "filament_minimal_purge_on_wipe_tower"
|| opt_key == "filament_max_volumetric_speed"
|| opt_key == "filament_adaptive_volumetric_speed"
|| opt_key == "filament_loading_speed"
|| opt_key == "filament_loading_speed_start"
|| opt_key == "filament_unloading_speed"
|| opt_key == "filament_unloading_speed_start"
|| opt_key == "filament_toolchange_delay"
|| opt_key == "filament_cooling_moves"
|| opt_key == "filament_stamping_loading_speed"
|| opt_key == "filament_stamping_distance"
|| opt_key == "filament_cooling_initial_speed"
|| opt_key == "filament_cooling_final_speed"
|| opt_key == "filament_ramming_parameters"
|| opt_key == "filament_multitool_ramming"
|| opt_key == "filament_multitool_ramming_volume"
|| opt_key == "filament_multitool_ramming_flow"
|| opt_key == "filament_max_volumetric_speed"
|| opt_key == "gcode_flavor"
|| opt_key == "single_extruder_multi_material"
|| opt_key == "nozzle_temperature"
// BBS
|| opt_key == "supertack_plate_temp"
|| opt_key == "cool_plate_temp"
|| opt_key == "textured_cool_plate_temp"
|| opt_key == "eng_plate_temp"
|| opt_key == "hot_plate_temp"
|| opt_key == "textured_plate_temp"
|| opt_key == "enable_prime_tower"
|| opt_key == "enable_wrapping_detection"
|| opt_key == "prime_tower_enable_framework"
|| opt_key == "prime_tower_width"
|| opt_key == "prime_tower_brim_width"
|| opt_key == "wipe_tower_type"
|| opt_key == "prime_tower_skip_points"
|| opt_key == "prime_tower_flat_ironing"
|| opt_key == "enable_tower_interface_features"
|| opt_key == "first_layer_print_sequence"
|| opt_key == "other_layers_print_sequence"
|| opt_key == "other_layers_print_sequence_nums"
|| opt_key == "toolchange_ordering"
|| opt_key == "extruder_ams_count"
|| opt_key == "extruder_nozzle_stats"
|| opt_key == "filament_map_mode"
|| opt_key == "filament_map"
|| opt_key == "filament_nozzle_map"
|| opt_key == "filament_volume_map"
|| opt_key == "filament_adhesiveness_category"
|| opt_key == "filament_tower_interface_pre_extrusion_dist"
|| opt_key == "filament_tower_interface_pre_extrusion_length"
|| opt_key == "filament_tower_ironing_area"
|| opt_key == "filament_tower_interface_purge_volume"
|| opt_key == "filament_tower_interface_print_temp"
|| opt_key == "wipe_tower_bridging"
|| opt_key == "wipe_tower_extra_flow"
|| opt_key == "wipe_tower_no_sparse_layers"
|| opt_key == "flush_volumes_matrix"
|| opt_key == "prime_volume"
|| opt_key == "flush_into_infill"
|| opt_key == "flush_into_support"
|| opt_key == "initial_layer_infill_speed"
|| opt_key == "travel_speed"
|| opt_key == "travel_speed_z"
|| opt_key == "initial_layer_speed"
|| opt_key == "initial_layer_travel_speed"
|| opt_key == "initial_layer_travel_acceleration"
|| opt_key == "initial_layer_travel_jerk"
|| opt_key == "slow_down_layers"
|| opt_key == "idle_temperature"
|| opt_key == "wipe_tower_cone_angle"
|| opt_key == "wipe_tower_extra_spacing"
|| opt_key == "wipe_tower_max_purge_speed"
|| opt_key == "wipe_tower_wall_type"
|| opt_key == "wipe_tower_extra_rib_length"
|| opt_key == "wipe_tower_rib_width"
|| opt_key == "wipe_tower_fillet_wall"
|| opt_key == "wipe_tower_filament"
|| opt_key == "wiping_volumes_extruders"
|| opt_key == "enable_filament_ramming"
|| opt_key == "tool_change_on_wipe_tower"
|| opt_key == "purge_in_prime_tower"
|| opt_key == "z_offset"
|| opt_key == "support_multi_bed_types"
) {
steps.emplace_back(psWipeTower);
steps.emplace_back(psSkirtBrim);
} else if (opt_key == "filament_soluble"
|| opt_key == "filament_is_support"
|| opt_key == "filament_printable"
|| opt_key == "filament_change_length"
|| opt_key == "independent_support_layer_height") {
steps.emplace_back(psWipeTower);
// Soluble support interface / non-soluble base interface produces non-soluble interface layers below soluble interface layers.
// Thus switching between soluble / non-soluble interface layer material may require recalculation of supports.
//FIXME Killing supports on any change of "filament_soluble" is rough. We should check for each object whether that is necessary.
osteps.emplace_back(posSupportMaterial);
osteps.emplace_back(posSimplifySupportPath);
} else if (
opt_key == "initial_layer_line_width"
|| opt_key == "min_layer_height"
|| opt_key == "max_layer_height"
//|| opt_key == "resolution"
//BBS: when enable arc fitting, we must re-generate perimeter
|| opt_key == "enable_arc_fitting"
|| opt_key == "print_order"
|| opt_key == "wall_sequence") {
osteps.emplace_back(posPerimeters);
osteps.emplace_back(posEstimateCurledExtrusions);
osteps.emplace_back(posInfill);
osteps.emplace_back(posSupportMaterial);
osteps.emplace_back(posSimplifyPath);
osteps.emplace_back(posSimplifyInfill);
osteps.emplace_back(posSimplifySupportPath);
steps.emplace_back(psSkirtBrim);
}
else if (opt_key == "z_hop_types") {
osteps.emplace_back(posDetectOverhangsForLift);
} else {
// Unknown option — conservatively invalidate all steps
// for legacy, if we can't handle this option let's invalidate all steps
//FIXME invalidate all steps of all objects as well?
invalidated |= this->invalidate_all_steps();
// Continue with the other opt_keys to possibly invalidate any object specific steps.
}
}
@@ -450,7 +756,7 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print
#if 0 //do not sort anymore, use the order in object list
auto bed_points = get_bed_shape(print_config);
float bed_width = bed_points[1].x() - bed_points[0].x();
// 如果扩大以后的多边形的距离小于这个值,就需要严格保证从左到右的打印顺序,否则会撞工具头右侧
// 如果扩大以后的多边形的距离小于这个值,就需要严格保证从左到右的打印顺序,否则会撞工具头右侧
float unsafe_dist = scale_(print_config.extruder_clearance_max_radius.value - print_config.extruder_clearance_radius.value);
struct VecHash
{
@@ -484,7 +790,7 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print
auto inter_max = std::min(ly2, ry2);
auto inter_y = inter_max - inter_min;
// 如果y方向的重合超过轮廓的膨胀量,说明两个物体在一行,应该先打左边的物体,即先比较二者的x坐标。
// 如果y方向的重合超过轮廓的膨胀量,说明两个物体在一行,应该先打左边的物体,即先比较二者的x坐标。
// If the overlap in the y direction exceeds the expansion of the contour, it means that the two objects are in a row and the object on the left should be hit first, that is, the x coordinates of the two should be compared first.
if (inter_y > scale_(0.5 * print.config().extruder_clearance_radius.value)) {
if (std::max(rx1 - lx2, lx1 - rx2) < unsafe_dist) {
@@ -500,13 +806,13 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print
}
}
if (l.height > hc1 && r.height < hc1) {
// 当前物体超过了顶盖高度,必须后打
// 当前物体超过了顶盖高度,必须后打
left_right_pair.insert({j, i});
BOOST_LOG_TRIVIAL(debug) << "height>hc1, print_instance " << r.print_instance->model_instance->get_object()->name << "(" << r.arrange_score << ")"
<< " -> " << l.print_instance->model_instance->get_object()->name << "(" << l.arrange_score << ")";
}
else if (l.height > hc2 && l.height > r.height && l.arrange_score<r.arrange_score) {
// 如果当前物体的高度超过滑杆,且比r高,就给它加一点代价,尽量让高的物体后打(只有物体高度超过滑杆时才有必要按高度来)
// 如果当前物体的高度超过滑杆,且比r高,就给它加一点代价,尽量让高的物体后打(只有物体高度超过滑杆时才有必要按高度来)
if (l.arrange_score < r.arrange_score)
l.arrange_score = r.arrange_score + 10;
BOOST_LOG_TRIVIAL(debug) << "height>hc2, print_instance " << inst.print_instance->model_instance->get_object()->name
@@ -516,8 +822,8 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print
}
}
}
// 多做几次代价传播,因为前一次有些值没有更新。
// TODO 更好的办法是建立一颗树,一步到位。不过我暂时没精力搞,先就这样吧
// 多做几次代价传播,因为前一次有些值没有更新。
// TODO 更好的办法是建立一颗树,一步到位。不过我暂时没精力搞,先就这样吧
for (int k=0;k<5;k++)
for (auto p : left_right_pair) {
auto &l = print_instance_with_bounding_box[p(0)];
@@ -585,7 +891,7 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print
for (int k = 0; k < print_instance_count; k++)
{
auto inst = print_instance_with_bounding_box[k].print_instance;
// 只需要考虑喷嘴到滑杆的偏移量,这个比整个工具头的碰撞半径要小得多
// 只需要考虑喷嘴到滑杆的偏移量,这个比整个工具头的碰撞半径要小得多
// Only the offset from the nozzle to the slide bar needs to be considered, which is much smaller than the collision radius of the entire tool head.
auto bbox = print_instance_with_bounding_box[k].bounding_box.inflated(-scale_(0.5 * print.config().extruder_clearance_radius.value + object_skirt_offset));
auto iy1 = bbox.min.y();
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -2410,7 +2410,8 @@ namespace cereal {
archive(serialization_key_ordinal);
assert(serialization_key_ordinal > 0);
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));
}
}
+13
View File
@@ -190,6 +190,19 @@ public:
os << self.to_string();
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:
semver_t ver;
-9
View File
@@ -722,15 +722,6 @@ void copy_directory_recursively(const boost::filesystem::path& source,
std::function<bool(const std::string)> filter = nullptr,
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
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);
-70
View File
@@ -1724,76 +1724,6 @@ void copy_directory_recursively(const boost::filesystem::path& source,
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)
{
boost::nowide::ofstream file;
-11
View File
@@ -490,7 +490,6 @@ set(SLIC3R_GUI_SOURCES
GUI/TabButton.hpp
GUI/Tab.cpp
GUI/Tab.hpp
GUI/TabLayoutExtra.cpp
GUI/TaskManager.cpp
GUI/TaskManager.hpp
GUI/TextLines.cpp
@@ -831,16 +830,6 @@ else ()
endif ()
target_include_directories(libslic3r_gui PRIVATE Utils ${CMAKE_CURRENT_BINARY_DIR})
# Tab.cpp #includes GUI/generated/TabLayout_generated.cpp, so it must not be compiled
# while the codegen is rewriting it (same contract as libslic3r's generated includes).
if(TARGET codegen_config)
add_dependencies(libslic3r_gui codegen_config)
set_source_files_properties(
"${CMAKE_CURRENT_SOURCE_DIR}/GUI/Tab.cpp"
PROPERTIES OBJECT_DEPENDS "${CONFIG_GENERATED_SOURCES}"
)
endif()
if (WIN32)
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../deps/WebView2/include)
target_link_libraries(libslic3r_gui Advapi32)
+3 -8
View File
@@ -432,14 +432,9 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
cfg.models_variants_installed.erase(it ++);
else
++ it;
// Read the active config bundle, parse the config version.
PresetBundle bundle;
//BBS: change directoties by design
//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;
// Orca: the version the vendor is installed at, read from its profile or —
// where the cache is the whole installation — from the cache's own stamp.
cfg.version.config_version = installed_vendor_version(cfg.name);
snapshot.vendor_configs.emplace_back(std::move(cfg));
}
+3 -3
View File
@@ -192,7 +192,7 @@ PingCodeBindDialog::PingCodeBindDialog(Plater* plater /*= nullptr*/)
SetSizer(sizer_main);
SetSizerAndFit(sizer_main);
Layout();
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_simplebook, 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, ButtonProps::ChoiceButtonGap());
SetSizer(m_sizer_main);
SetSizerAndFit(m_sizer_main);
Layout();
Fit();
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(0, 0, 0, wxTOP, FromDIP(20));
SetSizer(m_sizer_main);
SetSizerAndFit(m_sizer_main);
Layout();
Fit();
Centre(wxBOTH);
+2 -4
View File
@@ -632,9 +632,8 @@ EditCalibrationHistoryDialog::EditCalibrationHistoryDialog(wxWindow
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
SetSizer(main_sizer);
SetSizerAndFit(main_sizer);
Layout();
Fit();
CenterOnParent();
wxGetApp().UpdateDlgDarkUI(this);
@@ -910,9 +909,8 @@ NewCalibrationHistoryDialog::NewCalibrationHistoryDialog(wxWindow *parent, const
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
SetSizer(main_sizer);
SetSizerAndFit(main_sizer);
Layout();
Fit();
CenterOnParent();
wxGetApp().UpdateDlgDarkUI(this);
+1 -1
View File
@@ -162,7 +162,7 @@ CalibrationDialog::CalibrationDialog(Plater *plater)
body_panel->Layout();
m_sizer_main->Add(body_panel, 0, wxEXPAND | wxALL, FromDIP(25));
SetSizer(m_sizer_main);
SetSizerAndFit(m_sizer_main);
Layout();
Fit();
+1 -2
View File
@@ -112,9 +112,8 @@ CloneDialog::CloneDialog(wxWindow *parent)
v_sizer->Add(bottom_sizer, 0, wxEXPAND);
this->SetSizer(v_sizer);
this->SetSizerAndFit(v_sizer);
this->Layout();
v_sizer->Fit(this);
wxGetApp().UpdateDlgDarkUI(this);
+29 -39
View File
@@ -66,41 +66,41 @@ using Config::SnapshotDB;
// Configuration data structures extensions needed for the wizard
//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->is_in_resources = ais_in_resources;
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
std::string vendor_name = source_path.filename().string();
if (Slic3r::is_json_file(path_string)) {
// Remove the .json suffix.
vendor_name.erase(vendor_name.size() - 5);
}
else
// Orca: served from the vendor's preset cache where one covers it — which is
// how a shipped build carries its vendors — and parsed from the JSONs otherwise.
// A vendor that can be neither read nor parsed — a cache the build cannot use
// 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.
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;
// 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();
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;
}
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;
}
}
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;
return true;
}
@@ -125,15 +125,10 @@ BundleMap BundleMap::load()
//Orca: add custom as default
//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;
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));
}
@@ -141,18 +136,13 @@ BundleMap BundleMap::load()
// and then additionally from resources/profiles.
bool is_in_resources = false;
for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) {
for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) {
//BBS: add json logic for vendor bundle
if (Slic3r::is_json_file(dir_entry.path().string())) {
std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".json" part
for (const std::string &id : vendor_names_in(*dir)) {
// Don't load this bundle if we've already loaded it.
if (res.find(id) != res.end()) { continue; }
// Don't load this bundle if we've already loaded it.
if (res.find(id) != res.end()) { continue; }
Bundle bundle;
if (bundle.load(dir_entry.path(), is_in_resources))
res.emplace(std::move(id), std::move(bundle));
}
Bundle bundle;
if (bundle.load(*dir, id, is_in_resources))
res.emplace(id, std::move(bundle));
}
is_in_resources = true;
+3 -1
View File
@@ -71,9 +71,11 @@ struct Bundle
Bundle() = default;
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.
//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; }
};
+1 -2
View File
@@ -80,9 +80,8 @@ ConnectPrinterDialog::ConnectPrinterDialog(wxWindow *parent, wxWindowID id, cons
main_sizer->Add(sizer_top);
this->SetSizer(main_sizer);
this->SetSizerAndFit(main_sizer);
this->Layout();
this->Fit();
CentreOnParent();
m_textCtrl_code->Bind(wxEVT_TEXT, &ConnectPrinterDialog::on_input_enter, this);
+4 -15
View File
@@ -2201,25 +2201,14 @@ bool CreatePrinterPresetDialog::load_system_and_user_presets_with_curr_model(Pre
} else {
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 {
// 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
// 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,
ForwardCompatibilitySubstitutionRule::EnableSilent,
wxGetApp().preset_bundle);
+1 -2
View File
@@ -112,9 +112,8 @@ DownloadProgressDialog::DownloadProgressDialog(wxString title)
m_simplebook_status->AddPage(m_panel_download_failed, wxEmptyString, false);
m_simplebook_status->AddPage(m_panel_install_failed, wxEmptyString, false);
SetSizer(m_sizer_main);
SetSizerAndFit(m_sizer_main);
Layout();
Fit();
CentreOnParent();
Bind(wxEVT_CLOSE_WINDOW, &DownloadProgressDialog::on_close, this);
+1 -2
View File
@@ -261,7 +261,7 @@ void ExtrusionCalibration::create()
top_sizer->Add(FromDIP(24), 0);
top_sizer->Add(sizer_main, 1, wxEXPAND);
top_sizer->Add(FromDIP(24), 0);
SetSizer(top_sizer);
SetSizerAndFit(top_sizer);
// set default nozzle
m_comboBox_nozzle_dia->SetSelection(1);
@@ -271,7 +271,6 @@ void ExtrusionCalibration::create()
set_step(1);
Layout();
Fit();
m_k_val->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent& e) {
input_value_finish();
+1 -2
View File
@@ -105,9 +105,8 @@ FilamentPickerDialog::FilamentPickerDialog(wxWindow *parent, const wxString& fil
container_sizer->Add(main_sizer, 1, wxEXPAND | wxALL, FromDIP(10));
container_sizer->Add(dlg_btns, 0, wxEXPAND);
SetSizer(container_sizer);
SetSizerAndFit(container_sizer);
Layout();
container_sizer->Fit(this);
// Position the dialog relative to the parent window
if (GetParent()) {
+5 -5
View File
@@ -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));
apply_style(style);
SetSizerAndFit(main_sizer);
SetSizer(main_sizer);
wxGetApp().UpdateDlgDarkUI(this);
}
@@ -221,6 +221,7 @@ void MsgDialog::apply_style(long style)
void MsgDialog::finalize()
{
GetSizer()->SetSizeHints(this);
Layout();
Fit();
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_cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_CANCEL); });
SetSizer(m_main_sizer);
SetSizerAndFit(m_main_sizer);
Layout();
Fit();
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(get_btn_sizer(), 0, wxEXPAND | wxALL, FromDIP(5));
this->SetSizer(main_sizer);
this->SetSizerAndFit(main_sizer);
Layout();
Fit();
wxGetApp().UpdateDlgDarkUI(this);
@@ -745,9 +746,8 @@ NetworkErrorDialog::NetworkErrorDialog(wxWindow* parent)
sizer_main->Add(sizer_button, 1, wxEXPAND | wxLEFT | wxRIGHT, 15);
sizer_main->Add(0, 0, 0, wxTOP, 18);
SetSizer(sizer_main);
SetSizerAndFit(sizer_main);
Layout();
sizer_main->Fit(this);
Centre(wxBOTH);
}
+1
View File
@@ -47,6 +47,7 @@ NetworkPluginDownloadDialog::NetworkPluginDownloadDialog(wxWindow* parent, Mode
} else {
create_missing_plugin_ui();
}
main_sizer->SetSizeHints(this);
Layout();
Fit();
CentreOnParent();
+1 -1
View File
@@ -47,7 +47,7 @@ NetworkTestDialog::NetworkTestDialog(wxWindow* parent, wxWindowID id, const wxSt
init_bind();
this->SetSizer(main_sizer);
this->SetSizerAndFit(main_sizer);
this->Layout();
this->Centre(wxBOTH);
+1 -2
View File
@@ -270,7 +270,7 @@ PartSkipDialog::PartSkipDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _
m_simplebook->AddPage(m_book_third_panel, _("dialog page"), false);
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_out_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomOut, 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);
Layout();
Fit();
CentreOnParent();
}
+1 -1
View File
@@ -15134,7 +15134,7 @@ ProjectDropDialog::ProjectDropDialog(const std::string &filename)
m_sizer_main->Add(dlg_btns, 0, wxEXPAND);
SetSizer(m_sizer_main);
SetSizerAndFit(m_sizer_main);
Layout();
Fit();
Centre(wxBOTH);
+6 -7
View File
@@ -28,7 +28,7 @@ PrintOptionsDialog::PrintOptionsDialog(wxWindow* parent)
{
this->SetDoubleBuffered(true);
SetBackgroundColour(*wxWHITE);
SetSize(FromDIP(480),FromDIP(520));
// SetMinSize(FromDIP(wxSize{wxDefaultCoord,520}));
m_scrollwindow = new wxScrolledWindow(this, wxID_ANY);
@@ -50,7 +50,8 @@ PrintOptionsDialog::PrintOptionsDialog(wxWindow* parent)
m_scrollwindow->FitInside();
this->Layout();
// mainSizer->Fit(this);
mainSizer->SetMinSize(wxDefaultCoord, FromDIP(520));
mainSizer->Fit(this);
//this->Fit();
m_cb_ai_monitoring->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &evt) {
@@ -1670,12 +1671,9 @@ PrinterPartsDialog::PrinterPartsDialog(wxWindow* parent)
/*inset data*/
sizer->Add(single_panel, 0, wxEXPAND, 0);
sizer->Add(multiple_panel, 0, wxEXPAND, 0);
SetSizer(sizer);
Layout();
Fit();
single_panel->Hide();
SetSizerAndFit(sizer);
Layout();
wxGetApp().UpdateDlgDarkUI(this);
}
@@ -1752,6 +1750,7 @@ bool PrinterPartsDialog::Show(bool show)
}
}
GetSizer()->SetSizeHints(this);
Layout();
Fit();
}
+1 -1
View File
@@ -119,7 +119,7 @@ PublishDialog::PublishDialog(Plater *plater)
top_sizer->Add(m_main_sizer, 1, wxALL | wxEXPAND, 0);
top_sizer->Add(FromDIP(30), 0, 0, wxEXPAND, 0);
this->SetSizer(top_sizer);
this->SetSizerAndFit(top_sizer);
this->Layout();
this->Centre(wxBOTH);
+1 -2
View File
@@ -310,10 +310,9 @@ StepMeshDialog::StepMeshDialog(wxWindow* parent, Slic3r::Step& file, double line
bSizer->Add(bSizer_button, 1, wxEXPAND);
this->SetSizer(bSizer);
this->SetSizerAndFit(bSizer);
update_mesh_number_text();
this->Layout();
bSizer->Fit(this);
this->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {
SetFocusIgnoringChildren();
+1017 -12
View File
File diff suppressed because it is too large Load Diff
-6
View File
@@ -590,7 +590,6 @@ private:
void add_filament_overrides_page();
void update_filament_overrides_page(const DynamicPrintConfig* printers_config);
void build_extra_layout(); // TabLayoutExtra.cpp: toolchange/ramming/dependencies/notes
void update_volumetric_flow_preset_hints();
std::map<std::string, ::CheckBox*> m_overrides_options;
@@ -664,11 +663,6 @@ public:
void extruders_count_changed(size_t extruders_count);
PageShp build_kinematics_page();
void build_unregular_pages(bool from_initial_build = false);
void build_fff_extra_layout(); // TabLayoutExtra.cpp: Basic info page with bed-shape widget
// Hook methods called from TabPrinter_build_basic_info_layout (TabLayout_generated.cpp)
void layout_hook_printable_space(ConfigOptionsGroup* optgroup); // bed shape widget
void layout_hook_advanced(ConfigOptionsGroup* optgroup); // thumbnail m_on_change
void layout_hook_cooling_fan(ConfigOptionsGroup* optgroup); // multi-option line
void on_preset_loaded() override;
void init_options_list() override;
void msw_rescale() override;
-217
View File
@@ -1,217 +0,0 @@
// TabLayoutExtra.cpp
//
// TabFilament::build_extra_layout() - member function implementation.
//
// Supplements TabLayout_generated.cpp with UI sections that require custom
// widget factories (create_line_with_widget) and therefore cannot be
// auto-generated from the proto schema.
//
// Called from TabFilament::build() after TabFilament_build_main_layout():
// 1. Toolchange + ramming parameters sections (single + multi extruder)
// 2. Setting Overrides page (checkbox-driven nullable retraction overrides)
// 3. Dependencies page (compatible_printers and compatible_prints widgets)
// 4. Notes page
#include "Tab.hpp"
#include "GUI_App.hpp" // dots, wxGetApp()
#include "libslic3r/FlushVolCalc.hpp" // g_max_flush_volume (needed by WipeTowerDialog)
#include "WipeTowerDialog.hpp" // RammingDialog
#include "MsgDialog.hpp" // InfoDialog
#include "libslic3r/GCode/Thumbnails.hpp"
#include "format.hpp"
namespace Slic3r { namespace GUI {
void TabFilament::build_extra_layout()
{
constexpr int notes_field_height = 25;
// -- Continue the Multimaterial page ----------------------------------
// TabFilament_build_main_layout() ended after the Multi Filament optgroup.
// We continue on the same page by accessing m_pages.back().
{
PageShp page = m_pages.back();
// Tool change parameters - single extruder MM printers
// (includes filament_ramming_parameters which needs a custom button widget)
{
auto optgroup = page->new_optgroup(L("Tool change parameters with single extruder MM printers"), L"param_toolchange");
optgroup->append_single_option_line("filament_loading_speed_start", "material_multimaterial#loading-speed-at-the-start");
optgroup->append_single_option_line("filament_loading_speed", "material_multimaterial#loading-speed");
optgroup->append_single_option_line("filament_unloading_speed_start", "material_multimaterial#unloading-speed-at-the-start");
optgroup->append_single_option_line("filament_unloading_speed", "material_multimaterial#unloading-speed");
optgroup->append_single_option_line("filament_toolchange_delay", "material_multimaterial#delay-after-unloading");
optgroup->append_single_option_line("filament_cooling_moves", "material_multimaterial#number-of-cooling-moves");
optgroup->append_single_option_line("filament_cooling_initial_speed", "material_multimaterial#speed-of-the-first-cooling-move");
optgroup->append_single_option_line("filament_cooling_final_speed", "material_multimaterial#speed-of-the-last-cooling-move");
optgroup->append_single_option_line("filament_stamping_loading_speed", "material_multimaterial#stamping-loading-speed");
optgroup->append_single_option_line("filament_stamping_distance", "material_multimaterial#stamping-distance");
create_line_with_widget(optgroup.get(), "filament_ramming_parameters",
"material_multimaterial#ramming-parameters",
[this](wxWindow* parent) {
Button* btn = new Button(parent, _(L("Set")) + " " + dots);
btn->SetStyle(ButtonStyle::Regular, ButtonType::Parameter);
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
RammingDialog dlg(this,
(m_config->option<ConfigOptionStrings>("filament_ramming_parameters"))->get_at(0));
if (dlg.ShowModal() == wxID_OK) {
load_key_value("filament_ramming_parameters", dlg.get_parameters());
update_changed_ui();
}
});
return sizer;
});
}
// Tool change parameters - multi extruder MM printers
{
auto optgroup = page->new_optgroup(L("Tool change parameters with multi extruder MM printers"), L"param_toolchange_multi_extruder");
optgroup->append_single_option_line("filament_multitool_ramming", "material_multimaterial#tool-change-parameters-with-multi-extruder");
optgroup->append_single_option_line("filament_multitool_ramming_volume", "material_multimaterial#multi-tool-ramming-volume");
optgroup->append_single_option_line("filament_multitool_ramming_flow", "material_multimaterial#multi-tool-ramming-flow");
}
}
// -- Setting Overrides page --------------------------------------------
// Checkbox-driven nullable retraction overrides - complex runtime logic,
// cannot be expressed in the proto schema.
add_filament_overrides_page();
// -- Dependencies page -------------------------------------------------
// compatible_printers and compatible_prints require create_line_with_widget
// and must come BEFORE the text condition inputs in each optgroup.
{
auto page = add_options_page(L("Dependencies"), "advanced");
{
auto optgroup = page->new_optgroup(L("Compatible printers"), L"param_dependencies_printers");
create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) {
return compatible_widget_create(parent, m_compatible_printers);
});
Option option = optgroup->get_option("compatible_printers_condition");
option.opt.full_width = true;
optgroup->append_single_option_line(option, "material_dependencies#compatible-printers");
}
{
auto optgroup = page->new_optgroup(L("Compatible process profiles"), L"param_dependencies_presets");
create_line_with_widget(optgroup.get(), "compatible_prints", "", [this](wxWindow* parent) {
return compatible_widget_create(parent, m_compatible_prints);
});
Option option = optgroup->get_option("compatible_prints_condition");
option.opt.full_width = true;
optgroup->append_single_option_line(option, "material_dependencies#compatible-process-profiles");
}
}
// -- Notes page --------------------------------------------------------
{
auto page = add_options_page(L("Notes"), "custom-gcode_note");
auto optgroup = page->new_optgroup(L("Notes"), L"note", 0);
Option option = optgroup->get_option("filament_notes");
option.opt.full_width = true;
option.opt.height = notes_field_height;
optgroup->append_single_option_line(option);
}
}
// -----------------------------------------------------------------------------
// TabPrinter::build_fff_extra_layout()
//
// Builds the "Basic information" page for the Printer/FFF tab.
// Kept here (not generated) because it contains:
// - create_line_with_widget for printable_area (bed shape editor)
// - m_on_change for the thumbnails field (thumbnail format sync logic)
// - append_line for Cooling Fan multi-option line (fan_speedup_time + fan_speedup_overhangs)
//
// Called from TabPrinter::build_fff() before TabPrinter_build_gcode_layout().
// -----------------------------------------------------------------------------
// ── TabPrinter hook methods called by TabPrinter_build_basic_information_layout ──
// These implement the optgroups that cannot be auto-generated from yaml because they
// require custom widget factories, special m_on_change callbacks, or multi-option lines.
void TabPrinter::layout_hook_printable_space(ConfigOptionsGroup* optgroup)
{
// Bed shape widget must be FIRST in the group
create_line_with_widget(optgroup, "printable_area", "custom-svg-and-png-bed-textures_124612", [this](wxWindow* parent) {
return create_bed_shape_widget(parent);
});
Option option = optgroup->get_option("bed_exclude_area");
option.opt.full_width = true;
optgroup->append_single_option_line(option, "printer_basic_information_printable_space#excluded-bed-area");
optgroup->append_single_option_line("printable_height", "printer_basic_information_printable_space#printable-height");
optgroup->append_single_option_line("support_multi_bed_types", "printer_basic_information_printable_space#support-multi-bed-types");
optgroup->append_single_option_line("best_object_pos", "printer_basic_information_printable_space#best-object-position");
optgroup->append_single_option_line("z_offset", "printer_basic_information_printable_space#z-offset");
optgroup->append_single_option_line("preferred_orientation", "printer_basic_information_printable_space#preferred-orientation");
}
void TabPrinter::layout_hook_advanced(ConfigOptionsGroup* optgroup)
{
optgroup->append_single_option_line("printer_structure", "printer_basic_information_advanced#printer-structure");
optgroup->append_single_option_line("gcode_flavor", "printer_basic_information_advanced#g-code-flavor");
optgroup->append_single_option_line("pellet_modded_printer", "printer_basic_information_advanced#pellet-modded-printer");
optgroup->append_single_option_line("bbl_use_printhost", "printer_basic_information_advanced#use-3rd-party-print-host");
optgroup->append_single_option_line("scan_first_layer", "printer_basic_information_advanced#scan-first-layer");
optgroup->append_single_option_line("enable_power_loss_recovery", "printer_basic_information_advanced#power-loss-recovery");
optgroup->append_single_option_line("disable_m73", "printer_basic_information_advanced#disable-set-remaining-print-time");
{
Option option = optgroup->get_option("thumbnails");
option.opt.full_width = true;
optgroup->append_single_option_line(option, "printer_basic_information_advanced#g-code-thumbnails");
}
// Thumbnail format sync — cannot be expressed in proto schema
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
wxTheApp->CallAfter([this, opt_key, value]() {
if (opt_key == "thumbnails" && m_config->has("thumbnails_format")) {
const std::string val = boost::any_cast<std::string>(value);
if (!value.empty()) {
auto [thumbnails_list, errors] = GCodeThumbnails::make_and_check_thumbnail_list(val);
if (errors != enum_bitmask<ThumbnailError>()) {
std::string error_str = format(_u8L("Invalid value provided for parameter %1%: %2%"), "thumbnails", val);
error_str += GCodeThumbnails::get_error_string(errors);
InfoDialog(parent(), _L("G-code flavor is switched"), from_u8(error_str)).ShowModal();
}
if (!thumbnails_list.empty()) {
GCodeThumbnailsFormat old_format = GCodeThumbnailsFormat(m_config->option("thumbnails_format")->getInt());
GCodeThumbnailsFormat new_format = thumbnails_list.begin()->first;
if (old_format != new_format) {
DynamicPrintConfig new_conf = *m_config;
auto* opt = m_config->option("thumbnails_format")->clone();
opt->setInt(int(new_format));
new_conf.set_key_value("thumbnails_format", opt);
load_config(new_conf);
}
}
}
}
update_dirty();
on_value_change(opt_key, value);
});
};
optgroup->append_single_option_line("use_relative_e_distances", "printer_basic_information_advanced#use-relative-e-distances");
optgroup->append_single_option_line("use_firmware_retraction", "printer_basic_information_advanced#use-firmware-retraction");
optgroup->append_single_option_line("time_cost", "printer_basic_information_advanced#time-cost");
}
void TabPrinter::layout_hook_cooling_fan(ConfigOptionsGroup* optgroup)
{
Line line = Line{ L("Fan speed-up time"), optgroup->get_option("fan_speedup_time").opt.tooltip };
line.label_path = "printer_basic_information_cooling_fan#fan-speed-up-time";
line.append_option(optgroup->get_option("fan_speedup_time"));
line.append_option(optgroup->get_option("fan_speedup_overhangs"));
optgroup->append_line(line);
optgroup->append_single_option_line("fan_kickstart", "printer_basic_information_cooling_fan#fan-kick-start-time");
optgroup->append_single_option_line("part_cooling_fan_min_pwm", "printer_basic_information_cooling_fan#minimum-non-zero-part-cooling-fan-speed");
}
// build_fff_extra_layout is kept for backward compatibility.
// It now delegates to the yaml-generated TabPrinter_build_basic_info_layout
// which calls the hook methods above.
void TabPrinter::build_fff_extra_layout()
{
// Superseded: Tab.cpp now calls TabPrinter_build_basic_info_layout(*this) directly.
// Hook methods below are called by that generated function.
}
} } // namespace Slic3r::GUI
+1 -1
View File
@@ -354,7 +354,7 @@ TroubleshootDialog::TroubleshootDialog()
m_sizer->AddSpacer(FromDIP(20));
m_sizer->Add(right_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM | wxRIGHT, FromDIP(15));
SetSizer(m_sizer);
SetSizerAndFit(m_sizer);
Layout();
Fit();
CenterOnParent();
+3
View File
@@ -168,6 +168,9 @@ private:
}
wxClientDC dc(this);
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;
for (size_t i = 0; i < m_lines.size(); ++i) {
+7 -6
View File
@@ -773,7 +773,7 @@ std::vector<std::string> DiffViewCtrl::selected_options()
static std::string none{"none"};
#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_VALUE_WIDTH FromDIP(150)
#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(0, 0, 1, wxTOP, 18);
SetSizer(m_sizer_main);
Layout();
Fit();
Centre(wxBOTH);
if (params) {
if (params->left_to_right)
update_tree(type, params->config, params->from, params->to);
@@ -1095,6 +1090,11 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection *dependent_
//topSizer->SetSizeHints(this);
show_info_line(Action::Undef);
SetSizerAndFit(m_sizer_main);
Layout();
Fit();
// Centre(wxBOTH);
}
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->Wrap(UNSAVE_CHANGE_DIALOG_SCROLL_WINDOW_SIZE.x);
update_tree(type, presets);
update_list();
+1 -2
View File
@@ -213,9 +213,8 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_
m_scrollwindw_release_note->Layout();
SetSizer(m_sizer_main);
SetSizerAndFit(m_sizer_main);
Layout();
m_sizer_main->Fit(this);
Centre(wxBOTH);
wxGetApp().UpdateDlgDarkUI(this);
+288 -91
View File
@@ -1,7 +1,9 @@
#include "WebGuideDialog.hpp"
#include "ConfigWizard.hpp"
#include <boost/algorithm/string/join.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/iostreams/detail/select.hpp>
#include <boost/log/trivial.hpp>
@@ -9,6 +11,7 @@
#include "I18N.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/GUI_App.hpp"
@@ -41,8 +44,6 @@ using namespace nlohmann;
namespace Slic3r { namespace GUI {
json m_ProfileJson;
static wxString update_custom_filaments()
{
json m_Res = json::object();
@@ -190,12 +191,10 @@ GuideFrame::GuideFrame(GUI_App *pGUI, long style)
GuideFrame::~GuideFrame()
{
m_destroy = true;
if (m_load_task && m_load_task->joinable()) {
*m_cancel_token = true; // stop the loading thread and any queued CallAfter lambdas before join
if (m_load_task && m_load_task->joinable())
m_load_task->join();
delete m_load_task;
m_load_task = nullptr;
}
m_load_task.reset();
if (m_browser) {
delete m_browser;
m_browser = nullptr;
@@ -301,15 +300,71 @@ void GuideFrame::OnNavigationRequest(wxWebViewEvent &evt)
/**
* 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)
{
//wxLogMessage("%s", "Navigation complete; url='" + evt.GetURL() + "'");
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;
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();
@@ -762,11 +817,9 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle
bool check_unsaved_preset_changes = false;
std::vector<std::string> install_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) {
if (it.second.size() > 0) {
auto vendor_file = vendor_dir/(it.first + ".json");
if (!fs::exists(vendor_file)) {
if (!is_vendor_installed(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 (enabled_vendors.find(it.first) != enabled_vendors.end())
continue;
auto vendor_file = vendor_dir/(it.first + ".json");
if (fs::exists(vendor_file)) {
if (is_vendor_installed(it.first)) {
remove_bundles.emplace_back(it.first);
}
}
@@ -1127,99 +1179,244 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
return status;
}
int GuideFrame::LoadProfileData()
bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_resource_vendors)
{
try {
m_ProfileJson = json::parse("{}");
m_ProfileJson["model"] = json::array();
m_ProfileJson["machine"] = json::object();
m_ProfileJson["filament"] = json::object();
m_ProfileJson["process"] = json::array();
// Models from vendor profiles
for (const auto& [vendor_id, vp] : bundle.vendors) {
for (const auto& model : vp.models) {
std::string nozzle_str;
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();
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
// Orca: add custom as default
// Orca: add json logic for vendor bundle
orca_bundle_rsrc = true;
// search if there exists a .json file in vendor_dir folder, if exists, set orca_bundle_rsrc to false
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;
json entry;
entry["model"] = model.id;
entry["name"] = model.name;
entry["vendor"] = vp.id;
entry["nozzle_diameter"] = nozzle_str;
entry["materials"] = materials_str;
entry["cover"] = cover_path.string();
entry["nozzle_selected"] = "";
entry["sub_path"] = "";
m_ProfileJson["model"].push_back(entry);
}
}
// load the default filament library first
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)) {
m_OrcaFilaLibPath = (vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (vendor_dir / filament_library_name).string());
} 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);
// Machine map: preset name -> {model, nozzle variant}
for (const Preset& p : bundle.printers()) {
if (!p.is_system || !p.vendor) continue;
const auto* printer_model = p.config.option<ConfigOptionString>("printer_model");
const auto* printer_variant = p.config.option<ConfigOptionString>("printer_variant");
if (!printer_model || printer_model->value.empty() || !printer_variant) continue;
//load custom bundle from user data path
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_destroy)
return 0;
json mach;
mach["model"] = printer_model->value;
mach["nozzle"] = printer_variant->value;
m_ProfileJson["machine"][p.name] = mach;
}
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;
// Filament map from system filament presets (vendor/type already resolved in config)
const json& machines = m_ProfileJson["machine"];
for (const Preset& p : bundle.filaments()) {
if (!p.is_system || !p.vendor) continue;
const auto* fila_vendor = p.config.option<ConfigOptionStrings>("filament_vendor");
const auto* fila_type = p.config.option<ConfigOptionStrings>("filament_type");
const auto* compat_printers = p.config.option<ConfigOptionStrings>("compatible_printers");
LoadProfileFamily(w2s(strVendor), iter->path().string());
loaded_vendors.insert(w2s(strVendor));
std::string vendor = (fila_vendor && !fila_vendor->values.empty()) ? fila_vendor->values[0] : "";
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] {
if (!m_destroy) {
//sync to appconfig first to populate current selections
SaveProfileData();
// Process list from visible system print presets
for (const Preset& p : bundle.prints()) {
if (!p.is_system || !p.vendor || !p.is_visible) continue;
json entry;
entry["name"] = p.name;
entry["sub_path"] = p.file;
m_ProfileJson["process"].push_back(entry);
}
//sync to web after selections are populated
std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
if (require_all_resource_vendors) {
// 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;
json m_Res = json::object();
m_Res["command"] = "userguide_profile_load_finish";
m_Res["sequence_id"] = "10001";
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', true));
BOOST_LOG_TRIVIAL(info) << "GuideFrame: built profile data ("
<< m_ProfileJson["model"].size() << " models, "
<< m_ProfileJson["machine"].size() << " machines, "
<< m_ProfileJson["filament"].size() << " filaments)";
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) {
// wxLogMessage("GUIDE: load_profile_error %s ", e.what());
// wxMessageBox(e.what(), "", MB_OK);
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what() << std::endl;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what();
}
filament_info_cache.clear();
+16 -2
View File
@@ -30,10 +30,14 @@
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include <atomic>
#include <memory>
#include <unordered_map>
#include <nlohmann/json.hpp>
#include <boost/thread.hpp>
namespace Slic3r { namespace GUI {
class GuideFrame : public DPIDialog
@@ -78,6 +82,12 @@ public:
int LoadProfileData();
int SaveProfileData();
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 GetFilamentInfo( std::string VendorDirectory,json & pFilaList, std::string filepath, std::string &sVendor, std::string &sType);
@@ -112,8 +122,11 @@ private:
//First Load
bool bFirstComplete{false};
bool m_destroy{false};
boost::thread* m_load_task{ nullptr };
// Set once in the destructor. Read through `this` by the loading thread
// (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
bool PrivacyUse;
@@ -123,6 +136,7 @@ private:
bool InstallNetplugin;
bool network_plugin_ready {false};
json m_ProfileJson;
json m_OrcaFilaList;
std::string m_OrcaFilaLibPath;
+29 -32
View File
@@ -1042,47 +1042,43 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const
std::set<std::string> bundles;
// Orca: always install filament library
bundles.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
for (auto &dir_entry : boost::filesystem::directory_iterator(rsrc_path)) {
const auto &path = dir_entry.path();
std::string file_path = path.string();
if (is_json_file(file_path)) {
const auto path_in_vendor = vendor_path / path.filename();
std::string vendor_name = path.filename().string();
// Remove the .json suffix.
vendor_name.erase(vendor_name.size() - 5);
if (bundles.find(vendor_name) != bundles.end())continue;
// A vendor is named by its profile or, where the build ships preset caches
// instead of the raw profile JSONs, by its cache alone.
for (const std::string &vendor_name : vendor_names_in(rsrc_path)) {
if (bundles.find(vendor_name) != bundles.end())continue;
const auto is_vendor_enabled = (vendor_name == PresetBundle::ORCA_DEFAULT_BUNDLE) // always update configs from resource to vendor for ORCA_DEFAULT_BUNDLE
|| (enabled_vendors.find(vendor_name) != enabled_vendors.end());
if (enabled_config_update) {
if ( fs::exists(path_in_vendor)) {
if (is_vendor_enabled) {
Semver resource_ver = get_version_from_json(file_path);
Semver vendor_ver = get_version_from_json(path_in_vendor.string());
const auto is_vendor_enabled = (vendor_name == PresetBundle::ORCA_DEFAULT_BUNDLE) // always update configs from resource to vendor for ORCA_DEFAULT_BUNDLE
|| (enabled_vendors.find(vendor_name) != enabled_vendors.end());
if (enabled_config_update) {
if (is_vendor_installed(vendor_name)) {
if (is_vendor_enabled) {
// Orca: whichever form of the vendor resources ships at the newer
// version is the one installing lays down, and the one to judge
// what is installed against.
Semver resource_ver = resource_vendor_version(vendor_name);
// Orca: a vendor installed as a preset cache has no profile
// beside it; the version it was installed at is in the cache.
Semver vendor_ver = installed_vendor_version(vendor_name);
bool version_match = ((resource_ver.maj() == vendor_ver.maj()) && (resource_ver.min() == vendor_ver.min()));
bool version_match = ((resource_ver.maj() == vendor_ver.maj()) && (resource_ver.min() == vendor_ver.min()));
if (!version_match || (vendor_ver < resource_ver)) {
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:found vendor "<<vendor_name<<" newer version "<<resource_ver.to_string() <<" from resource, old version "<<vendor_ver.to_string();
bundles.insert(vendor_name);
}
}
else {
//need to be removed because not installed
fs::remove(path_in_vendor);
const auto path_of_vendor = vendor_path / vendor_name;
if (fs::exists(path_of_vendor))
fs::remove_all(path_of_vendor);
if (!version_match || (vendor_ver < resource_ver)) {
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:found vendor "<<vendor_name<<" newer version "<<resource_ver.to_string() <<" from resource, old version "<<vendor_ver.to_string();
bundles.insert(vendor_name);
}
}
else if (is_vendor_enabled) {
bundles.insert(vendor_name);
else {
//need to be removed because not installed
remove_installed_vendor(vendor_name);
}
}
else if (is_vendor_enabled) {
bundles.insert(vendor_name);
}
}
else if (is_vendor_enabled) {
bundles.insert(vendor_name);
}
}
if (bundles.size() > 0) {
@@ -1162,11 +1158,12 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
auto filament_in_cache = (cache_profile_path / vendor_name / PRESET_FILAMENT_NAME);
auto machine_in_cache = (cache_profile_path / vendor_name / PRESET_PRINTER_NAME);
if (( fs::exists(path_in_vendor))
if (is_vendor_installed(vendor_name)
|| fs::exists(print_in_cache)
|| fs::exists(filament_in_cache)
|| fs::exists(machine_in_cache)) {
Semver vendor_ver = get_version_from_json(path_in_vendor.string());
// Orca: a vendor installed as a preset cache carries its version there.
Semver vendor_ver = installed_vendor_version(vendor_name);
std::map<std::string, std::string> key_values;
std::vector<std::string> keys(3);
+18
View File
@@ -153,6 +153,24 @@ TEST_CASE("Object brims are generated per instance", "[SkirtBrim]")
}
}
TEST_CASE("Uncombined neighboring brims precede their respective objects", "[SkirtBrim]")
{
Print print;
Model model;
place_two_cubes_apart(0, {
{ "skirt_loops", 0 },
{ "brim_type", "outer_only" },
{ "brim_width", 5 },
{ "combine_brims", 0 },
}, print, model);
print.process();
REQUIRE(print.skirt_brim_groups().size() == 1);
REQUIRE(print.skirt_brim_groups().front().brims.size() == 2);
CHECK(role_sequence(gcode(print), { "brim", "perimeter" }) ==
std::vector<std::string>{ "brim", "perimeter", "brim", "perimeter" });
}
TEST_CASE("Combine brims merges neighboring object instances", "[SkirtBrim]")
{
Print print;
+1
View File
@@ -17,6 +17,7 @@ add_executable(${_TEST_NAME}_tests
test_preset_bundle_loading.cpp
test_preset_setting_id.cpp
test_preset_diff.cpp
test_vendor_cache.cpp
test_elephant_foot_compensation.cpp
test_geometry.cpp
test_multimaterial_segmentation.cpp
@@ -146,7 +146,7 @@ TEST_CASE("Current vendor type tolerates missing printer model", "[Preset][Bundl
{
PresetBundle bundle;
VendorProfile orca_vendor("ORCA");
VendorProfile orca_vendor; orca_vendor.id = "ORCA";
VendorProfile::PrinterModel model;
model.name = "Orca Test";
orca_vendor.models.emplace_back(model);
File diff suppressed because it is too large Load Diff
-260
View File
@@ -1,260 +0,0 @@
#!/usr/bin/env python3
"""
Toolchain resolution for the config codegen.
The codegen needs exactly three things: a protoc binary, the protobuf Python
runtime and pyyaml. Every entry point used to install `grpcio-tools` to get
protoc, which drags in the grpcio C extension: it has no Windows/ARM64 wheel and
falls back to building from source there, which is what broke the ARM64 build
("Failed building wheel for grpcio" -> "protoc not found"). Nothing in the
codegen uses gRPC.
Resolution order, so callers only ever run `python tools/run_codegen.py`:
protoc $PROTOC -> PATH -> cached download -> grpc_tools (if installed) ->
pinned, checksum-verified protoc release downloaded into
.codegen-tools/ (gitignored)
runtime the current interpreter, else a cached virtualenv under
.codegen-tools/venv that the entry point re-execs into
Set PROTOC=/path/to/protoc (or put protoc on PATH) to build offline.
"""
import hashlib
import importlib.util
import os
import platform
import shutil
import subprocess
import sys
import tempfile
import urllib.request
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
CACHE_DIR = ROOT / ".codegen-tools"
# Pinned so every machine generates with the same compiler, and checksummed
# because we execute what we download. To bump: change the version and refresh
# every hash from https://github.com/protocolbuffers/protobuf/releases/tag/v<ver>
PROTOC_VERSION = "28.3"
PROTOC_ARCHIVE_SHA256 = {
"win64": "ce64f49bdeddef49ce4bd313a8f59bcf92fcf67b5831efbf66170386d2e66948",
"linux-x86_64": "0ad949f04a6a174da83cdcbdb36dee0a4925272a5b6d83f79a6bf9852076d53f",
"linux-aarch_64": "1de522032a8b194002fe35cab86d747848238b5e4de4f99648372079f5b46f9a",
"osx-universal_binary": "52df502b263da20f3311b23b5c6553d10cc25c6ebb85df381d80a2806b6a698b",
}
# pip name -> import name
PYTHON_PACKAGES = {"protobuf": "google.protobuf", "pyyaml": "yaml"}
# Guards against an endless re-exec loop if the virtualenv still can't import.
_BOOTSTRAP_ENV = "ORCA_CODEGEN_BOOTSTRAPPED"
_PROTOC_EXE = "protoc.exe" if os.name == "nt" else "protoc"
def _protoc_dir():
return CACHE_DIR / f"protoc-{PROTOC_VERSION}"
def _archive_key():
"""Release asset for this host, or None if protobuf ships no build for it."""
if sys.platform == "win32":
# There is no win/arm64 release; the x64 build runs under Windows'
# emulation, which is how the ARM64 CI job gets a protoc.
return "win64"
if sys.platform == "darwin":
return "osx-universal_binary"
if sys.platform.startswith("linux"):
machine = platform.machine().lower()
if machine in ("x86_64", "amd64"):
return "linux-x86_64"
if machine in ("aarch64", "arm64"):
return "linux-aarch_64"
return None
def _download_protoc():
"""Fetch and unpack the pinned protoc. Returns the binary path, or None."""
key = _archive_key()
if key is None:
print(f" ERROR: no pinned protoc release for {sys.platform}/{platform.machine()}.")
print(" Install protoc from your package manager and re-run, or set PROTOC=<path>.")
return None
url = (f"https://github.com/protocolbuffers/protobuf/releases/download/"
f"v{PROTOC_VERSION}/protoc-{PROTOC_VERSION}-{key}.zip")
print(f" Downloading protoc {PROTOC_VERSION} ({key})...")
CACHE_DIR.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(dir=CACHE_DIR) as tmp:
archive = Path(tmp) / "protoc.zip"
try:
with urllib.request.urlopen(url, timeout=120) as response:
archive.write_bytes(response.read())
except OSError as exc:
print(f" ERROR: download failed: {exc}")
print(" Install protoc manually and re-run, or set PROTOC=<path>.")
return None
actual = hashlib.sha256(archive.read_bytes()).hexdigest()
expected = PROTOC_ARCHIVE_SHA256[key]
if actual != expected:
print(f" ERROR: protoc archive checksum mismatch for {key} {PROTOC_VERSION}:")
print(f" expected {expected}")
print(f" actual {actual}")
return None
# Unpack the whole archive, not just bin/: protoc resolves the well-known
# imports (google/protobuf/descriptor.proto) from ../include next to it.
staging = Path(tmp) / "unpacked"
with zipfile.ZipFile(archive) as zf:
zf.extractall(staging)
target = _protoc_dir()
if target.exists():
shutil.rmtree(target)
# Move into place only once complete, so an interrupted run never leaves
# a half-extracted toolchain that later runs would happily use.
shutil.move(str(staging), str(target))
binary = target / "bin" / _PROTOC_EXE
if not binary.exists():
print(f" ERROR: protoc archive did not contain bin/{_PROTOC_EXE}")
return None
binary.chmod(binary.stat().st_mode | 0o755)
return binary
def find_protoc(allow_download=True):
"""Return the protoc command as a list, or None if it can't be resolved."""
override = os.environ.get("PROTOC")
if override:
return [override]
on_path = shutil.which("protoc")
if on_path:
return [on_path]
cached = _protoc_dir() / "bin" / _PROTOC_EXE
if cached.exists():
return [str(cached)]
# Honour a pre-existing grpcio-tools install rather than downloading.
if importlib.util.find_spec("grpc_tools") is not None:
return [sys.executable, "-m", "grpc_tools.protoc"]
if allow_download:
binary = _download_protoc()
if binary is not None:
return [str(binary)]
return None
def missing_packages():
"""pip names of the required Python packages this interpreter can't import."""
missing = []
for pip_name, module in PYTHON_PACKAGES.items():
try:
found = importlib.util.find_spec(module) is not None
except (ImportError, ValueError):
found = False
if not found:
missing.append(pip_name)
return missing
def _venv_python():
venv_dir = CACHE_DIR / "venv"
if os.name == "nt":
return venv_dir / "Scripts" / "python.exe"
return venv_dir / "bin" / "python"
def bootstrap_python():
"""The cached virtualenv interpreter, if it exists and has the packages."""
python = _venv_python()
if not python.exists():
return None
probe = subprocess.run([str(python), "-c", "import google.protobuf, yaml"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return python if probe.returncode == 0 else None
def _ensure_venv(missing):
"""Create/reuse .codegen-tools/venv with the required packages installed."""
python = _venv_python()
if not python.exists():
print(f" Creating codegen virtualenv in {python.parent.parent}...")
if subprocess.run([sys.executable, "-m", "venv", str(python.parent.parent)]).returncode != 0 \
or not python.exists():
return None
print(f" Installing {', '.join(missing)} into the codegen virtualenv...")
result = subprocess.run([str(python), "-m", "pip", "install", "--quiet",
"--disable-pip-version-check", *missing])
return python if result.returncode == 0 else None
def ensure_python_runtime():
"""
Guarantee protobuf + pyyaml are importable.
If they aren't, re-run the calling script in a cached virtualenv that has
them and exit with its status. Keeping the packages out of the caller's
interpreter is what lets the build scripts work on distros that refuse
`pip install` into a system Python (PEP 668).
"""
missing = missing_packages()
if not missing:
return
if os.environ.get(_BOOTSTRAP_ENV):
# We are already inside the bootstrapped environment: installing again
# would just fail the same way.
print(f" ERROR: {', '.join(missing)} still missing after bootstrap.")
sys.exit(1)
# Reuse a complete virtualenv as-is: builds call this every time, and pip
# would otherwise hit the network on each one.
python = bootstrap_python() or _ensure_venv(missing)
if python is None:
print(f" ERROR: could not provide {', '.join(missing)}.")
print(f" Install them for this interpreter: {sys.executable} -m pip install "
f"{' '.join(missing)}")
sys.exit(1)
env = dict(os.environ, **{_BOOTSTRAP_ENV: "1"})
sys.exit(subprocess.run([str(python), *sys.argv], env=env).returncode)
def toolchain_ready():
"""
True if the codegen can run without downloading or installing anything --
either from this interpreter or by re-execing into an existing bootstrap
virtualenv. This is what decides whether a build regenerates on proto edits.
"""
if missing_packages() and bootstrap_python() is None:
return False
return find_protoc(allow_download=False) is not None
def main():
# --check is what ConfigCodegen.cmake probes with: it answers whether this
# interpreter can regenerate during a build, and must not download, install
# or create anything while doing so.
if "--check" in sys.argv[1:]:
return 0 if toolchain_ready() else 1
missing = missing_packages()
print(f"python: {sys.executable}")
print(f"packages: {'all present' if not missing else 'missing ' + ', '.join(missing)}")
protoc = find_protoc()
print(f"protoc: {' '.join(protoc) if protoc else 'NOT FOUND'}")
return 0 if protoc else 1
if __name__ == "__main__":
sys.exit(main())
File diff suppressed because it is too large Load Diff
-67
View File
@@ -1,67 +0,0 @@
#!/usr/bin/env python3
"""
Access to the orca.* option extensions declared in config_metadata.proto.
The extensions are read straight out of the compiled descriptor set: protoc runs
with --include_imports, so config_metadata.proto travels inside the .desc file
that the codegen already consumes. Registering it in the default descriptor pool
before the descriptor set is parsed is what makes the custom options resolve
instead of landing in unknown fields.
Doing it this way keeps generated code out of git. The alternative -- a checked-in
config_metadata_pb2.py -- also pinned the protobuf runtime to whichever protoc
produced it, because gencode embeds a hard ValidateProtobufRuntimeVersion() check.
"""
from google.protobuf import descriptor_pb2, descriptor_pool
METADATA_PROTO = "config_metadata.proto"
class Metadata:
"""
Stand-in for the generated config_metadata_pb2 module.
Exposes each orca extension as an attribute holding its FieldDescriptor
(usable as `options.Extensions[meta.label]`) and each enum value as an int
constant (`meta.MODE_SIMPLE`, `meta.STEP_SLICE`, ...), matching how the
generated module was used.
"""
def __init__(self, file_descriptor):
for name, extension in file_descriptor.extensions_by_name.items():
setattr(self, name, extension)
for enum in file_descriptor.enum_types_by_name.values():
for value in enum.values:
setattr(self, value.name, value.number)
def load_descriptor_set(path):
"""
Read a protoc descriptor set and return (FileDescriptorSet, Metadata).
The file is parsed twice on purpose: the first pass only locates the embedded
config_metadata.proto so its extensions can be registered, the second one
parses with those extensions known.
"""
with open(path, 'rb') as f:
raw = f.read()
probe = descriptor_pb2.FileDescriptorSet()
probe.ParseFromString(raw)
metadata_file = next((f for f in probe.file if f.name == METADATA_PROTO), None)
if metadata_file is None:
raise RuntimeError(
f"{path} does not contain {METADATA_PROTO} -- protoc must be run with "
"--include_imports")
pool = descriptor_pool.Default()
try:
file_descriptor = pool.FindFileByName(METADATA_PROTO)
except KeyError:
pool.Add(metadata_file)
file_descriptor = pool.FindFileByName(METADATA_PROTO)
descriptor_set = descriptor_pb2.FileDescriptorSet()
descriptor_set.ParseFromString(raw)
return descriptor_set, Metadata(file_descriptor)
-106
View File
@@ -1,106 +0,0 @@
#!/usr/bin/env python3
"""
Convenience script: runs the codegen pipeline.
1. Compile .proto -> binary descriptor set (protoc)
2. Generate C++ from descriptors (config_codegen.py)
3. Validate output against original
The toolchain (protoc, protobuf, pyyaml) is resolved by codegen_toolchain.py, so
this script is the single entry point every build script and CI job calls -- no
`pip install` lines needed around it.
Usage:
python tools/run_codegen.py # full pipeline
python tools/run_codegen.py --validate-only # just validate
"""
import argparse
import subprocess
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import codegen_toolchain # noqa: E402
ROOT = Path(__file__).resolve().parent.parent
PROTO_DIR = ROOT / "src" / "PrintConfigs"
CODEGEN_OUT = ROOT / "src" / "slic3r" / "GUI" / "generated"
DESC_FILE = ROOT / "config.desc"
LAYOUT_YAML = PROTO_DIR / "layout.yaml"
def run(cmd, **kwargs):
print(f" $ {' '.join(str(c) for c in cmd)}")
result = subprocess.run(cmd, **kwargs)
if result.returncode != 0:
print(f" FAILED (exit code {result.returncode})")
return False
return True
def step_compile():
print("\n=== Step 1: Compile .proto -> descriptor set ===")
proto_files = [f for f in PROTO_DIR.glob("*.proto") if not f.name.endswith("_gen.proto") and f.name != "config_metadata.proto"]
if not proto_files:
print(" ERROR: No .proto files found")
return False
protoc = codegen_toolchain.find_protoc()
if protoc is None:
return False
return run(protoc + [
f"--proto_path={PROTO_DIR}",
f"--descriptor_set_out={DESC_FILE}",
"--include_imports",
] + [str(f) for f in proto_files])
def step_generate():
print("\n=== Step 2: Generate C++ from descriptors + layout.yaml ===")
return run([sys.executable, str(ROOT / "tools" / "config_codegen.py"),
str(DESC_FILE), str(CODEGEN_OUT)])
def step_lint():
print("\n=== Lint: proto sanity checks ===")
return run([sys.executable, str(ROOT / "tools" / "config_codegen.py"),
str(DESC_FILE), str(CODEGEN_OUT), "--lint-only"])
def step_validate():
print("\n=== Step 3: Validate ===")
return run([sys.executable, str(ROOT / "tools" / "validate_codegen.py")])
def main():
parser = argparse.ArgumentParser(description="Run OrcaSlicer config codegen pipeline")
parser.add_argument("--validate-only", action="store_true",
help="Only run validation")
parser.add_argument("--no-validate", action="store_true",
help="Skip validation step (used by cmake build)")
args = parser.parse_args()
# Re-execs into a virtualenv with protobuf/pyyaml if this interpreter lacks them.
codegen_toolchain.ensure_python_runtime()
if args.validate_only:
# Compile + lint the protos, then check the generated files against PrintConfig.cpp.
sys.exit(0 if (step_compile() and step_lint() and step_validate()) else 1)
for name, fn in [("Compile", step_compile), ("Generate", step_generate)]:
if not fn():
print(f"\n*** Pipeline FAILED at: {name} ***")
sys.exit(1)
if not args.no_validate:
if not step_validate():
print("\n*** Validate FAILED (run with --no-validate to skip) ***")
sys.exit(1)
print("\n=== Pipeline completed successfully ===")
if __name__ == "__main__":
main()
-214
View File
@@ -1,214 +0,0 @@
#!/usr/bin/env python3
"""
Validates generated PrintConfigDef code against the original PrintConfig.cpp.
Compares setting keys, types, defaults, enum values/labels, and metadata
to ensure the codegen output is a faithful reproduction.
"""
import re
import json
import sys
from pathlib import Path
from collections import OrderedDict
def parse_original_settings(cpp_path):
"""Parse the original init_fff_params() into a dict of key -> properties."""
with open(cpp_path, 'r', encoding='utf-8') as f:
text = f.read()
m = re.search(r'void PrintConfigDef::init_fff_params\(\)(.*?)^\}', text, re.DOTALL | re.MULTILINE)
if not m:
print("ERROR: Could not find init_fff_params()")
sys.exit(1)
body = m.group(1)
settings = OrderedDict()
current_key = None
for line in body.split('\n'):
stripped = line.strip()
# Detect this->add("key", coType)
add_match = re.search(r'this->add(?:_nullable)?\s*\(\s*"([^"]+)"\s*,\s*(co\w+)\s*\)', stripped)
if add_match:
current_key = add_match.group(1)
co_type = add_match.group(2)
# Last definition wins (handles duplicates)
settings[current_key] = {
'co_type': co_type,
'has_default': False,
'enum_values': 0,
'enum_labels': 0,
'has_enum_map': False,
}
continue
if current_key and current_key in settings:
s = settings[current_key]
if 'set_default_value' in stripped:
s['has_default'] = True
if re.search(r'enum_values\.(?:push_back|emplace_back)', stripped):
s['enum_values'] += 1
if re.search(r'enum_labels\.(?:push_back|emplace_back)', stripped):
s['enum_labels'] += 1
if 'enum_keys_map' in stripped and '=' in stripped:
s['has_enum_map'] = True
return settings
def parse_generated_settings(gen_path):
"""Parse the generated PrintConfigDef code into a dict of key -> properties."""
with open(gen_path, 'r', encoding='utf-8') as f:
text = f.read()
settings = OrderedDict()
current_key = None
for line in text.split('\n'):
stripped = line.strip()
add_match = re.search(r'this->add(?:_nullable)?\s*\(\s*"([^"]+)"\s*,\s*(co\w+)\s*\)', stripped)
if add_match:
current_key = add_match.group(1)
co_type = add_match.group(2)
settings[current_key] = {
'co_type': co_type,
'has_default': False,
'enum_values': 0,
'enum_labels': 0,
'has_enum_map': False,
}
continue
if current_key and current_key in settings:
s = settings[current_key]
if 'set_default_value' in stripped:
s['has_default'] = True
if 'enum_values.push_back' in stripped:
s['enum_values'] += 1
if 'enum_labels.push_back' in stripped:
s['enum_labels'] += 1
if 'enum_keys_map' in stripped and '=' in stripped:
s['has_enum_map'] = True
return settings
def main():
root = Path(__file__).resolve().parent.parent
orig_path = root / "src/libslic3r/PrintConfig.cpp"
gen_path = root / "src/slic3r/GUI/generated/PrintConfigDef_generated.cpp"
if not orig_path.exists() or not gen_path.exists():
print("ERROR: Required files not found")
sys.exit(1)
print("Parsing original...")
orig = parse_original_settings(orig_path)
print(f" {len(orig)} settings")
print("Parsing generated...")
gen = parse_generated_settings(gen_path)
print(f" {len(gen)} settings")
# Known exceptions: settings that exist in original but are commented out
# or have runtime-generated enums
known_exceptions = {
'adaptive_layer_height', # Commented out in original
'spaghetti_detector', # Commented out in original
}
# Settings with runtime-generated enum values (loop over MaterialType::all())
runtime_enum_keys = {
'filament_type', # enum_values from runtime loop
}
# Compare
errors = []
warnings = []
# Missing keys
orig_keys = set(orig.keys())
gen_keys = set(gen.keys())
missing = orig_keys - gen_keys
extra = gen_keys - orig_keys
if missing:
real_missing = missing - known_exceptions
if real_missing:
errors.append(f"MISSING from generated ({len(real_missing)}): {sorted(real_missing)}")
noted = missing & known_exceptions
if noted:
warnings.append(f"Known exceptions (commented out in original): {sorted(noted)}")
if extra:
warnings.append(f"EXTRA in generated ({len(extra)}): {sorted(extra)}")
# Compare shared keys
shared = orig_keys & gen_keys
type_mismatches = []
default_mismatches = []
enum_val_mismatches = []
enum_lbl_mismatches = []
enum_map_mismatches = []
for key in sorted(shared):
o = orig[key]
g = gen[key]
if o['co_type'] != g['co_type']:
type_mismatches.append(f" {key}: orig={o['co_type']} gen={g['co_type']}")
if o['has_default'] != g['has_default'] and key not in known_exceptions:
default_mismatches.append(f" {key}: orig={o['has_default']} gen={g['has_default']}")
if o['enum_values'] != g['enum_values'] and key not in runtime_enum_keys:
enum_val_mismatches.append(f" {key}: orig={o['enum_values']} gen={g['enum_values']}")
if o['enum_labels'] != g['enum_labels']:
enum_lbl_mismatches.append(f" {key}: orig={o['enum_labels']} gen={g['enum_labels']}")
if o['has_enum_map'] != g['has_enum_map']:
enum_map_mismatches.append(f" {key}: orig={o['has_enum_map']} gen={g['has_enum_map']}")
# Report
print("\n=== VALIDATION RESULTS ===\n")
if type_mismatches:
errors.append(f"TYPE MISMATCHES ({len(type_mismatches)}):\n" + "\n".join(type_mismatches))
if default_mismatches:
errors.append(f"DEFAULT MISMATCHES ({len(default_mismatches)}):\n" + "\n".join(default_mismatches))
if enum_val_mismatches:
warnings.append(f"ENUM VALUE COUNT MISMATCHES ({len(enum_val_mismatches)}):\n" + "\n".join(enum_val_mismatches))
if enum_lbl_mismatches:
warnings.append(f"ENUM LABEL COUNT MISMATCHES ({len(enum_lbl_mismatches)}):\n" + "\n".join(enum_lbl_mismatches))
if enum_map_mismatches:
warnings.append(f"ENUM MAP MISMATCHES ({len(enum_map_mismatches)}):\n" + "\n".join(enum_map_mismatches))
if warnings:
print("WARNINGS:")
for w in warnings:
print(f" {w}")
print()
if errors:
print("ERRORS:")
for e in errors:
print(f" {e}")
print(f"\nValidation FAILED with {len(errors)} error(s)")
sys.exit(1)
else:
print(f"All {len(shared)} shared settings validated successfully")
if extra:
print(f" ({len(extra)} extra settings from axis expansion)")
print("\nValidation PASSED")
if __name__ == "__main__":
main()