feat: mixed-filament phase 2 — batch color match with manual/recommended modes - #702
Merged
Conversation
- MixedFilamentBatchEntry (wx-free, libslic3r layer) - add_batch_custom_filaments declaration - ModelColorEntry, ColorMappingEntry, BatchMatchResult (GUI layer) - extract_model_colors implementation (ModelVolume::get_extruders + filament_colour) - Function declarations for batch_match_model_colors, deduplicate, assign IDs, populate
- Color parsing dropped most model colors: try_parse_color_match_hex required exactly 7 chars, so alpha-bearing palette entries (#RRGGBBAA, as Bambu 3MF stores filament_colour) were rejected. Only 4 of 17 model colors survived and batch match produced 4 mappings. normalize_color_match_hex now strips alpha (#RRGGBBAA -> #RRGGBB), matching Bambu parse_color / STUDIO-17977. - Crash on repeated re-match after MANUAL -> RECOMMENDED: update_recommended_card deleted a wxBitmap returned by get_extruder_color_icon, but that pointer is owned by its static BitmapCache. The delete corrupted the cache and double-freed on the next match. Removed the delete; SetBitmap already copies, as all other callers do. - Ghost pixels between legend rows on re-match: update_mapping_legend used deferred Destroy(), so removed rows kept owning their pixel regions and the parent never erased the gaps. Use Clear(true) (immediate delete) instead, like MixedFilamentDialog::rebuild_legend. - Preserve the previous match result when switching mode or changing manual filament selection; only Start/Re-match clears it (new reset_match_preview() shared by the clear points).
Mixed-filament phase 2: integrate the prusa-fdm-mixer colour model as a
toggleable A/B demo, and fix several Mixed Color Match dialog defects.
prusa-fdm-mixer demo:
- Add ENABLE_PRUSA_FDM_MIXER_DEMO in Technologies.hpp (default 1). 0 keeps
the legacy polynomial prediction byte-identical; 1 routes blend prediction
through the calibrated prusa model -> CIELab for ΔE.
- Guard all call sites (MixedFilament, MixedColorMatchHelpers,
MixedFilamentColorMapPanel, MixedGradientSelector, MixedFilamentDialog)
with #if/#else.
- Add prusa_fdm_mixer.{cpp,hpp} + self-guarded wrap header; wire into
CMake. Self-contained and removable.
batch match fixes:
- Color parsing dropped most palette colors: normalize_color_match_hex
rejected alpha-bearing #RRGGBBAA (Bambu 3MF format) -> only 4 of 17
matched. Strip alpha (#RRGGBBAA -> #RRGGBB).
- Crash on repeated re-match (MANUAL -> RECOMMENDED): removed delete of a
wxBitmap owned by get_extruder_color_icon's static BitmapCache.
- Legend ghost pixels: update_mapping_legend uses Clear(true) instead of
deferred Destroy().
- Preserve previous match result across mode/selection change; only
Start/Re-match clears it (reset_match_preview).
- Recommended palette -> #08ABFB/#D93B90/#F9ED3D/#9199A4; keep input order
in the result (stable for >4 candidates).
…move prusa demo
1. Batch match confirm flow: in-place rewrite + redundant cleanup
- MixedFilamentManager::add_batch_custom_filaments gains an optional
out_assigned_ids out-param so the caller can learn each entry's
assigned virtual id before cleanup. Every rejection path (n<2, cap
reached, a==b, references out of range) pushes 0u, keeping the output
strictly 1:1 with entries.
- New pure helper compute_redundant_filaments: given the keep sets it
decides which physical/mixed filaments are redundant (physical ids
emitted descending for direct deletion; a survivor floor guarantees at
least one physical is kept). Cascade detection reuses remove_physical_
filament's authoritative tokenizer to avoid drift.
- New Sidebar::cleanup_unused_filaments_after_batch_match: capture each
redundant mixed row's stable_id -> delete physical filaments (triggers
remap) -> mark non-cascade redundant mixed rows by stable_id.
- The confirm flow rewrites EXISTING custom mixed rows in place (new
in_place_edited flag), preserving their virtual id and painting so
dropping a component physical does not strand painting.
- R3 guard: before cleanup, scan every ModelVolume::get_extruders() and
force-keep any still-painted mixed virtual id, preventing a deleted
painted row from being re-aliased into a silently wrong color.
- MixedFilamentManager::refresh_display_colors promoted from private to
public.
- Sidebar::delete_filament gains skip_dependency_check / skip_update
default params (back-compatible) so the cleanup path can trust
compute_redundant_filaments and skip the dependency check.
- MixedFilamentBatchDialog / MixedColorMatchHelpers updated for the flow.
2. Remove prusa_fdm_mixer demo code
- Delete prusa_fdm_mixer.{cpp,hpp}, prusa_fdm_mixer_wrap.hpp, LICENSE,
PROVENANCE (5 files).
- Remove the ENABLE_PRUSA_FDM_MIXER_DEMO macro (Technologies.hpp) and all
#if/#else branches, keeping the legacy path.
- Update CMakeLists to drop the translation units; remove the A/B
comparison test test_mix_model_ab_comparison.cpp.
3. Tests (test_mixed_filament.cpp +900 lines)
- Cover compute_redundant_filaments paths: physical-only / cascade /
survivor-floor / out-of-range / manual-pattern token resolution.
- Differential [!shouldfail] "m1": pins the known divergence between
compute and remove_physical on out-of-range literal tokens (currently
unreachable in production, rejected by references_exceed_physical at
every write path).
- add_batch rejection paths, stable_id serialize round-trip, and an R3
root-cause regression guard.
- Add a num_physical == 0 guard (compute_redundant_filaments early-
returns an empty result) plus its regression test.
…il viewpoints MixedFilamentBatchDialog — UI overhaul aligned to Figma spec: - Card-based layout (filament config / dual preview / color mapping); dialog width 760 -> 540. - RoundedPreviewPanel: single wxBG_STYLE_PAINT handler draws rounded bg + thumbnail + corner mask + badge, replacing StaticBox + wxStaticBitmap (avoids MSW compositing quirks erasing rounded corners/badges). - View combobox with 8 named viewpoints; thumbnail cache bucketed by viewpoint, lazily rendered per (viewpoint, plate). Original side now follows the selected viewpoint (was hardcoded to top). - Plate nav with disabled state at first/last plate. - Manual filament count defaults to physical filament count (capped [2,4]); add/remove buttons mirror MixedFilamentDialog. GLCanvas3D — named thumbnail viewpoints (pure addition, zero impact on existing call sites): - New render_thumbnail overload takes ThumbnailView instead of bool use_top_view. bool and scoped enum don't implicitly convert, so overload resolution is unambiguous. - New internal branch for named viewpoints (Camera::select_view + zoom_to_box + set_type). Existing call sites see view=Iso (default), routing to the original iso branch; the sole use_top_view=true caller (Plater's top_thumbnail_data for 3MF export) still hits the legacy top branch unchanged. - New ThumbnailView.hpp keeps the enum out of the GLCanvas3D include chain. Self-review fixes applied on top: - Failed-match path routes through set_match_buttons_state(false) so the Start/Re-match enable mask follows m_match_completed. - update_view() no longer discards the current viewpoint's match bucket on every switch (buckets stay valid across toggles; invalidation belongs in reset_match_preview / rebuild_match_thumb_cache). - Preview size floors promoted to named constants matching panel size. - Removed empty build_progress() no-op. - std::array size derived from enum + static_assert locking enum order.
…s, tooltips - Add inline "Stop Matching" button beside the progress bar (terminates the in-flight match without closing the dialog). - Manual mode: numbered color swatch beside each filament combo, color tracks the selection; warn when one physical filament dominates >70%. - Mode combo tooltip describes Auto vs Manual on hover. - Mode row on a white host panel (fixes bg bleed-through). - Plater entry: pre-check for empty model / <2 filaments via show_info. - Dialog opens on the plater's currently selected plate. - Icon touch-ups (icon_minus/plus/info) and zh_CN translations.
MixedFilamentBatchDialog: - Inline numbered color badge into ComboBox SetIcon (arrow + badge composite) and remove m_filament_swatch[4] - Fix wxStaticText bg colour on "Filament Setup" labels per wxWidgets pitfall §137 (SetForegroundColour + SetBackgroundColour) Plater / Sidebar: - Add BatchPhysicalDeletionGuard RAII to skip per-deletion panel rebuild and painting remap during batch cleanup; apply one composite remap after the loop - Add ProgressDialog for batch-match apply flow, stepping 0→20→40→60→80→100 so wxPD_AUTO_HIDE dismisses cleanly Tab: - Show advisory warning when Subdivide Mix Layer is active with layer height ≤0.1 mm (triggered on feature enable or layer-height change) ThumbnailView: - Extract ThumbnailView enum to lightweight header, add to CMakeLists, unify inline comments to English Tests: - Add shrink / tail-truncation remap tests (surviving id identity, tail→NONE mapping, compact→empty redundant_physical) Housekeeping: - i18n: 2 new zh_CN strings for Subdivide Mix Layer warnings
feat: Mixed Filament Phase 2 — batch color match dialog with multi-view preview
The object list's filament column still displayed old physical-slot extruder IDs after confirming a batch color match because the earlier on_filaments_change refresh ran before apply_batch_match_to_model did the actual extruder remapping. Root causes and fixes: 1. MixedFilamentBatchDialog::load_model_colors() was enumerating the extruder palette array instead of walking the model's actual painted extruder IDs via get_extruders(). This missed volume config-level extruder assignments (no MMU paint) and virtual mixed-filament IDs that were deduplicated against the physical palette. Rewrote to walk every MODEL_PART volume, look up each extruder's hex colour from the physical palette or the mixed-filament display_color table, and accumulate all matching extruder IDs per colour. 2. Recommended-mode confirm path (Plater.cpp) was losing pre-existing custom mixed filaments because set_num_filaments calls clear_custom_entries(). Fixed by snapshotting the old mixed list before the call, reloading custom entries from project_config afterwards to preserve stable_ids, and building an old→new virtual ID remap so on_filaments_change correctly translates existing painted mixed-IDs before apply_batch_match_to_model runs. 3. Added obj_list()->update_objects_list_filament_column() call after apply_batch_match_to_model completes so the object list reflects the post-remap extruder IDs.
fix: object list filament column not updating after batch color match
…ancel-aware rematch (#617) * feat(mixed-filament): per-component ratio cap, Full Spectrum rename, safety hardening Add max_component_percent through the color-match pipeline (build_best_color_match_recipe, batch_match_model_colors, recommend_best_filament_combo) to cap any single filament's share of a mix: - RECOMMENDED: [0%, 70%] — product spec enforces no dominant component - MANUAL: [15%, 100%] — near-pure allowed; over-70% surfaces as a post-match advisory via new check_manual_recipe_ratio(), listing all over-threshold IDs Rename "CMYW" → "Full Spectrum"; add canonical preset constant (kFullSpectrumPresetName) and auto-assign it to slots 1-4 after batch match so the combobox shows the preset name instead of F1-F4. Harden cleanup_unused_filaments_after_batch_match: promote the redundant_physical descending invariant from assert() to a runtime check (assert is compiled out under NDEBUG), with a per-deletion fallback that keeps colours correct when violated. Fix dangling reference in extract_model_colors — the ternary bound a temporary MixedFilamentManager to a reference (use-after-scope UB); now a raw pointer with null-check at the virtual-lookup site only. Robustness: clamp mix_b_percent to [0,100], defer 64-color warning until after build_ui, static_cast cleanup, null-guard preset_bundle at startup. Harness review fixes (dev-guidelines): validate max_component_percent range in build_best_color_match_recipe, replace residual C-style cast, document m_match_running as UI-thread-only. * fix: object list filament column not updating after batch color match The object list's filament column still displayed old physical-slot extruder IDs after confirming a batch color match because the earlier on_filaments_change refresh ran before apply_batch_match_to_model did the actual extruder remapping. Root causes and fixes: 1. MixedFilamentBatchDialog::load_model_colors() was enumerating the extruder palette array instead of walking the model's actual painted extruder IDs via get_extruders(). This missed volume config-level extruder assignments (no MMU paint) and virtual mixed-filament IDs that were deduplicated against the physical palette. Rewrote to walk every MODEL_PART volume, look up each extruder's hex colour from the physical palette or the mixed-filament display_color table, and accumulate all matching extruder IDs per colour. 2. Recommended-mode confirm path (Plater.cpp) was losing pre-existing custom mixed filaments because set_num_filaments calls clear_custom_entries(). Fixed by snapshotting the old mixed list before the call, reloading custom entries from project_config afterwards to preserve stable_ids, and building an old→new virtual ID remap so on_filaments_change correctly translates existing painted mixed-IDs before apply_batch_match_to_model runs. 3. Added obj_list()->update_objects_list_filament_column() call after apply_batch_match_to_model completes so the object list reflects the post-remap extruder IDs. * feat(mixed-filament): recommended-card tooltip, bordered row, discard-match confirm - Per-row hover tooltip: localized color name + hex + TD (resolved by color family, not palette position — survives ΔE fallback reorder) - Row border wraps swatch + name together (was: name field only) - Cancel prompts discard confirmation only when a match result exists - Skip refresh on method re-select (wxMSW combobox quirk) - Consolidate Full Spectrum preset name into kFullSpectrumPresetName * feat(mixed-filament): dedupe identical recipes, cancel-aware re-match, UX polish - merge_duplicate_recipe_mappings: byte-identical non-pure recipes share one virtual slot - distinguish recommend_best_filament_combo cancel vs no-combo (error_code 2) - restore prior preview on failed re-match only when input unchanged - triple-loop wb bound respects max_component_percent - banners moved below mode row; canvas3D() -> get_view3D_canvas3D() * refactor(mixed-filament): log null preset_bundle once in extract_model_colors pb is loop-invariant; the per-iteration null check stayed (correct — physical-colour path must not be skipped), but its warning fired once per virtual-painted face. Hoist the warning above the loop, leave the inner check as a silent continue. * feat(mixed-filament): per-component ratio cap, Full Spectrum rename, safety hardening Add max_component_percent through the color-match pipeline (build_best_color_match_recipe, batch_match_model_colors, recommend_best_filament_combo) to cap any single filament's share of a mix: - RECOMMENDED: [0%, 70%] — product spec enforces no dominant component - MANUAL: [15%, 100%] — near-pure allowed; over-70% surfaces as a post-match advisory via new check_manual_recipe_ratio(), listing all over-threshold IDs Rename "CMYW" → "Full Spectrum"; add canonical preset constant (kFullSpectrumPresetName) and auto-assign it to slots 1-4 after batch match so the combobox shows the preset name instead of F1-F4. Harden cleanup_unused_filaments_after_batch_match: promote the redundant_physical descending invariant from assert() to a runtime check (assert is compiled out under NDEBUG), with a per-deletion fallback that keeps colours correct when violated. Fix dangling reference in extract_model_colors — the ternary bound a temporary MixedFilamentManager to a reference (use-after-scope UB); now a raw pointer with null-check at the virtual-lookup site only. Robustness: clamp mix_b_percent to [0,100], defer 64-color warning until after build_ui, static_cast cleanup, null-guard preset_bundle at startup. Harness review fixes (dev-guidelines): validate max_component_percent range in build_best_color_match_recipe, replace residual C-style cast, document m_match_running as UI-thread-only. * feat(mixed-filament): recommended-card tooltip, bordered row, discard-match confirm - Per-row hover tooltip: localized color name + hex + TD (resolved by color family, not palette position — survives ΔE fallback reorder) - Row border wraps swatch + name together (was: name field only) - Cancel prompts discard confirmation only when a match result exists - Skip refresh on method re-select (wxMSW combobox quirk) - Consolidate Full Spectrum preset name into kFullSpectrumPresetName * feat(mixed-filament): dedupe identical recipes, cancel-aware re-match, UX polish - merge_duplicate_recipe_mappings: byte-identical non-pure recipes share one virtual slot - distinguish recommend_best_filament_combo cancel vs no-combo (error_code 2) - restore prior preview on failed re-match only when input unchanged - triple-loop wb bound respects max_component_percent - banners moved below mode row; canvas3D() -> get_view3D_canvas3D() * refactor(mixed-filament): log null preset_bundle once in extract_model_colors pb is loop-invariant; the per-iteration null check stayed (correct — physical-colour path must not be skipped), but its warning fired once per virtual-painted face. Hoist the warning above the loop, leave the inner check as a silent continue. * feat(mixed-filament): allow cross-type mixes in manual batch mode Add check_compatible through the recipe-search pipeline so manual mode can create cross-type mixes (PLA+PETG). RECOMMENDED keeps the same-type filter; the slice gate (has_incompatible_mixed_filament_in_use) still blocks them at slice time — widen in, strict out. Also: empty error_message on cancel (code 2 is a silent rollback), discard-confirm focus fix on wxMSW, banner spacing, RichMessageDialog for the no-model/<2-filament prompts. --------- Co-authored-by: kenshin627 <87zhaoxiaobo@163.com>
…-core hardening (#628) * feat(mixed-filament): per-component ratio cap, Full Spectrum rename, safety hardening Add max_component_percent through the color-match pipeline (build_best_color_match_recipe, batch_match_model_colors, recommend_best_filament_combo) to cap any single filament's share of a mix: - RECOMMENDED: [0%, 70%] — product spec enforces no dominant component - MANUAL: [15%, 100%] — near-pure allowed; over-70% surfaces as a post-match advisory via new check_manual_recipe_ratio(), listing all over-threshold IDs Rename "CMYW" → "Full Spectrum"; add canonical preset constant (kFullSpectrumPresetName) and auto-assign it to slots 1-4 after batch match so the combobox shows the preset name instead of F1-F4. Harden cleanup_unused_filaments_after_batch_match: promote the redundant_physical descending invariant from assert() to a runtime check (assert is compiled out under NDEBUG), with a per-deletion fallback that keeps colours correct when violated. Fix dangling reference in extract_model_colors — the ternary bound a temporary MixedFilamentManager to a reference (use-after-scope UB); now a raw pointer with null-check at the virtual-lookup site only. Robustness: clamp mix_b_percent to [0,100], defer 64-color warning until after build_ui, static_cast cleanup, null-guard preset_bundle at startup. Harness review fixes (dev-guidelines): validate max_component_percent range in build_best_color_match_recipe, replace residual C-style cast, document m_match_running as UI-thread-only. * feat(mixed-filament): recommended-card tooltip, bordered row, discard-match confirm - Per-row hover tooltip: localized color name + hex + TD (resolved by color family, not palette position — survives ΔE fallback reorder) - Row border wraps swatch + name together (was: name field only) - Cancel prompts discard confirmation only when a match result exists - Skip refresh on method re-select (wxMSW combobox quirk) - Consolidate Full Spectrum preset name into kFullSpectrumPresetName * feat(mixed-filament): dedupe identical recipes, cancel-aware re-match, UX polish - merge_duplicate_recipe_mappings: byte-identical non-pure recipes share one virtual slot - distinguish recommend_best_filament_combo cancel vs no-combo (error_code 2) - restore prior preview on failed re-match only when input unchanged - triple-loop wb bound respects max_component_percent - banners moved below mode row; canvas3D() -> get_view3D_canvas3D() * refactor(mixed-filament): log null preset_bundle once in extract_model_colors pb is loop-invariant; the per-iteration null check stayed (correct — physical-colour path must not be skipped), but its warning fired once per virtual-painted face. Hoist the warning above the loop, leave the inner check as a silent continue. * feat(mixed-filament): per-component ratio cap, Full Spectrum rename, safety hardening Add max_component_percent through the color-match pipeline (build_best_color_match_recipe, batch_match_model_colors, recommend_best_filament_combo) to cap any single filament's share of a mix: - RECOMMENDED: [0%, 70%] — product spec enforces no dominant component - MANUAL: [15%, 100%] — near-pure allowed; over-70% surfaces as a post-match advisory via new check_manual_recipe_ratio(), listing all over-threshold IDs Rename "CMYW" → "Full Spectrum"; add canonical preset constant (kFullSpectrumPresetName) and auto-assign it to slots 1-4 after batch match so the combobox shows the preset name instead of F1-F4. Harden cleanup_unused_filaments_after_batch_match: promote the redundant_physical descending invariant from assert() to a runtime check (assert is compiled out under NDEBUG), with a per-deletion fallback that keeps colours correct when violated. Fix dangling reference in extract_model_colors — the ternary bound a temporary MixedFilamentManager to a reference (use-after-scope UB); now a raw pointer with null-check at the virtual-lookup site only. Robustness: clamp mix_b_percent to [0,100], defer 64-color warning until after build_ui, static_cast cleanup, null-guard preset_bundle at startup. Harness review fixes (dev-guidelines): validate max_component_percent range in build_best_color_match_recipe, replace residual C-style cast, document m_match_running as UI-thread-only. * feat(mixed-filament): recommended-card tooltip, bordered row, discard-match confirm - Per-row hover tooltip: localized color name + hex + TD (resolved by color family, not palette position — survives ΔE fallback reorder) - Row border wraps swatch + name together (was: name field only) - Cancel prompts discard confirmation only when a match result exists - Skip refresh on method re-select (wxMSW combobox quirk) - Consolidate Full Spectrum preset name into kFullSpectrumPresetName * feat(mixed-filament): dedupe identical recipes, cancel-aware re-match, UX polish - merge_duplicate_recipe_mappings: byte-identical non-pure recipes share one virtual slot - distinguish recommend_best_filament_combo cancel vs no-combo (error_code 2) - restore prior preview on failed re-match only when input unchanged - triple-loop wb bound respects max_component_percent - banners moved below mode row; canvas3D() -> get_view3D_canvas3D() * refactor(mixed-filament): log null preset_bundle once in extract_model_colors pb is loop-invariant; the per-iteration null check stayed (correct — physical-colour path must not be skipped), but its warning fired once per virtual-painted face. Hoist the warning above the loop, leave the inner check as a silent continue. * feat(mixed-filament): allow cross-type mixes in manual batch mode Add check_compatible through the recipe-search pipeline so manual mode can create cross-type mixes (PLA+PETG). RECOMMENDED keeps the same-type filter; the slice gate (has_incompatible_mixed_filament_in_use) still blocks them at slice time — widen in, strict out. Also: empty error_message on cancel (code 2 is a silent rollback), discard-confirm focus fix on wxMSW, banner spacing, RichMessageDialog for the no-model/<2-filament prompts. * fix(mixed-filament): harden color-match core, align batch dialog with PRD - Fix data race on s_compat_loaded (atomic DCLP) and toupper UB on non-ASCII filament types - Add max_component_percent [50,100] validation in batch_match_model_colors - Switch batch match to full project palette (load_palette_colors); align ΔE grade thresholds with PRD - Set icon bg for wxMSW transparency; refine banner spacing * feat(mixed-filament): nozzle-aware preset + dark-mode preview polish - Replace inline `kFullSpectrumPresetName` constant with `full_spectrum_preset_name()`; resolves the canonical 0.4 SKU and falls back when no matching preset exists for the current nozzle_diameter. Future nozzle variants ship as preset JSON only — no code change. - Make MixedFilament preview theme-aware: panel bg #F5F5F5 → #E7E7E7, add #E7E7E7→#54545B and #939393→#000000 dark mappings (StateColor), blend thumbnail alpha against the resolved bg so cached bitmaps follow the active theme instead of baking in a hardcoded light bg. - Rename "Batch Match" → "Color Mixing Match" with explanatory tooltip; parameterize the mix-ratio tooltip/warning via wxString::Format (%d%%–%d%%) instead of the X%–Y% placeholder. - Update zh_CN translations for new/changed strings.
The RoundedPreviewPanel used a wxImage color-mask corner overlay approach that did not render correctly on MSW — the preview images appeared with square corners regardless of the mask. Replaced the mask-based approach with OS-level window region clipping (SetWindowRgn / CreateRoundRectRgn on Windows, wxRegion::Intersect on macOS/Linux). The panel is physically shaped to a rounded rectangle by the OS, so corners are transparent regardless of what is painted.
- Manual-mode combo box and combo icon now use GetFilamentColorIcon for multi-color aware filament swatches (segment/gradient) instead of solid-color get_extruder_color_icon. - Mapping legend source swatches render dual-color segment/gradient blocks for pure-recipe mappings when the target physical filament has multi-color data, matching the visual style used elsewhere. - Read m_physical_multi_colors and m_physical_color_modes from project_config at dialog construction; resize to match physical filament count for safe downstream index access. - Fix tooltip binding: use src_bmp_ctrl (wxStaticBitmap) instead of raw wxBitmap pointer which is not a window and cannot show tooltips.
…lish - Keep-aware batch remap: map survivors by position in kept set so manual subsets like [2,6,8,10] keep their painting instead of being truncated to NONE (PresetBundle, Plater). - Preview: substitute every source_extruder_id per mapping so shared-color slots all render the matched color. - Confirm gate: warn at commitment when a match would exceed the 64-filament cap (predict_slot_overflow). - Performance: freeze sidebar across the batch deletion loop; per-deletion progress via on_progress callback (~10.7s vs ~29.9s). - UX: nozzle != 0.4 blocks recommended mode; strict <3/<5 grade bands; add-row cap tracks physical spool count; relayout_scrolled_content helper. - Tests: +835 lines covering remap shape matrix, apply target==src skip, preview multi-slot, redundant-set non-contiguous subset.
…nd persist across mode switch - Change check_manual_recipe_ratio() to report target_filament_id (legend badge number) instead of physical slot index, so the warning lists the mapped filament IDs the user actually sees. - Re-run ratio check when switching back to Manual mode so the advisory banner is restored without requiring a re-match (m_match_completed stays true across mode toggles).
…or preview rounded corners
The RoundedPreviewPanel used SetWindowRgn (MSW) / SetShape (macOS/Linux)
to physically clip child wxPanel to a rounded rect. This caused three
blocking defects:
N1 (MSW): ~RoundedPreviewPanel called DeleteObject on an HRGN whose
ownership had been transferred to the OS by SetWindowRgn(…, TRUE).
Double-free of a GDI handle → non-deterministic heap corruption.
N2 (MSW): apply_rounded_shape (wxEVT_SIZE) called DeleteObject on
the cached HRGN from the previous call, which was also already
owned by the OS. Triggered on every resize.
N3 (macOS/Linux): SetShape(const wxRegion&) is only defined on
wxNonOwnedWindowBase (top-level windows with wxFRAME_SHAPED).
Calling it on a child wxPanel is a hard compile error.
Replace the physical-clip approach with a paint-layer solution:
1. Fill the entire panel with white (parent card background).
2. Build a rounded-rect wxRegion via a mask bitmap, then call
SetDeviceClippingRegion — standard wxDC API, child-window safe
on all platforms, no GDI ownership transfer.
3. Draw the rounded background (#E7E7E7) + thumbnail + badge.
The four corners remain white, reading as transparent over the
parent card.
Also restore the wxEVT_DPI_CHANGED handler (lost in the PR rewrite) to
re-rasterize the placeholder SVG at the new DPI on monitor switch.
No other functional changes — the Partial 2 multi-color rendering code
(dual-color swatches, GetFilamentColorIcon, legend source-switch logic)
is untouched.
…leCorner Fix mixed filament shotcut circle corner
- close data race: pass check_compatible=false in the RECOMMENDED worker
call site (build_compatibility_matrix read preset_bundle->filament_presets
unsynchronized); safe because RECOMMENDED palette is always a single
same-material Full Spectrum preset, and the slice gate still backstops
real incompatibility
- validate min/max percent range in recommend_best_filament_combo (reject
with a log instead of silently scoring with an empty window)
- assert non-zero stable_id contract in allocate_stable_id (debug-only
guard; max(1,...) already guarantees it)
- revise predict_slot_overflow comment to match actual execution order
(cleanup runs after add_batch, so it is an upper bound)
- UI: keep progress bar as native wxGauge; fix dark-mode button colors
(Confirm/Tab text #FFFFFF -> #FEFEFE stays white via dark map)
- copy: Filament Setup -> Filament Setup (CMYG); nozzle-unsupported hint
rewritten; warning id list "{x,y}" -> "x, y"
- progress dialog title/body reworded (Applying color match -> Loading)
- add 0.10 Color Mixing @Snapmaker U1 (0.4 nozzle) process preset
- document wxStaticText bg-inheritance pitfall in AGENTS.md
- zh_CN translations for the new strings
…se2_pr Conflict in MixedFilamentBatchDialog::check_manual_recipe_ratio resolved by taking upstream's no-dedup version (target ids from non-pure mappings are already unique & ascending via next_virtual_id++, preserved through merge_duplicate_recipe_mappings), plus keeping the target_filament_id == 0 guard (skip unmapped rows that have no legend badge to report).
…flict" Rename both Subdivide Mixing Layers warning dialogs from "Warning" to "Configuration Conflict" and drop the wxICON_WARNING icon. Add the zh_CN translation.
…2_pr fix(mixed-filament): preserve painting for non-contiguous manual palette subsets
Batch color match only remapped object/volume extruders and ignored layer_config_ranges entirely. A layer object's filament stayed on its old physical slot after the palette was rewritten, so when cleanup deleted the unreferenced physical filaments the layer was reset to default — surfacing as "layer filament >N lost / reset to default". Include layer_config_ranges alongside volumes: - apply_batch_match_to_model: add a Level-3 pass that remaps each layer object's extruder with the same extruder_remap used for volumes; - load_model_colors: collect layer-object extruders as match sources too, so a layer whose filament no volume paints still gets matched and remapped.
Address the review findings on the modifier/layer batch-match fix (PR #674). No behavioural change in the common (single-mapping) case; the changes below make the apply path correct under remap chains and remove ineffective/cosmetic churn. apply_batch_match_to_model (MixedColorMatchHelpers.cpp): - Snapshot the object's extruder ONCE before iterating volumes instead of re-reading ModelVolume::extruder_id() per volume. extruder_id() falls back to the OBJECT config, which this loop rewrites mid- iteration, so a later inheriting volume would resolve the just-written value and — under a remap chain like {5->2, 2->7} — diverge from an earlier one (order-dependent). All inheriting volumes (part or modifier) now follow the same target. Drop the now-unreachable else-branch and the misleading comment that claimed it handled "inheriting volumes with different targets". - Remove the unused `Print& print` parameter (the function walks wxGetApp().model(), never `print`); sync the header and the lone caller in Plater.cpp. Dead-code cleanup (MixedFilamentBatchDialog.cpp / MixedColorMatchHelpers.cpp): - Revert the modifier/layer edits made to the two DEAD functions load_model_colors and extract_model_colors (both have zero callers; the ctor calls load_palette_colors instead). Those edits had no runtime effect and only inflated the diff. The functions are kept (marked "legacy / future use") and restored to their pre-PR MODEL_PART-only form. The live layer fix lives entirely in apply_batch_match_to_model's Level-3 pass; load_palette_colors already enumerates every physical slot as a match source, so physical layer filaments are covered without the dead-function edit. Tests (tests/libslic3r/test_mixed_filament.cpp): - Add 4 Catch2 cases for the Level-2/Level-3 apply path. The GUI function cannot be linked from the test binary, so they mirror the loop over the real libslic3r Model API — same approach as the existing build_apply_extruder_remap tests: * extruder_id() falls back to the LIVE object config (pure libslic3r, pins why the snapshot is needed); * an inheriting modifier follows the object consistently; * a modifier with its own extruder is remapped alone; * layer_config_ranges: a hit is remapped, a miss is left untouched.
Fix mix filament bug01
Call update_data() instead of update_gizmos_on_off_state() after update_volumes_colors_by_extruder() so the active painting gizmo picks up new colors (legend + painted facets) without being deactivated.
…idle height Idle footer leaked ~12 DIP because the old progress row (gauge + Stop Matching) only hid its widgets — the sizer's spacers and border still occupied vertical space. Wrap the progress row plus its lower hairline in a wxBoxSizer block and toggle it as a unit via ShowItems(), so the whole block — widgets, spacers, and gaps — collapses to 0 in idle. Matching-state layout (44-DIP row, divider) is unchanged; Layout() is still called after every toggle so Cancel/Confirm stay pinned. Also tighten preview-card spacing: 8-DIP gap to/from the divider via AddSpacer (replacing the wxTOP border on the crow), and adjust the crow inter-element gaps to match Figma.
This reverts commit 1304cfd.
…2_pr fix: mixed-filament issues including painting remap and UI updates
…t gate
- normalize_color_match_weights: guard against count == 0. The old code
wrote out[0] on an empty vector in the remainder-fill loop (heap
out-of-bounds write, UB) when count == 0 and weights was non-empty.
Return an empty vector up front; all call sites already tolerate it.
- MixedFilamentBatchDialog: record the user's manual ComboBox selection in
m_filament_selections on wxEVT_COMBOBOX, so the failed/cancelled re-match
restore gate (input_intact) can detect that the manual input changed
instead of always restoring a stale preview.
- zh_CN: fix stray braces in the "mix ratios" format string ({%s} -> %s);
specifier count still matches the msgid.
- Snapmaker.json: drop the dangling duplicate "0.10 Color Mixing @Snapmaker
U1 (0.4 Nozzle)" process entry whose sub_path points to a non-existent
file (case mismatch); the lowercase entry and its file are retained.
- MixedFilament.hpp: add #pragma once (keep existing include guard).
- Replace C-style casts with static_cast in MixedColorMatchHelpers.cpp and
MixedColorMatchPanel.cpp.
…refs on cleanup - MixedFilamentBatchDialog: add slot_match_color to derive a dual-color slot's effective primary color from filament_multi_colors (PrimaryColor rule); apply it to the thumb cache, original-plate thumbs, palette loading and background match so slots with an empty filament_colour are matched by their real color instead of being skipped or rendered black. - MixedFilamentBatchDialog: render one legend row per source color for merged (mixed-recipe) mappings via merged_model_indices, each with its own swatch and per-row ΔE (color_delta_e00), so merged colors are no longer hidden under the first source color. - Plater: in batch-match cleanup, guard config-level "extruder" references (object/volume/layer) so mixed rows referenced only from config are not flagged redundant, and remap those references with mixed_deletion_remap alongside painting. - tests: pin delete-list dedupe and dual-color primary derivation; document the known cascade-aware config remap gap as an intentionally-failing ([.]) test.
… painted rows Plater.cpp cleanup_unused_filaments_after_batch_match: the get_extruders force-keep is not absolute — a mixed row whose components reference a deleted physical is still erased by remove_physical_filament, and its stranded painting falls back to build_filament_id_remap's pair-fallback (possibly a wrong survivor). Comment-only change; no behavior.
The batch color match remapped the parent object's filament only inside the "volume inherits the object's extruder" branch of apply_batch_match_to_model. For multi-part objects where EVERY volume owns its own extruder (e.g. Creality 3mf exports — object extruder = 1, parts 1..6 each carry extruder 1,2,3,4,5,5), no volume inherits, so that branch never ran and the parent row stayed on its stale filament while the volumes were remapped — "parent object's filament not mapped". The BambuStudio 3mf (single volume, no per-volume extruder) worked because the volume inherited the object's extruder, which the branch rewrote once. Fix: remap the object-level extruder independently, before iterating volumes — write the object whenever its extruder is in the remap table. Inheriting volumes then resolve through the already-rewritten object config, so the volume loop no longer needs the inheritance branch. Behaviour for the inheriting case is unchanged.
feat: Enhance mixed filament batch matching with UI and algorithm updates
fix(mixed-filament): remap parent object filament in batch color match
…l-count expansion Rebuild the old->new virtual-id remap whenever the physical filament count changes in the batch-match recommended flow, not only when the mixed-row list differs. A count change shifts every mixed row's virtual id even when rows are structurally identical (MixedFilament::operator== ignores display_color), so the previous check skipped the remap and left painted triangles and config "extruder" keys on stale ids (e.g. mixed row vid 3 -> 5 on a 2->4 expansion). - Sidebar batch-match branch: run update_mixed_filament_id_remap when current_count != target_count OR the row list differs. - Remap object/volume/layer "extruder" config keys with the same state_map as the triangle remap, scoped to the batch-match branch only; other remap paths (row delete/enable, manual add/remove) are intentionally left unchanged. - Add regression test: expanding 2 physical + 1 mixed row to 4 physicals with a structurally identical row list still remaps mixed vid 3 -> 5 (verified remap=[0,1,2,5], stable_id match, 5 assertions).
The batch color match remapped the parent object's filament only inside the "volume inherits the object's extruder" branch of apply_batch_match_to_model. For multi-part objects where EVERY volume owns its own extruder (e.g. Creality 3mf exports — object extruder = 1, parts 1..6 each carry extruder 1,2,3,4,5,5), no volume inherits, so that branch never ran and the parent row stayed on its stale filament while the volumes were remapped — "parent object's filament not mapped". The BambuStudio 3mf (single volume, no per-volume extruder) worked because the volume inherited the object's extruder, which the branch rewrote once. Fix: remap the object-level extruder independently, before iterating volumes — write the object whenever its extruder is in the remap table. Inheriting volumes then resolve through the already-rewritten object config, so the volume loop no longer needs the inheritance branch. Behaviour for the inheriting case is unchanged.
Batch-match and cleanup paths both remapped a deleted/expired mixed-row
"extruder" config key by erasing it on objects/volumes while writing 0 on
layers. A missing key makes ModelConfig::extruder() (opt_int on an absent
option) a nullptr dereference for any reader that does not check has() first
(e.g. GUI_ObjectList's layer-range delete path), and erasing diverged from
the out-of-range normalization in update_filament_values_for_items which
writes an explicit 0.
Unify all three config levels (object/volume/layer) on cfg.set("extruder", 0):
the key stays present so no unprotected reader can crash, the explicit 0
matches the existing GUI normalization, and objects/volumes with 0 still
resolve to "default" via ModelVolume::extruder_id()'s inherit-from-object
fallback. Drop the now-redundant is_layer parameter in both remap lambdas.
Also copy batch_remap instead of borrowing PresetBundle::last_filament_id_remap()
so the config remap block no longer depends on on_filaments_change consuming
(moving + clearing) the internal buffer before the reference is used.
fix: rebuild id remap and sync config keys on physical-count expansion
…ssage Replace the hardcoded "0.4 mm nozzle" hint with a generic smaller-size nozzle wording, since any preset-defined nozzle size is auto-supported. Sync the zh_CN translation to match the updated source string.
fix: reword auto color-match nozzle-diameter block message
… manual edit In manual mode, after a match predicted a 64-slot overflow (red banner), editing a filament combo re-entered check_manual_recipe_ratio(), whose display_warning() call hid the overflow error and replaced it with the less-serious ratio hint. The overflow-wins- over-ratio invariant was only enforced in handle_batch_match_result, not on the re-evaluation paths (on_manual_selection_changed, on_method_changed). Centralize the priority in a new try_show_slot_overflow_advisory() helper and call it at the top of check_manual_recipe_ratio so every entry point honours it. Also lets on_method_changed re-show the overflow advisory when toggling back to manual.
fix(mixed-filament): keep slot-overflow banner over ratio advisory on manual edit
|
❌ Documentation validation failed 🔗 Link Validation Errors📄 doc/developer-reference\Built-in-placeholders-variables.md:
📄 doc/developer-reference\How-to-wiki.md:
📄 doc/Home.md:
📄 doc/Tab.cpp:
🖼️ Image Validation Errors📄 doc/developer-reference\How-to-create-profiles.md:
|
fix(mixed-filament): signal cancel before dialog close to avoid UI-thread join stall
|
❌ Documentation validation failed 🔗 Link Validation Errors📄 doc/developer-reference\Built-in-placeholders-variables.md:
📄 doc/developer-reference\How-to-wiki.md:
📄 doc/Home.md:
📄 doc/Tab.cpp:
🖼️ Image Validation Errors📄 doc/developer-reference\How-to-create-profiles.md:
|
LiuLikeQian
approved these changes
Aug 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 2 of the mixed-filament feature. Adds a batch color-match workflow that
extracts model colors, matches them against the Full Spectrum / physical
filament palette in CIELAB space, and generates mixed-filament recipes
(mix ratio or manual pattern) automatically, while keeping the Manual mode
fully editable.
What's new
Batch color match dialog (
MixedFilamentBatchDialog)thumbnail viewpoints, and a mapping card showing model color → filament.
thread-safety (cancellation-aware).
Matching pipeline (
MixedColorMatchHelpers,MixedFilament.cpp)(
BlendLUT) and two-pass recipe generation (pure pairs first, then blends).hard floor removed to allow legitimate near-pure recipes), and cancel-aware
rematch.
filaments are marked for cleanup with painting-remap (pure,
unit-testable
build_mixed_deletion_painting_remap).Integration & fixes
parent objects, modifier volumes, and layer-object extruders; painting gizmo
refresh on color change; object-list filament column updates after match.
keys to 0 instead of erasing, skip write-back when Full Spectrum is not
selectable, gate Recommended mode on actual preset existence for the current
nozzle diameter (no hard-coded 0.4).
extruder handling in delete/batch-match/icon paths.
artifacts), opaque preview panel background, dual-color swatches, stable
combo icons, collapsed footer progress to pin idle height.
Resources & localization
mixed_filament_mapping_right_arrow*,mixed_filament_preview_placeholder,color_delta_e_label*, …).0.10 Color Mixingprocess preset reworded; Snapmaker.json updated.Tests
tests/libslic3r/test_mixed_filament.cpp: +2329 lines covering corealgorithms, deletion painting remap (single/multiple/all/empty/unsorted),
batch-match recipe generation, redundant-filament computation, and physical
token bounds guard.
tests/libslic3r/test_mixed_filament_color_golden.cpp: golden color-blendchecks.
Verification
editing, row delete → painting remap, nozzle-diameter preset switching.