Skip to content

feat(mixed-filament): phase 2 hardening — ratio cap, recipe dedupe, cancel-aware rematch - #617

Merged
Kenshin627 merged 12 commits into
Snapmaker:feat_mix_filament_Phase2from
zhangzhend0ng:feature/mix_filament_phase2
Jul 27, 2026
Merged

feat(mixed-filament): phase 2 hardening — ratio cap, recipe dedupe, cancel-aware rematch#617
Kenshin627 merged 12 commits into
Snapmaker:feat_mix_filament_Phase2from
zhangzhend0ng:feature/mix_filament_phase2

Conversation

@zhangzhend0ng

@zhangzhend0ng zhangzhend0ng commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Description

Phase 2 hardening of the mixed-color batch-match flow. Builds on the phase-1
batch dialog with four areas of work: a per-component ratio cap, duplicate-
recipe deduplication, cancel-aware re-match, and UX polish.

What's new

Per-component ratio cap (max_component_percent)
Threaded through the whole match chain (build_best_color_match_recipe
batch_match_model_colorsrecommend_best_filament_combo), limiting any
single filament's share of a mix:

  • RECOMMENDED mode: [0%, 70%] — product spec: no single component > 70%.
  • MANUAL mode: [15%, 100%] — near-pure allowed; over-70% surfaces as a
    post-match advisory via check_manual_recipe_ratio(), listing all
    over-threshold filament IDs.

Duplicate-recipe deduplication (merge_duplicate_recipe_mappings)
Mappings whose non-pure recipes are byte-identical (same components + ratio +
gradient) now share one virtual slot instead of each allocating a duplicate
mixed-filament row. The survivor unions source_extruder_ids and
merged_model_indices; runs after both ID-assignment phases so it keeps the
final target_filament_id. Pure recipes pass through untouched.

Cancel-aware re-match
recommend_best_filament_combo returns {} for both "no combo" and
"cancelled" — disambiguated via a post-call cancel_token re-check, routing
a cancelled result (error_code 2) directly with no error banner and skipping
the Pass-2 match. On a failed/cancelled re-match, the prior preview is
restored only when user-facing input is unchanged (mode + manual selections).

"CMYW" → "Full Spectrum" rename
New canonical constant kFullSpectrumPresetName; slots 1-4 auto-assigned the
preset after batch match so the combobox shows the preset name, not F1-F4.

Bug & safety fixes

  • Dangling reference (UB) in extract_model_colors: ternary bound a
    temporary to a reference (use-after-scope). Now a raw pointer with null-check
    at the virtual-lookup site; null warning logged once (was per-face).
  • Descending-invariant hardening in cleanup_unused_filaments_after_batch_match:
    promoted from assert() (compiled out under NDEBUG) to a runtime check
    with a per-deletion fallback that keeps colours correct on violation.
  • mix_b_percent clamped to [0,100]; 64-color warning deferred until after
    build_ui(); preset_bundle null-guarded at startup.
  • Triple-loop wb upper bound now respects max_component_percent.
  • C-style casts → static_cast; m_match_running documented UI-thread-only.

UX polish

  • Recommended-card hover tooltip: localized color name + hex + TD (resolved by
    color family, not palette position).
  • Row border wraps swatch + name together; banners moved below the mode row.
  • canvas3D()get_view3D_canvas3D() on the thumbnail render path.
  • Skip refresh on method re-select (wxMSW combobox quirk).

Tests

  • Build: cmake --build build --target Snapmaker_Orca --config Release
  • Recommended-mode match: no single component > 70%
  • Manual-mode match with near-pure selection: over-threshold advisory lists all offending IDs
  • After confirm, combobox shows Full Spectrum preset names (not F1-F4)
  • Two models whose recipes collide share one virtual slot (no duplicate row)
  • Cancel a running match: no error banner, prior preview restored when input unchanged
  • Regression: batch-match cleanup produces correct colours when redundant filaments removed
  • Regression: no crash on early-startup paths where preset_bundle is null

…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.
// Virtual mixed filament — look up its display_color.
// Early startup or rare call paths may have no preset_bundle;
// skip virtual-colour resolution then rather than dereference null.
if (pb == nullptr) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for循环外层判断?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为什么不能直接"提到循环外"
pb 只在虚拟混合丝分支(idx >= filament_colours.size(),line 1334)里需要。物理颜色分支(line 1331-1333)完全不依赖 pb:

cpp
if (idx < filament_colours.size()) {
color_hex = filament_colours[idx]; // 物理颜色 — 不需要 pb
} else {
if (pb == nullptr) { continue; } // 虚拟颜色 — 需要 pb
...
}
如果把 if (pb == nullptr) 提到最外层循环并 continue/return,会把物理颜色也一起跳过——那是错的。所以这个 null 检查必须留在 else 分支里,它检查的是"这次访问需不需要 pb",不是"整个循环要不要跑"。

Kenshin627 and others added 4 commits July 26, 2026 21:56
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.
…-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
…, 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()
…l_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.
@zhangzhend0ng zhangzhend0ng changed the title feat(mixed-filament): per-component ratio cap, Full Spectrum rename, and safety hardening feat(mixed-filament): phase 2 hardening — ratio cap, recipe dedupe, cancel-aware rematch Jul 27, 2026
fix: object list filament column not updating after batch color match
…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.
…-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
…, 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()
…l_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.
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.
All 4 remote commits had identical counterparts locally, so every
conflict resolved as "Use HEAD" — no behavioural change from remote.
@Kenshin627
Kenshin627 merged commit 8e2d854 into Snapmaker:feat_mix_filament_Phase2 Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants