Files
OrcaSlicer/tests/libslic3r/test_review_fixes.cpp
SoftFever 9c8caf121e init work to integrate OrcaSlicer-FullSpectrum fork
Integrations up to commit b3c41fda4180a2946812726218d0a849be0aedb6.

  - libslic3r: vendor FilamentMixer; MixedFilamentManager (auto-gen, resolve,
    serialize; manual-pattern / gradient / pointillism); 19 new PrintConfig
    keys; PresetBundle owns the canonical manager with 3MF + AppConfig
    roundtrip and AMS-safe strip+restore; Print owns the slicing-time copy
    with PrintApply auto-regen on color change; TriangleSelector::
    shift_states_above + filament-id remap; inset_idx propagation through
    ExtrusionPath/Loop/MultiPath copy/assign.
  - Slicing: virtual filament IDs in painted regions (same-physical channels
    collapse when mixed_filament_region_collapse is on); ByObject
    collect_filament_data expands mixed slots; pair-cadence + whole-object +
    3+component Local-Z plan generators; LocalZOrderOptimizer utility.
  - GCode + ToolOrdering: LayerTools resolves virtual IDs through wall /
    infill / sparse / solid queries; SameLayerPointillisme in process_layer
    (uniform-segment + grouped per-perimeter-index splitters); WipeTower2
    Local-Z reservation + sub-layer G-code emission; per-layer infill
    filament override.
  - PartPlate: get_extruders* expand virtual slots into physical components;
    CLI path rebuilds a local manager from full_config.
  - GUI: five widget files extracted from FS Plater.cpp (~5000 LOC) —
    MixedMixPreview, MixedGradientSelector + WeightsDialog,
    MixedFilamentColorMapPanel, MixedFilamentColorMatchDialog (ΔE₀₀ recipe
    search), MixedFilamentConfigPanel; Sidebar Mixed Filaments panel
    (drag-reorder, enable/delete, Add Gradient/Pattern/Color); Tab exposure
    of mixed-filament / dithering / per-layer infill-override settings +
    ConfigManipulation visibility and slot-validation rules;
    BBLMixedFilamentBroken / BBLSingleExtruderMixedFilamentRisk
    notifications + slice gate; WipeTowerDialog edits physical P×P
    sub-matrix; bounds-safe extruder_id guards in 3DScene / GLCanvas3D /
    GLGizmoMmuSegmentation; change_filament merge guard and
    on_filaments_delete is_mixed_before_delete propagation.
  - Tests: 4 Catch2 tests for 3MF roundtrip (auto/custom persistence,
    PresetBundle string path, total_filaments stability); full-pipeline
    slice E2E deferred — TODO in file.

  Co-authored-by: Rad <radugheorghiu96@gmail.com>
  Co-authored-by: Justin Hayes <justinh@rahb.ca>
  Co-authored-by: Calogero Guagenti <calogeroguagenti@gmail.com>
  Co-authored-by: xSil3nt <ahmedshazin21@gmail.com>
  Co-authored-by: ratdoux <62392831+ratdoux@users.noreply.github.com>

Co-authored-by: Rad <radugheorghiu96@gmail.com>
Co-authored-by: Justin Hayes <justinh@rahb.ca>
Co-authored-by: Calogero Guagenti <calogeroguagenti@gmail.com>
Co-authored-by: xSil3nt <ahmedshazin21@gmail.com>
Co-authored-by: ratdoux <62392831+ratdoux@users.noreply.github.com>
2026-05-02 12:32:38 +08:00

133 lines
5.4 KiB
C++

