mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-20 06:01:16 +00:00
The physical_extruder_map translation used by IMEX per-tool PA emission was
inlined identically at two sites in GCode.cpp (tool-change and second-layer
transition). Extract to IMEXHelpers so the routing rule ("parallel mode AND
populated pem → physical index, else -1") is testable in isolation and the
call sites read as intent rather than re-deriving the conditional.
Production change is behavior-preserving:
- Same predicate (`!mode.empty() && mode != "primary"`)
- Same empty-pem short-circuit returning -1
- Same get_at() dispatch on hit
- Both call sites replaced with a single call
Four unit tests in [IMEX] cover the routing matrix:
- non-IMEX ("") and primary mode short-circuit
- parallel mode + empty pem short-circuits (defense-in-depth; get_at would
throw on empty values otherwise)
- identity pem (non-MMU IDEX) routes filament to itself
- MMU collapse routes multiple logical slots to one physical (7-slot profile
with 4-lane MMU on physical 0 and direct drives on 1/2/3)
All IMEX + Variant regression suites pass post-refactor (133 assertions / 48
cases under libslic3r, 25 assertions / 6 cases under fff_print [PressureAdvance]).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
121 lines
6.7 KiB
C++
121 lines
6.7 KiB
C++
#pragma once
|
|
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <Eigen/Geometry>
|
|
#include "libslic3r/PrintConfig.hpp"
|
|
#include "libslic3r/Point.hpp"
|
|
|
|
namespace Slic3r {
|
|
|
|
class PresetBundle;
|
|
|
|
// Returns the effective physical_extruder_map given an optionally-explicit map and
|
|
// the printer's `printer_extruder_id`. If `explicit_pem` has size >= 2 the caller
|
|
// authored one, and it is returned verbatim. Otherwise the map is auto-derived
|
|
// from `printer_extruder_id` by converting each 1-indexed value to 0-indexed.
|
|
// Returns an empty ConfigOptionInts if neither source yields any values.
|
|
// Slice-time (PrintApply) and GUI ghost-color paths both call this so a printer
|
|
// profile without an explicit pem still gets a consistent mapping.
|
|
ConfigOptionInts effective_physical_extruder_map(const ConfigOptionInts* explicit_pem,
|
|
const ConfigOptionInts* printer_extruder_id);
|
|
|
|
// GUI overload: resolves the effective pem from a live PresetBundle using the
|
|
// project_config → printer preset fallback, then derives from printer_extruder_id
|
|
// if neither holds a user-authored map (size >= 2). Use this instead of open-coding
|
|
// the lookup at ghost-color, tooltip, click-gate, and cache-key call sites so they
|
|
// all agree on what the slicer will see.
|
|
ConfigOptionInts effective_physical_extruder_map(const PresetBundle& pb);
|
|
|
|
// Returns the physical extruder index to emit as a per-tool qualifier (PA / temperature)
|
|
// for the given filament slot, or -1 when the current IMEX state does not warrant a
|
|
// per-tool qualification: non-IMEX / primary mode, or the pem is empty / unpopulated.
|
|
// Used at tool-change and second-layer transition call sites where IMEX parallel modes
|
|
// route emission through the physical extruder, while ordinary tool changes emit bare
|
|
// firmware commands like any non-IMEX printer.
|
|
int imex_pem_tool_for(int filament_id, const std::string& parallel_mode, const ConfigOptionInts& pem);
|
|
|
|
// Returns the lowest 0-based logical filament index L such that pem[L] == physical.
|
|
// Returns -1 if no filament routes to `physical`.
|
|
// Degenerate case: an empty pem returns 0 when `physical == 0` (identity-on-head-0
|
|
// fallback for printers that never configured a pem) and -1 otherwise.
|
|
// With identity pem, the caller sees logical == physical behavior because
|
|
// position L holds value L.
|
|
int first_filament_for_physical_head(const ConfigOptionInts& pem, int physical);
|
|
|
|
// Returns true if the printer has at least one physical head fed by >= 2 logical
|
|
// filaments (i.e., an MMU/AFC is present on some head). Drives the plater's
|
|
// click-handler branch: true -> open popover; false -> retain cycle-through.
|
|
bool has_mmu(const ConfigOptionInts& pem);
|
|
|
|
// Returns true if any physical head OTHER than `primary_physical` is fed by
|
|
// >= 2 logical filaments. Primary is the head whose filament is already
|
|
// controlled by the left sidebar's object->filament assignment, so an MMU
|
|
// there needs no popover row; only non-primary MMU heads do.
|
|
// Returns false when pem is empty or size < 2.
|
|
bool has_non_primary_mmu(const ConfigOptionInts& pem, int primary_physical);
|
|
|
|
// Parses one mode's entry from `imex_mode_active_tools` and returns the
|
|
// 0-based physical T-index carrying the `:P` (Primary) role marker.
|
|
// Accepts two forms:
|
|
// "0:P,1:C,2:C" (explicit role suffix)
|
|
// "0" (backwards-compat: a bare index == Primary)
|
|
// Returns -1 on empty/malformed input or if no primary is found.
|
|
// The caller is responsible for indexing into imex_mode_active_tools by mode.
|
|
int imex_primary_tool_for_mode(const std::string& active_tools_for_mode);
|
|
|
|
// Parses the "phys:slot,phys:slot" serialization of imex_head_filament_map.
|
|
// Keys are physical T-indices (0-based); values are 1-based filament slots.
|
|
// Returns empty map on empty/malformed input.
|
|
std::map<int,int> parse_imex_head_filament_map(const std::string& s);
|
|
|
|
// Resolves which 0-based logical filament to use for a physical head.
|
|
// 1. If the plate map overrides `physical`, returns (slot - 1) — 1-based → 0-based.
|
|
// 2. Else falls back to first_filament_for_physical_head(pem, physical).
|
|
// Returns -1 if neither source yields a valid filament.
|
|
int resolve_filament_for_head(const std::map<int,int>& plate_map,
|
|
const ConfigOptionInts& pem,
|
|
int physical);
|
|
|
|
enum class ImexRole { Primary, Copy, Mirror };
|
|
|
|
// Parses `imex_mode_active_tools[mode]` into a list of (physical_head, role) pairs.
|
|
// Accepted token forms (comma-separated, whitespace-tolerant):
|
|
// "phys" — bare index, role defaults to Copy
|
|
// "phys:P" — Primary
|
|
// "phys:C" — Copy
|
|
// "phys:M" — Mirror
|
|
// "phys:???" — unknown role suffix, treated as Copy
|
|
// Malformed tokens (unparseable int, negative phys) are skipped.
|
|
// NOTE: the bare-token → Copy default differs from `imex_primary_tool_for_mode`,
|
|
// which separately scans for a Primary; callers needing the primary index should
|
|
// use that helper. This parser is for call sites that already have the primary
|
|
// in hand and need the full head/role list (e.g. ghost factory/updater).
|
|
std::vector<std::pair<int, ImexRole>> parse_imex_active_tools(const std::string& active_tools_for_mode);
|
|
|
|
// World-space transform composed as `head_xf * primary_instance_world` to place a ghost
|
|
// copy of the primary into `target`'s frame under `role`.
|
|
// `gantry_offset` = center_for(target) - center_for(primary) (XY, in mm).
|
|
// `primary_zone_center` = XY center of the primary head's zone in world coords. Only
|
|
// consulted for Mirror; Copy/Primary ignore it.
|
|
// Copy: pure translation by gantry_offset. Ghost tracks primary 1:1 during drag.
|
|
// Mirror: true reflection about the zone-boundary plane between primary and target.
|
|
// The plane is perpendicular to the primary-row gantry axis (X for all current
|
|
// IMEX printers) and passes through `primary_zone_center.x + gantry_offset.x / 2`.
|
|
// Ghost origin lands at the mirrored position within the target zone (matches
|
|
// where the mirror tool will actually print), and primary drag reflects across
|
|
// that plane so the ghost's X moves opposite the primary's X while Y tracks 1:1
|
|
// — i.e. the ghost stays a true mirror as you drag. Geometry is X-flipped
|
|
// regardless of gantry_offset direction so off-row Mirror targets (e.g. T3 on
|
|
// a 2x2) reflect across the same plane as on-row peers.
|
|
// Zero-length gantry_offset degenerates to identity.
|
|
// Primary: identity.
|
|
Transform3d imex_head_transform(int primary, int target, ImexRole role,
|
|
const Vec2d& gantry_offset,
|
|
const Vec2d& primary_zone_center = Vec2d::Zero());
|
|
|
|
} // namespace Slic3r
|