mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 21:42:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32ead05bdd | ||
|
|
3ab9cf53d0 | ||
|
|
252df70ec4 | ||
|
|
086bbf986b | ||
|
|
67e1ce03a6 | ||
|
|
36bd453ac8 | ||
|
|
9292db2f9f | ||
|
|
bef47b2c70 | ||
|
|
5b475e5e98 | ||
|
|
56810c8c7f | ||
|
|
c3c37e474a | ||
|
|
1696d5ca39 | ||
|
|
466c36eaa3 | ||
|
|
5792fef805 | ||
|
|
bc016af1c9 | ||
|
|
7a378d2fc4 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -46,11 +46,6 @@ 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
|
||||
|
||||
@@ -59,31 +59,7 @@ ctest --test-dir ./tests/fff_print
|
||||
|
||||
## Localization & translations
|
||||
|
||||
Catalogs live in `localization/i18n/<lang>/OrcaSlicer_<lang>.po`; the template is `OrcaSlicer.pot`.
|
||||
See the [Localization guide](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_guide.md) for the human-facing version of these principles.
|
||||
|
||||
### Terminology
|
||||
|
||||
- Use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language, and terms that must stay in English (brand/product names, acronyms, materials, file formats, G-code tokens, macros/variables/identifiers) are not translated.
|
||||
- If a term's established translation changes, update both the affected `.po` files and the glossary (`localization_glossary.tsv`, then regenerate) so they stay in sync.
|
||||
- Translate the *meaning*, not the words. Check what the string actually controls before translating it — English reuses one word for different things. `Flow ratio` (multiplier), `Flow Rate` (throughput) and `Flow Dynamics` (pressure compensation) are three different terms; `extruder` may mean the toolhead, the feeder motor, or the nozzle depending on the string.
|
||||
- Reuse one template per recurring message shape (`Failed to connect to …`, `Are you sure you want to …?`), even where the English wording varies.
|
||||
|
||||
### Editing rules
|
||||
|
||||
- Only edit `msgstr` — **never** change `msgid`, and never "fix" wrong English in the translation alone. Report the source string instead.
|
||||
- Preserve exactly: placeholders (`%s`, `%d`, `%1%`, `%zu`, `%%`), every `\n` (count *and* position, including leading/trailing), leading/trailing spaces, HTML tags, `℃`, and the file's encoding and line endings.
|
||||
- **Never reorder positional arguments** in a `c-format` string. If the msgid is `%d` then `%s`, that order must hold — swapping them breaks at runtime.
|
||||
- `msgctxt` separates homonyms — always read it. `Back`/`Camera View` is the rear view of the 3D navigator, while `Back`/`Navigation` is the go-back button; `Top` exists in the *Alignment*, *Layers* and *Camera View* senses.
|
||||
- When a string needs disambiguating, add context in the source (`_L_CONTEXT`/`_u8L_CONTEXT`), don't work around it in the translation.
|
||||
- A literal `%` inside a string xgettext flagged `possible-c-format` will fail `msgfmt`. Fix it with a `// xgettext:no-c-format, no-boost-format` comment above the string in the source — do not mangle the translation or use `%%` in text that is never passed through printf.
|
||||
- Plural entries: read `nplurals` from the catalog's `Plural-Forms` header (it is **not** always 2 — ja/ko/zh/th/vi use 1, ru/cs/pl/lt use 3, uk uses 4). Each form must be genuinely inflected for its quantity; repeating one sentence across all forms is a bug in Slavic/Baltic languages, though it is correct for Turkish and Hungarian.
|
||||
- An entry whose `msgstr` equals its `msgid` is untranslated even though it is not empty; a plural entry with any empty form is likewise incomplete.
|
||||
- Mark machine-produced translations with an `# AI Translated` translator comment. Don't add it to a human translation you didn't actually rewrite.
|
||||
- Don't reflow or re-wrap unrelated entries — keep the diff limited to the strings you changed.
|
||||
|
||||
### Verifying
|
||||
|
||||
- `scripts/run_gettext.bat --full` (Windows) regenerates the template, merges every catalog and compiles the `.mo` files. It must exit 0.
|
||||
- Or check a single catalog with `msgfmt --check-format -o <out>.mo localization/i18n/<lang>/OrcaSlicer_<lang>.po`.
|
||||
- Fuzzy entries are not shown to users. If you correct one, clear its `fuzzy` flag, otherwise the fix never ships.
|
||||
- Translation catalogs live in `localization/i18n/<lang>/OrcaSlicer_<lang>.po`.
|
||||
- When creating or reviewing translations, use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language and terms that must stay in English (brand/product names, acronyms, file formats, G-code, macros/variables) are not translated.
|
||||
- If a term's established translation changes, update both the affected `.po` files and the glossary so they stay in sync.
|
||||
- Only edit `msgstr` (never `msgid`); keep placeholders (`%s`, `%1%`, `\n`), context (`msgctxt`), and file encoding/line endings intact.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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%
|
||||
|
||||
|
||||
@@ -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%
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
```
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-26 21:59-0300\n"
|
||||
"POT-Creation-Date: 2026-07-23 15:24-0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -982,7 +982,7 @@ msgstr ""
|
||||
#, possible-boost-format
|
||||
msgid ""
|
||||
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
||||
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
||||
"Thank you."
|
||||
msgstr ""
|
||||
|
||||
@@ -3385,6 +3385,7 @@ msgstr ""
|
||||
msgid "Innerloop"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
|
||||
@@ -5469,24 +5470,10 @@ msgstr ""
|
||||
msgid "Align to Y axis"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgctxt "Camera View"
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgctxt "Camera View"
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
@@ -5867,13 +5854,19 @@ msgstr ""
|
||||
msgid "Top View"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgid "Bottom View"
|
||||
msgstr ""
|
||||
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
msgid "Front View"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Rear"
|
||||
msgstr ""
|
||||
|
||||
@@ -14736,7 +14729,7 @@ msgstr ""
|
||||
msgid "Retract amount after wipe"
|
||||
msgstr ""
|
||||
|
||||
#, no-c-format, no-boost-format
|
||||
#, possible-c-format
|
||||
msgid ""
|
||||
"The length of fast retraction after wipe, relative to retraction length.\n"
|
||||
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
||||
|
||||
+1422
-2289
File diff suppressed because it is too large
Load Diff
+1391
-2210
File diff suppressed because it is too large
Load Diff
+921
-1021
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Orca Slicer\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-26 21:59-0300\n"
|
||||
"POT-Creation-Date: 2026-07-23 15:24-0300\n"
|
||||
"PO-Revision-Date: 2026-06-17 15:44-0300\n"
|
||||
"Last-Translator: Alexandre Folle de Menezes\n"
|
||||
"Language-Team: \n"
|
||||
@@ -978,7 +978,7 @@ msgstr ""
|
||||
#, boost-format
|
||||
msgid ""
|
||||
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
||||
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
||||
"Thank you."
|
||||
msgstr ""
|
||||
|
||||
@@ -3381,6 +3381,7 @@ msgstr ""
|
||||
msgid "Innerloop"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
|
||||
@@ -5465,24 +5466,10 @@ msgstr ""
|
||||
msgid "Align to Y axis"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgctxt "Camera View"
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgctxt "Camera View"
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
@@ -5863,13 +5850,19 @@ msgstr ""
|
||||
msgid "Top View"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgid "Bottom View"
|
||||
msgstr ""
|
||||
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
msgid "Front View"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Rear"
|
||||
msgstr ""
|
||||
|
||||
@@ -14732,7 +14725,7 @@ msgstr ""
|
||||
msgid "Retract amount after wipe"
|
||||
msgstr ""
|
||||
|
||||
#, no-c-format, no-boost-format
|
||||
#, c-format
|
||||
msgid ""
|
||||
"The length of fast retraction after wipe, relative to retraction length.\n"
|
||||
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
||||
|
||||
@@ -3,7 +3,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Orca Slicer\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-26 21:59-0300\n"
|
||||
"POT-Creation-Date: 2026-07-23 15:24-0300\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Ian A. Bassi <>\n"
|
||||
"Language-Team: \n"
|
||||
@@ -983,11 +983,11 @@ msgstr "Conector"
|
||||
#, boost-format
|
||||
msgid ""
|
||||
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
||||
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
||||
"Thank you."
|
||||
msgstr ""
|
||||
"Los objetos(%1%) tienen conectores duplicados. Es posible que falten algunos conectores en el resultado del laminado.\n"
|
||||
"Informe al equipo de OrcaSlicer sobre el escenario en el que se produjo este problema.\n"
|
||||
"Informe al equipo de PrusaSlicer sobre el escenario en el que se produjo este problema.\n"
|
||||
"Gracias."
|
||||
|
||||
msgid "Cut by Plane"
|
||||
@@ -3459,6 +3459,7 @@ msgstr "Recámara"
|
||||
msgid "Innerloop"
|
||||
msgstr "Bucle interno"
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgid "Top"
|
||||
msgstr "Superior"
|
||||
|
||||
@@ -5629,27 +5630,10 @@ msgstr "Evitar la zona de calibración del extrusor"
|
||||
msgid "Align to Y axis"
|
||||
msgstr "Alinear con el eje Y"
|
||||
|
||||
# AI Translated
|
||||
msgctxt "Camera View"
|
||||
msgid "Front"
|
||||
msgstr "Frontal"
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Back"
|
||||
msgstr "Posterior"
|
||||
|
||||
# AI Translated
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgctxt "Camera View"
|
||||
msgid "Top"
|
||||
msgstr "Superior"
|
||||
|
||||
# AI Translated
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgctxt "Camera View"
|
||||
msgid "Bottom"
|
||||
msgstr "Inferior"
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Left"
|
||||
msgstr "Izquierda"
|
||||
@@ -6036,14 +6020,19 @@ msgstr "Vista por Defecto"
|
||||
msgid "Top View"
|
||||
msgstr "Vista superior"
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgid "Bottom"
|
||||
msgstr "Inferior"
|
||||
|
||||
msgid "Bottom View"
|
||||
msgstr "Vista inferior"
|
||||
|
||||
msgid "Front"
|
||||
msgstr "Frontal"
|
||||
|
||||
msgid "Front View"
|
||||
msgstr "Vista frontal"
|
||||
|
||||
# AI Translated
|
||||
msgctxt "Camera View"
|
||||
msgid "Rear"
|
||||
msgstr "Posterior"
|
||||
|
||||
@@ -15501,7 +15490,7 @@ msgstr "La longitud de la retracción rápida antes de la purga, en relación co
|
||||
msgid "Retract amount after wipe"
|
||||
msgstr "Cantidad de retracción después de la limpieza"
|
||||
|
||||
#, no-c-format, no-boost-format
|
||||
#, c-format
|
||||
msgid ""
|
||||
"The length of fast retraction after wipe, relative to retraction length.\n"
|
||||
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
||||
@@ -20744,15 +20733,6 @@ msgstr ""
|
||||
"Evita la deformación\n"
|
||||
"¿Sabías que al imprimir materiales propensos a la deformación como el ABS, aumentar adecuadamente la temperatura de la cama térmica puede reducir la probabilidad de deformaciones?"
|
||||
|
||||
#~ msgid "Bottom"
|
||||
#~ msgstr "Inferior"
|
||||
|
||||
#~ msgid "Front"
|
||||
#~ msgstr "Frontal"
|
||||
|
||||
#~ msgid "Rear"
|
||||
#~ msgstr "Posterior"
|
||||
|
||||
#~ msgid "Enter"
|
||||
#~ msgstr "Enter"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1169
-1686
File diff suppressed because it is too large
Load Diff
+1171
-1737
File diff suppressed because it is too large
Load Diff
+1663
-3324
File diff suppressed because it is too large
Load Diff
+1592
-2749
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+3151
-6445
File diff suppressed because it is too large
Load Diff
+1698
-3017
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+3414
-7058
File diff suppressed because it is too large
Load Diff
+506
-1168
File diff suppressed because it is too large
Load Diff
+1391
-2267
File diff suppressed because it is too large
Load Diff
+1449
-2462
File diff suppressed because it is too large
Load Diff
+2211
-4065
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -70,7 +70,7 @@
|
||||
"wipe_tower_no_sparse_layers": "0",
|
||||
"wipe_tower_cone_angle": "30",
|
||||
"wipe_tower_wall_type": "rib",
|
||||
"wipe_tower_extra_rib_length": "0",
|
||||
"wipe_tower_extra_rib_length": "8",
|
||||
"prime_tower_width": "35",
|
||||
"prime_volume": "30",
|
||||
"wall_generator": "arachne",
|
||||
|
||||
@@ -393,9 +393,6 @@ modules:
|
||||
- type: dir
|
||||
path: ../../localization
|
||||
dest: localization
|
||||
- type: dir
|
||||
path: ../../tools
|
||||
dest: tools
|
||||
|
||||
- type: file
|
||||
path: ../../CMakeLists.txt
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
];
|
||||
|
||||
}
|
||||
@@ -248,8 +248,6 @@ set(lisbslic3r_sources
|
||||
GCode/Thumbnails.hpp
|
||||
GCode/ToolOrdering.cpp
|
||||
GCode/ToolOrdering.hpp
|
||||
GCode/OrderingStrategies.cpp
|
||||
GCode/OrderingStrategies.hpp
|
||||
GCode/WipeTower2.cpp
|
||||
GCode/WipeTower2.hpp
|
||||
GCode/WipeTower.cpp
|
||||
@@ -493,21 +491,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 ()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define slic3r_Config_hpp_
|
||||
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <climits>
|
||||
#include <cfloat>
|
||||
@@ -780,10 +781,14 @@ public:
|
||||
this->values[i] = rhs_vec->values[i];
|
||||
modified = true;
|
||||
} else {
|
||||
if ((i < default_index.size()) && (default_index[i] < default_value.size()))
|
||||
// Orca: a negative slot (failed variant lookup) must not silently collapse the
|
||||
// whole array to the first slot's value — the int-vs-size_t comparison used to
|
||||
// promote -1 past the bounds check. Keep the slot's own value (get_at-style
|
||||
// clamp) when no valid index is available.
|
||||
if ((i < default_index.size()) && (default_index[i] >= 0) && (size_t(default_index[i]) < default_value.size()))
|
||||
this->values[i] = default_value[default_index[i]];
|
||||
else
|
||||
this->values[i] = default_value[0];
|
||||
this->values[i] = default_value[std::min(i, default_value.size() - 1)];
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
@@ -2106,6 +2111,11 @@ public:
|
||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||
this->value = rhs->getInt();
|
||||
// Orca: options embedded in a StaticPrintConfig are constructed without a keys_map;
|
||||
// adopt the source's so a later serialize() can emit names.
|
||||
if (this->keys_map == nullptr)
|
||||
if (auto rhs_generic = dynamic_cast<const ConfigOptionEnumGeneric *>(rhs))
|
||||
this->keys_map = rhs_generic->keys_map;
|
||||
}
|
||||
|
||||
std::string serialize() const override
|
||||
@@ -2162,7 +2172,12 @@ public:
|
||||
if (rhs->type() != this->type())
|
||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||
// rhs could be of the following type: ConfigOptionEnumsGeneric
|
||||
this->values = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs)->values;
|
||||
auto rhs_enums = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs);
|
||||
this->values = rhs_enums->values;
|
||||
// Orca: options embedded in a StaticPrintConfig are constructed without a keys_map;
|
||||
// adopt the source's so a later serialize() emits names instead of empty tokens.
|
||||
if (this->keys_map == nullptr)
|
||||
this->keys_map = rhs_enums->keys_map;
|
||||
}
|
||||
|
||||
std::string serialize() const override
|
||||
|
||||
+163
-218
@@ -13,8 +13,8 @@
|
||||
#include "GCode/PrintExtents.hpp"
|
||||
#include "GCode/Thumbnails.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
#include "GCode/WipeTower2.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include "GCode/OrderingStrategies.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
@@ -889,6 +889,65 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
return res;
|
||||
}
|
||||
|
||||
// Type2 tower-local point -> bed frame. The rib-wall offset is tower-local, so it
|
||||
// rotates with the tower (unlike the BBL tower in append_tcr, which never rotates).
|
||||
Vec2f WipeTowerIntegration::transform_wt2_pt(const Vec2f &pt) const
|
||||
{
|
||||
const float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
|
||||
return Eigen::Rotation2Df(alpha) * (pt + m_rib_offset) + m_wipe_tower_pos;
|
||||
}
|
||||
|
||||
// Printable-area bounds for tower-approach routing, in object coordinates (shared by
|
||||
// the BBL avoid-perimeter path in append_tcr and the Type2 skip-points router).
|
||||
// Multi-nozzle: clamp the travel bounds to the region every extruder can reach
|
||||
// (get_extruder_shared_printable_polygon) instead of the full bed. Gated on the
|
||||
// multi-nozzle predicate so every existing single/dual printer keeps the historic
|
||||
// full-printable_area routing byte-identical.
|
||||
BoundingBox WipeTowerIntegration::printer_travel_bounds(GCode &gcodegen) const
|
||||
{
|
||||
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
||||
BoundingBox printer_bbx;
|
||||
if (is_multi_nozzle_printer(gcodegen.m_config)) {
|
||||
printer_bbx = get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon());
|
||||
printer_bbx.min = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.min) + plate_origin_2d);
|
||||
printer_bbx.max = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.max) + plate_origin_2d);
|
||||
} else {
|
||||
Points bed_points;
|
||||
for (const auto& p : gcodegen.m_config.printable_area.values)
|
||||
bed_points.push_back(wipe_tower_point_to_object_point(gcodegen, p.cast<float>() + plate_origin_2d));
|
||||
printer_bbx = BoundingBox(bed_points);
|
||||
}
|
||||
return printer_bbx;
|
||||
}
|
||||
|
||||
// With skip points enabled the Type2 tower wall has an opening at each toolchange's
|
||||
// entry (tcr.start_pos): route the approach around the tower's bounding box so the
|
||||
// nozzle enters through that opening instead of dragging across the printed wall
|
||||
// (append_tcr parity). Emits only the waypoints leading up to the opening — the
|
||||
// caller still travels to start_wipe_pos itself. Returns an empty string when the
|
||||
// gap wall is off (option off or cone wall) or the approach already starts inside
|
||||
// the tower: such hops never cross the wall and must stay direct.
|
||||
std::string WipeTowerIntegration::travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const
|
||||
{
|
||||
if (!WipeTower2::use_gap_wall(gcodegen.m_config))
|
||||
return {};
|
||||
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
||||
// Transform the tower-local bbx corners exactly like the tcr points; a rotated
|
||||
// tower gets a conservative axis-aligned envelope.
|
||||
Polygon avoid_points = scaled(m_wipe_tower_bbx).polygon();
|
||||
for (auto& p : avoid_points.points)
|
||||
p = wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(unscale(p).cast<float>()) + plate_origin_2d);
|
||||
BoundingBox avoid_bbx(avoid_points.points);
|
||||
if (avoid_bbx.contains(route_start))
|
||||
return {};
|
||||
Polyline travel_polyline = generate_path_to_wipe_tower(route_start, start_wipe_pos, avoid_bbx, printer_travel_bounds(gcodegen));
|
||||
std::string gcode;
|
||||
// The polyline's last point is start_wipe_pos itself — emitted by the caller.
|
||||
for (size_t i = 0; i + 1 < travel_polyline.points.size(); ++i)
|
||||
gcode += gcodegen.travel_to(travel_polyline.points[i], erMixed, "Travel to a Wipe Tower");
|
||||
return gcode;
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_filament_id, double z) const
|
||||
{
|
||||
if (new_filament_id != -1 && new_filament_id != tcr.new_tool)
|
||||
@@ -999,6 +1058,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
std::string change_filament_gcode = gcodegen.config().change_filament_gcode.value;
|
||||
|
||||
bool is_used_travel_avoid_perimeter = gcodegen.m_config.prime_tower_skip_points.value;
|
||||
if (is_nozzle_change && !tcr.nozzle_change_result.is_extruder_change) is_used_travel_avoid_perimeter = false;
|
||||
|
||||
// add nozzle change gcode into change filament gcode
|
||||
std::string nozzle_change_gcode_trans;
|
||||
@@ -1261,24 +1321,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
Vec2f gcode_last_pos2d{gcode_last_pos[0], gcode_last_pos[1]};
|
||||
Point gcode_last_pos2d_object = gcodegen.gcode_to_point(gcode_last_pos2d.cast<double>() + plate_origin_2d.cast<double>());
|
||||
Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d);
|
||||
BoundingBox avoid_bbx, printer_bbx;
|
||||
{
|
||||
// set printer_bbx
|
||||
// Multi-nozzle: clamp the avoid-perimeter travel bounds to the region every
|
||||
// extruder can reach (get_extruder_shared_printable_polygon) instead of the full
|
||||
// bed. Gated on the multi-nozzle predicate so H2D and every existing single/dual
|
||||
// printer keep the historic full-printable_area routing byte-identical.
|
||||
if (is_multi_nozzle_printer(gcodegen.m_config)) {
|
||||
printer_bbx = get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon());
|
||||
printer_bbx.min = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.min) + plate_origin_2d);
|
||||
printer_bbx.max = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.max) + plate_origin_2d);
|
||||
} else {
|
||||
Pointfs bed_pointsf = gcodegen.m_config.printable_area.values;
|
||||
Points bed_points;
|
||||
for (auto p : bed_pointsf) { bed_points.push_back(wipe_tower_point_to_object_point(gcodegen, p.cast<float>() + plate_origin_2d)); }
|
||||
printer_bbx = BoundingBox(bed_points);
|
||||
}
|
||||
}
|
||||
BoundingBox avoid_bbx, printer_bbx = printer_travel_bounds(gcodegen);
|
||||
{
|
||||
// set avoid_bbx
|
||||
avoid_bbx = scaled(m_wipe_tower_bbx);
|
||||
@@ -1308,20 +1351,23 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
}
|
||||
|
||||
// do unretract after setting current extruder_id
|
||||
// PETG filaments on a device with a filament switcher get a small (2 mm) pre-extrusion
|
||||
// before the tool change. has_filament_switcher is a develop-only key read defensively from the
|
||||
// full config (Orca does not carry it as a static PrintConfig member — same convention as
|
||||
// enable_filament_dynamic_map); no shipping profile sets it (grep resources/profiles = 0), so
|
||||
// is_petg_pre_extrusion is always false -> extra_unretract stays 0 -> byte-identical to the plain
|
||||
// unretract() fleet-wide. The tower-interface contact pre-extrusion length (the
|
||||
// is_contact_pre_extrusion branch) is NOT applied here; it is only computed as the guard used to
|
||||
// give the contact path priority over PETG.
|
||||
// BBS pattern: the wipe tower shifts the toolchange start position outward for the
|
||||
// tower-interface (contact) pre-extrusion and for the PETG-with-filament-switcher case;
|
||||
// the pre-extrusion material itself is laid down here as extra unretract on the approach.
|
||||
// has_filament_switcher is a develop-only key read defensively from the full config (Orca
|
||||
// does not carry it as a static PrintConfig member — same convention as
|
||||
// enable_filament_dynamic_map); no shipping profile sets it, so is_petg_pre_extrusion is
|
||||
// always false fleet-wide.
|
||||
const ConfigOptionBool* has_filament_switcher_opt = gcodegen.m_print->full_print_config().option<ConfigOptionBool>("has_filament_switcher");
|
||||
bool is_contact_pre_extrusion = tcr.is_contact && gcodegen.m_config.enable_tower_interface_features;
|
||||
bool is_petg_pre_extrusion = !is_contact_pre_extrusion
|
||||
&& gcodegen.config().filament_type.get_at(tcr.new_tool) == "PETG"
|
||||
&& has_filament_switcher_opt && has_filament_switcher_opt->value;
|
||||
float extra_unretract = is_petg_pre_extrusion ? 2.f : 0.f;
|
||||
float extra_unretract = 0.f;
|
||||
if (is_contact_pre_extrusion)
|
||||
extra_unretract = gcodegen.m_config.filament_tower_interface_pre_extrusion_length.get_at(tcr.new_tool);
|
||||
else if (is_petg_pre_extrusion)
|
||||
extra_unretract = 2.f;
|
||||
std::string toolchange_unretract_str = (extra_unretract > 0.f) ? gcodegen.unretract(extra_unretract) : gcodegen.unretract();
|
||||
check_add_eol(toolchange_unretract_str);
|
||||
|
||||
@@ -1419,20 +1465,16 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
|
||||
float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
|
||||
|
||||
auto transform_wt_pt = [&alpha, this](const Vec2f &pt) -> Vec2f {
|
||||
Vec2f out = Eigen::Rotation2Df(alpha) * pt;
|
||||
out += m_wipe_tower_pos;
|
||||
return out;
|
||||
};
|
||||
|
||||
// Priming lines are absolute bed moves; everything else is tower-local
|
||||
// (transform_wt2_pt).
|
||||
Vec2f start_pos = tcr.start_pos;
|
||||
Vec2f end_pos = tcr.end_pos;
|
||||
if (!tcr.priming) {
|
||||
start_pos = transform_wt_pt(start_pos);
|
||||
end_pos = transform_wt_pt(end_pos);
|
||||
start_pos = transform_wt2_pt(start_pos);
|
||||
end_pos = transform_wt2_pt(end_pos);
|
||||
}
|
||||
|
||||
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
|
||||
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : Vec2f(m_wipe_tower_pos + Eigen::Rotation2Df(alpha) * m_rib_offset);
|
||||
float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
|
||||
Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
||||
|
||||
@@ -1462,16 +1504,22 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
|| is_ramming
|
||||
|| tool_change_on_wipe_tower);
|
||||
|
||||
if (should_travel_to_tower || gcodegen.m_need_change_layer_lift_z) {
|
||||
const Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d);
|
||||
const bool travel_to_tower_now = should_travel_to_tower || gcodegen.m_need_change_layer_lift_z;
|
||||
if (travel_to_tower_now) {
|
||||
// FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges,
|
||||
// then we could simplify the condition and make it more readable.
|
||||
gcode += gcodegen.retract();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcode += gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed, "Travel to a Wipe Tower");
|
||||
if (!tcr.priming && gcodegen.last_pos_defined())
|
||||
gcode += travel_to_tower_gap(gcodegen, gcodegen.last_pos(), start_wipe_pos);
|
||||
gcode += gcodegen.travel_to(start_wipe_pos, erMixed, "Travel to a Wipe Tower");
|
||||
gcode += gcodegen.unretract();
|
||||
} else {
|
||||
// When this is multiextruder printer without any ramming, we can just change
|
||||
// the tool without travelling to the tower.
|
||||
// the tool without travelling to the tower. The tower entry travel then lives
|
||||
// inside the tcr gcode; with skip points on it is rerouted below, once the
|
||||
// toolchange gcode (and the head position it ends at) is known.
|
||||
}
|
||||
|
||||
if (will_go_down) {
|
||||
@@ -1494,6 +1542,36 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
toolchange_temp_override = interface_temp;
|
||||
}
|
||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override); // TODO: toolchange_z vs print_z
|
||||
if (!travel_to_tower_now && !tcr.priming && WipeTower2::use_gap_wall(gcodegen.m_config)) {
|
||||
// The tool changed in place (multi-tool printer without ramming), so the
|
||||
// tower entry is the tcr's own positioning move — a straight line across
|
||||
// the printed wall. Route it around the tower and in through the wall
|
||||
// opening instead, riding at the end of the change_filament_gcode
|
||||
// substitution so the generator's positioning move degrades to a
|
||||
// zero-length one (append_tcr parity: travel after the filament change,
|
||||
// retracted, with the new filament).
|
||||
Vec3f last_gcode_pos = gcodegen.writer().get_position().cast<float>();
|
||||
Point route_start;
|
||||
bool have_start = false;
|
||||
if (GCodeProcessor::get_last_position_from_gcode(toolchange_gcode_str, last_gcode_pos)) {
|
||||
// A custom change_filament_gcode may have moved the head (tool docks
|
||||
// etc.); recover the real position from the emitted gcode.
|
||||
route_start = gcodegen.gcode_to_point(Vec2d(last_gcode_pos.x(), last_gcode_pos.y()) + plate_origin_2d.cast<double>());
|
||||
have_start = true;
|
||||
} else if (gcodegen.last_pos_defined()) {
|
||||
route_start = gcodegen.last_pos();
|
||||
have_start = true;
|
||||
}
|
||||
if (have_start) {
|
||||
gcodegen.set_last_pos(route_start);
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
std::string travel = travel_to_tower_gap(gcodegen, route_start, start_wipe_pos);
|
||||
travel += gcodegen.travel_to(start_wipe_pos, erMixed, "Travel to a Wipe Tower");
|
||||
check_add_eol(travel);
|
||||
toolchange_gcode_str += travel;
|
||||
gcodegen.set_last_pos(start_wipe_pos);
|
||||
}
|
||||
}
|
||||
if (gcodegen.config().enable_prime_tower) {
|
||||
deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true);
|
||||
Vec3d position{gcodegen.writer().get_position()};
|
||||
@@ -1679,7 +1757,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
// Prepare a future wipe.
|
||||
gcodegen.m_wipe.reset_path();
|
||||
for (const Vec2f& wipe_pt : tcr.wipe_path)
|
||||
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt) + plate_origin_2d));
|
||||
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(wipe_pt) + plate_origin_2d));
|
||||
}
|
||||
|
||||
// Let the planner know we are traveling between objects.
|
||||
@@ -2805,7 +2883,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
m_role_based_fan_marker_layer.fill(-1);
|
||||
|
||||
m_fan_mover.release();
|
||||
m_ordering_cache.clear();
|
||||
|
||||
m_writer.set_is_bbl_machine(is_bbl_printers);
|
||||
|
||||
@@ -3126,20 +3203,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z.
|
||||
// Therefore initialize the printing extruders from there.
|
||||
this->set_extruders(tool_ordering.all_extruders());
|
||||
print_object_instances_ordering =
|
||||
// By default, order object instances using nearest-neighbor chaining plus
|
||||
// 2-opt and crossing-removal post-processing.
|
||||
(print.config().print_order == PrintOrder::Default ? chain_print_object_instances(print)
|
||||
// Snake: serpentine row traversal + 2-opt
|
||||
: (print.config().print_order == PrintOrder::Snake ? chain_print_object_instances_snake(print)
|
||||
// Best of all: run every strategy, pick the shortest total path
|
||||
: (print.config().print_order == PrintOrder::BestOfStrategies ? chain_print_object_instances_best_of(print)
|
||||
print_object_instances_ordering =
|
||||
// By default, order object instances using a nearest neighbor search.
|
||||
print.config().print_order == PrintOrder::Default ? chain_print_object_instances(print)
|
||||
// Otherwise same order as the object list
|
||||
: sort_object_instances_by_model_order(print))));
|
||||
|
||||
|
||||
|
||||
|
||||
: sort_object_instances_by_model_order(print);
|
||||
}
|
||||
if (initial_extruder_id == (unsigned int)-1) {
|
||||
// Nothing to print!
|
||||
@@ -5530,9 +5598,7 @@ LayerResult GCode::process_layer(
|
||||
//Calibration Layer-specific GCode
|
||||
switch (print.calib_mode()) {
|
||||
case CalibMode::Calib_PA_Tower: {
|
||||
gcode += writer().set_pressure_advance(this->interpolate_value_across_layers(static_cast<float>(print.calib_params().start),
|
||||
static_cast<float>(print.calib_params().end),
|
||||
static_cast<float>(print.calib_params().step)));
|
||||
gcode += writer().set_pressure_advance(print.calib_params().start + static_cast<int>(print_z) * print.calib_params().step);
|
||||
break;
|
||||
}
|
||||
case CalibMode::Calib_Temp_Tower: {
|
||||
@@ -5540,12 +5606,7 @@ LayerResult GCode::process_layer(
|
||||
break;
|
||||
}
|
||||
case CalibMode::Calib_VFA_Tower: {
|
||||
// Step the outer wall speed from start to end across the tower's layers. Plater::calib_VFA sizes the
|
||||
// geometry so each speed step spans one visual block (a fixed number of layers), so the layer-based
|
||||
// stepping stays aligned with the blocks regardless of nozzle size / layer height.
|
||||
float _speed = this->interpolate_value_across_layers(static_cast<float>(print.calib_params().start),
|
||||
static_cast<float>(print.calib_params().end),
|
||||
static_cast<float>(print.calib_params().step));
|
||||
auto _speed = print.calib_params().start + std::floor(print_z / 5.0) * print.calib_params().step;
|
||||
m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloatsNullable({std::round(_speed)}));
|
||||
break;
|
||||
}
|
||||
@@ -5977,128 +6038,41 @@ LayerResult GCode::process_layer(
|
||||
if (m_farthest_point_timelapse.enabled)
|
||||
compute_farthest_point(layers, most_used_extruder, support_filaments);
|
||||
|
||||
// Per filament: instances to print, and the visit sequence over them. Island-level ordering
|
||||
// may visit an instance more than once per layer; otherwise one visit per instance.
|
||||
std::map<unsigned int, std::pair<std::vector<InstanceToPrint>, std::vector<InstanceVisit>>> filament_to_print_instances;
|
||||
std::map<unsigned int, std::vector<InstanceToPrint>> filament_to_print_instances;
|
||||
{
|
||||
// Order individual islands rather than whole instances. Off for by-object sequencing,
|
||||
// sequential printing, and the explicit AsObjectList order, which tour whole instances.
|
||||
const bool island_level_ordering = print.config().print_sequence != PrintSequence::ByObject &&
|
||||
single_object_instance_idx == size_t(-1) &&
|
||||
print.config().print_order != PrintOrder::AsObjectList;
|
||||
for (unsigned int filament_id : layer_tools.extruders) {
|
||||
auto objects_by_extruder_it = by_extruder.find(filament_id);
|
||||
if (objects_by_extruder_it == by_extruder.end()) continue;
|
||||
|
||||
auto &filament_plan = filament_to_print_instances[filament_id];
|
||||
|
||||
if (!island_level_ordering) {
|
||||
// One visit per instance, printing all of its islands.
|
||||
filament_plan.first = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
|
||||
filament_plan.second.reserve(filament_plan.first.size());
|
||||
for (size_t i = 0; i < filament_plan.first.size(); ++i)
|
||||
filament_plan.second.push_back({i, {}, true});
|
||||
continue;
|
||||
}
|
||||
|
||||
int plate_idx = print.get_plate_index();
|
||||
Point wt_pos(print.config().wipe_tower_x.get_at(plate_idx), print.config().wipe_tower_y.get_at(plate_idx));
|
||||
|
||||
// Build the instances and one tour node per non-empty island (a single node for
|
||||
// instances without chainable islands). Positions quantized to 1 mm so small
|
||||
// centroid drift between layers still hits the tour cache below.
|
||||
std::vector<GCode::ObjectByExtruder> &objects_by_extruder = objects_by_extruder_it->second;
|
||||
std::vector<InstanceToPrint> &instances = filament_plan.first;
|
||||
std::vector<IslandOrderNode> nodes;
|
||||
std::vector<size_t> node_instances;
|
||||
auto quantize_to_mm = [](const Point &pt) -> Point {
|
||||
const coord_t grid = coord_t(scale_(1.));
|
||||
// Round to the nearest 1 mm symmetrically (integer division truncates toward
|
||||
// zero, which would make the bucket straddling the origin twice as wide).
|
||||
auto q = [grid](coord_t v) -> coord_t {
|
||||
return ((v >= 0 ? v + grid / 2 : v - grid / 2) / grid) * grid;
|
||||
};
|
||||
return Point(q(pt.x()), q(pt.y()));
|
||||
};
|
||||
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
|
||||
std::vector<const PrintObject *> print_objects;
|
||||
for (int obj_idx = 0; obj_idx < objects_by_extruder.size(); obj_idx++) {
|
||||
auto &object_by_extruder = objects_by_extruder[obj_idx];
|
||||
if (object_by_extruder.islands.empty() && (object_by_extruder.support == nullptr || object_by_extruder.support->empty())) continue;
|
||||
|
||||
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
|
||||
const PrintObject *print_object = layers[layer_id].original_object;
|
||||
if (print_object == nullptr)
|
||||
continue;
|
||||
const Layer *obj_layer = layers[layer_id].object_layer;
|
||||
std::vector<ObjectByExtruder::Island> &islands = object_by_extruder.islands;
|
||||
const bool islands_chainable = obj_layer != nullptr && islands.size() == obj_layer->lslices.size() + 1;
|
||||
for (size_t instance_id = 0; instance_id < print_object->instances().size(); ++instance_id) {
|
||||
const size_t instance_idx = instances.size();
|
||||
instances.emplace_back(object_by_extruder, layer_id, *print_object, instance_id,
|
||||
print_object->instances()[instance_id].model_instance->get_labeled_id());
|
||||
const Point &shift = print_object->instances()[instance_id].shift;
|
||||
const size_t first_node = nodes.size();
|
||||
if (islands_chainable)
|
||||
for (size_t i = 0; i + 1 < islands.size(); ++i)
|
||||
if (!islands[i].by_region.empty()) {
|
||||
nodes.push_back({print_object->id(), instance_id, i,
|
||||
quantize_to_mm(obj_layer->lslices[i].contour.centroid() + shift)});
|
||||
node_instances.emplace_back(instance_idx);
|
||||
}
|
||||
if (nodes.size() == first_node) {
|
||||
// No chainable islands: tour the whole instance as one stop.
|
||||
nodes.push_back({print_object->id(), instance_id, size_t(-1), quantize_to_mm(shift)});
|
||||
node_instances.emplace_back(instance_idx);
|
||||
}
|
||||
}
|
||||
print_objects.push_back(print.get_object(obj_idx));
|
||||
}
|
||||
|
||||
// Reuse the cached tour while this filament's island layout is unchanged.
|
||||
auto &cache_entry = m_ordering_cache[filament_id];
|
||||
if (!(cache_entry.first == nodes)) {
|
||||
cache_entry.first = nodes;
|
||||
Points node_points;
|
||||
node_points.reserve(nodes.size());
|
||||
for (const IslandOrderNode &node : nodes)
|
||||
node_points.emplace_back(node.pos);
|
||||
std::vector<size_t> tour = order_points_with_strategy(node_points, print.config().print_order, &wt_pos);
|
||||
// Chained starting near the wipe tower, reversed so the layer ends near it.
|
||||
std::reverse(tour.begin(), tour.end());
|
||||
std::vector<const PrintInstance *> new_ordering = chain_print_object_instances(print_objects, &wt_pos);
|
||||
std::reverse(new_ordering.begin(), new_ordering.end());
|
||||
|
||||
// Group consecutive tour stops of the same instance into visits.
|
||||
std::vector<InstanceVisit> visits;
|
||||
std::vector<bool> instance_seen(instances.size(), false);
|
||||
std::vector<int> last_visit_of_instance(instances.size(), -1);
|
||||
for (size_t node_idx : tour) {
|
||||
const size_t instance_idx = node_instances[node_idx];
|
||||
if (visits.empty() || visits.back().instance_idx != instance_idx) {
|
||||
visits.push_back({instance_idx, {}, !instance_seen[instance_idx]});
|
||||
instance_seen[instance_idx] = true;
|
||||
}
|
||||
if (nodes[node_idx].island_idx != size_t(-1))
|
||||
visits.back().islands.emplace_back(nodes[node_idx].island_idx);
|
||||
last_visit_of_instance[instance_idx] = int(visits.size()) - 1;
|
||||
}
|
||||
// The trailing catch-all island has no geometry to chain by; append it to the
|
||||
// instance's last visit.
|
||||
for (size_t i = 0; i < instances.size(); ++i) {
|
||||
if (last_visit_of_instance[i] < 0)
|
||||
continue;
|
||||
InstanceVisit &last_visit = visits[size_t(last_visit_of_instance[i])];
|
||||
if (last_visit.islands.empty())
|
||||
// A visit without explicit islands already prints everything.
|
||||
continue;
|
||||
std::vector<ObjectByExtruder::Island> &islands = instances[i].object_by_extruder.islands;
|
||||
if (!islands.back().by_region.empty())
|
||||
last_visit.islands.emplace_back(islands.size() - 1);
|
||||
}
|
||||
cache_entry.second = std::move(visits);
|
||||
if (print.config().print_sequence == PrintSequence::ByObject) {
|
||||
filament_to_print_instances[filament_id] = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
|
||||
} else {
|
||||
|
||||
// PrintSequence::ByLayer to use global ordering ( per object ordering ) if intra-layer order PrintOrder::AsObjectList is specified while keeping behaviour of PrintSequence::ByLayer
|
||||
const std::vector<const PrintInstance*>* ordering_for_filament = (print.config().print_order == PrintOrder::AsObjectList && ordering != nullptr) ? ordering: &new_ordering;
|
||||
filament_to_print_instances[filament_id] = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering_for_filament, single_object_instance_idx);
|
||||
}
|
||||
filament_plan.second = cache_entry.second;
|
||||
}
|
||||
}
|
||||
|
||||
std::set<size_t> layer_object_label_ids;
|
||||
for (auto iter = filament_to_print_instances.begin(); iter != filament_to_print_instances.end(); ++iter) {
|
||||
for (const InstanceToPrint &instance : iter->second.first) {
|
||||
for (const InstanceToPrint &instance : iter->second) {
|
||||
layer_object_label_ids.insert(instance.label_object_id);
|
||||
}
|
||||
}
|
||||
@@ -6168,7 +6142,7 @@ LayerResult GCode::process_layer(
|
||||
|
||||
if (print.config().print_sequence == PrintSequence::ByLayer && m_enable_exclude_object && print.config().support_object_skip_flush.value) {
|
||||
std::vector<size_t> filament_instances_id;
|
||||
for (InstanceToPrint &instance : filament_to_print_instances[extruder_id].first) filament_instances_id.emplace_back(instance.label_object_id);
|
||||
for (InstanceToPrint &instance : filament_to_print_instances[extruder_id]) filament_instances_id.emplace_back(instance.label_object_id);
|
||||
m_filament_instances_code = _encode_label_ids_to_base64(filament_instances_id);
|
||||
}
|
||||
|
||||
@@ -6249,9 +6223,7 @@ LayerResult GCode::process_layer(
|
||||
if (layer_tools.has_wipe_tower && m_wipe_tower)
|
||||
m_last_processor_extrusion_role = erWipeTower;
|
||||
|
||||
auto &filament_plan = filament_to_print_instances[extruder_id];
|
||||
std::vector<InstanceToPrint> &instances_to_print = filament_plan.first;
|
||||
const std::vector<InstanceVisit> &instance_visits = filament_plan.second;
|
||||
std::vector<InstanceToPrint> &instances_to_print = filament_to_print_instances[extruder_id];
|
||||
|
||||
// We are almost ready to print. However, we must go through all the objects twice to print the overridden extrusions first (infill/perimeter wiping feature):
|
||||
std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
|
||||
@@ -6259,11 +6231,10 @@ LayerResult GCode::process_layer(
|
||||
if (is_anything_overridden && print_wipe_extrusions == 0)
|
||||
gcode+="; PURGING FINISHED\n";
|
||||
|
||||
for (const InstanceVisit &visit : instance_visits) {
|
||||
InstanceToPrint &instance_to_print = instances_to_print[visit.instance_idx];
|
||||
for (InstanceToPrint &instance_to_print : instances_to_print) {
|
||||
const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id];
|
||||
const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id];
|
||||
if (visit.first_visit && print_wipe_extrusions == (is_anything_overridden ? 1 : 0)) {
|
||||
if (print_wipe_extrusions == (is_anything_overridden ? 1 : 0)) {
|
||||
gcode += generate_object_skirt_group(print, instance_to_print.print_object, instance_to_print.instance_id, layer_tools, layer, extruder_id);
|
||||
gcode += generate_object_brim(print, instance_to_print.print_object, instance_to_print.instance_id, first_layer);
|
||||
}
|
||||
@@ -6316,7 +6287,7 @@ LayerResult GCode::process_layer(
|
||||
m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
m_last_obj_copy = this_object_copy;
|
||||
this->set_origin(unscale(offset));
|
||||
if (visit.first_visit && instance_to_print.object_by_extruder.support != nullptr) {
|
||||
if (instance_to_print.object_by_extruder.support != nullptr) {
|
||||
m_layer = layers[instance_to_print.layer_id].support_layer;
|
||||
m_object_layer_over_raft = false;
|
||||
|
||||
@@ -6350,42 +6321,9 @@ LayerResult GCode::process_layer(
|
||||
m_layer = layer_to_print.layer();
|
||||
m_object_layer_over_raft = object_layer_over_raft;
|
||||
}
|
||||
//FIXME order islands?
|
||||
// Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
|
||||
// Island print order. Use the islands the tour assigned to this visit; if none,
|
||||
// chain all islands nearest-neighbor from the current nozzle position (last_pos(),
|
||||
// in this instance's frame after set_origin() above). Empty islands are skipped;
|
||||
// the trailing catch-all island has no centroid to chain by and always goes last.
|
||||
std::vector<ObjectByExtruder::Island> &islands = instance_to_print.object_by_extruder.islands;
|
||||
std::vector<size_t> island_order = visit.islands;
|
||||
if (island_order.empty()) {
|
||||
island_order.reserve(islands.size());
|
||||
if (layer_to_print.object_layer != nullptr && islands.size() == layer_to_print.object_layer->lslices.size() + 1) {
|
||||
for (size_t i = 0; i + 1 < islands.size(); ++i)
|
||||
if (!islands[i].by_region.empty())
|
||||
island_order.emplace_back(i);
|
||||
if (island_order.size() > 1) {
|
||||
Points island_centroids;
|
||||
island_centroids.reserve(island_order.size());
|
||||
for (size_t i : island_order)
|
||||
island_centroids.emplace_back(layer_to_print.object_layer->lslices[i].contour.centroid());
|
||||
const Point start_near = this->last_pos();
|
||||
std::vector<size_t> chain = chain_points(island_centroids, this->last_pos_defined() ? &start_near : nullptr);
|
||||
std::vector<size_t> ordered;
|
||||
ordered.reserve(island_order.size());
|
||||
for (size_t k : chain)
|
||||
ordered.emplace_back(island_order[k]);
|
||||
island_order = std::move(ordered);
|
||||
}
|
||||
if (!islands.back().by_region.empty())
|
||||
island_order.emplace_back(islands.size() - 1);
|
||||
} else {
|
||||
// Unexpected islands layout, keep the stored order.
|
||||
for (size_t i = 0; i < islands.size(); ++i)
|
||||
island_order.emplace_back(i);
|
||||
}
|
||||
}
|
||||
for (size_t island_idx : island_order) {
|
||||
ObjectByExtruder::Island &island = islands[island_idx];
|
||||
for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
|
||||
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast<unsigned int>(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region;
|
||||
// When starting a new object, use the external motion planner for the first travel move.
|
||||
const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
|
||||
@@ -8350,22 +8288,29 @@ std::string GCode::extrusion_role_to_string_for_parser(const ExtrusionRole & rol
|
||||
}
|
||||
|
||||
// Calculate the interpolated value for the current layer between start_value and end_value.
|
||||
// Step > 0 splits the range into equal-width bands from first to last value (both inclusive).
|
||||
// Step will create equal layers steps from first to last value.
|
||||
// Step = 0 means gradual interpolation finishing at last value.
|
||||
float GCode::interpolate_value_across_layers(float start_value, float end_value, float step) const
|
||||
{
|
||||
if (m_layer_index <= 1) {
|
||||
return start_value;
|
||||
}
|
||||
const float ratio = m_layer_index / (m_layer_count - 1.f);
|
||||
if (step > 0.f) {
|
||||
// Discrete equal-width bands. band is clamped to the last band so the result can't overshoot the range:
|
||||
// at the top layer ratio * n_bands == n_bands, which would otherwise index one band past the end.
|
||||
const int n_bands = std::lround(std::abs(end_value - start_value) / step) + 1;
|
||||
const int band = std::min(n_bands - 1, static_cast<int>(ratio * n_bands));
|
||||
return start_value + (end_value >= start_value ? 1.f : -1.f) * band * step;
|
||||
else {
|
||||
bool use_steps = step > 0.f;
|
||||
if (use_steps) {
|
||||
if (start_value > end_value) {
|
||||
start_value += step;
|
||||
} else {
|
||||
end_value += step;
|
||||
}
|
||||
}
|
||||
float ratio = m_layer_index / (m_layer_count - 1.f);
|
||||
float value = start_value + ratio * (end_value - start_value);
|
||||
if (use_steps) {
|
||||
value = trunc(value / step) * step;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return start_value + ratio * (end_value - start_value);
|
||||
}
|
||||
|
||||
std::string encodeBase64(uint64_t value)
|
||||
|
||||
+3
-34
@@ -132,6 +132,9 @@ private:
|
||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const BoundingBox &printer_bbx) const;
|
||||
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
std::string travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const;
|
||||
Vec2f transform_wt2_pt(const Vec2f &pt) const;
|
||||
BoundingBox printer_travel_bounds(GCode &gcodegen) const;
|
||||
|
||||
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
||||
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
||||
@@ -539,40 +542,6 @@ private:
|
||||
// Cache for custom seam enforcers/blockers for each layer.
|
||||
SeamPlacer m_seam_placer;
|
||||
|
||||
// One stop of the island-level tour: consecutive islands of a single instance. An instance
|
||||
// can have several visits per layer when its islands are toured non-consecutively.
|
||||
struct InstanceVisit
|
||||
{
|
||||
// Index into the per-filament InstanceToPrint vector.
|
||||
size_t instance_idx;
|
||||
// Islands to print, in order (indices into ObjectByExtruder::islands). Empty: print all
|
||||
// islands, ordered at extrusion time.
|
||||
std::vector<size_t> islands;
|
||||
// First visit of this instance this layer; skirt, brim and support are emitted here.
|
||||
bool first_visit;
|
||||
};
|
||||
|
||||
// One node of the island-level tour, also used as cache key: identity plus quantized position.
|
||||
struct IslandOrderNode
|
||||
{
|
||||
ObjectID object_id;
|
||||
size_t instance_id;
|
||||
// Index into ObjectByExtruder::islands, or size_t(-1) for an instance without chainable
|
||||
// islands (e.g. support only), which is toured as a single stop.
|
||||
size_t island_idx;
|
||||
// Island centroid in G-code coordinates, quantized to 1 mm for cache stability.
|
||||
Point pos;
|
||||
bool operator==(const IslandOrderNode &rhs) const {
|
||||
return object_id == rhs.object_id && instance_id == rhs.instance_id &&
|
||||
island_idx == rhs.island_idx && pos == rhs.pos;
|
||||
}
|
||||
};
|
||||
|
||||
// Cache the per-filament island tour to avoid recomputing while the layer's island layout is
|
||||
// unchanged. Key: filament_id. Value: {nodes the tour was computed from, resulting visits}.
|
||||
std::map<unsigned int, std::pair<std::vector<IslandOrderNode>, std::vector<InstanceVisit>>>
|
||||
m_ordering_cache;
|
||||
|
||||
ExtrusionQualityEstimator m_extrusion_quality_estimator;
|
||||
|
||||
|
||||
|
||||
@@ -1450,8 +1450,8 @@ void GCodeProcessor::run_post_process()
|
||||
// flag) runs none of this. It is pure data construction — it only fills m_filament_blocks /
|
||||
// m_extruder_blocks / m_machine_*_gcode_*_line_id and never touches the exported g-code, so even
|
||||
// the enable_pre_heating fleet stays byte-identical (nothing reads the blocks until the injection
|
||||
// pass). In practice it also stays empty/degenerate today because no template/code yet emits the
|
||||
// MACHINE_*_GCODE_* / NOZZLE_CHANGE_* / CP_TOOLCHANGE_WIPE markers it keys off.
|
||||
// pass). The wipe tower emits the NOZZLE_CHANGE_* (ramming) and CP_TOOLCHANGE_WIPE markers this
|
||||
// builder keys off; the MACHINE_*_GCODE_* markers come from the machine g-code templates.
|
||||
m_filament_blocks.clear();
|
||||
m_extruder_blocks.clear();
|
||||
m_machine_start_gcode_end_line_id = (unsigned int) (-1);
|
||||
|
||||
@@ -1,435 +0,0 @@
|
||||
// Print-object ordering strategies: implementation.
|
||||
// Consolidates TSP post-processing, Snake, and Best-of-Strategies.
|
||||
|
||||
#include "OrderingStrategies.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/* ====================================================================
|
||||
* TSP post-processing utilities
|
||||
* ==================================================================== */
|
||||
|
||||
bool tsp_2opt_improve(std::vector<size_t>& path, const Points& centers, int max_passes)
|
||||
{
|
||||
size_t pn = path.size();
|
||||
if (pn <= 2) return false;
|
||||
|
||||
// Pre-compute edge lengths once per pass to avoid redundant norm() calls.
|
||||
auto recompute_edges = [&]() {
|
||||
std::vector<double> el(pn);
|
||||
for (size_t i = 0; i < pn; ++i) {
|
||||
size_t ni = (i + 1) % pn;
|
||||
el[i] = (centers[path[i]].cast<double>() - centers[path[ni]].cast<double>()).norm();
|
||||
}
|
||||
return el;
|
||||
};
|
||||
std::vector<double> el = recompute_edges();
|
||||
|
||||
// Pre-compute squared edge lengths for early rejection in the inner loop.
|
||||
auto recompute_edges_sq = [&]() {
|
||||
std::vector<double> elsq(pn);
|
||||
for (size_t i = 0; i < pn; ++i) {
|
||||
size_t ni = (i + 1) % pn;
|
||||
elsq[i] = (centers[path[i]].cast<double>() - centers[path[ni]].cast<double>()).squaredNorm();
|
||||
}
|
||||
return elsq;
|
||||
};
|
||||
std::vector<double> elsq = recompute_edges_sq();
|
||||
|
||||
bool improved = false;
|
||||
for (int pass = 0; max_passes <= 0 || pass < max_passes; ++pass) {
|
||||
size_t best_i = pn, best_j = pn;
|
||||
double best_gain = 0;
|
||||
|
||||
for (size_t i = 0; i < pn; ++i) {
|
||||
const Vec2d& pi = centers[path[i]].cast<double>();
|
||||
const Vec2d& p_in = centers[path[(i + 1) % pn]].cast<double>();
|
||||
double d_i = el[i];
|
||||
double d_i_sq = elsq[i];
|
||||
|
||||
for (size_t j = i + 2; j < pn; ++j) {
|
||||
size_t j_next = (j + 1) % pn;
|
||||
// Skip the swap that would reverse the entire cycle (removes both
|
||||
// edges (0,1) and (pn-1,0), equivalent to traversing the cycle backwards).
|
||||
if (i == 0 && j_next == 0) continue;
|
||||
|
||||
const Vec2d& pj = centers[path[j]].cast<double>();
|
||||
const Vec2d& p_jn = centers[path[j_next]].cast<double>();
|
||||
double d_j = el[j];
|
||||
|
||||
// Early rejection using squared distances (avoids 2 sqrt calls).
|
||||
double new_a_sq = (pj - pi).squaredNorm();
|
||||
double new_b_sq = (p_jn - p_in).squaredNorm();
|
||||
if (new_a_sq >= d_i_sq && new_b_sq >= elsq[j]) continue;
|
||||
|
||||
double new_a = std::sqrt(new_a_sq);
|
||||
double new_b = std::sqrt(new_b_sq);
|
||||
double gain = d_i + d_j - new_a - new_b;
|
||||
|
||||
if (gain > best_gain) {
|
||||
best_gain = gain;
|
||||
best_i = i; best_j = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best_i == pn) break;
|
||||
improved = true;
|
||||
// Reverse the best swap segment
|
||||
std::reverse(path.begin() + best_i + 1, path.begin() + best_j + 1);
|
||||
|
||||
// Recompute edge lengths after reversal
|
||||
el = recompute_edges();
|
||||
elsq = recompute_edges_sq();
|
||||
}
|
||||
return improved;
|
||||
}
|
||||
|
||||
// Fast bounding-box overlap test (rejects most non-intersecting pairs).
|
||||
static inline bool bboxes_overlap(const Point& a, const Point& b, const Point& c, const Point& d)
|
||||
{
|
||||
return !(std::max(a.x(), b.x()) < std::min(c.x(), d.x()) ||
|
||||
std::max(c.x(), d.x()) < std::min(a.x(), b.x()) ||
|
||||
std::max(a.y(), b.y()) < std::min(c.y(), d.y()) ||
|
||||
std::max(c.y(), d.y()) < std::min(a.y(), b.y()));
|
||||
}
|
||||
|
||||
bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
size_t pn = path.size();
|
||||
if (pn <= 3) return false;
|
||||
|
||||
// Treat path as a cycle: include the closing edge (pn-1 -> 0), consistent with the other
|
||||
// TSP helpers (2-opt, closing-edge rotation) that operate on the full cycle.
|
||||
size_t n_edges = pn;
|
||||
|
||||
// Scan for first crossing; returns {i, j} or {npos, npos} if none.
|
||||
auto find_crossing = [&]() -> std::pair<size_t, size_t> {
|
||||
for (size_t i = 0; i < n_edges; ++i) {
|
||||
const Point& ai = centers[path[i]];
|
||||
const Point& bi = centers[path[(i + 1) % pn]];
|
||||
|
||||
for (size_t j = i + 2; j < n_edges; ++j) {
|
||||
// Skip the (0, pn-1) pair: edges (0,1) and (pn-1,0) share node 0.
|
||||
if (i == 0 && j == pn - 1) continue;
|
||||
|
||||
const Point& aj = centers[path[j]];
|
||||
const Point& bj = centers[path[(j + 1) % pn]];
|
||||
|
||||
if (!bboxes_overlap(ai, bi, aj, bj)) continue;
|
||||
if (Geometry::segments_intersect(ai, bi, aj, bj))
|
||||
return {i, j};
|
||||
}
|
||||
}
|
||||
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
|
||||
};
|
||||
|
||||
// Process crossings one at a time: find first, reverse it, restart scan.
|
||||
// Cap iterations to prevent infinite loops on collinear/overlapping segments.
|
||||
int max_iters = static_cast<int>(pn * pn);
|
||||
bool improved = false;
|
||||
while (max_iters-- > 0) {
|
||||
auto [ci, cj] = find_crossing();
|
||||
if (ci == std::numeric_limits<size_t>::max()) break;
|
||||
improved = true;
|
||||
std::reverse(path.begin() + ci + 1, path.begin() + cj + 1);
|
||||
}
|
||||
return improved;
|
||||
}
|
||||
|
||||
void tsp_rotate_minimize_closing(std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
size_t pn = path.size();
|
||||
size_t best_start = 0;
|
||||
double best_closing2 = std::numeric_limits<double>::max();
|
||||
for (size_t start = 0; start < pn; ++start) {
|
||||
size_t last = (start + pn - 1) % pn;
|
||||
double d2 = (centers[path[start]].cast<double>() - centers[path[last]].cast<double>()).squaredNorm();
|
||||
if (d2 < best_closing2) { best_closing2 = d2; best_start = start; }
|
||||
}
|
||||
std::rotate(path.begin(), path.begin() + best_start, path.end());
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* Snake ordering
|
||||
* ==================================================================== */
|
||||
|
||||
struct SnakeRow { double avg_y; std::vector<size_t> indices; };
|
||||
|
||||
// --- Row threshold computation ---
|
||||
// Extract unique Y values and use the median gap between them to determine
|
||||
// the row threshold.
|
||||
static double compute_row_threshold(const std::vector<double>& sorted_ys,
|
||||
double y_min, double y_max,
|
||||
size_t n,
|
||||
double fraction_of_y_range,
|
||||
double min_threshold_um)
|
||||
{
|
||||
constexpr double MIN_GAP_FILTER = 1.0; // ignore sub-micron gaps (coord_t = 1/100mm)
|
||||
|
||||
// Extract unique Y values
|
||||
std::vector<double> unique_ys;
|
||||
unique_ys.reserve(sorted_ys.size());
|
||||
unique_ys.push_back(sorted_ys[0]);
|
||||
for (size_t i = 1; i < sorted_ys.size(); ++i) {
|
||||
if (sorted_ys[i] - sorted_ys[i - 1] > MIN_GAP_FILTER)
|
||||
unique_ys.push_back(sorted_ys[i]);
|
||||
}
|
||||
|
||||
double fallback_threshold = (y_max - y_min) * fraction_of_y_range;
|
||||
if (unique_ys.size() <= 1) {
|
||||
return std::max(fallback_threshold, min_threshold_um);
|
||||
}
|
||||
|
||||
// Compute gaps between consecutive unique Y values
|
||||
std::vector<double> gaps;
|
||||
gaps.reserve(unique_ys.size() - 1);
|
||||
for (size_t i = 1; i < unique_ys.size(); ++i)
|
||||
gaps.push_back(unique_ys[i] - unique_ys[i - 1]);
|
||||
|
||||
if (gaps.empty()) {
|
||||
return std::max(fallback_threshold, min_threshold_um);
|
||||
}
|
||||
|
||||
// Sort gaps to find the median
|
||||
std::sort(gaps.begin(), gaps.end());
|
||||
double median_gap = gaps[gaps.size() / 2];
|
||||
double min_gap = gaps.front();
|
||||
|
||||
// Threshold: half the gap between consecutive unique Y values.
|
||||
double threshold = (median_gap < min_gap * 1.5) ? min_gap * 0.5 : median_gap * 0.5;
|
||||
|
||||
bool has_row_structure;
|
||||
if (unique_ys.size() * 2 <= n) {
|
||||
has_row_structure = true;
|
||||
} else {
|
||||
// Single-column or sparse: uniform gaps indicate a deliberate grid
|
||||
double max_gap = *std::max_element(gaps.begin(), gaps.end());
|
||||
has_row_structure = (max_gap < min_gap * 2.0);
|
||||
}
|
||||
|
||||
if (has_row_structure) {
|
||||
// For grid-like data, use the gap-based threshold directly.
|
||||
return threshold;
|
||||
}
|
||||
|
||||
return std::max(fallback_threshold, min_threshold_um);
|
||||
}
|
||||
|
||||
// --- Row grouping ---
|
||||
// Bin points into rows by quantising Y / threshold
|
||||
static std::vector<SnakeRow> group_into_rows(const Points& centers, double row_threshold)
|
||||
{
|
||||
size_t n = centers.size();
|
||||
std::unordered_map<int64_t, std::vector<size_t>> row_map;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
int64_t y_key = static_cast<int64_t>(std::floor(static_cast<double>(centers[i].y()) / row_threshold));
|
||||
row_map[y_key].push_back(i);
|
||||
}
|
||||
|
||||
std::vector<SnakeRow> rows;
|
||||
rows.reserve(row_map.size());
|
||||
for (auto& [key, indices] : row_map) {
|
||||
double avg_y = std::accumulate(indices.begin(), indices.end(), 0.0,
|
||||
[&](double acc, size_t idx) { return acc + static_cast<double>(centers[idx].y()); })
|
||||
/ indices.size();
|
||||
rows.push_back({avg_y, std::move(indices)});
|
||||
}
|
||||
|
||||
std::sort(rows.begin(), rows.end(),
|
||||
[](const SnakeRow& a, const SnakeRow& b) { return a.avg_y < b.avg_y; });
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
// Sort each row by X and greedily pick the direction (left->right or right->left)
|
||||
// that minimises the transition distance from the previous row's endpoint.
|
||||
static std::vector<size_t> build_serpentine_path(const Points& centers,
|
||||
std::vector<SnakeRow>& rows)
|
||||
{
|
||||
std::vector<size_t> path;
|
||||
path.reserve(centers.size());
|
||||
|
||||
for (size_t ri = 0; ri < rows.size(); ++ri) {
|
||||
auto& row = rows[ri].indices;
|
||||
std::sort(row.begin(), row.end(),
|
||||
[&](size_t a, size_t b) { return centers[a].x() < centers[b].x(); });
|
||||
|
||||
if (ri == 0) {
|
||||
path.insert(path.end(), row.begin(), row.end());
|
||||
} else {
|
||||
const Point& prev_end = centers[path.back()];
|
||||
double dist_to_left = (prev_end.cast<double>() - centers[row.front()].cast<double>()).squaredNorm();
|
||||
double dist_to_right = (prev_end.cast<double>() - centers[row.back()].cast<double>()).squaredNorm();
|
||||
|
||||
if (dist_to_left <= dist_to_right)
|
||||
path.insert(path.end(), row.begin(), row.end());
|
||||
else
|
||||
path.insert(path.end(), row.rbegin(), row.rend());
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// Row-based serpentine traversal: detect rows, bin points, snake through them.
|
||||
static std::vector<size_t> row_serpentine_path(const Points& centers,
|
||||
double fraction_of_y_range = 0.02,
|
||||
double min_threshold_um = 1e4)
|
||||
{
|
||||
if (centers.empty()) return {};
|
||||
|
||||
size_t n = centers.size();
|
||||
|
||||
// Collect and sort Y coordinates.
|
||||
std::vector<double> sorted_ys;
|
||||
sorted_ys.reserve(n);
|
||||
for (const auto& p : centers) sorted_ys.push_back(static_cast<double>(p.y()));
|
||||
std::sort(sorted_ys.begin(), sorted_ys.end());
|
||||
|
||||
auto [ymin, ymax] = std::minmax_element(sorted_ys.begin(), sorted_ys.end());
|
||||
double y_min = *ymin, y_max = *ymax;
|
||||
|
||||
double row_threshold = compute_row_threshold(sorted_ys, y_min, y_max, n,
|
||||
fraction_of_y_range, min_threshold_um);
|
||||
|
||||
auto rows = group_into_rows(centers, row_threshold);
|
||||
return build_serpentine_path(centers, rows);
|
||||
}
|
||||
|
||||
std::vector<size_t> snake_core(const Points& centers)
|
||||
{
|
||||
if (centers.empty()) return {};
|
||||
|
||||
std::vector<size_t> path = row_serpentine_path(centers);
|
||||
|
||||
for (int iter = 0; iter < 3; ++iter) {
|
||||
bool improved = tsp_2opt_improve(path, centers);
|
||||
improved |= tsp_remove_crossings(path, centers);
|
||||
if (!improved) break;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_snake(const std::vector<const PrintObject*>& print_objects, const Point* start_near)
|
||||
{
|
||||
return chain_instances_with_core(print_objects, start_near, snake_core);
|
||||
}
|
||||
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_snake(const Print& print)
|
||||
{
|
||||
return chain_print_object_instances_snake(print.objects().vector(), nullptr);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* Best-of-strategies meta-strategy
|
||||
* ==================================================================== */
|
||||
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const std::vector<const PrintObject*>& print_objects, const Point* start_near)
|
||||
{
|
||||
if (print_objects.empty())
|
||||
return {};
|
||||
|
||||
// Run all strategies.
|
||||
std::vector<std::vector<const PrintInstance*>> candidates;
|
||||
candidates.push_back(chain_print_object_instances(print_objects, start_near));
|
||||
candidates.push_back(chain_print_object_instances_snake(print_objects, start_near));
|
||||
|
||||
// Compute metrics for each candidate.
|
||||
struct Candidate { double total_len; double max_edge; };
|
||||
std::vector<Candidate> metrics;
|
||||
metrics.reserve(candidates.size());
|
||||
|
||||
for (size_t i = 0; i < candidates.size(); ++i) {
|
||||
double total = 0.0;
|
||||
double mx = 0.0;
|
||||
for (size_t j = 0; j < candidates[i].size(); ++j) {
|
||||
size_t k = (j + 1) % candidates[i].size();
|
||||
double d = (candidates[i][j]->shift.cast<double>() - candidates[i][k]->shift.cast<double>()).norm();
|
||||
total += d;
|
||||
if (d > mx) mx = d;
|
||||
}
|
||||
metrics.push_back({total, mx});
|
||||
}
|
||||
|
||||
// Pick shortest total path; tiebreak on smallest max edge.
|
||||
auto best_it = std::min_element(metrics.begin(), metrics.end(),
|
||||
[](const Candidate& a, const Candidate& b) {
|
||||
return a.total_len < b.total_len ||
|
||||
(a.total_len == b.total_len && a.max_edge < b.max_edge);
|
||||
});
|
||||
size_t best = static_cast<size_t>(std::distance(metrics.begin(), best_it));
|
||||
|
||||
return candidates[best];
|
||||
}
|
||||
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const Print& print)
|
||||
{
|
||||
return chain_print_object_instances_best_of(print.objects().vector(), nullptr);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* Island-level ordering entry point
|
||||
* ==================================================================== */
|
||||
|
||||
std::vector<size_t> order_points_with_strategy(const Points& points, PrintOrder print_order, const Point* start_near)
|
||||
{
|
||||
if (points.empty())
|
||||
return {};
|
||||
|
||||
if (print_order != PrintOrder::Snake && print_order != PrintOrder::BestOfStrategies)
|
||||
// Nearest neighbor + post-processing; honours start_near natively.
|
||||
return chain_points_with_postprocessing(points, start_near);
|
||||
|
||||
auto run_snake = [&points, start_near]() {
|
||||
std::vector<size_t> path = snake_core(points);
|
||||
if (start_near != nullptr && !path.empty()) {
|
||||
// Start the cycle at the point closest to start_near.
|
||||
size_t best_start = 0;
|
||||
double best_d2 = std::numeric_limits<double>::max();
|
||||
for (size_t k = 0; k < points.size(); ++k) {
|
||||
double d2 = (points[k].cast<double>() - start_near->cast<double>()).squaredNorm();
|
||||
if (d2 < best_d2) { best_d2 = d2; best_start = k; }
|
||||
}
|
||||
auto it = std::find(path.begin(), path.end(), best_start);
|
||||
if (it != path.begin() && it != path.end())
|
||||
std::rotate(path.begin(), it, path.end());
|
||||
} else {
|
||||
tsp_rotate_minimize_closing(path, points);
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
if (print_order == PrintOrder::Snake)
|
||||
return run_snake();
|
||||
|
||||
// Best-of: pick the shortest total cycle; tiebreak on smallest max edge.
|
||||
std::vector<std::vector<size_t>> candidates;
|
||||
candidates.emplace_back(chain_points_with_postprocessing(points, start_near));
|
||||
candidates.emplace_back(run_snake());
|
||||
|
||||
size_t best = 0;
|
||||
double best_len = std::numeric_limits<double>::max();
|
||||
double best_edge = std::numeric_limits<double>::max();
|
||||
for (size_t i = 0; i < candidates.size(); ++i) {
|
||||
double len = tsp_cycle_path_length(candidates[i], points);
|
||||
double edge = tsp_max_edge_length(candidates[i], points);
|
||||
if (len < best_len || (len == best_len && edge < best_edge)) {
|
||||
best_len = len; best_edge = edge; best = i;
|
||||
}
|
||||
}
|
||||
return candidates[best];
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -1,148 +0,0 @@
|
||||
// Print-object ordering strategies and shared TSP post-processing utilities.
|
||||
|
||||
#ifndef slic3r_OrderingStrategies_hpp_
|
||||
#define slic3r_OrderingStrategies_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../Point.hpp"
|
||||
|
||||
#ifndef SLIC3R_TEST_HARNESS
|
||||
#include "../Print.hpp"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// --- Path improvement (operate on index vectors into `centers`) ---
|
||||
|
||||
// 2-opt improvement: reverses segments that reduce total cycle path length.
|
||||
// Returns true if any improvement was made.
|
||||
bool tsp_2opt_improve(std::vector<size_t>& path, const Points& centers, int max_passes = 10);
|
||||
|
||||
// Crossing removal: reverse any segment pair whose edges geometrically cross.
|
||||
// Returns true if any crossing was removed.
|
||||
bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers);
|
||||
|
||||
// Rotate the cycle so the closing edge (last -> first) is minimized.
|
||||
void tsp_rotate_minimize_closing(std::vector<size_t>& path, const Points& centers);
|
||||
|
||||
// Total Euclidean path length of a cycle (including closing edge).
|
||||
inline double tsp_cycle_path_length(const std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
if (path.size() < 2) return 0.0;
|
||||
double total = 0.0;
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
size_t next = (i + 1) % path.size();
|
||||
total += (centers[path[i]].cast<double>() - centers[path[next]].cast<double>()).norm();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// Maximum edge length of a cycle (including closing edge).
|
||||
inline double tsp_max_edge_length(const std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
if (path.size() < 2) return 0.0;
|
||||
double mx = 0.0;
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
size_t next = (i + 1) % path.size();
|
||||
double d = (centers[path[i]].cast<double>() - centers[path[next]].cast<double>()).norm();
|
||||
if (d > mx) mx = d;
|
||||
}
|
||||
return mx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifndef SLIC3R_TEST_HARNESS
|
||||
|
||||
// --- Wrapper boilerplate ---
|
||||
|
||||
// Collect instance centers from PrintObjects, optionally pre-rotate to honour
|
||||
// start_near, call a core algorithm, and map the result back to PrintInstance*.
|
||||
template<typename CoreFn>
|
||||
std::vector<const PrintInstance*> chain_instances_with_core(
|
||||
const std::vector<const PrintObject*>& print_objects,
|
||||
const Point* start_near,
|
||||
CoreFn&& core_fn)
|
||||
{
|
||||
Points instance_centers;
|
||||
std::vector<std::pair<size_t, size_t>> instances;
|
||||
for (size_t i = 0; i < print_objects.size(); ++i) {
|
||||
const PrintObject& object = *print_objects[i];
|
||||
for (size_t j = 0; j < object.instances().size(); ++j) {
|
||||
instance_centers.emplace_back(object.instances()[j].shift);
|
||||
instances.emplace_back(i, j);
|
||||
}
|
||||
}
|
||||
|
||||
if (instance_centers.empty()) return {};
|
||||
|
||||
// If start_near is provided, pre-rotate so closest point is first.
|
||||
if (start_near != nullptr) {
|
||||
size_t best_start = 0;
|
||||
double best_d2 = std::numeric_limits<double>::max();
|
||||
for (size_t k = 0; k < instance_centers.size(); ++k) {
|
||||
double d2 = (instance_centers[k].cast<double>() - start_near->cast<double>()).squaredNorm();
|
||||
if (d2 < best_d2) { best_d2 = d2; best_start = k; }
|
||||
}
|
||||
std::rotate(instance_centers.begin(), instance_centers.begin() + best_start, instance_centers.end());
|
||||
std::rotate(instances.begin(), instances.begin() + best_start, instances.end());
|
||||
}
|
||||
|
||||
auto path = core_fn(instance_centers);
|
||||
|
||||
// Rotate the cycle so the first element is the best starting point.
|
||||
// When start_near is provided, pick the point closest to it (preserving
|
||||
// the pre-rotation). Otherwise minimise the closing edge.
|
||||
if (start_near != nullptr && !path.empty()) {
|
||||
// Pre-rotation already put the closest point at index 0.
|
||||
// Find where index 0 appears in the path and rotate it to the front.
|
||||
auto it = std::find(path.begin(), path.end(), size_t(0));
|
||||
if (it != path.begin())
|
||||
std::rotate(path.begin(), it, path.end());
|
||||
} else {
|
||||
tsp_rotate_minimize_closing(path, instance_centers);
|
||||
}
|
||||
|
||||
std::vector<const PrintInstance*> out;
|
||||
out.reserve(path.size());
|
||||
for (size_t step : path) {
|
||||
out.emplace_back(&print_objects[instances[step].first]->instances()[instances[step].second]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif // SLIC3R_TEST_HARNESS
|
||||
|
||||
// --- Core algorithms (operate on raw Points, return index permutations) ---
|
||||
|
||||
// Snake ordering: row grouping + serpentine traversal + post-processing.
|
||||
std::vector<size_t> snake_core(const Points& centers);
|
||||
|
||||
#ifndef SLIC3R_TEST_HARNESS
|
||||
|
||||
// --- Production wrappers ---
|
||||
|
||||
// Snake ordering.
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_snake(const std::vector<const PrintObject*>& print_objects, const Point* start_near);
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_snake(const Print& print);
|
||||
|
||||
// Best-of-strategies: run all strategies and return the shortest result.
|
||||
// Primary: shortest total path; secondary tiebreaker: smallest max edge.
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const std::vector<const PrintObject*>& print_objects, const Point* start_near);
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const Print& print);
|
||||
|
||||
// Order raw points with the selected strategy, returning an index permutation. Island-level
|
||||
// counterpart of the chain_print_object_instances_* helpers. The returned cycle starts at the
|
||||
// point closest to start_near; orders without a dedicated strategy use nearest-neighbor chaining.
|
||||
std::vector<size_t> order_points_with_strategy(const Points& points, PrintOrder print_order, const Point* start_near);
|
||||
|
||||
#endif // SLIC3R_TEST_HARNESS
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_OrderingStrategies_hpp_ */
|
||||
@@ -143,7 +143,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
|
||||
double wipe_tower_y = print.config().wipe_tower_y.get_at(plate_idx) + plate_origin(1);
|
||||
Transform2d trafo =
|
||||
Eigen::Translation2d(wipe_tower_x, wipe_tower_y) *
|
||||
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value));
|
||||
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value)) *
|
||||
Eigen::Translation2d(print.wipe_tower_data().rib_offset.cast<double>()); // tower-local rib-wall shift, zero unless rib
|
||||
|
||||
BoundingBoxf bbox;
|
||||
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
|
||||
|
||||
+1292
-831
File diff suppressed because it is too large
Load Diff
+112
-112
@@ -12,7 +12,7 @@
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include <unordered_set>
|
||||
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
@@ -20,6 +20,11 @@ class WipeTowerWriter;
|
||||
class PrintConfig;
|
||||
enum GCodeFlavor : unsigned char;
|
||||
|
||||
// Cuts the tower wall polygon open at each skip point (a toolchange's entry position)
|
||||
// so the entry travel can pass through instead of crossing the printed wall. Defined in
|
||||
// WipeTower.cpp, shared by WipeTower and WipeTower2.
|
||||
Polylines contrust_gap_for_skip_points(
|
||||
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon);
|
||||
|
||||
class WipeTower
|
||||
{
|
||||
@@ -84,7 +89,6 @@ public:
|
||||
bool priming;
|
||||
|
||||
bool is_tool_change{false};
|
||||
bool is_contact{false};
|
||||
Vec2f tool_change_start_pos;
|
||||
|
||||
// Pass a polyline so that normal G-code generator can do a wipe for us.
|
||||
@@ -108,6 +112,7 @@ public:
|
||||
// executing the gcode finish_layer_tcr.
|
||||
bool is_finish_first = false;
|
||||
|
||||
bool is_contact = false;
|
||||
NozzleChangeResult nozzle_change_result;
|
||||
|
||||
// Sum the total length of the extrusion.
|
||||
@@ -122,6 +127,8 @@ public:
|
||||
}
|
||||
return e_length;
|
||||
}
|
||||
// Orca: set by WipeTower2 (non-BBL tower) to force a travel to the tower even when the
|
||||
// previous position is unknown; read by WipeTowerIntegration::append_tcr2 (GCode.cpp).
|
||||
bool force_travel = false;
|
||||
};
|
||||
|
||||
@@ -162,15 +169,12 @@ public:
|
||||
bool priming,
|
||||
size_t old_tool,
|
||||
bool is_finish,
|
||||
bool is_tool_change,
|
||||
float purge_volume,
|
||||
bool is_contact = false) const;
|
||||
bool is_tool_change, float purge_volume, bool is_contact) const;
|
||||
|
||||
ToolChangeResult construct_block_tcr(WipeTowerWriter& writer,
|
||||
bool priming,
|
||||
size_t filament_id,
|
||||
bool is_finish,
|
||||
float purge_volume) const;
|
||||
bool is_finish, float purge_volume) const;
|
||||
|
||||
|
||||
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
||||
@@ -184,9 +188,14 @@ public:
|
||||
// Set the extruder properties.
|
||||
void set_extruder(size_t idx, const PrintConfig& config);
|
||||
|
||||
void set_shared_print_bed(const Polygons &bed) { m_shared_print_bed = bed; }
|
||||
// Orca: has_filament_switcher is not a static PrintConfig member here, so it is pushed in from
|
||||
// Print via a setter rather than read in the ctor. Device-set only.
|
||||
void set_has_filament_switcher(bool v) { m_has_filament_switcher = v; }
|
||||
// Appends into internal structure m_plan containing info about the future wipe tower
|
||||
// to be used before building begins. The entries must be added ordered in z.
|
||||
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f, float prime_volume = 0.f);
|
||||
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume_ec = 0.f, float wipe_volume_nc = 0.f, float prime_volume = 0.f);
|
||||
|
||||
|
||||
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
|
||||
void generate(std::vector<std::vector<ToolChangeResult>> &result);
|
||||
@@ -219,9 +228,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void set_wipe_volume(std::vector<std::vector<float>>& wiping_matrix) {
|
||||
wipe_volumes = wiping_matrix;
|
||||
}
|
||||
|
||||
// Switch to a next layer.
|
||||
void set_layer(
|
||||
@@ -250,7 +256,6 @@ public:
|
||||
|
||||
// Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
|
||||
m_extrusion_flow = extrusion_flow(layer_height);
|
||||
|
||||
// Advance m_layer_info iterator, making sure we got it right
|
||||
while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end())
|
||||
++m_layer_info;
|
||||
@@ -309,20 +314,9 @@ public:
|
||||
std::vector<float> get_used_filament() const { return m_used_filament_length; }
|
||||
int get_number_of_toolchanges() const { return m_num_tool_changes; }
|
||||
|
||||
void set_filament_map(const std::vector<int> &filament_map) { m_filament_map = filament_map; }
|
||||
// Vortek H2C: filament_id → physical nozzle_id for carousel rotation detection
|
||||
void set_filament_nozzle_map(const std::vector<int> &nozzle_map) { m_filament_nozzle_map = nozzle_map; }
|
||||
|
||||
void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; }
|
||||
|
||||
bool has_tpu_filament() const { return m_has_tpu_filament; }
|
||||
|
||||
// Orca: has_filament_switcher is not a static PrintConfig member, so it is pushed in from Print
|
||||
// via a setter rather than read in the ctor. Device-set only.
|
||||
void set_has_filament_switcher(bool v) { m_has_filament_switcher = v; }
|
||||
// The region every extruder can reach, used to clamp the PETG pre-extrusion offset to the
|
||||
// printable bed.
|
||||
void set_shared_print_bed(const Polygons &bed) { m_shared_print_bed = bed; }
|
||||
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
int category;
|
||||
@@ -331,15 +325,15 @@ public:
|
||||
bool is_support = false;
|
||||
int nozzle_temperature = 0;
|
||||
int nozzle_temperature_initial_layer = 0;
|
||||
int interface_print_temperature = 0;
|
||||
float loading_speed = 0.f;
|
||||
float loading_speed_start = 0.f;
|
||||
float unloading_speed = 0.f;
|
||||
float unloading_speed_start = 0.f;
|
||||
float delay = 0.f ;
|
||||
int cooling_moves = 0;
|
||||
float cooling_initial_speed = 0.f;
|
||||
float cooling_final_speed = 0.f;
|
||||
// BBS: remove useless config
|
||||
//float loading_speed = 0.f;
|
||||
//float loading_speed_start = 0.f;
|
||||
//float unloading_speed = 0.f;
|
||||
//float unloading_speed_start = 0.f;
|
||||
//float delay = 0.f ;
|
||||
//int cooling_moves = 0;
|
||||
//float cooling_initial_speed = 0.f;
|
||||
//float cooling_final_speed = 0.f;
|
||||
float ramming_line_width_multiplicator = 1.f;
|
||||
float ramming_step_multiplicator = 1.f;
|
||||
float max_e_speed = std::numeric_limits<float>::max();
|
||||
@@ -349,41 +343,41 @@ public:
|
||||
float retract_length;
|
||||
float retract_speed;
|
||||
float wipe_dist;
|
||||
float tower_interface_pre_extrusion_dist = 0.f;
|
||||
float tower_interface_pre_extrusion_length = 0.f;
|
||||
// Outward shift of the wipe start for a PETG pre-extrusion on filament-switcher devices;
|
||||
// set from filament_tower_interface_pre_extrusion_dist.
|
||||
float petg_pre_extrusion_offset_dist = 0.f;
|
||||
float tower_ironing_area = 4.f;
|
||||
float tower_interface_purge_length = 0.f;
|
||||
// Distance (in mm of filament) that a hotend is allowed to pre-cool before the
|
||||
// tower is reached; drives the prime-tower heating-during-wipe model (multi-nozzle only).
|
||||
float filament_cooling_before_tower = 0.f;
|
||||
// .first = extruder change, .second = nozzle change (carousel)
|
||||
std::pair<float,float> max_e_ramming_speed{0.f, 0.f};
|
||||
std::pair<float,float> ramming_travel_time{0.f, 0.f};
|
||||
std::pair<int,int> precool_target_temp{0, 0};
|
||||
std::pair<std::vector<float>,std::vector<float>> precool_t;
|
||||
std::pair<std::vector<float>,std::vector<float>> precool_t_first_layer;
|
||||
std::pair<float,float> max_e_ramming_speed;//[0]extruder change [1]nozzle change
|
||||
std::pair<float, float> ramming_travel_time; // Travel time after ramming
|
||||
std::pair<std::vector<float>,std::vector<float>> precool_t;//Pre-cooling time, set to 0 to ensure the ramming speed is controlled solely by ramming volumetric speed.
|
||||
std::pair<std::vector<float>, std::vector<float>> precool_t_first_layer;
|
||||
std::pair<int,int> precool_target_temp;
|
||||
float filament_cooling_before_tower = 0.f;
|
||||
float flat_iron_area;
|
||||
float filament_tower_interface_print_temp;
|
||||
float filament_tower_interface_pre_extrusion_dist = 0;
|
||||
float filament_tower_interface_pre_extrusion_length = 0;
|
||||
float filament_petg_pre_extrusion_offset_dist = 0;
|
||||
};
|
||||
|
||||
|
||||
void set_used_filament_ids(const std::vector<int> &used_filament_ids) { m_used_filament_ids = used_filament_ids; };
|
||||
void set_used_filament_ids(const std::vector<int> &used_filament_ids) { m_used_filament_ids = used_filament_ids; };
|
||||
void set_filament_categories(const std::vector<int> & filament_categories) { m_filament_categories = filament_categories;};
|
||||
std::vector<int> m_used_filament_ids;
|
||||
void set_nozzle_group_result(const MultiNozzleUtils::LayeredNozzleGroupResult &multi_nozzle_group_result) { m_multi_nozzle_group_result = &multi_nozzle_group_result; };
|
||||
std::vector<int> m_used_filament_ids;
|
||||
std::vector<int> m_filament_categories;
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult *m_multi_nozzle_group_result{nullptr};
|
||||
|
||||
enum class WipeTowerLayerType : unsigned char { Normal, Contact, Solid, Contact_UP};// Contact layer should be solid and reduce feed
|
||||
|
||||
struct WipeTowerBlock
|
||||
{
|
||||
int block_id{0};
|
||||
int filament_adhesiveness_category{0};
|
||||
std::vector<float> layer_depths;
|
||||
std::vector<bool> solid_infill;
|
||||
//std::vector<bool> solid_infill;
|
||||
std::vector<float> finish_depth{0}; // the start pos of finish frame for every layer
|
||||
std::vector<WipeTowerLayerType> layers_type; // type of the layer, normal, Contact or Solid
|
||||
float depth{0};
|
||||
float start_depth{0};
|
||||
float cur_depth{0};
|
||||
int last_filament_change_id{-1};
|
||||
int last_filament_change_id{-1};
|
||||
int last_nozzle_change_id{-1};
|
||||
};
|
||||
|
||||
@@ -403,25 +397,33 @@ public:
|
||||
WipeTowerBlock* get_block_by_category(int filament_adhesiveness_category, bool create);
|
||||
void add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change = false);
|
||||
int get_filament_category(int filament_id);
|
||||
bool is_in_same_extruder(int filament_id_1, int filament_id_2);
|
||||
// Vortek H2C: format BBS-compatible NOZZLE_CHANGE_START/END tag with OF/NF/ON/NN payload
|
||||
std::string format_nozzle_change_tag(bool start, int old_filament_id, int new_filament_id) const;
|
||||
void reset_block_status();
|
||||
int get_wall_filament_for_all_layer();
|
||||
// for generate new wipe tower
|
||||
void generate_new(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
|
||||
|
||||
void plan_tower_new();
|
||||
void generate_wipe_tower_blocks();
|
||||
void generate_wipe_tower_blocks(bool add_solid_flag);
|
||||
void update_all_layer_depth(float wipe_tower_depth);
|
||||
|
||||
void set_nozzle_last_layer_id();
|
||||
void set_first_layer_flow_ratio(const float flow_ratio);
|
||||
// Orca: default/initial-layer/travel acceleration are object-scope options here (PrintConfig
|
||||
// members in BBS), so Print pushes the resolved per-variant columns in via this setter.
|
||||
void set_accelerations(const std::vector<double> &normal, const std::vector<double> &first_layer_normal,
|
||||
const std::vector<double> &travel, const std::vector<double> &first_layer_travel);
|
||||
void calc_block_infill_gap();
|
||||
ToolChangeResult tool_change_new(size_t new_tool, bool solid_change = false, bool solid_nozzlechange=false);
|
||||
NozzleChangeResult nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_change = false);
|
||||
NozzleChangeResult ramming(int old_filament_id, int new_filament_id, bool solid_change = false, bool extruder_change = true); // extruder_chang means nozzle_change
|
||||
ToolChangeResult finish_layer_new(bool extrude_perimeter = true, bool extrude_fill = true, bool extrude_fill_wall = true);
|
||||
ToolChangeResult finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true);
|
||||
ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true ,bool interface_solid =false);
|
||||
ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true, WipeTowerLayerType layer_type = WipeTowerLayerType::Normal);
|
||||
void toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_length,bool solid_toolchange=false);
|
||||
Vec2f get_rib_offset() const { return m_rib_offset; }
|
||||
bool is_need_ramming(int filament_id_1, int filament_id_2, int layer_id) const;
|
||||
bool is_same_extruder(int filament_id_1, int filament_id_2, int layer_id) const;
|
||||
bool is_same_nozzle(int filament_id_1, int filament_id_2, int layer_id) const;
|
||||
int get_nozzle_id(int filament_id, int layer_id) const;
|
||||
int get_extruder_id(int filament_id, int layer_id) const;
|
||||
|
||||
private:
|
||||
enum wipe_shape // A fill-in direction
|
||||
@@ -441,7 +443,6 @@ private:
|
||||
bool m_enable_wrapping_detection = false;
|
||||
bool m_enable_timelapse_print = false;
|
||||
bool m_semm = true; // Are we using a single extruder multimaterial printer?
|
||||
bool m_purge_in_prime_tower = false; // Do we purge in the prime tower?
|
||||
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
|
||||
float m_wipe_tower_width; // Width of the wipe tower.
|
||||
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
|
||||
@@ -459,12 +460,11 @@ private:
|
||||
float m_travel_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
size_t m_first_layer_idx = size_t(-1);
|
||||
|
||||
std::vector<double> m_filaments_change_length;
|
||||
Vec2f m_origin;
|
||||
std::vector<int> m_last_layer_id;
|
||||
std::pair<std::vector<double>,std::vector<double>> m_filaments_change_length;//[0]extruder change [1]nozzle change
|
||||
size_t m_cur_layer_id;
|
||||
NozzleChangeResult m_nozzle_change_result;
|
||||
std::vector<int> m_filament_map;
|
||||
std::vector<int> m_filament_nozzle_map; // Vortek H2C: filament_id → physical nozzle_id
|
||||
bool m_has_tpu_filament{false};
|
||||
bool m_is_multi_extruder{false};
|
||||
bool m_use_gap_wall{false};
|
||||
@@ -475,33 +475,32 @@ private:
|
||||
bool m_used_fillet{false};
|
||||
Vec2f m_rib_offset{Vec2f(0.f, 0.f)};
|
||||
bool m_tower_framework{false};
|
||||
|
||||
bool m_need_reverse_travel{false};
|
||||
bool m_enable_tower_interface_features{false};
|
||||
// G-code generator parameters.
|
||||
float m_cooling_tube_retraction = 0.f;
|
||||
float m_cooling_tube_length = 0.f;
|
||||
float m_parking_pos_retraction = 0.f;
|
||||
float m_extra_loading_move = 0.f;
|
||||
// BBS: remove useless config
|
||||
//float m_cooling_tube_retraction = 0.f;
|
||||
//float m_cooling_tube_length = 0.f;
|
||||
//float m_parking_pos_retraction = 0.f;
|
||||
//float m_extra_loading_move = 0.f;
|
||||
float m_bridging = 0.f;
|
||||
bool m_no_sparse_layers = false;
|
||||
bool m_set_extruder_trimpot = false;
|
||||
// BBS: remove useless config
|
||||
//bool m_set_extruder_trimpot = false;
|
||||
bool m_adhesion = true;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
|
||||
// Multi-nozzle prime-tower heating during wipe. m_is_multiple_nozzle gates the whole
|
||||
// feature; it is false for every current (single-nozzle) printer (extruder_max_nozzle_count
|
||||
// defaults to 1), so the pre-heat/pre-cool path is inert and wipe-tower g-code is unchanged.
|
||||
bool m_is_multiple_nozzle = false;
|
||||
std::vector<double> m_hotend_heating_rate; // config.hotend_heating_rate (deg/s per extruder)
|
||||
std::vector<int> m_physical_extruder_map; // logical extruder -> physical tool number (M104 T param)
|
||||
|
||||
// Per-extruder printable-height clamp. m_printable_height = config.extruder_printable_height
|
||||
// (per-extruder Z limit; empty for single-extruder printers, [320,325] for H2D). m_last_layer_id
|
||||
// records, per extruder, the last wipe-tower layer that uses it. is_valid_last_layer() is gated on
|
||||
// m_is_multi_extruder so single-extruder wipe-tower g-code is unchanged; the clamp only bites a
|
||||
// multi-extruder wipe tower whose final per-extruder layer exceeds that extruder's printable
|
||||
// height (near the Z limit).
|
||||
std::vector<double> m_printable_height;
|
||||
std::vector<int> m_last_layer_id;
|
||||
bool m_is_multiple_nozzle = false;
|
||||
std::vector<unsigned int> m_normal_accels;
|
||||
std::vector<unsigned int> m_first_layer_normal_accels;
|
||||
std::vector<unsigned int> m_travel_accels;
|
||||
std::vector<unsigned int> m_first_layer_travel_accels;
|
||||
unsigned int m_max_accels;
|
||||
bool m_accel_to_decel_enable;
|
||||
float m_accel_to_decel_factor;
|
||||
bool m_enable_arc_fitting = true;
|
||||
std::vector<double> m_hotend_heating_rate;
|
||||
std::vector<double> m_hotend_cooling_rate;
|
||||
Polygons m_shared_print_bed;
|
||||
|
||||
// Bed properties
|
||||
enum {
|
||||
@@ -512,10 +511,11 @@ private:
|
||||
float m_bed_width; // width of the bed bounding box
|
||||
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
|
||||
|
||||
float m_first_layer_flow_ratio;
|
||||
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
|
||||
float m_nozzle_change_perimeter_width = 0.4f * Width_To_Nozzle_Ratio;
|
||||
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
|
||||
|
||||
std::unordered_map<int, std::pair<float,float>> m_block_infill_gap_width; // categories to infill_gap: toolchange gap, nozzlechange gap
|
||||
// Extruder specific parameters.
|
||||
std::vector<FilamentParameters> m_filpar;
|
||||
|
||||
@@ -528,50 +528,52 @@ private:
|
||||
// A fill-in direction (positive Y, negative Y) alternates with each layer.
|
||||
wipe_shape m_current_shape = SHAPE_NORMAL;
|
||||
size_t m_current_tool = 0;
|
||||
// Orca: support mmu wipe tower
|
||||
std::vector<std::vector<float>> wipe_volumes;
|
||||
// BBS
|
||||
//const std::vector<std::vector<float>> wipe_volumes;
|
||||
|
||||
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
|
||||
bool m_current_layer_finished = false;
|
||||
bool m_left_to_right = true;
|
||||
float m_extra_spacing = 1.f;
|
||||
float m_tpu_fixed_spacing = 2;
|
||||
std::vector<Vec2f> m_wall_skip_points;
|
||||
float m_max_speed = 5400.f; // the maximum printing speed on the prime tower.
|
||||
std::vector<std::vector<Vec2f>> m_wall_skip_points;
|
||||
std::map<float,Polylines> m_outer_wall;
|
||||
std::vector<double> m_printable_height;
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
bool is_valid_last_layer(int tool, int layer_id, double layer_z) const;
|
||||
bool m_flat_ironing=false;
|
||||
bool m_enable_tower_interface_features=false;
|
||||
bool m_enable_tower_interface_cooldown_during_tower=false;
|
||||
// Filament-switcher device flag + shared printable bed for the PETG pre-extrusion offset.
|
||||
// m_has_filament_switcher is false for the whole shipping fleet (no profile sets the key), so
|
||||
// the PETG branch in get_next_pos never runs -> no change fleet-wide.
|
||||
bool m_has_filament_switcher=false;
|
||||
Polygons m_shared_print_bed;
|
||||
bool m_prev_layer_had_interface=false;
|
||||
bool m_current_layer_has_interface=false;
|
||||
bool m_contact_ironing = false;
|
||||
bool m_has_filament_switcher = false;
|
||||
float m_contact_speed = 20 * 60.f;
|
||||
std::vector<int> m_physical_extruder_map;
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
float volume_to_length(float volume, float line_width, float layer_height) const {
|
||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
|
||||
// Calculates volume of extrusion line
|
||||
float length_to_volume(float length,float line_width, float layer_height) const
|
||||
{
|
||||
return std::max(0.f, length * (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
// Calculates depth for all layers and propagates them downwards
|
||||
void plan_tower();
|
||||
|
||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||
void make_wipe_tower_square();
|
||||
|
||||
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool interface_layer, size_t interface_tool);
|
||||
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool solid_toolchange);
|
||||
|
||||
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
|
||||
void save_on_last_wipe();
|
||||
|
||||
bool is_tpu_filament(int filament_id) const;
|
||||
bool is_petg_filament(int filament_id) const;
|
||||
bool is_need_reverse_travel(int filament_id, bool extruder_change) const;
|
||||
|
||||
bool is_need_reverse_travel(int filament, bool extruder_change) const;
|
||||
// BBS
|
||||
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
||||
|
||||
void set_for_wipe_tower_writer(WipeTowerWriter &writer);
|
||||
|
||||
// to store information about tool changes for a given layer
|
||||
struct WipeTowerInfo{
|
||||
@@ -584,6 +586,7 @@ private:
|
||||
float wipe_volume;
|
||||
float wipe_length;
|
||||
float nozzle_change_depth{0};
|
||||
float nozzle_change_length{0};
|
||||
// BBS
|
||||
float purge_volume;
|
||||
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f, float wl = 0, float pv = 0)
|
||||
@@ -613,7 +616,7 @@ private:
|
||||
// ot -1 if there is no such toolchange.
|
||||
int first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
||||
|
||||
WipeTowerInfo::ToolChange set_toolchange(int old_tool, int new_tool, float layer_height, float wipe_volume, float purge_volume,int layer_id);
|
||||
void toolchange_Unload(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
@@ -633,13 +636,10 @@ private:
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
float wipe_volume);
|
||||
void get_wall_skip_points(const WipeTowerInfo &layer);
|
||||
|
||||
// Per-extruder printable-height clamp (see m_printable_height). is_valid_last_layer returns
|
||||
// false only for a multi-extruder wipe tower's final per-extruder layer that exceeds that
|
||||
// extruder's printable height; returns true (no clamp) in every other case.
|
||||
bool is_valid_last_layer(int tool, int layer_id, double layer_z) const;
|
||||
void set_nozzle_last_layer_id();
|
||||
void get_wall_skip_points(const WipeTowerInfo &layer,int layer_id);
|
||||
void get_all_wall_skip_points();
|
||||
ToolChangeResult merge_tcr(ToolChangeResult &first, ToolChangeResult &second);
|
||||
float get_block_gap_width(int tool, bool is_nozzlechangle = false);
|
||||
};
|
||||
|
||||
|
||||
|
||||
+260
-321
@@ -24,7 +24,6 @@
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
static constexpr float flat_iron_area = 4.f;
|
||||
constexpr float flat_iron_speed = 10.f * 60.f;
|
||||
static const double wipe_tower_wall_infill_overlap = 0.0;
|
||||
static constexpr double WIPE_TOWER_RESOLUTION = 0.1;
|
||||
@@ -234,24 +233,6 @@ static Polygon rounding_rectangle(Polygon& polygon, double rounding = 2., double
|
||||
return res;
|
||||
}
|
||||
|
||||
static std::pair<bool, Vec2f> ray_intersetion_line(const Vec2f& a, const Vec2f& v1, const Vec2f& b, const Vec2f& c)
|
||||
{
|
||||
const Vec2f v2 = c - b;
|
||||
double denom = cross2(v1, v2);
|
||||
if (fabs(denom) < EPSILON)
|
||||
return {false, Vec2f(0, 0)};
|
||||
const Vec2f v12 = (a - b);
|
||||
double nume_a = cross2(v2, v12);
|
||||
double nume_b = cross2(v1, v12);
|
||||
double t1 = nume_a / denom;
|
||||
double t2 = nume_b / denom;
|
||||
if (t1 >= 0 && t2 >= 0 && t2 <= 1.) {
|
||||
// Get the intersection point.
|
||||
Vec2f res = a + t1 * v1;
|
||||
return std::pair<bool, Vec2f>(true, res);
|
||||
}
|
||||
return std::pair<bool, Vec2f>(false, Vec2f{0, 0});
|
||||
}
|
||||
static Polygon scale_polygon(const std::vector<Vec2f>& points)
|
||||
{
|
||||
Polygon res;
|
||||
@@ -296,6 +277,7 @@ static Polygon generate_rectange(const Line& line, coord_t offset)
|
||||
return poly;
|
||||
};
|
||||
|
||||
// Straight or arc-fitted wall segment used by WipeTowerWriter2::generate_path().
|
||||
struct Segment
|
||||
{
|
||||
Vec2f start;
|
||||
@@ -306,234 +288,6 @@ struct Segment
|
||||
bool is_valid() const { return start.y() < end.y(); }
|
||||
};
|
||||
|
||||
static std::vector<Segment> remove_points_from_segment(const Segment& segment, const std::vector<Vec2f>& skip_points, double range)
|
||||
{
|
||||
std::vector<Segment> result;
|
||||
result.push_back(segment);
|
||||
float x = segment.start.x();
|
||||
|
||||
for (const Vec2f& point : skip_points) {
|
||||
std::vector<Segment> newResult;
|
||||
for (const auto& seg : result) {
|
||||
if (point.y() + range <= seg.start.y() || point.y() - range >= seg.end.y()) {
|
||||
newResult.push_back(seg);
|
||||
} else {
|
||||
if (point.y() - range > seg.start.y()) {
|
||||
newResult.push_back(Segment(Vec2f(x, seg.start.y()), Vec2f(x, point.y() - range)));
|
||||
}
|
||||
if (point.y() + range < seg.end.y()) {
|
||||
newResult.push_back(Segment(Vec2f(x, point.y() + range), Vec2f(x, seg.end.y())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = newResult;
|
||||
}
|
||||
|
||||
result.erase(std::remove_if(result.begin(), result.end(), [](const Segment& seg) { return !seg.is_valid(); }), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
struct IntersectionInfo
|
||||
{
|
||||
Vec2f pos;
|
||||
int idx;
|
||||
int pair_idx; // gap_pair idx
|
||||
float dis_from_idx;
|
||||
bool is_forward;
|
||||
};
|
||||
|
||||
struct PointWithFlag
|
||||
{
|
||||
Vec2f pos;
|
||||
int pair_idx; // gap_pair idx
|
||||
bool is_forward;
|
||||
};
|
||||
static IntersectionInfo move_point_along_polygon(
|
||||
const std::vector<Vec2f>& points, const Vec2f& startPoint, int startIdx, float offset, bool forward, int pair_idx)
|
||||
{
|
||||
float remainingDistance = offset;
|
||||
IntersectionInfo res;
|
||||
int mod = points.size();
|
||||
if (forward) {
|
||||
int next = (startIdx + 1) % mod;
|
||||
remainingDistance -= (points[next] - startPoint).norm();
|
||||
if (remainingDistance <= 0) {
|
||||
res.idx = startIdx;
|
||||
res.pos = startPoint + (points[next] - startPoint).normalized() * offset;
|
||||
res.pair_idx = pair_idx;
|
||||
res.dis_from_idx = (points[startIdx] - res.pos).norm();
|
||||
return res;
|
||||
} else {
|
||||
for (int i = (startIdx + 1) % mod; i != startIdx; i = (i + 1) % mod) {
|
||||
float segmentLength = (points[(i + 1) % mod] - points[i]).norm();
|
||||
if (remainingDistance <= segmentLength) {
|
||||
float ratio = remainingDistance / segmentLength;
|
||||
res.idx = i;
|
||||
res.pos = points[i] + ratio * (points[(i + 1) % mod] - points[i]);
|
||||
res.dis_from_idx = remainingDistance;
|
||||
res.pair_idx = pair_idx;
|
||||
return res;
|
||||
}
|
||||
remainingDistance -= segmentLength;
|
||||
}
|
||||
res.idx = (startIdx - 1 + mod) % mod;
|
||||
res.pos = points[startIdx];
|
||||
res.pair_idx = pair_idx;
|
||||
res.dis_from_idx = (res.pos - points[res.idx]).norm();
|
||||
}
|
||||
} else {
|
||||
int next = (startIdx + 1) % mod;
|
||||
remainingDistance -= (points[startIdx] - startPoint).norm();
|
||||
if (remainingDistance <= 0) {
|
||||
res.idx = startIdx;
|
||||
res.pos = startPoint - (points[next] - points[startIdx]).normalized() * offset;
|
||||
res.dis_from_idx = (res.pos - points[startIdx]).norm();
|
||||
res.pair_idx = pair_idx;
|
||||
return res;
|
||||
}
|
||||
for (int i = (startIdx - 1 + mod) % mod; i != startIdx; i = (i - 1 + mod) % mod) {
|
||||
float segmentLength = (points[(i + 1) % mod] - points[i]).norm();
|
||||
if (remainingDistance <= segmentLength) {
|
||||
float ratio = remainingDistance / segmentLength;
|
||||
res.idx = i;
|
||||
res.pos = points[(i + 1) % mod] - ratio * (points[(i + 1) % mod] - points[i]);
|
||||
res.dis_from_idx = segmentLength - remainingDistance;
|
||||
res.pair_idx = pair_idx;
|
||||
return res;
|
||||
}
|
||||
remainingDistance -= segmentLength;
|
||||
}
|
||||
res.idx = startIdx;
|
||||
res.pos = points[res.idx];
|
||||
res.pair_idx = pair_idx;
|
||||
res.dis_from_idx = 0;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
static void insert_points(std::vector<PointWithFlag>& pl, int idx, Vec2f pos, int pair_idx, bool is_forward)
|
||||
{
|
||||
int next = (idx + 1) % pl.size();
|
||||
Vec2f pos1 = pl[idx].pos;
|
||||
Vec2f pos2 = pl[next].pos;
|
||||
if ((pos - pos1).squaredNorm() < EPSILON) {
|
||||
pl[idx].pair_idx = pair_idx;
|
||||
pl[idx].is_forward = is_forward;
|
||||
} else if ((pos - pos2).squaredNorm() < EPSILON) {
|
||||
pl[next].pair_idx = pair_idx;
|
||||
pl[next].is_forward = is_forward;
|
||||
} else {
|
||||
pl.insert(pl.begin() + idx + 1, PointWithFlag{pos, pair_idx, is_forward});
|
||||
}
|
||||
}
|
||||
|
||||
static Polylines remove_points_from_polygon(
|
||||
const Polygon& polygon, const std::vector<Vec2f>& skip_points, double range, bool is_left, Polygon& insert_skip_pg)
|
||||
{
|
||||
assert(polygon.size() > 2);
|
||||
Polylines result;
|
||||
std::vector<PointWithFlag> new_pl; // add intersection points for gaps, where bool indicates whether it's a gap point.
|
||||
std::vector<IntersectionInfo> inter_info;
|
||||
Vec2f ray = is_left ? Vec2f(-1, 0) : Vec2f(1, 0);
|
||||
auto polygon_box = get_extents(polygon);
|
||||
Point anchor_point = is_left ? Point{polygon_box.max[0], polygon_box.min[1]} : polygon_box.min; // rd:ld
|
||||
std::vector<Vec2f> points;
|
||||
{
|
||||
points.reserve(polygon.points.size());
|
||||
int idx = polygon.closest_point_index(anchor_point);
|
||||
Polyline tmp_poly = polygon.split_at_index(idx);
|
||||
for (auto& p : tmp_poly)
|
||||
points.push_back(unscale(p).cast<float>());
|
||||
points.pop_back();
|
||||
}
|
||||
|
||||
for (int i = 0; i < skip_points.size(); i++) {
|
||||
for (int j = 0; j < points.size(); j++) {
|
||||
Vec2f& p1 = points[j];
|
||||
Vec2f& p2 = points[(j + 1) % points.size()];
|
||||
auto [is_inter, inter_pos] = ray_intersetion_line(skip_points[i], ray, p1, p2);
|
||||
if (is_inter) {
|
||||
IntersectionInfo forward = move_point_along_polygon(points, inter_pos, j, range, true, i);
|
||||
IntersectionInfo backward = move_point_along_polygon(points, inter_pos, j, range, false, i);
|
||||
backward.is_forward = false;
|
||||
forward.is_forward = true;
|
||||
inter_info.push_back(backward);
|
||||
inter_info.push_back(forward);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert point to new_pl
|
||||
for (const auto& p : points)
|
||||
new_pl.push_back({p, -1});
|
||||
std::sort(inter_info.begin(), inter_info.end(), [](const IntersectionInfo& lhs, const IntersectionInfo& rhs) {
|
||||
if (rhs.idx == lhs.idx)
|
||||
return lhs.dis_from_idx < rhs.dis_from_idx;
|
||||
return lhs.idx < rhs.idx;
|
||||
});
|
||||
for (int i = inter_info.size() - 1; i >= 0; i--) {
|
||||
insert_points(new_pl, inter_info[i].idx, inter_info[i].pos, inter_info[i].pair_idx, inter_info[i].is_forward);
|
||||
}
|
||||
|
||||
{
|
||||
// set insert_pg for wipe_path
|
||||
for (auto& p : new_pl)
|
||||
insert_skip_pg.points.push_back(scaled(p.pos));
|
||||
}
|
||||
|
||||
int beg = 0;
|
||||
bool skip = true;
|
||||
int i = beg;
|
||||
Polyline pl;
|
||||
|
||||
do {
|
||||
if (skip || new_pl[i].pair_idx == -1) {
|
||||
pl.points.push_back(scaled(new_pl[i].pos));
|
||||
i = (i + 1) % new_pl.size();
|
||||
skip = false;
|
||||
} else {
|
||||
if (!pl.points.empty()) {
|
||||
pl.points.push_back(scaled(new_pl[i].pos));
|
||||
result.push_back(pl);
|
||||
pl.points.clear();
|
||||
}
|
||||
int left = new_pl[i].pair_idx;
|
||||
int j = (i + 1) % new_pl.size();
|
||||
while (j != beg && new_pl[j].pair_idx != left) {
|
||||
if (new_pl[j].pair_idx != -1 && !new_pl[j].is_forward)
|
||||
left = new_pl[j].pair_idx;
|
||||
j = (j + 1) % new_pl.size();
|
||||
}
|
||||
i = j;
|
||||
skip = true;
|
||||
}
|
||||
} while (i != beg);
|
||||
|
||||
if (!pl.points.empty()) {
|
||||
if (new_pl[i].pair_idx == -1)
|
||||
pl.points.push_back(scaled(new_pl[i].pos));
|
||||
result.push_back(pl);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static Polylines contrust_gap_for_skip_points(
|
||||
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon)
|
||||
{
|
||||
if (skip_points.empty()) {
|
||||
insert_skip_polygon = polygon;
|
||||
return Polylines{to_polyline(polygon)};
|
||||
}
|
||||
bool is_left = false;
|
||||
const auto& pt = skip_points.front();
|
||||
if (abs(pt.x()) < wt_width / 2.f) {
|
||||
is_left = true;
|
||||
}
|
||||
return remove_points_from_polygon(polygon, skip_points, gap_length, is_left, insert_skip_polygon);
|
||||
};
|
||||
|
||||
static Polygon generate_rectange_polygon(const Vec2f& wt_box_min, const Vec2f& wt_box_max)
|
||||
{
|
||||
Polygon res;
|
||||
@@ -1245,6 +999,12 @@ WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer,
|
||||
|
||||
|
||||
|
||||
bool WipeTower2::use_gap_wall(const PrintConfig& config)
|
||||
{
|
||||
// The cone wall has its own fully separate generator with no gap machinery.
|
||||
return config.prime_tower_skip_points.value && config.wipe_tower_wall_type.value != wtwCone;
|
||||
}
|
||||
|
||||
WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& default_region_config,int plate_idx, Vec3d plate_origin, const std::vector<std::vector<float>>& wiping_matrix, size_t initial_tool) :
|
||||
m_semm(config.single_extruder_multi_material.value),
|
||||
m_enable_filament_ramming(config.enable_filament_ramming.value),
|
||||
@@ -1272,7 +1032,7 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
|
||||
m_rib_width(config.wipe_tower_rib_width),
|
||||
m_extra_rib_length(config.wipe_tower_extra_rib_length),
|
||||
m_wall_type((int)config.wipe_tower_wall_type),
|
||||
m_flat_ironing(config.prime_tower_flat_ironing.value),
|
||||
m_use_gap_wall(use_gap_wall(config)),
|
||||
m_enable_tower_interface_features(config.enable_tower_interface_features.value),
|
||||
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value)
|
||||
{
|
||||
@@ -1342,6 +1102,7 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config)
|
||||
m_filpar[idx].is_soluble = (idx != size_t(m_wipe_tower_filament - 1));
|
||||
else
|
||||
m_filpar[idx].is_soluble = config.filament_soluble.get_at(idx);
|
||||
m_filpar[idx].is_support = config.filament_is_support.get_at(idx);
|
||||
m_filpar[idx].temperature = config.nozzle_temperature.get_at(idx);
|
||||
m_filpar[idx].first_layer_temperature = config.nozzle_temperature_initial_layer.get_at(idx);
|
||||
m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx);
|
||||
@@ -1478,11 +1239,11 @@ std::vector<WipeTower::ToolChangeResult> WipeTower2::prime(
|
||||
toolchange_Load(writer, cleaning_box); // Prime the tool.
|
||||
if (idx_tool + 1 == tools.size()) {
|
||||
// Last tool should not be unloaded, but it should be wiped enough to become of a pure color.
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool], false);
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool], false, true);
|
||||
} else {
|
||||
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
|
||||
//writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200);
|
||||
toolchange_Wipe(writer, cleaning_box , 20.f, false);
|
||||
toolchange_Wipe(writer, cleaning_box , 20.f, false, true);
|
||||
WipeTower::box_coordinates box = cleaning_box;
|
||||
box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width);
|
||||
toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
|
||||
@@ -1525,8 +1286,9 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
||||
|
||||
float wipe_area = 0.f;
|
||||
float wipe_volume = 0.f;
|
||||
float ramming_depth = 0.f;
|
||||
bool interface_layer = m_enable_tower_interface_features && m_current_layer_has_interface;
|
||||
|
||||
|
||||
// Finds this toolchange info
|
||||
if (tool != (unsigned int)(-1))
|
||||
{
|
||||
@@ -1534,6 +1296,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
||||
if ( b.new_tool == tool ) {
|
||||
wipe_volume = b.wipe_volume;
|
||||
wipe_area = b.required_depth;
|
||||
ramming_depth = b.ramming_depth;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1571,7 +1334,9 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
||||
writer.speed_override_backup();
|
||||
writer.speed_override(100);
|
||||
|
||||
Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed);
|
||||
// On a boundary wipe start this enters at the wall gap on the first wipe row;
|
||||
// toolchange_Unload() then climbs back up to the ram band along the box interior.
|
||||
Vec2f initial_position = toolchange_entry_pos(m_depth_traversed, ramming_depth, is_first_layer());
|
||||
writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
|
||||
|
||||
// Increase the extruder driver current to allow fast ramming.
|
||||
@@ -1580,6 +1345,11 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
||||
|
||||
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
|
||||
if (tool != (unsigned int)-1){ // This is not the last change.
|
||||
// Without a ram — or with the boundary wipe start, where the ram band is
|
||||
// quantized to whole rows — the box is planned as whole wipe rows; the wipe
|
||||
// then fills it completely so adjacent purge blocks stay contiguous. Uses the
|
||||
// old tool (m_current_tool before toolchange_Change).
|
||||
const bool fill_box = !tool_ramming_enabled(m_current_tool) || boundary_wipe_start_enabled(m_current_tool);
|
||||
auto new_tool_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature;
|
||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
|
||||
(is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature),
|
||||
@@ -1602,7 +1372,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
||||
writer.extrude_explicit(target_x, writer.y(), pre_len, 600.f);
|
||||
}
|
||||
}
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volume, interface_layer); // Wipe the newly loaded filament until the end of the assigned wipe area.
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volume, interface_layer, false, fill_box); // Wipe the newly loaded filament until the end of the assigned wipe area.
|
||||
if (interface_layer) {
|
||||
int interface_temp = m_filpar[tool].interface_print_temperature;
|
||||
if (!m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp)
|
||||
@@ -1657,11 +1427,20 @@ void WipeTower2::toolchange_Unload(
|
||||
float remaining = xr - xl ; // keeps track of distance to the next turnaround
|
||||
float e_done = 0; // measures E move done from each segment
|
||||
|
||||
// Orca: Do ramming when SEMM and ramming is enabled or when multi tool head when ramming is enabled on the multi tool.
|
||||
const bool do_ramming = (m_semm && m_enable_filament_ramming) || m_filpar[m_current_tool].multitool_ramming;
|
||||
const bool do_ramming = tool_ramming_enabled(m_current_tool);
|
||||
const bool cold_ramming = m_is_mk4mmu3;
|
||||
// Orca: see set_toolchange() — quantized ram band + wipe restart at the boundary.
|
||||
const bool boundary_wipe_start = boundary_wipe_start_enabled(m_current_tool);
|
||||
float planned_ramming_depth = 0.f;
|
||||
if (boundary_wipe_start && m_layer_info != m_plan.end())
|
||||
for (const auto& tch : m_layer_info->tool_changes)
|
||||
if (tch.old_tool == m_current_tool) { planned_ramming_depth = tch.ramming_depth; break; }
|
||||
|
||||
if (do_ramming) {
|
||||
if (boundary_wipe_start)
|
||||
// The entry sits at the wall gap on the first wipe row past the reserved
|
||||
// ram band; step inward first, then move to the band clear of the wall.
|
||||
writer.travel(Vec2f(ramming_start_pos.x(), writer.y()));
|
||||
writer.travel(ramming_start_pos); // move to starting position
|
||||
if (! m_is_mk4mmu3)
|
||||
writer.disable_linear_advance();
|
||||
@@ -1672,7 +1451,8 @@ void WipeTower2::toolchange_Unload(
|
||||
writer.set_position(ramming_start_pos);
|
||||
|
||||
// if the ending point of the ram would end up in mid air, align it with the end of the wipe tower:
|
||||
if (do_ramming && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) {
|
||||
// (with a boundary wipe start the band is quantized to whole rows below, so no phase alignment is needed)
|
||||
if (do_ramming && !boundary_wipe_start && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) {
|
||||
|
||||
// this is y of the center of previous sparse infill border
|
||||
float sparse_beginning_y = 0.f;
|
||||
@@ -1731,6 +1511,28 @@ void WipeTower2::toolchange_Unload(
|
||||
e_done = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: quantize the ram band up to the whole reserved rows (BBL quantizes the
|
||||
// old-tool purge the same way) so no unprinted void is left between the band and
|
||||
// the wipe restarting at the boundary below it.
|
||||
if (planned_ramming_depth > 0.f) {
|
||||
const int reserved_rows = std::max(1, int(std::round(planned_ramming_depth / y_step)));
|
||||
const float last_row_y = ramming_start_pos.y() + (reserved_rows - 1) * y_step;
|
||||
// Same bead model as the ramming segments above: E per mm of ram line.
|
||||
const float e_per_mm = 1.f / (volume_to_length(1.f, line_width, m_layer_height) * filament_area());
|
||||
const float fill_feed = m_filpar[m_current_tool].ramming_speed.empty() ? 3000.f :
|
||||
60.f * volume_to_length(m_filpar[m_current_tool].ramming_speed.back(), line_width, m_layer_height);
|
||||
while (true) {
|
||||
const float target_x = m_left_to_right ? xr : xl;
|
||||
if (std::abs(target_x - writer.x()) > WT_EPSILON)
|
||||
writer.ram(writer.x(), target_x, 0.f, 0.f, e_per_mm * std::abs(target_x - writer.x()), fill_feed);
|
||||
if (writer.y() + 0.5f * y_step > last_row_y)
|
||||
break;
|
||||
writer.travel(writer.x(), writer.y() + y_step, 7200);
|
||||
m_left_to_right = !m_left_to_right;
|
||||
}
|
||||
}
|
||||
|
||||
Vec2f end_of_ramming(writer.x(),writer.y());
|
||||
writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier
|
||||
|
||||
@@ -1838,10 +1640,27 @@ void WipeTower2::toolchange_Unload(
|
||||
// this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
|
||||
// the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
|
||||
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f + m_perimeter_width);
|
||||
if (do_ramming)
|
||||
if (planned_ramming_depth > 0.f) {
|
||||
// Orca: restart the wipe at the left-edge boundary on a fresh row below the
|
||||
// quantized ram band so the entry scrub always runs at the wall gap (BBL keeps
|
||||
// CP_TOOLCHANGE_WIPE starting at a box corner the same way). Same lattice
|
||||
// formula as the no-ram branch below, offset by the ram band.
|
||||
writer.travel(Vec2f(ramming_start_pos.x(),
|
||||
cleaning_box.ld.y() + m_depth_traversed +
|
||||
wipe_start_offset_after_ram(planned_ramming_depth, is_first_layer()) + m_perimeter_width), 2400.f);
|
||||
m_left_to_right = true;
|
||||
}
|
||||
else if (do_ramming)
|
||||
writer.travel(pos, 2400.f);
|
||||
else
|
||||
writer.set_position(pos);
|
||||
else {
|
||||
// Orca: with no ram printed there is no ramming geometry to align with. Start the
|
||||
// first wipe row so the purge row lattice continues across the block boundary
|
||||
// (previous box's last row top edge sits at its box top): with the planned depth
|
||||
// of rows * dy, the last row's top edge then lands exactly on this box's top and
|
||||
// no blank band is left between adjacent purge blocks.
|
||||
writer.set_position(Vec2f(end_of_ramming.x(),
|
||||
cleaning_box.ld.y() + m_depth_traversed + wipe_start_offset_after_ram(0.f, is_first_layer()) + m_perimeter_width));
|
||||
}
|
||||
|
||||
writer.resume_preview()
|
||||
.flush_planner_queue();
|
||||
@@ -1920,7 +1739,9 @@ void WipeTower2::toolchange_Wipe(
|
||||
WipeTowerWriter2 &writer,
|
||||
const WipeTower::box_coordinates &cleaning_box,
|
||||
float wipe_volume,
|
||||
bool interface_layer)
|
||||
bool interface_layer,
|
||||
bool priming,
|
||||
bool fill_box)
|
||||
{
|
||||
// Increase flow on first layer, slow down print.
|
||||
writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.18f : 1.f))
|
||||
@@ -1929,7 +1750,7 @@ void WipeTower2::toolchange_Wipe(
|
||||
const float& xr = cleaning_box.rd.x();
|
||||
|
||||
writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow);
|
||||
const float line_width = m_perimeter_width * m_extra_flow;
|
||||
const float line_width = wipe_line_width();
|
||||
writer.change_analyzer_line_width(line_width);
|
||||
|
||||
// Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
|
||||
@@ -1937,7 +1758,7 @@ void WipeTower2::toolchange_Wipe(
|
||||
// wipe until the end of the assigned area.
|
||||
|
||||
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow;
|
||||
float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow.
|
||||
float dy = wipe_row_spacing(is_first_layer()); // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow.
|
||||
// All the calculations in all other places take the spacing into account for all the layers.
|
||||
|
||||
// If spare layers are excluded->if 1 or less toolchange has been done, it must be sill the first layer, too.So slow down.
|
||||
@@ -1950,9 +1771,6 @@ void WipeTower2::toolchange_Wipe(
|
||||
m_left_to_right = !m_left_to_right;
|
||||
}
|
||||
|
||||
const bool do_ironing = m_flat_ironing && (!interface_layer || !m_enable_tower_interface_features);
|
||||
const float ironing_area = m_filpar[m_current_tool].tower_ironing_area;
|
||||
|
||||
// now the wiping itself:
|
||||
for (int i = 0; true; ++i) {
|
||||
if (i!=0) {
|
||||
@@ -1963,22 +1781,45 @@ void WipeTower2::toolchange_Wipe(
|
||||
}
|
||||
|
||||
float traversed_x = writer.x();
|
||||
|
||||
// BBS gap wall: iron the first few mm of the purge, then drag the retracted nozzle
|
||||
// back out through the wall gap and scrub it with a dry spiral centred on the entry
|
||||
// point so the toolchange start blob is not left on the wall (same sequence as the
|
||||
// BBL tower's toolchange_wipe_new; the spiral self-disables when the filament's
|
||||
// tower ironing area is 0). WT2's entry gap always sits at the left-edge entry
|
||||
// point, so only iron when the purge actually starts there heading right (in-place
|
||||
// toolchangers do; SEMM ram/cooling moves leave the nozzle mid-box, far from any gap).
|
||||
if (i == 0 && m_use_gap_wall && !interface_layer && !priming && m_left_to_right &&
|
||||
writer.x() - xl < 2.5f * line_width) {
|
||||
float ironing_length = 3.f;
|
||||
if (xr - writer.x() < ironing_length)
|
||||
ironing_length = std::max(xr - writer.x(), 0.f);
|
||||
const float retract_length = m_filpar[m_current_tool].retract_length;
|
||||
const float retract_speed = m_filpar[m_current_tool].retract_speed * 60.f;
|
||||
writer.extrude(writer.x() + ironing_length, writer.y(), wipe_speed);
|
||||
writer.retract(retract_length, retract_speed);
|
||||
writer.travel(writer.x() - 1.5f * ironing_length, writer.y(), 600.f);
|
||||
writer.travel(writer.x() + 0.5f * ironing_length, writer.y(), 240.f);
|
||||
const Vec2f iron_end(writer.x() + ironing_length, writer.y());
|
||||
writer.spiral_flat_ironing(writer.pos(), m_filpar[m_current_tool].tower_ironing_area, m_perimeter_width, flat_iron_speed);
|
||||
writer.travel(iron_end, wipe_speed);
|
||||
writer.retract(-retract_length, retract_speed);
|
||||
}
|
||||
|
||||
if (m_left_to_right)
|
||||
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||
else
|
||||
writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||
|
||||
if (i == 0 && do_ironing && ironing_area > 0.f) {
|
||||
writer.travel(writer.x(), writer.y(), 600.f);
|
||||
writer.spiral_flat_ironing(writer.pos(), ironing_area, m_perimeter_width, 10.f * 60.f);
|
||||
}
|
||||
|
||||
if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*line_width)
|
||||
break; // in case next line would not fit
|
||||
|
||||
traversed_x -= writer.x();
|
||||
x_to_wipe -= std::abs(traversed_x);
|
||||
if (x_to_wipe < WT_EPSILON) {
|
||||
// Orca: with no ram printed the box was planned as whole wipe rows; fill it
|
||||
// completely (quantizing the purge up to the planned rows) so the next block
|
||||
// can start right above it without a blank band in between.
|
||||
if (!fill_box && x_to_wipe < WT_EPSILON) {
|
||||
writer.travel(m_left_to_right ? xl + 1.5f*line_width : xr - 1.5f*line_width, writer.y(), 7200);
|
||||
break;
|
||||
}
|
||||
@@ -2110,7 +1951,7 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
|
||||
poly = generate_support_cone_wall(writer, wt_box, feedrate, infill_cone, spacing);
|
||||
} else {
|
||||
WipeTower::box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
|
||||
poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int)wtwRib, true, false);
|
||||
poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int)wtwRib, true);
|
||||
}
|
||||
|
||||
// brim (first layer only)
|
||||
@@ -2228,15 +2069,32 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i
|
||||
return;
|
||||
|
||||
// this is an actual toolchange - let's calculate depth to reserve on the wipe tower
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width;
|
||||
float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
|
||||
m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
|
||||
layer_height_par);
|
||||
// Orca: Set ramming depth to 0 if ramming is disabled.
|
||||
float ramming_depth = m_enable_filament_ramming ? ((int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming) : 0;
|
||||
float first_wipe_line = - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width);
|
||||
const bool first_layer_plan = (m_plan.size() - 1) == m_first_layer_idx;
|
||||
m_plan.back().tool_changes.push_back(set_toolchange(old_tool, new_tool, layer_height_par, wipe_volume, first_layer_plan));
|
||||
}
|
||||
|
||||
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par);
|
||||
WipeTower2::WipeTowerInfo::ToolChange WipeTower2::set_toolchange(size_t old_tool, size_t new_tool, float layer_height, float wipe_volume, bool first_layer_plan)
|
||||
{
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width;
|
||||
float length_to_extrude = volume_to_length((m_semm ? 0.25f : m_filpar[old_tool].multitool_ramming_time) * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
|
||||
m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
|
||||
layer_height);
|
||||
// Orca: Reserve ramming depth only when toolchange_Unload() will actually ram,
|
||||
// otherwise the unprinted reservation leaves blank bands between the purge boxes.
|
||||
const bool do_ramming = tool_ramming_enabled(old_tool);
|
||||
// Orca: with the gap wall on a multi-tool printer the ram band is quantized up to
|
||||
// the whole reserved rows and the wipe restarts at the left-edge boundary on a
|
||||
// fresh row below it (BBL parity: the old-tool purge is whole rows and the wipe
|
||||
// always starts at the box corner, where the entry scrub runs).
|
||||
const bool boundary_wipe_start = boundary_wipe_start_enabled(old_tool);
|
||||
float ramming_depth = do_ramming ? ((int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming) : 0;
|
||||
// first_wipe_line rides for free on the last (partially used) ramming row, which
|
||||
// is already covered by ramming_depth. Without ramming that row does not exist
|
||||
// (and with a boundary wipe start the ram band is quantized to whole rows), so
|
||||
// the whole wipe volume needs reserved wiping depth.
|
||||
float first_wipe_line = (do_ramming && !boundary_wipe_start) ? - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width) : 0.f;
|
||||
|
||||
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height);
|
||||
|
||||
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
||||
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
||||
@@ -2245,12 +2103,11 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i
|
||||
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
||||
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
||||
// ORCA: and first-layer purge segments do not leave visible gaps.
|
||||
const bool first_layer_plan = (m_plan.size() - 1) == m_first_layer_idx;
|
||||
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
||||
|
||||
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||
|
||||
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume));
|
||||
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||
|
||||
return WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume);
|
||||
}
|
||||
|
||||
|
||||
@@ -2288,49 +2145,64 @@ void WipeTower2::save_on_last_wipe()
|
||||
continue;
|
||||
|
||||
// Which toolchange will finish_layer extrusions be subtracted from?
|
||||
int idx = first_toolchange_to_nonsoluble(m_layer_info->tool_changes);
|
||||
int idx = first_toolchange_to_nonsoluble_nonsupport(m_layer_info->tool_changes);
|
||||
|
||||
if (idx == -1) {
|
||||
// In this case, finish_layer will be called at the very beginning.
|
||||
finish_layer().total_extrusion_length_in_plane();
|
||||
}
|
||||
|
||||
const float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
|
||||
auto recompute_toolchange = [this, width](WipeTowerInfo::ToolChange& toolchange, float volume_to_save) {
|
||||
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
|
||||
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
|
||||
|
||||
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
||||
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
||||
// ORCA: m_extra_flow * m_perimeter_width, while later layers use
|
||||
// ORCA: m_extra_spacing_wipe * m_perimeter_width.
|
||||
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
||||
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
||||
// ORCA: and first-layer purge segments do not leave visible gaps.
|
||||
const bool first_layer_plan = size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx;
|
||||
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
||||
|
||||
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||
|
||||
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
|
||||
toolchange.wipe_volume = volume_left_to_wipe;
|
||||
};
|
||||
|
||||
for (int i=0; i<int(m_layer_info->tool_changes.size()); ++i) {
|
||||
auto& toolchange = m_layer_info->tool_changes[i];
|
||||
tool_change(toolchange.new_tool);
|
||||
|
||||
if (i == idx) {
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
|
||||
|
||||
float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height);
|
||||
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
|
||||
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
|
||||
|
||||
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
||||
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
||||
// ORCA: m_extra_flow * m_perimeter_width, while later layers use
|
||||
// ORCA: m_extra_spacing_wipe * m_perimeter_width.
|
||||
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
||||
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
||||
// ORCA: and first-layer purge segments do not leave visible gaps.
|
||||
const bool first_layer_plan = size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx;
|
||||
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
||||
|
||||
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||
|
||||
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
|
||||
toolchange.wipe_volume = volume_left_to_wipe;
|
||||
recompute_toolchange(toolchange, length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height));
|
||||
} else if (toolchange.wipe_volume < m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower) {
|
||||
// Keep filament_minimal_purge_on_wipe_tower enforced for toolchanges that get
|
||||
// no finish-layer saving, e.g. a support/soluble filament skipped as the
|
||||
// finish filament above. Recomputing only when the clamp binds leaves all
|
||||
// other toolchanges with their planned values bit-for-bit.
|
||||
recompute_toolchange(toolchange, 0.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Return index of first toolchange that switches to non-soluble extruder
|
||||
// ot -1 if there is no such toolchange.
|
||||
int WipeTower2::first_toolchange_to_nonsoluble(
|
||||
// Return the index of the toolchange whose new filament should print the layer's
|
||||
// finish extrusions (sparse infill + wall + brim), or -1 to print them with the
|
||||
// layer's incoming filament before any toolchange happens.
|
||||
// Like WipeTower::first_toolchange_to_nonsoluble_nonsupport(): support and soluble
|
||||
// filaments bond poorly to the material printed on top of them, so they must not
|
||||
// print the tower's shell when another filament is available on the layer.
|
||||
int WipeTower2::first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const
|
||||
{
|
||||
if (tool_changes.empty())
|
||||
return -1;
|
||||
|
||||
// If a specific wipe tower filament is forced, use it to decide where to finish the layer.
|
||||
if (m_wipe_tower_filament > 0) {
|
||||
for (size_t idx = 0; idx < tool_changes.size(); ++idx) {
|
||||
@@ -2339,8 +2211,19 @@ int WipeTower2::first_toolchange_to_nonsoluble(
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
// Orca: allow calculation of the required depth and wipe volume for soluble toolchanges as well.
|
||||
return tool_changes.empty() ? -1 : 0;
|
||||
|
||||
auto is_wall_filament = [this](size_t tool) {
|
||||
return !m_filpar[tool].is_soluble && !m_filpar[tool].is_support;
|
||||
};
|
||||
for (size_t idx = 0; idx < tool_changes.size(); ++idx)
|
||||
if (is_wall_filament(tool_changes[idx].new_tool))
|
||||
return idx;
|
||||
if (is_wall_filament(tool_changes.front().old_tool))
|
||||
return -1;
|
||||
// Only support/soluble filaments on this layer: keep the first toolchange so the
|
||||
// finish-layer saving and the minimal-purge clamp still apply to it (Orca depth
|
||||
// and wipe volume accounting, see save_on_last_wipe()).
|
||||
return 0;
|
||||
}
|
||||
|
||||
static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
|
||||
@@ -2363,6 +2246,24 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
|
||||
}
|
||||
|
||||
|
||||
// Precompute, for every plan layer, the wall openings ("skip points") at each toolchange's
|
||||
// entry position, like WipeTower::get_all_wall_skip_points(). toolchange_entry_pos()
|
||||
// reproduces from the finalized plan where tool_change() will start, so each gap coincides
|
||||
// with the entry travel's target (tcr.start_pos, pre-rotation frame). BBL parity: the gap
|
||||
// sits at the CP_TOOLCHANGE_WIPE start row, never at the ram band.
|
||||
void WipeTower2::compute_wall_skip_points()
|
||||
{
|
||||
m_wall_skip_points.assign(m_plan.size(), std::vector<Vec2f>());
|
||||
for (size_t layer_id = 0; layer_id < m_plan.size(); ++layer_id) {
|
||||
float depth_traversed = 0.f;
|
||||
for (const auto& toolchange : m_plan[layer_id].tool_changes) {
|
||||
m_wall_skip_points[layer_id].emplace_back(
|
||||
toolchange_entry_pos(depth_traversed, toolchange.ramming_depth, layer_id == m_first_layer_idx));
|
||||
depth_traversed += toolchange.required_depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
|
||||
// Resulting ToolChangeResults are appended into vector "result"
|
||||
void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
|
||||
@@ -2378,12 +2279,41 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
|
||||
}
|
||||
#endif
|
||||
|
||||
m_rib_length = std::max({m_rib_length, sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width)});
|
||||
if (m_wall_type == (int)wtwRib) {
|
||||
// Rib wall: force a square tower like WipeTower::plan_tower_new(), ignoring the
|
||||
// configured prime_tower_width (the GUI greys it out in rib mode). The planned depths
|
||||
// already include the extra-spacing factors, so sqrt(depth * width) preserves the
|
||||
// purge area. Replan every toolchange for the new width, then re-derive the depths.
|
||||
float max_depth = 0.f;
|
||||
for (const auto& current_plan : m_plan)
|
||||
max_depth = std::max(max_depth, current_plan.depth);
|
||||
if (max_depth > EPSILON) {
|
||||
m_wipe_tower_width = align_ceil(std::sqrt(max_depth * m_wipe_tower_width), m_perimeter_width);
|
||||
for (size_t idx = 0; idx < m_plan.size(); ++idx)
|
||||
for (auto& toolchange : m_plan[idx].tool_changes)
|
||||
toolchange = set_toolchange(toolchange.old_tool, toolchange.new_tool,
|
||||
m_plan[idx].height, toolchange.wipe_volume,
|
||||
idx == m_first_layer_idx);
|
||||
plan_tower();
|
||||
}
|
||||
|
||||
// Like WipeTower::plan_tower_new(): extend the ribs instead of the tower when the
|
||||
// tower is smaller than the height-based stability minimum.
|
||||
const float min_depth = WipeTower::get_limit_depth_by_height(m_wipe_tower_height);
|
||||
if (m_wipe_tower_depth + EPSILON < min_depth)
|
||||
m_rib_length = std::max(m_rib_length, min_depth * (float)std::sqrt(2.f));
|
||||
}
|
||||
|
||||
const float diagonal = std::sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width);
|
||||
m_rib_length = std::max(m_rib_length, diagonal);
|
||||
m_rib_length += m_extra_rib_length;
|
||||
m_rib_length = std::max(0.f, m_rib_length);
|
||||
m_rib_length = std::max(diagonal, m_rib_length); // a negative extra length must not shrink the ribs below the diagonal
|
||||
m_rib_width = std::min(m_rib_width, std::min(m_wipe_tower_depth, m_wipe_tower_width) /
|
||||
2.f); // Ensure that the rib wall of the wipetower are attached to the infill.
|
||||
|
||||
if (m_use_gap_wall)
|
||||
compute_wall_skip_points();
|
||||
|
||||
m_layer_info = m_plan.begin();
|
||||
m_current_height = 0.f;
|
||||
|
||||
@@ -2410,7 +2340,7 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
|
||||
if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width)
|
||||
m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f;
|
||||
|
||||
int idx = first_toolchange_to_nonsoluble(layer.tool_changes);
|
||||
int idx = first_toolchange_to_nonsoluble_nonsupport(layer.tool_changes);
|
||||
WipeTower::ToolChangeResult finish_layer_tcr;
|
||||
|
||||
if (idx == -1) {
|
||||
@@ -2503,8 +2433,7 @@ Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2&
|
||||
double feedrate,
|
||||
bool first_layer,
|
||||
bool rib_wall,
|
||||
bool extrude_perimeter,
|
||||
bool skip_points)
|
||||
bool extrude_perimeter)
|
||||
{
|
||||
|
||||
float retract_length = m_filpar[m_current_tool].retract_length;
|
||||
@@ -2524,18 +2453,28 @@ Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2&
|
||||
if (!extrude_perimeter)
|
||||
return wall_polygon;
|
||||
|
||||
if (skip_points) {
|
||||
result_wall = contrust_gap_for_skip_points(wall_polygon, std::vector<Vec2f>(), m_wipe_tower_width, 2.5 * m_perimeter_width,
|
||||
if (m_use_gap_wall) {
|
||||
// Cut the wall open at each toolchange's entry (see compute_wall_skip_points()).
|
||||
// The vector is empty during the save_on_last_wipe planning passes, which therefore
|
||||
// measure the un-gapped wall — same approximation as the BBL tower.
|
||||
static const std::vector<Vec2f> no_skip_points;
|
||||
const size_t layer_id = size_t(m_layer_info - m_plan.begin());
|
||||
const std::vector<Vec2f>& layer_skip_points =
|
||||
layer_id < m_wall_skip_points.size() ? m_wall_skip_points[layer_id] : no_skip_points;
|
||||
result_wall = contrust_gap_for_skip_points(wall_polygon, layer_skip_points, m_wipe_tower_width, 2.5 * m_perimeter_width,
|
||||
insert_skip_polygon);
|
||||
} else {
|
||||
result_wall.push_back(to_polyline(wall_polygon));
|
||||
insert_skip_polygon = wall_polygon;
|
||||
}
|
||||
writer.generate_path(result_wall, feedrate, retract_length, retract_speed, m_used_fillet);
|
||||
//if (m_cur_layer_id == 0) {
|
||||
// BoundingBox bbox = get_extents(result_wall);
|
||||
// m_rib_offset = Vec2f(-unscaled<float>(bbox.min.x()), -unscaled<float>(bbox.min.y()));
|
||||
//}
|
||||
// Tower-local shift that puts the rib wall's protruding first-layer min corner at the
|
||||
// configured tower position, like WipeTower::generate_support_wall_new(). Measured on
|
||||
// the un-gapped outline so a wall gap cannot shift the tower.
|
||||
if (rib_wall && is_first_layer()) {
|
||||
BoundingBox bbox = get_extents(insert_skip_polygon);
|
||||
m_rib_offset = Vec2f(-unscaled<float>(bbox.min.x()), -unscaled<float>(bbox.min.y()));
|
||||
}
|
||||
|
||||
return insert_skip_polygon;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ public:
|
||||
bool is_finish,
|
||||
bool is_contact = false) const;
|
||||
|
||||
// Whether this print cuts wall openings ("skip points") at the toolchange entries.
|
||||
// Shared with the entry routing in GCode.cpp so the router and the tower agree.
|
||||
static bool use_gap_wall(const PrintConfig& config);
|
||||
|
||||
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
||||
// y -- y coordinates of wipe tower in mm ( left bottom corner )
|
||||
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
|
||||
@@ -69,9 +73,9 @@ public:
|
||||
const float brim = m_wipe_tower_brim_width_real;
|
||||
return BoundingBoxf(Vec2d(-brim, -brim), Vec2d(double(m_wipe_tower_width) + brim, double(m_wipe_tower_depth) + brim));
|
||||
}
|
||||
// WT2 doesn't currently compute a rib-origin compensation like WipeTower (m_rib_offset),
|
||||
// so expose a zero offset for consistency purposes (to maintain API parity).
|
||||
Vec2f get_rib_offset() const { return Vec2f::Zero(); }
|
||||
// Tower-local shift that puts the rib wall's first-layer min corner at the configured
|
||||
// tower position, like WipeTower::get_rib_offset(). Zero unless the rib wall is used.
|
||||
Vec2f get_rib_offset() const { return m_rib_offset; }
|
||||
float get_rib_width() const { return m_rib_width; }
|
||||
float get_rib_length() const { return m_rib_length; }
|
||||
|
||||
@@ -149,6 +153,7 @@ public:
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
bool is_soluble = false;
|
||||
bool is_support = false;
|
||||
int temperature = 0;
|
||||
int first_layer_temperature = 0;
|
||||
int interface_print_temperature = 0;
|
||||
@@ -220,7 +225,6 @@ private:
|
||||
float m_perimeter_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
size_t m_first_layer_idx = size_t(-1);
|
||||
bool m_flat_ironing = false;
|
||||
bool m_enable_tower_interface_features = false;
|
||||
bool m_enable_tower_interface_cooldown_during_tower = false;
|
||||
bool m_prev_layer_had_interface = false;
|
||||
@@ -231,6 +235,12 @@ private:
|
||||
float m_rib_width = 10;
|
||||
float m_extra_rib_length = 0;
|
||||
float m_rib_length = 0;
|
||||
Vec2f m_rib_offset = Vec2f::Zero();
|
||||
bool m_use_gap_wall = false;
|
||||
// Per plan layer, each toolchange's entry position (tower-local, un-shifted frame):
|
||||
// where the wall is cut open so the entry travel does not cross the printed wall.
|
||||
// Filled by compute_wall_skip_points() once the plan is final.
|
||||
std::vector<std::vector<Vec2f>> m_wall_skip_points;
|
||||
|
||||
bool m_enable_arc_fitting = false;
|
||||
|
||||
@@ -278,6 +288,37 @@ private:
|
||||
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
|
||||
// Purge row lattice of toolchange_Wipe(): row pitch and extrusion width.
|
||||
float wipe_row_spacing(bool first_layer) const { return (first_layer ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; }
|
||||
float wipe_line_width() const { return m_perimeter_width * m_extra_flow; }
|
||||
|
||||
// Whether toolchange_Unload() rams this (old) tool out.
|
||||
bool tool_ramming_enabled(size_t tool) const { return (m_semm && m_enable_filament_ramming) || m_filpar[tool].multitool_ramming; }
|
||||
// Whether the wipe restarts at the box boundary on a fresh row below the quantized
|
||||
// ram band after ramming this (old) tool out (multi-tool gap wall; SEMM keeps the
|
||||
// stock continue-from-ram-end behavior).
|
||||
bool boundary_wipe_start_enabled(size_t tool) const { return tool_ramming_enabled(tool) && !m_semm && m_use_gap_wall; }
|
||||
|
||||
// With a boundary wipe start the wipe begins on a fresh row below the quantized ram
|
||||
// band. Y offset from the box start to that first wipe row.
|
||||
float wipe_start_offset_after_ram(float ramming_depth, bool first_layer) const
|
||||
{
|
||||
return ramming_depth + wipe_row_spacing(first_layer) - (m_perimeter_width + wipe_line_width()) / 2.f;
|
||||
}
|
||||
|
||||
// Tower-local entry position of a toolchange whose box starts depth_traversed into
|
||||
// the layer: the box corner, moved down to the first wipe row when the plan gives
|
||||
// it a boundary wipe start (ramming_depth > 0 iff the unload rams). tool_change()
|
||||
// enters here and compute_wall_skip_points() cuts the wall gap here, so the routed
|
||||
// entry, the gap and the wipe scrub all share one opening.
|
||||
Vec2f toolchange_entry_pos(float depth_traversed, float ramming_depth, bool first_layer) const
|
||||
{
|
||||
Vec2f pos(m_perimeter_width / 2.f, m_perimeter_width / 2.f + depth_traversed);
|
||||
if (!m_semm && m_use_gap_wall && ramming_depth > 0.f)
|
||||
pos.y() += wipe_start_offset_after_ram(ramming_depth, first_layer);
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Calculates extrusion flow needed to produce required line width for given layer height
|
||||
float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow
|
||||
{
|
||||
@@ -328,9 +369,10 @@ private:
|
||||
std::vector<float> m_used_filament_length;
|
||||
std::vector<std::pair<float, std::vector<float>>> m_used_filament_length_until_layer;
|
||||
|
||||
// Return index of first toolchange that switches to non-soluble extruder
|
||||
// ot -1 if there is no such toolchange.
|
||||
int first_toolchange_to_nonsoluble(
|
||||
// Return the index of the toolchange whose new filament should print the layer's
|
||||
// finish extrusions (sparse infill + wall + brim), or -1 to print them with the
|
||||
// layer's incoming filament before any toolchange happens.
|
||||
int first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
||||
|
||||
void toolchange_Unload(
|
||||
@@ -353,7 +395,9 @@ private:
|
||||
WipeTowerWriter2 &writer,
|
||||
const WipeTower::box_coordinates &cleaning_box,
|
||||
float wipe_volume,
|
||||
bool interface_layer);
|
||||
bool interface_layer,
|
||||
bool priming = false,
|
||||
bool fill_box = false);
|
||||
|
||||
|
||||
Polygon generate_support_rib_wall(WipeTowerWriter2& writer,
|
||||
@@ -361,8 +405,7 @@ private:
|
||||
double feedrate,
|
||||
bool first_layer,
|
||||
bool rib_wall,
|
||||
bool extrude_perimeter,
|
||||
bool skip_points);
|
||||
bool extrude_perimeter);
|
||||
|
||||
Polygon generate_support_cone_wall(
|
||||
WipeTowerWriter2& writer,
|
||||
@@ -372,6 +415,12 @@ private:
|
||||
float spacing);
|
||||
|
||||
Polygon generate_rib_polygon(const WipeTower::box_coordinates& wt_box);
|
||||
|
||||
void compute_wall_skip_points();
|
||||
|
||||
// Computes the depth reserved for a toolchange (shared by plan_toolchange() and the
|
||||
// rib-wall square-tower replanning in generate()).
|
||||
WipeTowerInfo::ToolChange set_toolchange(size_t old_tool, size_t new_tool, float layer_height, float wipe_volume, bool first_layer_plan);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -129,13 +129,13 @@ public:
|
||||
std::vector<PathFittingData> fitting_result;
|
||||
//BBS: simplify points by arc fitting
|
||||
void simplify_by_fitting_arc(double tolerance);
|
||||
//BBS:
|
||||
void reset_to_linear_move();
|
||||
//BBS:
|
||||
Polylines equally_spaced_lines(double distance) const;
|
||||
|
||||
private:
|
||||
void append_fitting_result_after_append_points();
|
||||
void append_fitting_result_after_append_polyline(const Polyline& src);
|
||||
void reset_to_linear_move();
|
||||
bool split_fitting_result_before_index(const size_t index, Point &new_endpoint, std::vector<PathFittingData>& data) const;
|
||||
bool split_fitting_result_after_index(const size_t index, Point &new_startpoint, std::vector<PathFittingData>& data) const;
|
||||
};
|
||||
|
||||
+413
-1
@@ -1002,8 +1002,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 +1400,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",
|
||||
|
||||
+371
-65
@@ -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();
|
||||
@@ -3103,7 +3409,11 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps, std::vector<
|
||||
}
|
||||
else if ((extruder_volume_type_count > extruder_count) && (m_config.filament_volume_map.values.size() > index))
|
||||
nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]);
|
||||
m_config.filament_map_2.values[index] = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
// Orca: when the process variant columns cannot be matched (degenerate
|
||||
// print_extruder_id), key the override by plain extruder index like the seeding
|
||||
// above instead of poisoning the map with -1.
|
||||
int slot_index = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
m_config.filament_map_2.values[index] = slot_index >= 0 ? slot_index : f_maps[index] - 1;
|
||||
}
|
||||
|
||||
m_full_print_config = m_ori_full_print_config;
|
||||
@@ -3711,10 +4021,33 @@ void Print::_make_wipe_tower()
|
||||
// in BBL machine, wipe tower is only use to prime extruder. So just use a global wipe volume.
|
||||
WipeTower wipe_tower(m_config, m_plate_index, m_origin, m_wipe_tower_data.tool_ordering.first_extruder(),
|
||||
m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z, m_wipe_tower_data.tool_ordering.all_extruders());
|
||||
// Orca: the tower's first-layer flow follows the user's first-layer flow ratio (BBS reads
|
||||
// its initial_layer_flow_ratio here — STUDIO-14254; first_layer_flow_ratio is Orca's analog,
|
||||
// default 1.0 in both). Honor the set_other_flow_ratios gate that governs the option
|
||||
// everywhere else.
|
||||
wipe_tower.set_first_layer_flow_ratio(m_default_object_config.set_other_flow_ratios
|
||||
? float(m_default_region_config.first_layer_flow_ratio)
|
||||
: 1.f);
|
||||
wipe_tower.set_has_tpu_filament(this->has_tpu_filament());
|
||||
wipe_tower.set_filament_map(this->get_filament_maps());
|
||||
// Vortek H2C: pass nozzle-level map for carousel rotation detection in tool_change_new()
|
||||
wipe_tower.set_filament_nozzle_map(this->get_filament_nozzle_maps());
|
||||
// Per-layer filament->nozzle grouping. sort_and_build_data() above publishes it on the Print
|
||||
// for by-layer prints; by-object prints publish only later (psSkirtBrim), so fall back to the
|
||||
// ToolOrdering's own copy there. set_extruder() below dereferences it, so it must be set first.
|
||||
auto print_group_result = get_layered_nozzle_group_result();
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult &nozzle_group_result =
|
||||
print_group_result ? *print_group_result : m_wipe_tower_data.tool_ordering.get_layered_nozzle_group_result();
|
||||
wipe_tower.set_nozzle_group_result(nozzle_group_result);
|
||||
{
|
||||
// Orca: acceleration options are object-scope (PrintConfig members in BBS), so resolve
|
||||
// the per-variant columns here; initial_layer_travel_acceleration is FloatOrPercent
|
||||
// over travel_acceleration and needs the full config to resolve.
|
||||
std::vector<double> first_layer_travel_accels;
|
||||
for (size_t i = 0; i < m_config.initial_layer_travel_acceleration.values.size(); ++i)
|
||||
first_layer_travel_accels.emplace_back(m_full_print_config.get_abs_value_at("initial_layer_travel_acceleration", i));
|
||||
wipe_tower.set_accelerations(m_default_object_config.default_acceleration.values,
|
||||
m_default_object_config.initial_layer_acceleration.values,
|
||||
m_default_object_config.travel_acceleration.values,
|
||||
first_layer_travel_accels);
|
||||
}
|
||||
// Feed the has_filament_switcher device flag (develop-only dynamic key, read defensively from
|
||||
// the full config — no shipping profile sets it) and the shared printable bed used by the PETG
|
||||
// pre-extrusion offset clamp. Both are inert unless has_filament_switcher is set.
|
||||
@@ -3750,27 +4083,19 @@ void Print::_make_wipe_tower()
|
||||
multi_extruder_flush.emplace_back(wipe_volumes);
|
||||
}
|
||||
|
||||
// Use NozzleStatusRecorder for per-carousel-slot tracking (BBS pattern).
|
||||
// The original Orca code tracked per-extruder (2 slots), which collapsed all
|
||||
// carousel filaments into one slot and caused massive redundant AMS flushing.
|
||||
auto group_result = get_layered_nozzle_group_result();
|
||||
// Per-carousel-slot purge tracking via NozzleStatusRecorder (BBS pattern); the layered
|
||||
// group result set on the tower above resolves each filament to its nozzle slot per layer.
|
||||
MultiNozzleUtils::NozzleStatusRecorder nozzle_recorder;
|
||||
// Fallback (group_result == null) per-physical-nozzle tracking, matching the original
|
||||
// pre-port behavior: remembers the last filament loaded in each physical nozzle slot.
|
||||
std::vector<unsigned int> nozzle_cur_filament_ids(nozzle_nums, (unsigned int) -1);
|
||||
|
||||
std::vector<int>filament_maps = get_filament_maps();
|
||||
int layer_idx = -1;
|
||||
|
||||
unsigned int current_filament_id = m_wipe_tower_data.tool_ordering.first_extruder();
|
||||
// Initialize NozzleStatusRecorder with the first filament's carousel slot
|
||||
if (group_result) {
|
||||
auto nozzle = group_result->get_nozzle_for_filament(current_filament_id, layer_idx);
|
||||
{
|
||||
auto nozzle = nozzle_group_result.get_nozzle_for_filament(current_filament_id, layer_idx);
|
||||
if (nozzle)
|
||||
nozzle_recorder.set_nozzle_status(nozzle->group_id, current_filament_id, nozzle->extruder_id);
|
||||
} else {
|
||||
size_t cur_nozzle_id = filament_maps[current_filament_id] - 1;
|
||||
nozzle_cur_filament_ids[cur_nozzle_id] = current_filament_id;
|
||||
}
|
||||
|
||||
for (auto& layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers
|
||||
@@ -3789,8 +4114,8 @@ void Print::_make_wipe_tower()
|
||||
float volume_to_purge = 0;
|
||||
|
||||
// Per-carousel-slot purge tracking via NozzleStatusRecorder
|
||||
if (group_result) {
|
||||
auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_idx);
|
||||
{
|
||||
auto nozzle_info = nozzle_group_result.get_nozzle_for_filament(filament_id, layer_idx);
|
||||
if (nozzle_info) {
|
||||
int extruder_id = nozzle_info->extruder_id;
|
||||
int nozzle_id = nozzle_info->group_id;
|
||||
@@ -3809,22 +4134,6 @@ void Print::_make_wipe_tower()
|
||||
}
|
||||
nozzle_recorder.set_nozzle_status(nozzle_id, filament_id, extruder_id);
|
||||
}
|
||||
} else {
|
||||
// Fallback: original Orca per-physical-nozzle path (non-carousel printers).
|
||||
// Flush source is the last filament that occupied THIS nozzle, guarded so the
|
||||
// first use of a nozzle incurs no flush.
|
||||
int nozzle_id = filament_maps[filament_id] - 1;
|
||||
unsigned int pre_filament_id = nozzle_cur_filament_ids[nozzle_id];
|
||||
if (pre_filament_id != (unsigned int) -1 && pre_filament_id != filament_id) {
|
||||
volume_to_purge = multi_extruder_flush[nozzle_id][pre_filament_id][filament_id];
|
||||
float flush_multiplier = (m_config.prime_volume_mode == PrimeVolumeMode::pvmFast)
|
||||
? m_config.flush_multiplier_fast.get_at(nozzle_id)
|
||||
: m_config.flush_multiplier.get_at(nozzle_id);
|
||||
volume_to_purge *= flush_multiplier;
|
||||
volume_to_purge = layer_tools.wiping_extrusions().mark_wiping_extrusions(
|
||||
*this, current_filament_id, filament_id, volume_to_purge);
|
||||
}
|
||||
nozzle_cur_filament_ids[nozzle_id] = filament_id;
|
||||
}
|
||||
|
||||
//During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length.
|
||||
@@ -3832,29 +4141,21 @@ void Print::_make_wipe_tower()
|
||||
float grab_purge_volume = m_config.grab_length.get_at(grab_extruder_id) * 2.4; //(diameter/2)^2*PI=2.4
|
||||
volume_to_purge = std::max(0.f, volume_to_purge - grab_purge_volume);
|
||||
|
||||
// Select prime volume per-filament: nozzle change (carousel rotation) uses
|
||||
// filament_prime_volume_nc, filament change (same nozzle slot) uses filament_prime_volume.
|
||||
// Prime volume per-filament: the tower now picks extruder-change vs nozzle-change
|
||||
// (carousel) internally per plan layer, so pass both candidates (BBS pattern).
|
||||
float wipe_volume_ec = filament_id < m_config.filament_prime_volume.values.size()
|
||||
? m_config.filament_prime_volume.values[filament_id]
|
||||
: (float) m_config.prime_volume;
|
||||
float wipe_volume_nc = filament_id < m_config.filament_prime_volume_nc.values.size()
|
||||
? m_config.filament_prime_volume_nc.values[filament_id]
|
||||
: (float) m_config.prime_volume;
|
||||
|
||||
float prime_volume = wipe_volume_ec;
|
||||
if (group_result) {
|
||||
bool is_nozzle_change = group_result->are_filaments_same_extruder(current_filament_id, filament_id, layer_idx) &&
|
||||
!group_result->are_filaments_same_nozzle(current_filament_id, filament_id, layer_idx);
|
||||
if (is_nozzle_change) {
|
||||
prime_volume = wipe_volume_nc;
|
||||
}
|
||||
}
|
||||
if (m_config.prime_volume_mode == PrimeVolumeMode::pvmSaving) {
|
||||
prime_volume = 15.f;
|
||||
wipe_volume_ec = 15.f;
|
||||
wipe_volume_nc = 15.f;
|
||||
}
|
||||
|
||||
wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, filament_id,
|
||||
prime_volume, volume_to_purge);
|
||||
wipe_volume_ec, wipe_volume_nc, volume_to_purge);
|
||||
current_filament_id = filament_id;
|
||||
}
|
||||
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
|
||||
@@ -4032,7 +4333,12 @@ void Print::_make_wipe_tower()
|
||||
wipe_tower.get_rib_width(), wipe_tower.get_rib_length(),
|
||||
config().wipe_tower_fillet_wall.value);
|
||||
const Vec3d origin = Vec3d::Zero();
|
||||
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_wipe_tower_height(),
|
||||
// FakeWipeTower::pos is a bed-frame translation applied after rotation
|
||||
// (getFakeExtrusionPathsFromWipeTower2 rotates about the local origin), so the
|
||||
// tower-local rib offset must be rotated into the bed frame first.
|
||||
m_fake_wipe_tower.rib_offset = Eigen::Rotation2Df(Geometry::deg2rad((float)config().wipe_tower_rotation_angle.value)) *
|
||||
wipe_tower.get_rib_offset();
|
||||
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position() + m_fake_wipe_tower.rib_offset, wipe_tower.width(), wipe_tower.get_wipe_tower_height(),
|
||||
config().initial_layer_print_height, m_wipe_tower_data.depth,
|
||||
m_wipe_tower_data.z_and_depth_pairs, m_wipe_tower_data.brim_width,
|
||||
config().wipe_tower_rotation_angle, config().wipe_tower_cone_angle,
|
||||
|
||||
@@ -1355,7 +1355,11 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps
|
||||
&& opt_filament_volume_maps->values.size() == filament_maps.size())
|
||||
nozzle_volume_type = (NozzleVolumeType)(opt_filament_volume_maps->values[index]);
|
||||
m_config.filament_map_2.values[index] = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
// Orca: when the process variant columns cannot be matched (degenerate
|
||||
// print_extruder_id), key the override by plain extruder index like the seeding
|
||||
// above instead of poisoning the map with -1.
|
||||
int slot_index = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
m_config.filament_map_2.values[index] = slot_index >= 0 ? slot_index : filament_maps[index] - 1;
|
||||
}
|
||||
|
||||
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
|
||||
@@ -1411,6 +1415,16 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
num_extruders_changed = true;
|
||||
}
|
||||
}
|
||||
else if (! print_diff.empty()) {
|
||||
// Orca: m_config can diverge from an unchanged full config (e.g. the in-slice retract
|
||||
// override recompute writing different values than the apply-time computation). The
|
||||
// invalidation above already fired for print_diff, so repair m_config here as well;
|
||||
// otherwise the divergence is never corrected and every subsequent apply of the same
|
||||
// config invalidates the result again, forever.
|
||||
m_placeholder_parser.apply_config(filament_overrides);
|
||||
m_config.apply_only(new_full_config, print_diff, true);
|
||||
m_config.apply(filament_overrides);
|
||||
}
|
||||
|
||||
ModelObjectStatusDB model_object_status_db;
|
||||
|
||||
|
||||
+7071
-9
File diff suppressed because it is too large
Load Diff
@@ -214,8 +214,6 @@ enum class PrintOrder
|
||||
{
|
||||
Default,
|
||||
AsObjectList,
|
||||
BestOfStrategies, // run all custom strategies, pick the shortest total path
|
||||
Snake, // snake-like row traversal (back-and-forth) + 2-opt
|
||||
Count,
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "KDTreeIndirect.hpp"
|
||||
#include "MutablePriorityQueue.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "GCode/OrderingStrategies.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
@@ -1104,7 +1103,7 @@ std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy) {
|
||||
return chain_points(points);
|
||||
}
|
||||
|
||||
std::vector<size_t> chain_points(const Points &points, const Point *start_near)
|
||||
std::vector<size_t> chain_points(const Points &points, Point *start_near)
|
||||
{
|
||||
auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; };
|
||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, points.size(), start_near);
|
||||
@@ -1112,26 +1111,9 @@ std::vector<size_t> chain_points(const Points &points, const Point *start_near)
|
||||
out.reserve(ordered.size());
|
||||
for (auto &segment_and_reversal : ordered)
|
||||
out.emplace_back(segment_and_reversal.first);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<size_t> chain_points_with_postprocessing(const Points &points, const Point *start_near)
|
||||
{
|
||||
std::vector<size_t> path = chain_points(points, start_near);
|
||||
// Alternate 2-opt and crossing removal until convergence.
|
||||
// 2-opt can create new crossings, and crossing removal can create new
|
||||
// opportunities for 2-opt improvement. Break early if neither improves.
|
||||
for (int iter = 0; iter < 3; ++iter) {
|
||||
bool improved = tsp_2opt_improve(path, points);
|
||||
improved |= tsp_remove_crossings(path, points);
|
||||
if (!improved) break;
|
||||
}
|
||||
if (start_near == nullptr)
|
||||
tsp_rotate_minimize_closing(path, points);
|
||||
return path;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// #define DEBUG_SVG_OUTPUT
|
||||
#endif /* NDEBUG */
|
||||
@@ -2043,13 +2025,12 @@ std::vector<const PrintInstance*> chain_print_object_instances(const std::vector
|
||||
instances.emplace_back(i, j);
|
||||
}
|
||||
}
|
||||
// Order objects using nearest neighbor + post-processing (crossing removal + 2-opt).
|
||||
std::vector<size_t> path = chain_points_with_postprocessing(object_reference_points, start_near);
|
||||
|
||||
auto segment_end_point = [&object_reference_points](size_t idx, bool /* first_point */) -> const Point& { return object_reference_points[idx]; };
|
||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, instances.size(), start_near);
|
||||
std::vector<const PrintInstance*> out;
|
||||
out.reserve(path.size());
|
||||
for (size_t idx : path) {
|
||||
const std::pair<size_t, size_t>& inst = instances[idx];
|
||||
out.reserve(instances.size());
|
||||
for (auto& segment_and_reversal : ordered) {
|
||||
const std::pair<size_t, size_t>& inst = instances[segment_and_reversal.first];
|
||||
out.emplace_back(&print_objects[inst.first]->instances()[inst.second]);
|
||||
}
|
||||
return out;
|
||||
|
||||
@@ -15,9 +15,7 @@ namespace Slic3r {
|
||||
using PolyNodes = std::vector<PolyNode*, PointsAllocator<PolyNode*>>;
|
||||
}
|
||||
|
||||
std::vector<size_t> chain_points(const Points &points, const Point *start_near = nullptr);
|
||||
// Variant with post-processing (crossing removal + 2-opt) for object ordering.
|
||||
std::vector<size_t> chain_points_with_postprocessing(const Points &points, const Point *start_near = nullptr);
|
||||
std::vector<size_t> chain_points(const Points &points, Point *start_near = nullptr);
|
||||
std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy);
|
||||
|
||||
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
|
||||
|
||||
@@ -65,15 +65,6 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
||||
const bool smooth_supports = support_params.support_style != smsGrid;
|
||||
SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first;
|
||||
SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second;
|
||||
// The user-facing interface layer counts include the contact layer. Internally,
|
||||
// contact layers are generated separately, so only the remaining layers are
|
||||
// projected into intermediate interface/base-interface layers here.
|
||||
const size_t num_top_interface_layers = support_params.has_top_contacts ? support_params.num_top_interface_layers - 1 : 0;
|
||||
const size_t num_bottom_interface_layers = support_params.has_bottom_contacts ? support_params.num_bottom_interface_layers - 1 : 0;
|
||||
const size_t num_top_base_interface_layers = std::min(support_params.num_top_base_interface_layers, num_top_interface_layers);
|
||||
const size_t num_bottom_base_interface_layers = std::min(support_params.num_bottom_base_interface_layers, num_bottom_interface_layers);
|
||||
const size_t num_top_interface_layers_only = num_top_interface_layers - num_top_base_interface_layers;
|
||||
const size_t num_bottom_interface_layers_only = num_bottom_interface_layers - num_bottom_base_interface_layers;
|
||||
|
||||
interface_layers.assign(intermediate_layers.size(), nullptr);
|
||||
if (support_params.has_base_interfaces())
|
||||
@@ -133,8 +124,6 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
||||
};
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())),
|
||||
[&bottom_contacts, &top_contacts, &top_interface_layers, &top_base_interface_layers, &intermediate_layers, &insert_layer, &support_params,
|
||||
num_top_interface_layers, num_bottom_interface_layers, num_top_base_interface_layers, num_bottom_base_interface_layers,
|
||||
num_top_interface_layers_only, num_bottom_interface_layers_only,
|
||||
snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range<int>& range) {
|
||||
// Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below
|
||||
// this intermediate layer.
|
||||
@@ -153,16 +142,16 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
||||
Polygons polygons_top_contact_projected_base;
|
||||
Polygons polygons_bottom_contact_projected_interface;
|
||||
Polygons polygons_bottom_contact_projected_base;
|
||||
if (num_top_interface_layers > 0) {
|
||||
if (support_params.num_top_interface_layers > 0) {
|
||||
// Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
|
||||
coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(num_top_interface_layers) - 1)]->print_z;
|
||||
coordf_t top_interface_z = std::numeric_limits<coordf_t>::max();
|
||||
if (num_top_base_interface_layers > 0)
|
||||
coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers) - 1)]->print_z;
|
||||
coordf_t top_inteface_z = std::numeric_limits<coordf_t>::max();
|
||||
if (support_params.num_top_base_interface_layers > 0)
|
||||
// Some top base interface layers will be generated.
|
||||
top_interface_z = num_top_interface_layers_only == 0 ?
|
||||
top_inteface_z = support_params.num_top_interface_layers_only() == 0 ?
|
||||
// Only base interface layers to generate.
|
||||
- std::numeric_limits<coordf_t>::max() :
|
||||
intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(num_top_interface_layers_only) - 1)]->print_z;
|
||||
intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers_only()) - 1)]->print_z;
|
||||
// Move idx_top_contact_first up until above the current print_z.
|
||||
idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON
|
||||
// Collect the top contact areas above this intermediate layer, below top_z.
|
||||
@@ -171,22 +160,22 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
||||
//FIXME maybe this adds one interface layer in excess?
|
||||
if (top_contact_layer.bottom_z - EPSILON > top_z)
|
||||
break;
|
||||
polygons_append(top_contact_layer.bottom_z - EPSILON > top_interface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface,
|
||||
polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface,
|
||||
// For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers.
|
||||
// For grid supports, merging of support regions will be performed by the projection into grid.
|
||||
snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons);
|
||||
}
|
||||
}
|
||||
if (num_bottom_interface_layers > 0) {
|
||||
if (support_params.num_bottom_interface_layers > 0) {
|
||||
// Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
|
||||
coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - int(num_bottom_interface_layers) + 1)]->bottom_z;
|
||||
coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers) + 1)]->bottom_z;
|
||||
coordf_t bottom_interface_z = - std::numeric_limits<coordf_t>::max();
|
||||
if (num_bottom_base_interface_layers > 0)
|
||||
if (support_params.num_bottom_base_interface_layers > 0)
|
||||
// Some bottom base interface layers will be generated.
|
||||
bottom_interface_z = num_bottom_interface_layers_only == 0 ?
|
||||
bottom_interface_z = support_params.num_bottom_interface_layers_only() == 0 ?
|
||||
// Only base interface layers to generate.
|
||||
std::numeric_limits<coordf_t>::max() :
|
||||
intermediate_layers[std::max(0, idx_intermediate_layer - int(num_bottom_interface_layers_only))]->bottom_z;
|
||||
intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers_only()))]->bottom_z;
|
||||
// Move idx_bottom_contact_first up until touching bottom_z.
|
||||
idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const SupportGeneratorLayer *layer){ return layer->print_z >= bottom_z - EPSILON; });
|
||||
// Collect the top contact areas above this intermediate layer, below top_z.
|
||||
@@ -1574,17 +1563,13 @@ void generate_support_toolpaths(
|
||||
// Pointer to the 1st layer interface filler.
|
||||
auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get();
|
||||
// Filler for the 1st layer interface, if different from filler_interface.
|
||||
const bool top_interfaces_enabled = support_params.num_top_interface_layers > 0;
|
||||
const bool bottom_interfaces_enabled = support_params.num_bottom_interface_layers > 0;
|
||||
const coordf_t base_interface_density = top_interfaces_enabled || !bottom_interfaces_enabled ?
|
||||
support_params.top_interface_density : support_params.bottom_interface_density;
|
||||
auto filler_raft_contact_ptr = std::unique_ptr<Fill>(range.begin() == n_raft_layers && !top_interfaces_enabled ?
|
||||
auto filler_raft_contact_ptr = std::unique_ptr<Fill>(range.begin() == n_raft_layers && config.support_interface_top_layers.value == 0 ?
|
||||
Fill::new_from_type(support_params.raft_interface_fill_pattern) : nullptr);
|
||||
// Pointer to the 1st layer interface filler.
|
||||
auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get();
|
||||
// Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer).
|
||||
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr :
|
||||
Fill::new_from_type(base_interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
|
||||
Fill::new_from_type(support_params.top_interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
|
||||
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(support_params.base_fill_pattern));
|
||||
filler_interface->set_bounding_box(bbox_object);
|
||||
if (filler_first_layer_ptr)
|
||||
@@ -1598,7 +1583,10 @@ void generate_support_toolpaths(
|
||||
{
|
||||
SupportLayer &support_layer = *support_layers[support_layer_id];
|
||||
LayerCache &layer_cache = layer_caches[support_layer_id];
|
||||
const float support_interface_angle = support_params.support_interface_angle(support_layer.interface_id());
|
||||
const float support_interface_angle = (config.support_interface_pattern == smipRectilinearInterlaced) ?
|
||||
support_params.raft_interface_angle(support_layer.interface_id()) :
|
||||
((support_params.support_style == smsGrid || config.support_interface_pattern == smipRectilinear) ?
|
||||
support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()));
|
||||
|
||||
// Find polygons with the same print_z.
|
||||
SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer;
|
||||
@@ -1631,9 +1619,7 @@ void generate_support_toolpaths(
|
||||
bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z);
|
||||
// ORCA: Organic tree uses projected contacts to build the interface stack; avoid extra bottom-contact extrusion.
|
||||
const bool organic_tree = support_params.support_style == SupportMaterialStyle::smsTreeOrganic;
|
||||
const bool top_interfaces = support_params.num_top_interface_layers > 0;
|
||||
const bool bottom_interfaces = support_params.num_bottom_interface_layers > 0;
|
||||
if (!top_interfaces) {
|
||||
if (config.support_interface_top_layers == 0) {
|
||||
// If no top interface layers were requested, we treat the contact layer exactly as a generic base layer.
|
||||
// Don't merge the raft contact layer though.
|
||||
if (support_params.can_merge_support_regions && ! raft_layer) {
|
||||
@@ -1656,29 +1642,15 @@ void generate_support_toolpaths(
|
||||
if (top_contact_layer.could_merge(interface_layer) && ! raft_layer)
|
||||
top_contact_layer.merge(std::move(interface_layer));
|
||||
}
|
||||
if (!bottom_interfaces && support_params.can_merge_support_regions) {
|
||||
if ((config.support_interface_top_layers == 0 || config.support_interface_bottom_layers == 0) && support_params.can_merge_support_regions) {
|
||||
if (base_layer.could_merge(bottom_contact_layer))
|
||||
base_layer.merge(std::move(bottom_contact_layer));
|
||||
else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging)
|
||||
base_layer = std::move(bottom_contact_layer);
|
||||
} else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) {
|
||||
if (top_interfaces && bottom_interfaces) {
|
||||
top_contact_layer.merge(std::move(bottom_contact_layer));
|
||||
} else if (bottom_interfaces) {
|
||||
top_contact_layer.set_polygons_to_extrude(
|
||||
diff(top_contact_layer.polygons_to_extrude(), bottom_contact_layer.polygons_to_extrude()));
|
||||
} else {
|
||||
bottom_contact_layer.set_polygons_to_extrude(
|
||||
diff(bottom_contact_layer.polygons_to_extrude(), top_contact_layer.polygons_to_extrude()));
|
||||
}
|
||||
top_contact_layer.merge(std::move(bottom_contact_layer));
|
||||
} else if (bottom_contact_layer.could_merge(interface_layer) && ! organic_tree) {
|
||||
const bool interface_layer_is_bottom = interface_layer.layer->layer_type == SupporLayerType::BottomInterface;
|
||||
if (bottom_interfaces && interface_layer_is_bottom) {
|
||||
bottom_contact_layer.merge(std::move(interface_layer));
|
||||
} else {
|
||||
bottom_contact_layer.set_polygons_to_extrude(
|
||||
diff(bottom_contact_layer.polygons_to_extrude(), interface_layer.polygons_to_extrude()));
|
||||
}
|
||||
bottom_contact_layer.merge(std::move(interface_layer));
|
||||
}
|
||||
|
||||
// Orca: For organic trees the support-material regions are generated from
|
||||
@@ -1758,12 +1730,12 @@ void generate_support_toolpaths(
|
||||
interface_as_base ? ExtrusionRole::erSupportMaterial : ExtrusionRole::erSupportMaterialInterface, interface_flow);
|
||||
}
|
||||
};
|
||||
const bool top_interfaces = support_params.num_top_interface_layers > 0;
|
||||
const bool bottom_interfaces = top_interfaces && support_params.num_bottom_interface_layers > 0;
|
||||
extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase);
|
||||
if (!organic_tree)
|
||||
extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase);
|
||||
const bool interface_layer_enabled = !interface_layer.empty() &&
|
||||
(interface_layer.layer->layer_type == SupporLayerType::BottomInterface ? bottom_interfaces : top_interfaces);
|
||||
extrude_interface(interface_layer, interface_layer_enabled ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase);
|
||||
extrude_interface(interface_layer, top_interfaces ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase);
|
||||
// Base interface layers under soluble interfaces
|
||||
if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) {
|
||||
Fill *filler = filler_base_interface.get();
|
||||
@@ -1773,7 +1745,7 @@ void generate_support_toolpaths(
|
||||
Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
|
||||
filler->angle = support_interface_angle;
|
||||
filler->spacing = support_params.support_material_interface_flow.spacing();
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / base_interface_density));
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.top_interface_density));
|
||||
fill_expolygons_generate_paths(
|
||||
// Destination
|
||||
base_interface_layer.extrusions,
|
||||
@@ -1781,7 +1753,7 @@ void generate_support_toolpaths(
|
||||
// Regions to fill
|
||||
union_safety_offset_ex(base_interface_layer.polygons_to_extrude()),
|
||||
// Filler and its parameters
|
||||
filler, float(base_interface_density),
|
||||
filler, float(support_params.top_interface_density),
|
||||
// Extrusion parameters
|
||||
ExtrusionRole::erSupportMaterial, interface_flow);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ struct SupportParameters {
|
||||
|
||||
{
|
||||
this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value);
|
||||
this->num_bottom_interface_layers = std::max(0, number_of_support_interface_bottom_layers(object_config));
|
||||
this->num_bottom_interface_layers = number_of_support_interface_bottom_layers(object_config);
|
||||
this->has_top_contacts = num_top_interface_layers > 0;
|
||||
this->has_bottom_contacts = num_bottom_interface_layers > 0;
|
||||
// BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion
|
||||
@@ -46,15 +46,15 @@ struct SupportParameters {
|
||||
if (non_soluble_base_top) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
|
||||
this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2));
|
||||
} else {
|
||||
// Keep at least one configured layer on the interface filament.
|
||||
this->num_top_base_interface_layers = different_support_interface_filament && num_top_interface_layers > 1 ? 1 : 0;
|
||||
this->num_top_base_interface_layers =
|
||||
(different_support_interface_filament && this->zero_gap_interface_top) ? 1 : 0;
|
||||
}
|
||||
|
||||
if (non_soluble_base_bottom) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
|
||||
this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2));
|
||||
} else {
|
||||
// Keep at least one configured layer on the interface filament.
|
||||
this->num_bottom_base_interface_layers = different_support_interface_filament && num_bottom_interface_layers > 1 ? 1 : 0;
|
||||
this->num_bottom_base_interface_layers =
|
||||
(different_support_interface_filament && this->zero_gap_interface_bottom) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height));
|
||||
@@ -74,7 +74,7 @@ struct SupportParameters {
|
||||
for (auto layer : object.layers())
|
||||
this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, layer->height));
|
||||
|
||||
if (this->num_top_interface_layers == 0 && this->num_bottom_interface_layers == 0) {
|
||||
if (object_config.support_interface_top_layers.value == 0) {
|
||||
// No interface layers allowed, print everything with the base support pattern.
|
||||
this->support_material_interface_flow = this->support_material_flow;
|
||||
}
|
||||
@@ -120,8 +120,8 @@ struct SupportParameters {
|
||||
this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing);
|
||||
this->support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing();
|
||||
this->support_density = std::min(1., this->support_material_flow.spacing() / this->support_spacing);
|
||||
if (this->num_top_interface_layers == 0) {
|
||||
// No top interface layers allowed; keep unused top interface parameters aligned with base support.
|
||||
if (object_config.support_interface_top_layers.value == 0) {
|
||||
// No interface layers allowed, print everything with the base support pattern.
|
||||
this->top_interface_spacing = this->support_spacing;
|
||||
this->top_interface_density = this->support_density;
|
||||
}
|
||||
@@ -133,20 +133,16 @@ struct SupportParameters {
|
||||
this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase;
|
||||
this->interface_fill_pattern = (this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase;
|
||||
const coordf_t contact_interface_density = this->num_top_interface_layers > 0 ?
|
||||
this->top_interface_density : this->bottom_interface_density;
|
||||
const bool zero_gap_contact_interface = this->num_top_interface_layers > 0 ?
|
||||
this->zero_gap_interface_top : this->zero_gap_interface_bottom;
|
||||
if (object_config.support_interface_pattern == smipGrid)
|
||||
this->contact_fill_pattern = ipGrid;
|
||||
else if (object_config.support_interface_pattern == smipRectilinearInterlaced)
|
||||
this->contact_fill_pattern = ipRectilinear;
|
||||
else
|
||||
this->contact_fill_pattern =
|
||||
(object_config.support_interface_pattern == smipAuto && zero_gap_contact_interface) ||
|
||||
(object_config.support_interface_pattern == smipAuto && this->zero_gap_interface_top) ||
|
||||
object_config.support_interface_pattern == smipConcentric ?
|
||||
ipConcentric :
|
||||
(contact_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
(this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
|
||||
this->raft_angle_1st_layer = 0.f;
|
||||
this->raft_angle_base = 0.f;
|
||||
@@ -192,7 +188,6 @@ struct SupportParameters {
|
||||
std::numeric_limits<double>::max();
|
||||
|
||||
support_style = object_config.support_style;
|
||||
support_interface_pattern = object_config.support_interface_pattern;
|
||||
if (support_style != smsDefault) {
|
||||
if ((support_style == smsSnug || support_style == smsGrid) && is_tree(object_config.support_type)) support_style = smsDefault;
|
||||
if ((support_style == smsTreeSlim || support_style == smsTreeStrong || support_style == smsTreeHybrid || support_style == smsTreeOrganic) &&
|
||||
@@ -216,9 +211,9 @@ struct SupportParameters {
|
||||
bool has_top_contacts;
|
||||
// Is there at least a bottom contact layer extruded below support base?
|
||||
bool has_bottom_contacts;
|
||||
// User-configured number of top interface layers, including the contact layer.
|
||||
// Number of top interface layers without counting the contact layer.
|
||||
size_t num_top_interface_layers;
|
||||
// User-configured number of bottom interface layers, including the contact layer.
|
||||
// Number of bottom interface layers without counting the contact layer.
|
||||
size_t num_bottom_interface_layers;
|
||||
// Number of top base interface layers.
|
||||
size_t num_top_base_interface_layers;
|
||||
@@ -240,7 +235,7 @@ struct SupportParameters {
|
||||
Flow support_material_interface_flow;
|
||||
// Flow at the bottom interfaces and contacts.
|
||||
Flow support_material_bottom_interface_flow;
|
||||
// Flow at raft interface & contact layers.
|
||||
// Flow at raft inteface & contact layers.
|
||||
Flow raft_interface_flow;
|
||||
coordf_t support_extrusion_width;
|
||||
// Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
|
||||
@@ -267,7 +262,6 @@ struct SupportParameters {
|
||||
// Density of the base support layers.
|
||||
coordf_t support_density;
|
||||
SupportMaterialStyle support_style = smsDefault;
|
||||
SupportMaterialInterfacePattern support_interface_pattern = smipAuto;
|
||||
|
||||
// Pattern of the sparse infill including sparse raft layers.
|
||||
InfillPattern base_fill_pattern;
|
||||
@@ -286,33 +280,9 @@ struct SupportParameters {
|
||||
float raft_angle_base;
|
||||
float raft_angle_interface;
|
||||
|
||||
// Produce a +/-45deg alternating raft interface angle for a given SupportLayer::interface_id().
|
||||
// Produce a raft interface angle for a given SupportLayer::interface_id()
|
||||
float raft_interface_angle(size_t interface_id) const
|
||||
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI_4) : float(+ M_PI_4)); }
|
||||
|
||||
// Produce support interface angle for a given SupportLayer::interface_id().
|
||||
// Angle will be shifted/rotated based on interface pattern.
|
||||
float support_interface_angle(size_t interface_id) const
|
||||
{
|
||||
float angle;
|
||||
|
||||
switch (this->support_interface_pattern) {
|
||||
case SupportMaterialInterfacePattern::smipRectilinear:
|
||||
angle = support_style == SupportMaterialStyle::smsSnug ? this->interface_angle - float(M_PI_4) : this->interface_angle;
|
||||
break;
|
||||
case SupportMaterialInterfacePattern::smipRectilinearInterlaced:
|
||||
angle = this->interface_angle + ((interface_id & 1) ? float(M_PI_4) : float(-M_PI_4));
|
||||
break;
|
||||
case SupportMaterialInterfacePattern::smipGrid:
|
||||
angle = this->base_angle;
|
||||
break;
|
||||
default:
|
||||
angle = this->interface_angle;
|
||||
break;
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); }
|
||||
|
||||
bool independent_layer_height = false;
|
||||
const double thresh_big_overhang = Slic3r::sqr(scale_(10));
|
||||
|
||||
@@ -469,7 +469,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
});
|
||||
|
||||
// 2) Sum over top / bottom ranges.
|
||||
const bool processing_last_mesh = outline_idx == layer_outline_indices.back();
|
||||
const bool processing_last_mesh = outline_idx == layer_outline_indices.size();
|
||||
tbb::parallel_for(tbb::blocked_range<LayerIndex>(data.begin(), data.end()),
|
||||
[&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, radius,
|
||||
xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, processing_last_mesh, &throw_on_cancel]
|
||||
|
||||
@@ -1511,9 +1511,7 @@ void TreeSupport::generate_toolpaths()
|
||||
// ORCA: reset interface Fill state per area group to keep angles deterministic.
|
||||
filler_interface->fixed_angle = false;
|
||||
filler_interface->layer_id = size_t(-1);
|
||||
filler_Roof1stLayer->fixed_angle = false;
|
||||
filler_Roof1stLayer->layer_id = size_t(-1);
|
||||
filler_interface->angle = m_support_params.support_interface_angle(area_group.interface_id);
|
||||
filler_interface->angle = base_support_angle + M_PI_2; // default interface angle is perpendicular to support angle
|
||||
if (area_group.type != SupportLayer::BaseType) {
|
||||
// interface
|
||||
if (layer_id == 0) {
|
||||
@@ -1539,10 +1537,8 @@ void TreeSupport::generate_toolpaths()
|
||||
fill_params.density = interface_density;
|
||||
// Note: spacing means the separation between two lines as if they are tightly extruded
|
||||
filler_Roof1stLayer->spacing = interface_flow.spacing();
|
||||
filler_Roof1stLayer->angle = m_support_params.support_interface_angle(area_group.interface_id);
|
||||
filler_Roof1stLayer->angle = base_support_angle;
|
||||
fill_params.dont_sort = true;
|
||||
filler_Roof1stLayer->fixed_angle = (m_object_config->support_interface_pattern == smipRectilinearInterlaced ||
|
||||
m_object_config->support_interface_pattern == smipRectilinear);
|
||||
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
||||
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
||||
// generate a perimeter first to support interface better
|
||||
@@ -1560,11 +1556,18 @@ void TreeSupport::generate_toolpaths()
|
||||
fill_params.density = bottom_interface_density;
|
||||
filler_interface->spacing = interface_flow.spacing();
|
||||
|
||||
fill_params.dont_sort = (m_object_config->support_interface_pattern == smipGrid ||
|
||||
m_object_config->support_interface_pattern == smipRectilinearInterlaced);
|
||||
if (m_object_config->support_interface_pattern == smipGrid) {
|
||||
filler_interface->angle = base_support_angle;
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
|
||||
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
|
||||
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
|
||||
filler_interface->fixed_angle = true;
|
||||
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
|
||||
filler_interface->fixed_angle = (m_object_config->support_interface_pattern == smipRectilinearInterlaced ||
|
||||
m_object_config->support_interface_pattern == smipRectilinear);
|
||||
|
||||
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
||||
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
||||
@@ -1576,11 +1579,17 @@ void TreeSupport::generate_toolpaths()
|
||||
fill_params.density = interface_density;
|
||||
filler_interface->spacing = interface_flow.spacing();
|
||||
|
||||
fill_params.dont_sort = (m_object_config->support_interface_pattern == smipGrid ||
|
||||
m_object_config->support_interface_pattern == smipRectilinearInterlaced);
|
||||
if (m_object_config->support_interface_pattern == smipGrid) {
|
||||
filler_interface->angle = base_support_angle;
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
|
||||
filler_interface->fixed_angle = (m_object_config->support_interface_pattern == smipRectilinearInterlaced ||
|
||||
m_object_config->support_interface_pattern == smipRectilinear);
|
||||
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
|
||||
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
|
||||
filler_interface->fixed_angle = true;
|
||||
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
|
||||
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
||||
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
||||
@@ -2005,9 +2014,6 @@ void TreeSupport::draw_circles()
|
||||
// generate areas
|
||||
const coordf_t layer_height = config.layer_height.value;
|
||||
const size_t top_interface_layers = m_support_params.num_top_interface_layers;
|
||||
const int top_base_interface_layers = std::min<int>(
|
||||
int(m_support_params.num_top_base_interface_layers),
|
||||
top_interface_layers > 0 ? int(top_interface_layers) - 1 : 0);
|
||||
const size_t bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
||||
const double nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(0);
|
||||
const coordf_t line_width = config.get_abs_value("support_line_width", nozzle_diameter);
|
||||
@@ -2048,14 +2054,12 @@ void TreeSupport::draw_circles()
|
||||
|
||||
ExPolygons& base_areas = ts_layer->base_areas;
|
||||
ExPolygons& roof_areas = ts_layer->roof_areas;
|
||||
ExPolygons roof_base_areas;
|
||||
ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
|
||||
ExPolygons& floor_areas = ts_layer->floor_areas;
|
||||
ExPolygons& roof_gap_areas = ts_layer->roof_gap_areas;
|
||||
coordf_t max_layers_above_base = 0;
|
||||
coordf_t max_layers_above_roof = 0;
|
||||
coordf_t max_layers_above_roof1 = 0;
|
||||
size_t first_base_roof_area = 0;
|
||||
bool floor_interface_as_base = false;
|
||||
bool has_circle_node = false;
|
||||
bool need_extra_wall = false;
|
||||
@@ -2090,6 +2094,8 @@ void TreeSupport::draw_circles()
|
||||
break;
|
||||
|
||||
const SupportNode& node = *p_node;
|
||||
// ORCA: Cap top interface height in mm based on per-node support layer height.
|
||||
const coordf_t top_interface_height = coordf_t(top_interface_layers) * node.height;
|
||||
ExPolygons area;
|
||||
// Generate directly from overhang polygon if one of the following is true:
|
||||
// 1) node is a normal part of hybrid support
|
||||
@@ -2153,16 +2159,18 @@ void TreeSupport::draw_circles()
|
||||
|
||||
if (obj_layer_nr>0 && node.distance_to_top < 0)
|
||||
append(roof_gap_areas, area);
|
||||
// ORCA: Roof1stLayer must also fit inside the mm cap.
|
||||
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 &&
|
||||
node.is_sharp_tail == false)
|
||||
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail==false)
|
||||
{
|
||||
append(roof_1st_layer, area);
|
||||
max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top);
|
||||
}
|
||||
// ORCA: Roof layers must also fit inside the mm cap.
|
||||
else if (obj_layer_nr > 0 && node.support_roof_layers_below > 1 &&
|
||||
node.is_sharp_tail == false)
|
||||
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail == false)
|
||||
{
|
||||
append(node.support_roof_layers_below <= top_base_interface_layers ? roof_base_areas : roof_areas, area);
|
||||
append(roof_areas, area);
|
||||
max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top);
|
||||
}
|
||||
else
|
||||
@@ -2176,17 +2184,9 @@ void TreeSupport::draw_circles()
|
||||
//m_object->print()->set_status(65, (boost::format( _u8L("Support: generate polygons at layer %d")) % layer_nr).str());
|
||||
|
||||
// join roof segments
|
||||
roof_areas = diff_clipped(closing_ex(roof_areas, line_width_scaled), get_collision(false));
|
||||
roof_areas = diff_clipped(offset2_ex(roof_areas, line_width_scaled, -line_width_scaled), get_collision(false));
|
||||
roof_areas = intersection_ex(roof_areas, m_machine_border);
|
||||
roof_base_areas = diff_clipped(closing_ex(roof_base_areas, line_width_scaled), get_collision(false));
|
||||
roof_base_areas = intersection_ex(roof_base_areas, m_machine_border);
|
||||
if (!roof_base_areas.empty() && !roof_areas.empty())
|
||||
roof_base_areas = diff_ex(roof_base_areas,
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas, get_extents(roof_base_areas)));
|
||||
|
||||
first_base_roof_area = roof_areas.size();
|
||||
append(roof_areas, std::move(roof_base_areas));
|
||||
roof_1st_layer = diff_clipped(closing_ex(roof_1st_layer, line_width_scaled), get_collision(false));
|
||||
roof_1st_layer = diff_clipped(offset2_ex(roof_1st_layer, line_width_scaled, -line_width_scaled), get_collision(false));
|
||||
|
||||
// roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer
|
||||
roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer)));
|
||||
@@ -2366,11 +2366,9 @@ void TreeSupport::draw_circles()
|
||||
area_groups.back().need_infill = overlaps({ expoly }, area_poly);
|
||||
area_groups.back().need_extra_wall = need_extra_wall && !area_groups.back().need_infill;
|
||||
}
|
||||
for (size_t roof_idx = 0; roof_idx < ts_layer->roof_areas.size(); ++roof_idx) {
|
||||
auto &expoly = ts_layer->roof_areas[roof_idx];
|
||||
for (auto& expoly : ts_layer->roof_areas) {
|
||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||
area_groups.emplace_back(&expoly, SupportLayer::RoofType, max_layers_above_roof);
|
||||
area_groups.back().interface_as_base = roof_idx >= first_base_roof_area;
|
||||
}
|
||||
for (auto &expoly : ts_layer->floor_areas) {
|
||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||
@@ -2380,7 +2378,6 @@ void TreeSupport::draw_circles()
|
||||
for (auto &expoly : ts_layer->roof_1st_layer) {
|
||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||
area_groups.emplace_back(&expoly, SupportLayer::Roof1stLayer, max_layers_above_roof1);
|
||||
area_groups.back().interface_as_base = top_base_interface_layers > 0;
|
||||
}
|
||||
|
||||
for (auto &area_group : area_groups) {
|
||||
@@ -2409,6 +2406,7 @@ void TreeSupport::draw_circles()
|
||||
}
|
||||
});
|
||||
// ORCA: normalize interface_id sequencing to follow printed interface layers only.
|
||||
const int top_base_layers = int(m_support_params.num_top_base_interface_layers);
|
||||
const bool interlaced = m_object_config->support_interface_pattern == smipRectilinearInterlaced;
|
||||
int roof_interface_id = 0;
|
||||
int floor_interface_id = 0;
|
||||
@@ -2427,6 +2425,7 @@ void TreeSupport::draw_circles()
|
||||
if (area_group.type == SupportLayer::RoofType || area_group.type == SupportLayer::Roof1stLayer) {
|
||||
if (interlaced)
|
||||
area_group.interface_id = roof_interface_id;
|
||||
area_group.interface_as_base = top_base_layers > 0 && roof_interface_id < top_base_layers;
|
||||
has_roof_interface = true;
|
||||
} else if (area_group.type == SupportLayer::FloorType) {
|
||||
if (interlaced)
|
||||
@@ -2898,7 +2897,7 @@ void TreeSupport::drop_nodes()
|
||||
node_parent->merged_neighbours.push_front(node_parent == p_node ? neighbour : p_node);
|
||||
const bool to_buildplate = !is_inside_ex(get_collision(0, obj_layer_nr_next), next_position);
|
||||
SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next,
|
||||
node_parent->support_roof_layers_below - (node_parent->distance_to_top >= 0 ? 1 : 0),
|
||||
node_parent->support_roof_layers_below - (node_parent->distance_to_top > 0 ? 1 : 0),
|
||||
to_buildplate, node_parent, print_z_next, height_next);
|
||||
get_max_move_dist(next_node);
|
||||
m_ts_data->m_mutex.lock();
|
||||
@@ -2950,7 +2949,7 @@ void TreeSupport::drop_nodes()
|
||||
for(auto& overhang:overhangs_next) {
|
||||
Point next_pt = overhang.contour.centroid();
|
||||
SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next,
|
||||
p_node->support_roof_layers_below - (p_node->distance_to_top >= 0 ? 1 : 0),
|
||||
p_node->support_roof_layers_below - (p_node->distance_to_top > 0 ? 1 : 0),
|
||||
to_buildplate, p_node, print_z_next, height_next);
|
||||
next_node->max_move_dist = 0;
|
||||
next_node->overhang = std::move(overhang);
|
||||
@@ -3097,7 +3096,7 @@ void TreeSupport::drop_nodes()
|
||||
auto next_collision = get_collision(0, obj_layer_nr_next);
|
||||
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex);
|
||||
SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next,
|
||||
node.support_roof_layers_below - (node.distance_to_top >= 0 ? 1 : 0),
|
||||
node.support_roof_layers_below - (node.distance_to_top > 0 ? 1 : 0),
|
||||
to_buildplate, p_node, print_z_next, height_next);
|
||||
// don't increase radius if next node will collide partially with the object (STUDIO-7883)
|
||||
to_outside = projection_onto(next_collision, next_node->position);
|
||||
@@ -3377,6 +3376,21 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights()
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: Recompute support_roof_layers_below from remaining interface height (independent heights).
|
||||
const int top_layers = m_object->config().support_interface_top_layers.value;
|
||||
if (m_support_params.independent_layer_height && top_layers > 0) {
|
||||
const coordf_t interface_height_mm = coordf_t(top_layers) * m_slicing_params.layer_height;
|
||||
for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) {
|
||||
if (contact_nodes[layer_nr].empty()) continue;
|
||||
for (SupportNode *node : contact_nodes[layer_nr]) {
|
||||
if (node->height <= EPSILON) continue;
|
||||
const coordf_t remaining_mm = interface_height_mm - (node->dist_mm_to_top - this->top_z_distance);
|
||||
const int layers_fit = remaining_mm < -EPSILON ? 0 : int(std::floor((remaining_mm + EPSILON) / node->height));
|
||||
node->support_roof_layers_below = std::min(layers_fit, top_layers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log layer_heights
|
||||
for (size_t i = 0; i < layer_heights.size(); i++) {
|
||||
//if (layer_heights[i].height > EPSILON)
|
||||
@@ -3484,7 +3498,7 @@ void TreeSupport::generate_contact_points()
|
||||
if (force_add || !already_inserted.count(hash_pos)) {
|
||||
already_inserted.emplace(hash_pos);
|
||||
bool to_buildplate = true;
|
||||
size_t roof_layers = add_interface ? support_roof_layers : 0;
|
||||
size_t roof_layers = add_interface ? (support_roof_layers > 0 ? support_roof_layers - 1 : 0) : 0; // subtract 1 because the contact node itself counts as one layer
|
||||
// add a new node as a virtual node which acts as the invisible gap between support and object
|
||||
// distance_to_top=-1: it's virtual
|
||||
// print_z=object_layer->bottom_z: it directly contacts the bottom
|
||||
|
||||
@@ -706,7 +706,7 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
|
||||
filler->spacing = flow.spacing();
|
||||
filler->angle = roof ?
|
||||
//fixme support_layer.interface_id() instead of layer_idx
|
||||
(support_params.interface_angle + ((layer_idx & 1) ? float(- M_PI_4) : float(+ M_PI_4))) :
|
||||
(support_params.interface_angle + (layer_idx & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)) :
|
||||
support_params.base_angle;
|
||||
|
||||
// ORCA: use top-specific interface density after separating top/bottom settings.
|
||||
|
||||
@@ -62,7 +62,7 @@ struct TreeSupportMeshGroupSettings {
|
||||
this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width();
|
||||
this->support_roof_line_width = support_material_interface_flow(&print_object, config.layer_height).scaled_width();
|
||||
const int bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
||||
this->support_bottom_enable = bottom_interface_layers > 0;
|
||||
this->support_bottom_enable = config.support_interface_top_layers.value > 0 && bottom_interface_layers > 0;
|
||||
this->support_bottom_height = this->support_bottom_enable ?
|
||||
bottom_interface_layers * this->layer_height :
|
||||
0;
|
||||
@@ -705,7 +705,7 @@ public:
|
||||
SupportGeneratorLayersPtr& top_contacts_mutable() { return this->top_contacts; }
|
||||
|
||||
public:
|
||||
// Insert the contact layer and some of the interface and base interface layers below.
|
||||
// Insert the contact layer and some of the inteface and base interface layers below.
|
||||
void add_roofs(std::vector<Polygons> &&new_roofs, const size_t insert_layer_idx)
|
||||
{
|
||||
if (! new_roofs.empty()) {
|
||||
|
||||
@@ -42,22 +42,10 @@ struct Calib_Params
|
||||
std::string shaper_type;
|
||||
std::vector<double> accelerations;
|
||||
std::vector<double> speeds;
|
||||
// Resolved layer height for the VFA tower (0 = auto: nozzle_diameter / 2). Each speed block is a
|
||||
// fixed number of layers tall, so this also determines the physical block height / tower height.
|
||||
double vfa_layer_height = 0.0;
|
||||
// Scale the calibration model to the nozzle diameter and set the layer height accordingly (temp tower / VFA).
|
||||
// When false the 0.4 mm / 0.2 mm reference model is printed as-is.
|
||||
bool nozzle_based_resize = true;
|
||||
|
||||
CalibMode mode;
|
||||
};
|
||||
|
||||
// Number of printed layers per speed block in the VFA tower. The base model has 5 mm blocks designed
|
||||
// for a 0.2 mm layer height (0.4 mm nozzle), i.e. 25 layers per block.
|
||||
static constexpr int vfa_layers_per_block = 25;
|
||||
static constexpr double vfa_base_block_height = 5.0;
|
||||
static constexpr double vfa_base_nozzle_diameter = 0.4;
|
||||
|
||||
enum FlowRatioCalibrationType {
|
||||
COMPLETE_CALIBRATION = 0,
|
||||
FINE_CALIBRATION,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -6047,10 +6047,10 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_X], "Y"); // ORCA use uppercase to match text on tranform widgets
|
||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Y], "Z"); // ORCA use uppercase to match text on tranform widgets
|
||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Z], "X"); // ORCA use uppercase to match text on tranform widgets
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_FRONT], _u8L_CONTEXT("Front", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_FRONT], _utf8("Front").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BACK], _u8L_CONTEXT("Back", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_TOP], _u8L_CONTEXT("Top", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BOTTOM], _u8L_CONTEXT("Bottom", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_TOP], _utf8("Top").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BOTTOM], _utf8("Bottom").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_LEFT], _u8L_CONTEXT("Left", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_RIGHT], _u8L_CONTEXT("Right", "Camera View").c_str());
|
||||
|
||||
|
||||
@@ -3502,7 +3502,7 @@ static void check_objects_after_cut(const ModelObjectPtrs& objects)
|
||||
names += ", " + from_u8(err_objects_names[i]);
|
||||
WarningDialog(wxGetApp().plater(), format_wxstr(_L("Objects(%1%) have duplicated connectors. "
|
||||
"Some connectors may be missing in slicing result.\n"
|
||||
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
||||
"Thank you."), names)).ShowModal();
|
||||
}
|
||||
|
||||
|
||||
@@ -2654,14 +2654,14 @@ static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame,
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
//view_menu->AppendSeparator();
|
||||
//TRN To be shown in the main menu View->Top
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Top", "Camera View") + "\t" + ctrl + "1", _L("Top View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("top"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Top") + "\t" + ctrl + "1", _L("Top View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("top"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
//TRN To be shown in the main menu View->Bottom
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Bottom", "Camera View") + "\t" + ctrl + "2", _L("Bottom View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("bottom"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Bottom") + "\t" + ctrl + "2", _L("Bottom View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("bottom"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Front", "Camera View") + "\t" + ctrl + "3", _L("Front View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("front"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Front") + "\t" + ctrl + "3", _L("Front View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("front"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Rear", "Camera View") + "\t" + ctrl + "4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Rear") + "\t" + ctrl + "4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Left", "Camera View") + "\t" + ctrl + "5", _L("Left View"),[mainFrame](wxCommandEvent &) {mainFrame->select_view("left"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
|
||||
@@ -2298,6 +2298,7 @@ arrangement::ArrangePolygon PartPlate::estimate_wipe_tower_polygon(const Dynamic
|
||||
bool enable_wrapping = (wrapping_opt != nullptr) && wrapping_opt->value;
|
||||
wt_size = estimate_wipe_tower_size(config, w, v, extruder_count, plate_extruder_size, use_global_objects, enable_wrapping);
|
||||
int plate_width=m_width, plate_depth=m_depth;
|
||||
w = wt_size(0); // effective width; differs from prime_tower_width when the rib wall squares the tower
|
||||
float depth = wt_size(1);
|
||||
float margin = WIPE_TOWER_MARGIN + tower_brim_width, wp_brim_width = 0.f;
|
||||
const ConfigOption* wipe_tower_brim_width_opt = config.option("prime_tower_brim_width");
|
||||
|
||||
+11
-51
@@ -14183,7 +14183,7 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
}
|
||||
}
|
||||
|
||||
if (params.nozzle_based_resize && std::abs(nozzle_scale - 1.0) > EPSILON)
|
||||
if (std::abs(nozzle_scale - 1.0) > EPSILON)
|
||||
model().objects[0]->scale(nozzle_scale, nozzle_scale, nozzle_scale);
|
||||
|
||||
model().objects[0]->ensure_on_bed();
|
||||
@@ -14191,9 +14191,7 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||
set_config_values<int, ConfigOptionInts>(filament_config, "nozzle_temperature_initial_layer", (int) start_temp);
|
||||
set_config_values<int, ConfigOptionInts>(filament_config, "nozzle_temperature", (int) start_temp);
|
||||
// When resizing is disabled the 0.4 mm / 0.2 mm reference model is printed as-is (preset layer height kept).
|
||||
if (params.nozzle_based_resize)
|
||||
model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||
model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||
model().objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btOuterOnly));
|
||||
model().objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(5.0));
|
||||
model().objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0));
|
||||
@@ -14204,8 +14202,7 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
|
||||
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
|
||||
if (params.nozzle_based_resize)
|
||||
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||
|
||||
|
||||
changed_objects({ 0 });
|
||||
@@ -14369,42 +14366,6 @@ void Plater::calib_VFA(const Calib_Params& params)
|
||||
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
|
||||
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
|
||||
const ConfigOptionFloats* nozzle_diameter_config = printer_config->option<ConfigOptionFloats>("nozzle_diameter");
|
||||
size_t nozzle_id = static_cast<size_t>(std::max(params.extruder_id, 0));
|
||||
double nozzle_diameter = vfa_base_nozzle_diameter;
|
||||
if (nozzle_diameter_config && !nozzle_diameter_config->values.empty()) {
|
||||
nozzle_id = std::min(nozzle_id, nozzle_diameter_config->values.size() - 1);
|
||||
nozzle_diameter = nozzle_diameter_config->values[nozzle_id];
|
||||
}
|
||||
if (nozzle_diameter <= 0.0)
|
||||
nozzle_diameter = vfa_base_nozzle_diameter;
|
||||
|
||||
// Resolved layer height: use the (possibly auto-adjusted) value from the dialog, else default to nozzle/2.
|
||||
double layer_height = params.vfa_layer_height > 0.0 ? params.vfa_layer_height : nozzle_diameter / 2.0;
|
||||
|
||||
// cut upper (on the unscaled model, using the base block height); the scaling below keeps the physical
|
||||
// block height (vfa_layers_per_block * layer_height) in sync with the speed stepping in GCode::process_layer.
|
||||
// Subtract EPSILON (as the temperature tower does) so the cut lands just below the flat block surface instead
|
||||
// of exactly on it, which would otherwise add a degenerate extra layer.
|
||||
auto obj_bb = model().objects[0]->bounding_box_exact();
|
||||
auto height = vfa_base_block_height * ((params.end - params.start) / params.step + 1) - EPSILON;
|
||||
if (height < obj_bb.size().z()) {
|
||||
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
|
||||
}
|
||||
|
||||
// When resizing is enabled, XY scales with the nozzle (footprint / line width) and Z scales so each base
|
||||
// block becomes vfa_layers_per_block layers of the resolved layer height. When disabled the 0.4 mm / 0.2 mm
|
||||
// reference model is printed as-is (preset layer height kept).
|
||||
if (params.nozzle_based_resize) {
|
||||
const double xy_scale = nozzle_diameter / vfa_base_nozzle_diameter;
|
||||
const double z_scale = (vfa_layers_per_block * layer_height) / vfa_base_block_height;
|
||||
if (std::abs(xy_scale - 1.0) > EPSILON || std::abs(z_scale - 1.0) > EPSILON)
|
||||
model().objects[0]->scale(xy_scale, xy_scale, z_scale);
|
||||
}
|
||||
|
||||
model().objects[0]->ensure_on_bed();
|
||||
|
||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||
filament_config->set_key_value("slow_down_layer_time", new ConfigOptionFloats { 0.0 });
|
||||
set_config_values<bool, ConfigOptionBoolsNullable>(print_config, "enable_overhang_speed", false);
|
||||
@@ -14418,10 +14379,6 @@ void Plater::calib_VFA(const Calib_Params& params)
|
||||
print_config->set_key_value("spiral_mode", new ConfigOptionBool(true));
|
||||
print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
|
||||
print_config->set_key_value("precise_z_height", new ConfigOptionBool(false));
|
||||
if (params.nozzle_based_resize) {
|
||||
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(layer_height));
|
||||
model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(layer_height));
|
||||
}
|
||||
model().objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btOuterOnly));
|
||||
model().objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(3.0));
|
||||
model().objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0));
|
||||
@@ -14432,11 +14389,14 @@ void Plater::calib_VFA(const Calib_Params& params)
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_ui_from_settings();
|
||||
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->update_ui_from_settings();
|
||||
|
||||
// Pass the resolved layer height on (only meaningful when resized). GCode's VFA stepping is layer-based, so
|
||||
// it does not require it, but keep it consistent with the geometry.
|
||||
Calib_Params calib_params = params;
|
||||
calib_params.vfa_layer_height = params.nozzle_based_resize ? layer_height : 0.0;
|
||||
p->background_process.fff_print()->set_calib_params(calib_params);
|
||||
// cut upper
|
||||
auto obj_bb = model().objects[0]->bounding_box_exact();
|
||||
auto height = 5 * ((params.end - params.start) / params.step + 1);
|
||||
if (height < obj_bb.size().z()) {
|
||||
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
|
||||
}
|
||||
|
||||
p->background_process.fff_print()->set_calib_params(params);
|
||||
}
|
||||
|
||||
void Plater::calib_input_shaping_freq(const Calib_Params& params)
|
||||
|
||||
+1017
-12
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -8,9 +8,7 @@
|
||||
#include "Widgets/HyperLink.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/Flow.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
@@ -36,23 +34,6 @@ int GetTextMax(wxWindow* parent, const std::vector<wxString>& labels)
|
||||
return text_size.x + parent->FromDIP(10);
|
||||
}
|
||||
|
||||
CheckBox* add_scale_checkbox(wxWindow* parent, wxSizer* settings_sizer)
|
||||
{
|
||||
auto row = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto cb = new CheckBox(parent);
|
||||
cb->SetValue(true);
|
||||
auto text = new wxStaticText(parent, wxID_ANY, _L("Auto-scale for nozzle"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||
cb->SetToolTip(_L("This model is designed around a 0.4 mm nozzle with a 0.2 mm layer height. \n"
|
||||
"When the scaling option is enabled (recommended), it dynamically resizes to match your current nozzle diameter"
|
||||
" and an appropriate layer height, making the test both accurate and easy to read.\n"
|
||||
"Turn scaling off only if you wish to print the reference model exactly as-is."));
|
||||
text->SetToolTip(cb->GetToolTipText());
|
||||
row->Add(cb , 0, wxALL | wxALIGN_CENTER_VERTICAL, parent->FromDIP(2));
|
||||
row->Add(text, 0, wxALL | wxALIGN_CENTER_VERTICAL, parent->FromDIP(2));
|
||||
settings_sizer->Add(row, 0, wxLEFT | wxTOP, parent->FromDIP(3));
|
||||
return cb;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_shaper_type_values()
|
||||
{
|
||||
if (auto* preset_bundle = wxGetApp().preset_bundle) {
|
||||
@@ -421,9 +402,6 @@ Temp_Calibration_Dlg::Temp_Calibration_Dlg(wxWindow* parent, wxWindowID id, Plat
|
||||
temp_step_sizer->Add(m_tiStep , 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2));
|
||||
settings_sizer->Add(temp_step_sizer, 0, wxLEFT, FromDIP(3));
|
||||
|
||||
// Resize the model to the nozzle diameter (recommended)
|
||||
m_cbResize = add_scale_checkbox(this, settings_sizer);
|
||||
|
||||
settings_sizer->AddSpacer(FromDIP(5));
|
||||
|
||||
v_sizer->Add(settings_sizer, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(10));
|
||||
@@ -497,7 +475,6 @@ void Temp_Calibration_Dlg::on_start(wxCommandEvent& event) {
|
||||
}
|
||||
m_params.start = start;
|
||||
m_params.end = end;
|
||||
m_params.nozzle_based_resize = m_cbResize->GetValue();
|
||||
m_params.mode = CalibMode::Calib_Temp_Tower;
|
||||
m_plater->calib_temp(m_params);
|
||||
EndModal(wxID_OK);
|
||||
@@ -714,22 +691,6 @@ VFA_Test_Dlg::VFA_Test_Dlg(wxWindow* parent, wxWindowID id, Plater* plater)
|
||||
vol_step_sizer->Add(m_tiStep , 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2));
|
||||
settings_sizer->Add(vol_step_sizer, 0, wxLEFT, FromDIP(3));
|
||||
|
||||
// Resize the model to the nozzle diameter (recommended)
|
||||
m_cbResize = add_scale_checkbox(this, settings_sizer);
|
||||
|
||||
// Auto-adjust parameters to the filament's max volumetric speed
|
||||
auto auto_adjust_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_cbAutoAdjust = new CheckBox(this);
|
||||
m_cbAutoAdjust->SetValue(true);
|
||||
auto auto_adjust_text = new wxStaticText(this, wxID_ANY, _L("Auto-adjust to max volumetric speed"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||
m_cbAutoAdjust->SetToolTip(_L("If the end speed would exceed the filament's maximum volumetric speed, automatically lower the layer "
|
||||
"height (keeping standard values and staying within the machine's limits) to reach it. If even the "
|
||||
"minimum layer height is not enough, lower the end speed instead."));
|
||||
auto_adjust_text->SetToolTip(m_cbAutoAdjust->GetToolTipText());
|
||||
auto_adjust_sizer->Add(m_cbAutoAdjust , 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2));
|
||||
auto_adjust_sizer->Add(auto_adjust_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2));
|
||||
settings_sizer->Add(auto_adjust_sizer, 0, wxLEFT | wxTOP, FromDIP(3));
|
||||
|
||||
settings_sizer->AddSpacer(FromDIP(5));
|
||||
|
||||
v_sizer->Add(settings_sizer, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(10));
|
||||
@@ -771,136 +732,6 @@ void VFA_Test_Dlg::on_start(wxCommandEvent& event)
|
||||
return;
|
||||
}
|
||||
|
||||
// If the requested end speed would exceed the filament's maximum volumetric speed, the slicer clamps the
|
||||
// outer wall speed, so the upper blocks of the tower would all print at the same (clamped) speed instead of
|
||||
// the requested one. Depending on the "Auto-adjust" option, either fix it automatically or just warn.
|
||||
m_params.vfa_layer_height = 0.0; // 0 = auto (nozzle/2); overridden below when auto-adjusting
|
||||
m_params.nozzle_based_resize = m_cbResize->GetValue();
|
||||
if (const auto* preset_bundle = wxGetApp().preset_bundle) {
|
||||
const auto& printer_config = preset_bundle->printers.get_edited_preset().config;
|
||||
const auto& print_config = preset_bundle->prints.get_edited_preset().config;
|
||||
const auto& filament_config = preset_bundle->filaments.get_edited_preset().config;
|
||||
|
||||
const int extruder_id = std::max(m_params.extruder_id, 0);
|
||||
auto get_at = [extruder_id](const ConfigOptionFloats* opt, double fallback) {
|
||||
if (opt == nullptr || opt->values.empty())
|
||||
return fallback;
|
||||
return opt->values[std::min(static_cast<size_t>(extruder_id), opt->values.size() - 1)];
|
||||
};
|
||||
|
||||
const double nozzle_diameter = get_at(printer_config.option<ConfigOptionFloats>("nozzle_diameter"), vfa_base_nozzle_diameter);
|
||||
double preset_lh = nozzle_diameter / 2.0;
|
||||
if (const auto* lh_opt = print_config.option<ConfigOptionFloat>("layer_height"))
|
||||
if (lh_opt->value > 0.0)
|
||||
preset_lh = lh_opt->value;
|
||||
// Layer height the tower will actually print at: nozzle/2 when resizing, else the preset value.
|
||||
const double default_lh = m_params.nozzle_based_resize ? nozzle_diameter / 2.0 : preset_lh;
|
||||
const double max_vol_speed = get_at(filament_config.option<ConfigOptionFloats>("filament_max_volumetric_speed"), 0.0);
|
||||
const double machine_min_lh = get_at(printer_config.option<ConfigOptionFloats>("min_layer_height"), 0.0);
|
||||
const double machine_max_lh = get_at(printer_config.option<ConfigOptionFloats>("max_layer_height"), 0.0);
|
||||
|
||||
double line_width = print_config.get_abs_value("outer_wall_line_width", nozzle_diameter);
|
||||
if (line_width <= 0.0)
|
||||
line_width = print_config.get_abs_value("line_width", nozzle_diameter);
|
||||
if (line_width <= 0.0)
|
||||
line_width = nozzle_diameter;
|
||||
|
||||
// Max outer-wall speed printable at a given layer height without exceeding the volumetric limit.
|
||||
auto speed_limit_for_lh = [&](double lh) -> double {
|
||||
const double mm3_per_mm = Flow(line_width, lh, nozzle_diameter).mm3_per_mm();
|
||||
return mm3_per_mm > 0.0 ? max_vol_speed / mm3_per_mm : 1e9;
|
||||
};
|
||||
|
||||
auto confirm_clamp = [&](const wxString& question) -> bool {
|
||||
MessageDialog msg_dlg(nullptr,
|
||||
wxString::Format(_L("The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed "
|
||||
"(%.1f mm³/s), which limits the outer wall to about %.0f mm/s at this line width and "
|
||||
"layer height.\n Speeds above this will be clamped, so the upper blocks of the tower "
|
||||
"will not print at the requested speed.\n\n%s"),
|
||||
m_params.end, max_vol_speed, speed_limit_for_lh(default_lh), question),
|
||||
_L("VFA test"), wxICON_WARNING | wxYES_NO | wxNO_DEFAULT);
|
||||
return msg_dlg.ShowModal() == wxID_YES;
|
||||
};
|
||||
|
||||
if (max_vol_speed > 0.0 && nozzle_diameter > 0.0 && m_params.end > speed_limit_for_lh(default_lh)) {
|
||||
// The layer-height auto-adjust only applies when resizing is enabled (it changes the layer height).
|
||||
if (m_cbAutoAdjust->GetValue() && m_params.nozzle_based_resize) {
|
||||
// Candidate layer heights are the ones actually used by the process profiles compatible with the
|
||||
// current printer (clamped to the machine's layer-height limits, when set). A smaller layer height
|
||||
// means a smaller cross-section, hence a higher printable speed under the volumetric limit; pick the
|
||||
// largest candidate that still reaches the end speed to keep the change from the default minimal.
|
||||
std::vector<double> candidates;
|
||||
for (const auto& preset : preset_bundle->prints.get_presets()) {
|
||||
if (!preset.is_compatible || preset.is_default)
|
||||
continue;
|
||||
const auto* lh_opt = preset.config.option<ConfigOptionFloat>("layer_height");
|
||||
if (lh_opt == nullptr || lh_opt->value <= 0.0)
|
||||
continue;
|
||||
const double lh = lh_opt->value;
|
||||
if ((machine_min_lh > 0.0 && lh < machine_min_lh - 1e-6) ||
|
||||
(machine_max_lh > 0.0 && lh > machine_max_lh + 1e-6))
|
||||
continue;
|
||||
candidates.push_back(lh);
|
||||
}
|
||||
std::sort(candidates.begin(), candidates.end());
|
||||
candidates.erase(std::unique(candidates.begin(), candidates.end(),
|
||||
[](double a, double b) { return std::abs(a - b) < 1e-6; }),
|
||||
candidates.end());
|
||||
|
||||
// Largest candidate <= the default layer height that still reaches the end speed (smallest change).
|
||||
double chosen_lh = 0.0;
|
||||
for (auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
|
||||
if (*it > default_lh + 1e-6)
|
||||
continue; // never increase the layer height above the default
|
||||
if (speed_limit_for_lh(*it) >= m_params.end) { chosen_lh = *it; break; }
|
||||
}
|
||||
|
||||
if (chosen_lh > 0.0) {
|
||||
// Reducing the layer height is enough to reach the requested end speed.
|
||||
m_params.vfa_layer_height = chosen_lh;
|
||||
MessageDialog msg_dlg(nullptr,
|
||||
wxString::Format(_L("The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed "
|
||||
"(%.1f mm³/s) at the default layer height (%.2f mm).\n\n"
|
||||
"The layer height has been reduced to %.2f mm (a value used by this printer's "
|
||||
"profiles) so the tower can reach the requested speed."),
|
||||
m_params.end, max_vol_speed, default_lh, chosen_lh),
|
||||
_L("VFA test"), wxICON_INFORMATION | wxOK);
|
||||
msg_dlg.ShowModal();
|
||||
} else if (!candidates.empty()) {
|
||||
// Even the smallest available layer height cannot reach the end speed; propose a lower end speed
|
||||
// based on that layer height, the line width and the maximum volumetric speed.
|
||||
const double min_lh = candidates.front();
|
||||
const double reachable = speed_limit_for_lh(min_lh);
|
||||
double new_end = std::floor(reachable / m_params.step) * m_params.step; // snap down to a step multiple
|
||||
if (new_end < m_params.start + m_params.step)
|
||||
new_end = m_params.start + m_params.step;
|
||||
MessageDialog msg_dlg(nullptr,
|
||||
wxString::Format(_L("Even at the smallest layer height used by this printer's profiles (%.2f mm) the "
|
||||
"end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed "
|
||||
"(%.1f mm³/s).\n\n"
|
||||
"The layer height will be set to %.2f mm and the end speed lowered to %.0f mm/s.\n\n"
|
||||
"Continue?"),
|
||||
min_lh, m_params.end, max_vol_speed, min_lh, new_end),
|
||||
_L("VFA test"), wxICON_WARNING | wxYES_NO | wxNO_DEFAULT);
|
||||
if (msg_dlg.ShowModal() != wxID_YES)
|
||||
return;
|
||||
m_params.end = new_end;
|
||||
m_params.vfa_layer_height = min_lh;
|
||||
} else {
|
||||
// No compatible process profiles to draw layer heights from: warn and let the user decide.
|
||||
if (!confirm_clamp(_L("Continue anyway?")))
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Auto-adjust off, or resizing disabled (which forbids changing the layer height): just warn.
|
||||
if (!confirm_clamp(m_params.nozzle_based_resize
|
||||
? _L("Enable \"Auto-adjust\" to fix this automatically, or continue anyway?")
|
||||
: _L("Enable \"Auto-scale for nozzle\" and \"Auto-adjust\" to fix this automatically, or continue anyway?")))
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_params.mode = CalibMode::Calib_VFA_Tower;
|
||||
m_plater->calib_VFA(m_params);
|
||||
EndModal(wxID_OK);
|
||||
|
||||
@@ -65,7 +65,6 @@ protected:
|
||||
TextInput* m_tiStart;
|
||||
TextInput* m_tiEnd;
|
||||
TextInput* m_tiStep;
|
||||
CheckBox* m_cbResize;
|
||||
Plater* m_plater;
|
||||
};
|
||||
|
||||
@@ -100,8 +99,6 @@ protected:
|
||||
TextInput* m_tiStart;
|
||||
TextInput* m_tiEnd;
|
||||
TextInput* m_tiStep;
|
||||
CheckBox* m_cbAutoAdjust;
|
||||
CheckBox* m_cbResize;
|
||||
Plater* m_plater;
|
||||
};
|
||||
|
||||
|
||||
@@ -871,7 +871,7 @@ void Bonjour::priv::lookup_perform()
|
||||
|
||||
std::vector<LookupSocket*> sockets;
|
||||
|
||||
// resolve interfaces - from PR#6646
|
||||
// resolve intefaces - from PR#6646
|
||||
std::vector<boost::asio::ip::address> interfaces;
|
||||
asio::ip::udp::resolver resolver(*io_service);
|
||||
boost::system::error_code ec;
|
||||
|
||||
@@ -1265,19 +1265,6 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message)
|
||||
DynamicPrintConfig filament_config = calib_info.filament_prest->config;
|
||||
DynamicPrintConfig printer_config = calib_info.printer_prest->config;
|
||||
|
||||
const ConfigOptionFloats* nozzle_diameter_config = printer_config.option<ConfigOptionFloats>("nozzle_diameter");
|
||||
size_t nozzle_id = static_cast<size_t>(std::max(params.extruder_id, 0));
|
||||
double nozzle_diameter = vfa_base_nozzle_diameter;
|
||||
if (nozzle_diameter_config && !nozzle_diameter_config->values.empty()) {
|
||||
nozzle_id = std::min(nozzle_id, nozzle_diameter_config->values.size() - 1);
|
||||
nozzle_diameter = nozzle_diameter_config->values[nozzle_id];
|
||||
}
|
||||
if (nozzle_diameter <= 0.0)
|
||||
nozzle_diameter = vfa_base_nozzle_diameter;
|
||||
|
||||
// Resolved layer height: use the (possibly auto-adjusted) value if provided, else default to nozzle/2.
|
||||
double layer_height = params.vfa_layer_height > 0.0 ? params.vfa_layer_height : nozzle_diameter / 2.0;
|
||||
|
||||
filament_config.set_key_value("slow_down_layer_time", new ConfigOptionInts{0});
|
||||
filament_config.set_key_value("filament_max_volumetric_speed", new ConfigOptionFloats{200});
|
||||
filament_config.set_key_value("curr_bed_type", new ConfigOptionEnum<BedType>(calib_info.bed_type));
|
||||
@@ -1293,18 +1280,13 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message)
|
||||
print_config.set_key_value("sparse_infill_density", new ConfigOptionPercent(0));
|
||||
print_config.set_key_value("overhang_reverse", new ConfigOptionBool(false));
|
||||
print_config.set_key_value("spiral_mode", new ConfigOptionBool(true));
|
||||
print_config.set_key_value("initial_layer_print_height", new ConfigOptionFloat(layer_height));
|
||||
model.objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(layer_height));
|
||||
model.objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btOuterOnly));
|
||||
model.objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(3.0));
|
||||
model.objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0));
|
||||
|
||||
// cut upper (on the unscaled model, using the base block height); the scaling below keeps the physical
|
||||
// block height (vfa_layers_per_block * layer_height) in sync with the speed stepping in GCode::process_layer.
|
||||
// Subtract EPSILON (as the temperature tower does) so the cut lands just below the flat block surface instead
|
||||
// of exactly on it, which would otherwise add a degenerate extra layer.
|
||||
// cut upper
|
||||
auto obj_bb = model.objects[0]->bounding_box_exact();
|
||||
auto height = vfa_base_block_height * ((params.end - params.start) / params.step + 1) - EPSILON;
|
||||
auto height = 5 * ((params.end - params.start) / params.step + 1);
|
||||
if (height < obj_bb.size().z()) {
|
||||
cut_model(model, height, ModelObjectCutAttribute::KeepLower);
|
||||
}
|
||||
@@ -1313,13 +1295,6 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message)
|
||||
return;
|
||||
}
|
||||
|
||||
// XY scales with the nozzle; Z scales so each base block becomes vfa_layers_per_block layers of layer_height.
|
||||
const double xy_scale = nozzle_diameter / vfa_base_nozzle_diameter;
|
||||
const double z_scale = (vfa_layers_per_block * layer_height) / vfa_base_block_height;
|
||||
if (std::abs(xy_scale - 1.0) > EPSILON || std::abs(z_scale - 1.0) > EPSILON)
|
||||
model.objects[0]->scale(xy_scale, xy_scale, z_scale);
|
||||
model.objects[0]->ensure_on_bed();
|
||||
|
||||
DynamicPrintConfig full_config;
|
||||
full_config.apply(FullPrintConfig::defaults());
|
||||
full_config.apply(print_config);
|
||||
@@ -1328,10 +1303,7 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message)
|
||||
|
||||
init_multi_extruder_params_for_cali(full_config, calib_info);
|
||||
|
||||
// Pass the resolved layer height on so the GCode speed stepping matches the geometry.
|
||||
Calib_Params store_params = params;
|
||||
store_params.vfa_layer_height = layer_height;
|
||||
process_and_store_3mf(&model, full_config, store_params, error_message);
|
||||
process_and_store_3mf(&model, full_config, params, error_message);
|
||||
if (!error_message.empty())
|
||||
return;
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_timeutils.cpp
|
||||
test_voronoi.cpp
|
||||
test_optimizers.cpp
|
||||
test_ordering_strategies.cpp
|
||||
# test_png_io.cpp
|
||||
test_indexed_triangle_set.cpp
|
||||
../libnest2d/printer_parts.cpp
|
||||
|
||||
@@ -43,18 +43,33 @@ TEST_CASE("apply_override fills nil entries from the 0-based default index", "[C
|
||||
REQUIRE(resolved.values == std::vector<double>({30., 42.}));
|
||||
}
|
||||
|
||||
SECTION("an index past the machine slots falls back to the first slot") {
|
||||
SECTION("an index past the machine slots keeps the slot's own value") {
|
||||
std::vector<int> slot_index{5, 0};
|
||||
ConfigOptionFloats resolved(machine);
|
||||
REQUIRE(resolved.apply_override(&filament, slot_index));
|
||||
REQUIRE(resolved.values == std::vector<double>({10., 42.}));
|
||||
}
|
||||
|
||||
SECTION("a negative index (unresolved slot) falls back to the first slot") {
|
||||
std::vector<int> slot_index{-1, 0};
|
||||
SECTION("a negative index (unresolved slot) keeps the slot's own value") {
|
||||
ConfigOptionFloatsNullable all_nil;
|
||||
all_nil.values = {ConfigOptionFloatsNullable::nil_value(), ConfigOptionFloatsNullable::nil_value(),
|
||||
ConfigOptionFloatsNullable::nil_value()};
|
||||
std::vector<int> slot_index{2, -1, 0};
|
||||
ConfigOptionFloats resolved(machine);
|
||||
REQUIRE(resolved.apply_override(&filament, slot_index));
|
||||
REQUIRE(resolved.values == std::vector<double>({10., 42.}));
|
||||
REQUIRE(!resolved.apply_override(&all_nil, slot_index));
|
||||
REQUIRE(resolved.values == std::vector<double>({30., 20., 10.}));
|
||||
}
|
||||
|
||||
SECTION("all-nil overrides keyed by unresolved slots leave the machine values intact") {
|
||||
// The failed-lookup map a degenerate print_extruder_id used to produce; the negative
|
||||
// slots must not collapse the machine array to its first value.
|
||||
ConfigOptionFloats per_extruder({100., 70., 70., 70., 100.});
|
||||
ConfigOptionFloatsNullable all_nil;
|
||||
all_nil.values.assign(5, ConfigOptionFloatsNullable::nil_value());
|
||||
std::vector<int> slot_index{0, -1, -1, -1, 0};
|
||||
ConfigOptionFloats resolved(per_extruder);
|
||||
REQUIRE(!resolved.apply_override(&all_nil, slot_index));
|
||||
REQUIRE(resolved.values == std::vector<double>({100., 70., 70., 70., 100.}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +287,102 @@ TEST_CASE("update_values_to_printer_extruders expands one slot per (extruder x v
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("update_values_to_printer_extruders synthesizes degenerate process variant columns", "[Config]")
|
||||
{
|
||||
// Non-BBL process presets and 3mf project configs keep the length-1 defaults for
|
||||
// print_extruder_id/print_extruder_variant; only BBL system presets ship full-width columns.
|
||||
auto add_degenerate_print_columns = [](DynamicPrintConfig &config) {
|
||||
config.option<ConfigOptionInts>("print_extruder_id", true)->values = {1};
|
||||
config.option<ConfigOptionStrings>("print_extruder_variant", true)->values = {"Direct Drive Standard"};
|
||||
config.option<ConfigOptionFloats>("outer_wall_speed", true)->values = {30.};
|
||||
};
|
||||
|
||||
SECTION("a single-column pair on a multi-extruder machine expands to one column per extruder") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtStandard};
|
||||
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard", "Direct Drive Standard"};
|
||||
add_degenerate_print_columns(config);
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 2;
|
||||
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
std::vector<int> variant_index = config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
|
||||
REQUIRE(variant_index == std::vector<int>({0, 1}));
|
||||
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values == std::vector<int>({1, 2}));
|
||||
REQUIRE(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||
std::vector<std::string>({"Direct Drive Standard", "Direct Drive Standard"}));
|
||||
// width-1 data arrays replicate their only column into every slot
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30., 30.}));
|
||||
}
|
||||
|
||||
SECTION("a multi-variant list synthesizes one column per (extruder x variant)") {
|
||||
DynamicPrintConfig config = make_hybrid_printer_config();
|
||||
add_degenerate_print_columns(config);
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 2;
|
||||
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
REQUIRE(count == 3);
|
||||
|
||||
std::vector<int> variant_index = config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
|
||||
// same slot resolution as the explicit BBL-style 4-column layout
|
||||
REQUIRE(variant_index == std::vector<int>({0, 2, 3}));
|
||||
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values == std::vector<int>({1, 2, 2}));
|
||||
REQUIRE(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||
std::vector<std::string>({"Direct Drive Standard", "Direct Drive Standard", "Direct Drive High Flow"}));
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30., 30., 30.}));
|
||||
}
|
||||
|
||||
SECTION("a single-extruder single-column layout is not treated as degenerate") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive};
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard};
|
||||
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard"};
|
||||
add_degenerate_print_columns(config);
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 1;
|
||||
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
|
||||
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values == std::vector<int>({1}));
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30.}));
|
||||
}
|
||||
|
||||
SECTION("a second expansion leaves the synthesized layout unchanged") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtStandard};
|
||||
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard", "Direct Drive Standard"};
|
||||
add_degenerate_print_columns(config);
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 2;
|
||||
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
DynamicPrintConfig once = config;
|
||||
config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
|
||||
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values ==
|
||||
once.option<ConfigOptionInts>("print_extruder_id")->values);
|
||||
REQUIRE(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||
once.option<ConfigOptionStrings>("print_extruder_variant")->values);
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values ==
|
||||
once.option<ConfigOptionFloats>("outer_wall_speed")->values);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves per-filament slots", "[Config]")
|
||||
{
|
||||
auto make_filament_arrays = [](DynamicPrintConfig &config) {
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#define SLIC3R_TEST_HARNESS
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/GCode/OrderingStrategies.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
static double euclidean_path_length(const std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
return tsp_cycle_path_length(path, centers);
|
||||
}
|
||||
|
||||
static bool has_crossings(const std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
size_t pn = path.size();
|
||||
if (pn < 4) return false;
|
||||
for (size_t i = 0; i < pn; ++i) {
|
||||
size_t i_next = (i + 1) % pn;
|
||||
for (size_t j = i + 2; j < pn; ++j) {
|
||||
if (j == i_next) continue;
|
||||
if (j == (pn - 1) && i == 0) continue;
|
||||
size_t j_next = (j + 1) % pn;
|
||||
if (Geometry::segments_intersect(
|
||||
centers[path[i]], centers[path[i_next]],
|
||||
centers[path[j]], centers[path[j_next]])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_permutation(const std::vector<size_t>& path, size_t n)
|
||||
{
|
||||
if (path.size() != n) return false;
|
||||
std::unordered_set<size_t> seen(path.begin(), path.end());
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (seen.count(i) != 1) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Test fixtures ---
|
||||
|
||||
static Points make_grid_4x4()
|
||||
{
|
||||
Points pts;
|
||||
for (int row = 0; row < 4; ++row)
|
||||
for (int col = 0; col < 4; ++col)
|
||||
pts.emplace_back(100000 * col, 100000 * row);
|
||||
return pts;
|
||||
}
|
||||
|
||||
static Points make_linear_5()
|
||||
{
|
||||
Points pts;
|
||||
for (int i = 0; i < 5; ++i)
|
||||
pts.emplace_back(100000 * i, 0);
|
||||
return pts;
|
||||
}
|
||||
|
||||
static Points make_ring_8()
|
||||
{
|
||||
Points pts;
|
||||
constexpr double R = 100000.0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
double angle = 2.0 * M_PI * i / 8.0;
|
||||
pts.emplace_back(static_cast<coord_t>(R * std::cos(angle)),
|
||||
static_cast<coord_t>(R * std::sin(angle)));
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
static Points make_random_16()
|
||||
{
|
||||
// Deterministic "random" points via simple hash.
|
||||
Points pts;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
uint32_t h = static_cast<uint32_t>(i * 2654435761u);
|
||||
coord_t x = static_cast<coord_t>((h >> 16) & 0xFFFF) * 10;
|
||||
coord_t y = static_cast<coord_t>(h & 0xFFFF) * 10;
|
||||
pts.emplace_back(x, y);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
// --- TSP Post-Processing Tests ---
|
||||
|
||||
TEST_CASE("tsp_2opt_improve reduces path length", "[TSPPostProcessing]") {
|
||||
Points centers = make_random_16();
|
||||
std::vector<size_t> path(centers.size());
|
||||
// Reverse half the path to create a deliberately bad ordering.
|
||||
for (size_t i = 0; i < path.size(); ++i) path[i] = i;
|
||||
std::reverse(path.begin(), path.end() - path.size() / 2);
|
||||
|
||||
double before = euclidean_path_length(path, centers);
|
||||
tsp_2opt_improve(path, centers);
|
||||
double after = euclidean_path_length(path, centers);
|
||||
|
||||
REQUIRE(is_permutation(path, centers.size()));
|
||||
CHECK(after <= before);
|
||||
}
|
||||
|
||||
TEST_CASE("tsp_remove_crossings eliminates crossings", "[TSPPostProcessing]") {
|
||||
Points centers = make_random_16();
|
||||
std::vector<size_t> path(centers.size());
|
||||
for (size_t i = 0; i < path.size(); ++i) path[i] = i;
|
||||
// Create a crossing by reversing a middle segment.
|
||||
if (path.size() >= 4) {
|
||||
std::reverse(path.begin() + 1, path.end() - 1);
|
||||
}
|
||||
|
||||
tsp_remove_crossings(path, centers);
|
||||
CHECK(!has_crossings(path, centers));
|
||||
REQUIRE(is_permutation(path, centers.size()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST_CASE("tsp_rotate_minimize_closing shortens closing edge", "[TSPPostProcessing]") {
|
||||
Points centers = make_random_16();
|
||||
std::vector<size_t> path(centers.size());
|
||||
for (size_t i = 0; i < path.size(); ++i) path[i] = i;
|
||||
|
||||
// Compute all possible closing edge lengths.
|
||||
size_t pn = path.size();
|
||||
double min_closing2 = std::numeric_limits<double>::max();
|
||||
for (size_t start = 0; start < pn; ++start) {
|
||||
size_t last = (start + pn - 1) % pn;
|
||||
double d2 = (centers[path[start]].cast<double>() - centers[path[last]].cast<double>()).squaredNorm();
|
||||
if (d2 < min_closing2) min_closing2 = d2;
|
||||
}
|
||||
|
||||
tsp_rotate_minimize_closing(path, centers);
|
||||
|
||||
// Closing edge should be the minimum possible.
|
||||
double actual_closing2 = (centers[path.front()].cast<double>() - centers[path.back()].cast<double>()).squaredNorm();
|
||||
CHECK(actual_closing2 == min_closing2);
|
||||
REQUIRE(is_permutation(path, centers.size()));
|
||||
}
|
||||
|
||||
TEST_CASE("tsp_cycle_path_length is correct for triangle", "[TSPPostProcessing]") {
|
||||
Points pts;
|
||||
pts.emplace_back(0, 0);
|
||||
pts.emplace_back(100000, 0);
|
||||
pts.emplace_back(50000, 86602); // equilateral ~100mm sides
|
||||
|
||||
std::vector<size_t> path = {0, 1, 2};
|
||||
double len = tsp_cycle_path_length(path, pts);
|
||||
// Perimeter of equilateral triangle with side ~100000.
|
||||
REQUIRE(len > 290000);
|
||||
REQUIRE(len < 310000);
|
||||
}
|
||||
|
||||
TEST_CASE("tsp_max_edge_length finds longest edge", "[TSPPostProcessing]") {
|
||||
Points pts;
|
||||
pts.emplace_back(0, 0);
|
||||
pts.emplace_back(100000, 0);
|
||||
pts.emplace_back(50000, 0);
|
||||
|
||||
std::vector<size_t> path = {0, 1, 2};
|
||||
double mx = tsp_max_edge_length(path, pts);
|
||||
// Longest edge is 0->1 = 100000.
|
||||
CHECK(mx == Catch::Approx(100000).margin(1));
|
||||
}
|
||||
|
||||
// --- Core Strategy Tests: Empty / Small Inputs ---
|
||||
|
||||
TEST_CASE("snake_core handles empty input", "[Snake]") {
|
||||
Points centers;
|
||||
auto path = snake_core(centers);
|
||||
REQUIRE(path.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("snake_core handles single point", "[Snake]") {
|
||||
Points pts{{100, 200}};
|
||||
CHECK(snake_core(pts) == std::vector<size_t>{0});
|
||||
}
|
||||
|
||||
TEST_CASE("snake_core handles two points", "[Snake]") {
|
||||
Points pts{{100, 200}, {300, 400}};
|
||||
auto p2 = snake_core(pts);
|
||||
|
||||
REQUIRE(is_permutation(p2, 2));
|
||||
}
|
||||
|
||||
// --- Core Strategy Tests: Grid Layout ---
|
||||
|
||||
TEST_CASE("snake produces good path on grid", "[Snake]") {
|
||||
Points centers = make_grid_4x4();
|
||||
auto path = snake_core(centers);
|
||||
|
||||
REQUIRE(is_permutation(path, centers.size()));
|
||||
CHECK(!has_crossings(path, centers));
|
||||
}
|
||||
|
||||
// --- Core Strategy Tests: Variable Row Spacing ---
|
||||
|
||||
TEST_CASE("snake handles variable Y spacing", "[Snake]") {
|
||||
// Rows at Y = 0, 50, 100, 1000 (large gap between last two rows).
|
||||
// The adaptive row detection should identify the tight cluster (0, 50, 100)
|
||||
// and the isolated row (1000) without splitting them incorrectly.
|
||||
Points pts;
|
||||
pts.emplace_back(0, 0); pts.emplace_back(100000, 0);
|
||||
pts.emplace_back(0, 50000); pts.emplace_back(100000, 50000);
|
||||
pts.emplace_back(0, 100000); pts.emplace_back(100000, 100000);
|
||||
pts.emplace_back(0, 1000000); pts.emplace_back(100000, 1000000);
|
||||
|
||||
auto path = snake_core(pts);
|
||||
REQUIRE(is_permutation(path, pts.size()));
|
||||
CHECK(!has_crossings(path, pts));
|
||||
}
|
||||
|
||||
// --- Core Strategy Tests: All Points Same Y ---
|
||||
|
||||
TEST_CASE("snake handles all points on same Y", "[Snake]") {
|
||||
// All points share the same Y coordinate. This exercises the
|
||||
// division-by-zero guard (ys.size() == 1).
|
||||
Points pts;
|
||||
for (int i = 0; i < 6; ++i)
|
||||
pts.emplace_back(100000 * i, 50000);
|
||||
|
||||
auto path = snake_core(pts);
|
||||
REQUIRE(is_permutation(path, pts.size()));
|
||||
}
|
||||
|
||||
// --- Core Strategy Tests: Collinear Points ---
|
||||
|
||||
TEST_CASE("snake_core handles collinear points", "[Snake]") {
|
||||
Points centers = make_linear_5();
|
||||
|
||||
auto p2 = snake_core(centers);
|
||||
|
||||
REQUIRE(is_permutation(p2, centers.size()));
|
||||
}
|
||||
|
||||
// --- Core Strategy Tests: Ring Layout ---
|
||||
|
||||
TEST_CASE("snake_core produces valid paths on ring", "[Snake]") {
|
||||
Points centers = make_ring_8();
|
||||
|
||||
auto p2 = snake_core(centers);
|
||||
|
||||
REQUIRE(is_permutation(p2, centers.size()));
|
||||
}
|
||||
|
||||
// --- Core Strategy Tests: Random Layout ---
|
||||
|
||||
TEST_CASE("snake_core produces valid paths on random input", "[Snake]") {
|
||||
Points centers = make_random_16();
|
||||
|
||||
auto p2 = snake_core(centers);
|
||||
|
||||
REQUIRE(is_permutation(p2, centers.size()));
|
||||
}
|
||||
|
||||
// --- Quality Comparison Tests ---
|
||||
|
||||
TEST_CASE("snake has no crossings on random input", "[Snake]") {
|
||||
Points centers = make_random_16();
|
||||
auto path = snake_core(centers);
|
||||
|
||||
REQUIRE(is_permutation(path, centers.size()));
|
||||
CHECK(!has_crossings(path, centers));
|
||||
}
|
||||
|
||||
// --- Edge Cases ---
|
||||
|
||||
TEST_CASE("snake_core handles duplicate points", "[Snake]") {
|
||||
Points pts;
|
||||
pts.emplace_back(100, 200);
|
||||
pts.emplace_back(100, 200); // duplicate
|
||||
pts.emplace_back(300, 400);
|
||||
|
||||
auto p2 = snake_core(pts);
|
||||
|
||||
REQUIRE(p2.size() == pts.size());
|
||||
}
|
||||
|
||||
TEST_CASE("snake_core handles three points", "[Snake]") {
|
||||
Points pts;
|
||||
pts.emplace_back(0, 0);
|
||||
pts.emplace_back(100000, 0);
|
||||
pts.emplace_back(50000, 86602);
|
||||
|
||||
auto p2 = snake_core(pts);
|
||||
|
||||
REQUIRE(is_permutation(p2, 3));
|
||||
}
|
||||
@@ -500,6 +500,52 @@ TEST_CASE("Re-applying an unchanged config after slicing keeps the result valid"
|
||||
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||
}
|
||||
|
||||
TEST_CASE("A degenerate process variant map on a custom multi-extruder printer slices to a stable result", "[Print][Regression]")
|
||||
{
|
||||
// Non-BBL multi-extruder printers get machine-scope variant columns synthesized on preset
|
||||
// load (extend_extruder_variant), but nothing ships process-scope print_extruder_id /
|
||||
// print_extruder_variant: presets and 3mf project configs carry the length-1 defaults. The
|
||||
// apply-time expansion must synthesize the process columns from extruder_variant_list;
|
||||
// otherwise the failed per-extruder lookups collapse the per-extruder retract overrides
|
||||
// during slicing and the post-slice re-apply invalidates every fresh result, forever.
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.option<ConfigOptionFloats>("nozzle_diameter", true)->values = {0.4, 0.4, 0.4, 0.4, 0.4};
|
||||
config.set_num_extruders(5);
|
||||
// per-extruder machine values that a first-slot collapse would destroy
|
||||
config.option<ConfigOptionPercents>("retract_before_wipe", true)->values = {100., 70., 70., 70., 100.};
|
||||
config.option<ConfigOptionEnumsGeneric>("z_hop_types", true)->values = {zhtSlope, zhtNormal, zhtNormal, zhtNormal, zhtSlope};
|
||||
// filament presets carry the nullable override twins (all-nil = "no override"); they are what
|
||||
// routes the machine values through apply_override in the in-slice override recompute
|
||||
config.option<ConfigOptionPercentsNullable>("filament_retract_before_wipe", true)->values =
|
||||
std::vector<double>(5, ConfigOptionPercentsNullable::nil_value());
|
||||
config.option<ConfigOptionEnumsGenericNullable>("filament_z_hop_types", true)->values =
|
||||
std::vector<int>(5, ConfigOptionEnumsGenericNullable::nil_value());
|
||||
config.option<ConfigOptionFloats>("filament_diameter", true)->values = std::vector<double>(5, 1.75);
|
||||
config.option<ConfigOptionStrings>("filament_colour", true)->values = {"#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#00FFFF"};
|
||||
config.option<ConfigOptionInts>("filament_map", true)->values = {1, 2, 3, 4, 1};
|
||||
|
||||
Model model;
|
||||
model.add_object("cube", "", make_cube(20, 20, 20))->add_instance()->set_offset(Vec3d(100., 100., 0.));
|
||||
|
||||
Print print;
|
||||
print.apply(model, config);
|
||||
print.process();
|
||||
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||
|
||||
// BackgroundSlicingProcess reads the engine-computed maps back into the plate config after
|
||||
// slicing; the next apply overlays that written-back state.
|
||||
config.option<ConfigOptionInts>("filament_map", true)->values = print.get_filament_maps();
|
||||
config.option<ConfigOptionInts>("filament_volume_map", true)->values = print.get_filament_volume_maps();
|
||||
config.option<ConfigOptionInts>("filament_nozzle_map", true)->values = print.get_filament_nozzle_maps();
|
||||
|
||||
auto status = print.apply(model, config);
|
||||
REQUIRE(status == PrintBase::APPLY_STATUS_UNCHANGED);
|
||||
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||
// the per-extruder machine values must survive the in-slice override recompute
|
||||
REQUIRE(print.config().retract_before_wipe.values == std::vector<double>({100., 70., 70., 70., 100.}));
|
||||
REQUIRE(print.config().z_hop_types.values == std::vector<int>({zhtSlope, zhtNormal, zhtNormal, zhtNormal, zhtSlope}));
|
||||
}
|
||||
|
||||
TEST_CASE("normalize_nozzle_map_per_layer makes per-filament assignments gap-free", "[MultiNozzle][H2C][Dynamic]")
|
||||
{
|
||||
SECTION("gaps inherit the last used nozzle, entries on used layers stay untouched") {
|
||||
|
||||
@@ -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
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user