#include <catch2/catch_all.hpp>
#include "libslic3r/MixedFilament.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Slicing.hpp"
#include <string>
#include <vector>
using namespace Slic3r;
// Finding 1 — the helper computing max supported filament ID after a 3MF
// project-config load must include enabled mixed rows.
TEST_CASE("[review-fixes] mixed-aware max filament id", "[review-fixes]")
{
MixedFilamentManager mgr;
const size_t physical_count = 3;
const std::vector<std::string> colors = {"#FF0000", "#00FF00", "#0000FF"};
// No mixed rows: max == physical.
REQUIRE(mgr.total_filaments(physical_count) == physical_count);
// Add an enabled mixed row spanning physical 1 + 2 (custom + enabled by default).
mgr.add_custom_filament(1u, 2u, 50, colors);
REQUIRE(mgr.mixed_filaments().size() == 1);
REQUIRE(mgr.total_filaments(physical_count) == physical_count + 1);
// A disabled row does not contribute.
mgr.add_custom_filament(1u, 2u, 25, colors);
REQUIRE(mgr.mixed_filaments().size() == 2);
mgr.mixed_filaments().back().enabled = false;
REQUIRE(mgr.total_filaments(physical_count) == physical_count + 1);
// A deleted row also does not contribute (enabled_count() filters deleted).
mgr.add_custom_filament(2u, 3u, 50, colors);
REQUIRE(mgr.mixed_filaments().size() == 3);
mgr.mixed_filaments().back().deleted = true;
mgr.mixed_filaments().back().enabled = false;
REQUIRE(mgr.total_filaments(physical_count) == physical_count + 1);
}
// Finding 2 — passing nullptr for the print object must keep the legacy
// behaviour: no mixed gradient or dithering ranges are applied, and the
// profile is still seeded from the slicing parameters.
TEST_CASE("[review-fixes] update_layer_height_profile passthrough without print object",
"[review-fixes]")
{
Model model;
ModelObject *mo = model.add_object();
REQUIRE(mo != nullptr);
SlicingParameters sp;
sp.layer_height = 0.2;
sp.first_object_layer_height = 0.2;
sp.object_print_z_min = 0.0;
sp.object_print_z_uncompensated_max = 10.0;
std::vector<coordf_t> profile;
const bool updated = PrintObject::update_layer_height_profile(*mo, sp, profile, nullptr);
REQUIRE(updated);
REQUIRE(!profile.empty());
// Strong check: the nullptr branch must be a passthrough to
// layer_height_profile_from_ranges with the model_object's own
// layer_config_ranges — i.e. no mixed-gradient or dithering overrides
// were applied. Computing the expected profile via the same call the
// function performs internally lets a regression that silently sneaks
// those overrides into the nullptr path fail this test.
const std::vector<coordf_t> expected =
Slic3r::layer_height_profile_from_ranges(sp, mo->layer_config_ranges);
REQUIRE(profile == expected);
}
// Finding 4 — ToolOrdering used to push raw 1-based virtual mixed-filament IDs
// directly onto layer_tools.extruders, which then got decremented as if they
// were physical extruder IDs. The fix routes those IDs through the
// MixedFilamentManager so that virtual IDs collapse to a real physical extruder
// while physical IDs pass through unchanged.
TEST_CASE("[review-fixes] resolve_mixed_1based passthrough on physical id",
"[review-fixes]")
{
MixedFilamentManager mgr;
const std::vector<std::string> colors = {"#FF0000", "#00FF00"};
// Add a custom mixed row spanning physical 1 + 2.
mgr.add_custom_filament(1u, 2u, 50, colors);
REQUIRE(mgr.mixed_filaments().size() == 1);
const size_t num_physical = colors.size();
// Physical id (1) must passthrough.
REQUIRE(mgr.resolve(1u, num_physical, /*layer_index=*/0, /*print_z=*/0.f, /*layer_height=*/0.2f) == 1u);
REQUIRE(mgr.resolve(2u, num_physical, /*layer_index=*/0, /*print_z=*/0.f, /*layer_height=*/0.2f) == 2u);
// Virtual id (3 = 2 physical + 1 mixed) must resolve to 1 or 2 depending on cadence.
const unsigned int resolved = mgr.resolve(3u, num_physical, 0, 0.f, 0.2f);
REQUIRE((resolved == 1u || resolved == 2u));
}
TEST_CASE("[review-fixes] mixed/dithering option keys exist in PrintConfig",
"[review-fixes]")
{
// Sanity guard for finding 6: any key the porter wires into invalidation
// must resolve in the config. Build a default-populated DynamicPrintConfig
// that contains every print_config_def key, then probe the option lookup.
static const std::vector<std::string> keys = {
"mixed_filament_definitions",
"mixed_filament_gradient_mode",
"mixed_filament_height_lower_bound",
"mixed_filament_height_upper_bound",
"mixed_filament_advanced_dithering",
"mixed_filament_component_bias_enabled",
"mixed_filament_surface_indentation",
"mixed_filament_region_collapse",
"dithering_z_step_size",
"dithering_local_z_mode",
"dithering_local_z_whole_objects",
"dithering_local_z_direct_multicolor",
"dithering_step_painted_zones_only",
};
std::unique_ptr<DynamicPrintConfig> cfg(DynamicPrintConfig::new_from_defaults_keys(keys));
REQUIRE(cfg != nullptr);
for (const std::string &k : keys) {
INFO("missing config key: " << k);
REQUIRE(cfg->option(k) != nullptr);
// And the canonical print_config_def must know about it.
REQUIRE(print_config_def.get(k) != nullptr);
}
}