Extract shared FilamentMatcher with multi-tier preset resolution #446

Open
opened 2026-04-05 16:21:04 +02:00 by MrUnknownDE · 0 comments
Owner

Originally created by @AMoo-Miki on 3/6/2026

Description

This PR extracts filament-to-preset matching into a shared FilamentMatcher utility so all printer agents use the same resolution logic, and introduces a multi-tier matching scheme that lets printers with rich filament metadata (vendor, name, color) get progressively more precise preset matches.

Today, when a printer reports what filament is loaded in each slot, OrcaSlicer needs to map that to an installed filament preset. Different agents do this differently -- QIDI has a numeric ID scheme, Moonraker/Snapmaker just match by type string. All of these are hardcoded inline with duplicated fallback logic.

This PR consolidates everything into a single FilamentMatcher::resolve() call that accepts a FilamentMatchInput struct. Agents fill in whatever fields their printer provides, and the resolver tries progressively less specific tiers, skipping any tier where data is missing.

Tier cascade

Tier Format Example
1 {prefix}_{FilamentVendor}_{FilamentName}_{ColorHex} QD_3_Acme_Inc_PLA_Plus_FAFAFA
2 {prefix}_{FilamentVendor}_{FilamentName} QD_3_Acme_Inc_PLA_Plus
3 {prefix}_{vendor_num}_{filament_idx} QD_3_7_52
4 filament_id_by_type(tray_type) searches presets for "PLA"
5 map_type_to_generic_id(tray_type) "PLA""OGFL99"

The prefix encodes the agent and printer model (e.g. QD_3 for QIDI X-Max 4, SM_J1 for Snapmaker J1). This lets filament profiles target a specific printer family.

Filament vendor names and filament names from the printer's filament config are sanitized for use in IDs: vendor "Acme Inc" becomes "Acme_Inc", filament "PLA Plus" becomes "PLA_Plus". Matching is case-insensitive.

Why this matters

Right now, if a user loads a third-party filament (say eSUN PLA+ in white) into their QIDI Box, OrcaSlicer can only match it to a generic "PLA" preset. It can't distinguish eSUN PLA+ from QIDI PLA from Polymaker PLA Matte -- they all collapse to the same preset.

Printers like the QIDI already report the filament vendor, filament name, and color -- OrcaSlicer just doesn't use it yet. This PR changes that by attempting a precise match first. If no preset exists for QD_3_eSUN_PLA_Plus_FAFAFA, it falls back to QD_3_eSUN_PLA_Plus, then to the numeric QD_3_2_51, and finally to the generic PLA type match.

A warning is logged when the printer provided enough data for precise matching but no matching preset was found, telling the user exactly what filament_id to use in a custom profile:

[warning] FilamentMatcher: no precise preset found for "PLA_Plus" (type PLA).
  Falling back to generic type match.  To get an exact match, create a filament
  profile with filament_id set to one of: "QD_3_Acme_Inc_PLA_Plus_FAFAFA",
  "QD_3_Acme_Inc_PLA_Plus", "QD_3_7_52"

This warning only fires when the printer reported rich metadata. Agents that only provide a material type (Moonraker, Snapmaker today) never trigger it because they can't do any better.

What changes for each agent

Agent Before After Behavior change
QIDI Inline 4-tier matching with has_visible_base_preset and sanitize_for_id in anonymous namespace Calls FilamentMatcher::resolve() with full input Identical matching, now with logging and fallback warning
Moonraker (AFC, Happy Hare) bundle ? filament_id_by_type : map_filament_type_to_generic_id duplicated in 2 places Calls FilamentMatcher::resolve() with only tray_type Identical behavior (skips tiers 1-3, hits tier 4 or 5). Slightly improved: if tier 4 returns UNKNOWN, tier 5 is now tried as fallback
Snapmaker Same as Moonraker Same as Moonraker Same as Moonraker

What this enables going forward

  • Profile authors can create filament profiles with filament_id like QD_3_eSUN_PLA_Plus and it will match when that exact vendor + filament is loaded
  • Printer manufacturers are incentivized to report richer filament metadata since OrcaSlicer now uses it
  • Future agents just fill in a FilamentMatchInput struct and call resolve()
  • New tiers can be added in one place without touching individual agents
  • If Snapmaker or Happy Hare start reporting vendor/color data, they populate more fields and get richer matching with no code changes

Graceful degradation

  • All existing filament profiles (e.g. QD_3_1_1) continue to match via Tier 3
  • Agents that don't populate rich metadata get identical behavior to before -- tiers 1-3 are skipped automatically when the data isn't available

Discussion points

I don't expect this to be merged as-is -- I'd like to start a conversation about standardizing filament preset resolution across agents. Some open questions:

  1. Should the warning be surfaced in the UI? Currently it's log-only. A toast notification or a badge on the filament slot would make it more discoverable.
  2. Should there be a standard prefix convention? This PR uses QD_3, but should OrcaSlicer define a registry of known prefixes?
  3. Should agents provide multiple lookup candidates, or should FilamentMatcher own the ID format? Currently FilamentMatcher::resolve() builds all the candidate IDs internally from the raw fields. An alternative is to have each agent construct its own list of candidate IDs and pass them in, giving agents more control over the format. Which approach leads to better standardization?

Screenshots/Recordings/Graphs

Before

https://github.com/user-attachments/assets/de476412-b49c-46c4-9673-608fb565ccf9

After

https://github.com/user-attachments/assets/e567e0d9-02a0-49a3-8d2e-2c585381e91f

Tests

Been using it for a few days on a Qidi Plus 4 with a QidiBox and it seems to be holding up well.

*Originally created by @AMoo-Miki on 3/6/2026* # Description This PR extracts filament-to-preset matching into a shared `FilamentMatcher` utility so all printer agents use the same resolution logic, and introduces a multi-tier matching scheme that lets printers with rich filament metadata (vendor, name, color) get progressively more precise preset matches. Today, when a printer reports what filament is loaded in each slot, OrcaSlicer needs to map that to an installed filament preset. Different agents do this differently -- QIDI has a numeric ID scheme, Moonraker/Snapmaker just match by type string. All of these are hardcoded inline with duplicated fallback logic. This PR consolidates everything into a single `FilamentMatcher::resolve()` call that accepts a `FilamentMatchInput` struct. Agents fill in whatever fields their printer provides, and the resolver tries progressively less specific tiers, skipping any tier where data is missing. ## Tier cascade | Tier | Format | Example | |------|--------|---------| | 1 | `{prefix}_{FilamentVendor}_{FilamentName}_{ColorHex}` | `QD_3_Acme_Inc_PLA_Plus_FAFAFA` | | 2 | `{prefix}_{FilamentVendor}_{FilamentName}` | `QD_3_Acme_Inc_PLA_Plus` | | 3 | `{prefix}_{vendor_num}_{filament_idx}` | `QD_3_7_52` | | 4 | `filament_id_by_type(tray_type)` | searches presets for `"PLA"` | | 5 | `map_type_to_generic_id(tray_type)` | `"PLA"` → `"OGFL99"` | The **prefix** encodes the agent and printer model (e.g. `QD_3` for QIDI X-Max 4, `SM_J1` for Snapmaker J1). This lets filament profiles target a specific printer family. Filament vendor names and filament names from the printer's filament config are sanitized for use in IDs: vendor `"Acme Inc"` becomes `"Acme_Inc"`, filament `"PLA Plus"` becomes `"PLA_Plus"`. Matching is case-insensitive. ## Why this matters Right now, if a user loads a third-party filament (say eSUN PLA+ in white) into their QIDI Box, OrcaSlicer can only match it to a generic "PLA" preset. It can't distinguish eSUN PLA+ from QIDI PLA from Polymaker PLA Matte -- they all collapse to the same preset. Printers like the QIDI already report the filament vendor, filament name, and color -- OrcaSlicer just doesn't use it yet. This PR changes that by attempting a precise match first. If no preset exists for `QD_3_eSUN_PLA_Plus_FAFAFA`, it falls back to `QD_3_eSUN_PLA_Plus`, then to the numeric `QD_3_2_51`, and finally to the generic PLA type match. A **warning is logged** when the printer provided enough data for precise matching but no matching preset was found, telling the user exactly what `filament_id` to use in a custom profile: ``` [warning] FilamentMatcher: no precise preset found for "PLA_Plus" (type PLA). Falling back to generic type match. To get an exact match, create a filament profile with filament_id set to one of: "QD_3_Acme_Inc_PLA_Plus_FAFAFA", "QD_3_Acme_Inc_PLA_Plus", "QD_3_7_52" ``` This warning **only** fires when the printer reported rich metadata. Agents that only provide a material type (Moonraker, Snapmaker today) never trigger it because they can't do any better. ## What changes for each agent | Agent | Before | After | Behavior change | |-------|--------|-------|-----------------| | **QIDI** | Inline 4-tier matching with `has_visible_base_preset` and `sanitize_for_id` in anonymous namespace | Calls `FilamentMatcher::resolve()` with full input | Identical matching, now with logging and fallback warning | | **Moonraker** (AFC, Happy Hare) | `bundle ? filament_id_by_type : map_filament_type_to_generic_id` duplicated in 2 places | Calls `FilamentMatcher::resolve()` with only `tray_type` | Identical behavior (skips tiers 1-3, hits tier 4 or 5). Slightly improved: if tier 4 returns UNKNOWN, tier 5 is now tried as fallback | | **Snapmaker** | Same as Moonraker | Same as Moonraker | Same as Moonraker | ## What this enables going forward - **Profile authors** can create filament profiles with `filament_id` like `QD_3_eSUN_PLA_Plus` and it will match when that exact vendor + filament is loaded - **Printer manufacturers** are incentivized to report richer filament metadata since OrcaSlicer now uses it - **Future agents** just fill in a `FilamentMatchInput` struct and call `resolve()` - **New tiers** can be added in one place without touching individual agents - If Snapmaker or Happy Hare start reporting vendor/color data, they populate more fields and get richer matching with no code changes ## Graceful degradation - All existing filament profiles (e.g. `QD_3_1_1`) continue to match via Tier 3 - Agents that don't populate rich metadata get identical behavior to before -- tiers 1-3 are skipped automatically when the data isn't available ## Discussion points I don't expect this to be merged as-is -- I'd like to start a conversation about standardizing filament preset resolution across agents. Some open questions: 1. **Should the warning be surfaced in the UI?** Currently it's log-only. A toast notification or a badge on the filament slot would make it more discoverable. 2. **Should there be a standard prefix convention?** This PR uses `QD_3`, but should OrcaSlicer define a registry of known prefixes? 3. **Should agents provide multiple lookup candidates, or should `FilamentMatcher` own the ID format?** Currently `FilamentMatcher::resolve()` builds all the candidate IDs internally from the raw fields. An alternative is to have each agent construct its own list of candidate IDs and pass them in, giving agents more control over the format. Which approach leads to better standardization? # Screenshots/Recordings/Graphs ## Before https://github.com/user-attachments/assets/de476412-b49c-46c4-9673-608fb565ccf9 ## After https://github.com/user-attachments/assets/e567e0d9-02a0-49a3-8d2e-2c585381e91f ## Tests Been using it for a few days on a Qidi Plus 4 with a QidiBox and it seems to be holding up well.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/OrcaSlicer#446