Files
OrcaSlicer/src/libslic3r/MultiNozzleUtils.hpp
T
HanifKoh 8a6377f087 Add Missing Includes Across src/libslic3r (#16068)
* Add Missing Includes Across src/libslic3r

Every libslic3r source and header now directly includes the headers declaring what it uses, rather than relying on the precompiled header or transitive includes. Generated with clang-tidy misc-include-cleaner, with libslic3r headers spelled libslic3r/... so they resolve outside the library's private include paths. MultiMaterialSegmentation.hpp, Support/SupportParameters.hpp and Format/STEP.hpp are made self-contained by hand.

* Make the libslic3r Headers Compile on Their Own

Each now includes, or forward-declares, what it uses instead of relying on what its includers happened to include first. Left out: I18N.hpp, which errors on purpose when included from GUI code, and VoxelizeCSGMesh.hpp and SLA/bicubic.h, which nothing includes and which no longer compile at all.

* Add the Includes Missing From the Hand-Fixed libslic3r Headers

clang-tidy would not edit these headers while they failed to compile on their own, so the first pass skipped them. With the headers now self-contained, a second pass adds the rest.

* Keep Windows Setup Ahead of the Added libslic3r Includes

Print.cpp and Thread.cpp open with a _WIN32 block that has to come first; without the precompiled header, Print.cpp otherwise reaches windows.h through OCCT with NONLS defined and boost/regex fails. OpenVDBUtils.cpp and SLA/SupportTreeBuilder.cpp had includes inside #ifndef NOMINMAX, which libslic3r defines on Windows, so those were skipped there. .clang-tidy also ignores the MSVC STL and UCRT internals, Boost.Multiprecision's fwd.hpp and CPython's Windows include directory.

* Re-Add libslic3r Includes After the Clipper2 2.0.1 Migration

Rebasing onto main took main's version of the files the Clipper2 migration rewrote, so their added includes are restored here, along with includes for main's new code. Clipper2's individual headers are now ignored by clang-tidy: they only build the Z variant through clipper2_z.hpp, which defines USINGZ first, so including clipper.core.h and the like directly broke ClipperZUtils.cpp.
2026-10-03 15:31:11 +08:00

299 lines
15 KiB
C++

#ifndef MULTI_NOZZLE_UTILS_HPP
#define MULTI_NOZZLE_UTILS_HPP
#include <string>
#include <cstddef>
#include <vector>
#include <map>
#include <optional>
#include <set>
#include <unordered_map>
#include "PrintConfig.hpp"
// Multi-nozzle support types.
// Declares the filament-grouping result types the slicing pipeline needs, plus the analytic
// filament-change-time model (FilamentChangeTimeParams, NozzleStatusRecorder,
// FilamentChangeSimResult, simulate_filament_change_time) — self-contained analytic code that
// never touches the time estimator; its first consumer is the filament_group golden harness.
// The gcode.3mf serialization surface lives here too: NozzleInfo/NozzleGroupInfo
// serialize+deserialize, the device-side StaticNozzleGroupResult,
// load_nozzle_infos_with_compatibility (the backward-compat 3mf reader) and
// LayeredNozzleGroupResult::estimate_seq_flush_weight. The change-time-tuning helpers
// calc_filament_change_gap_for_assignment / find_optimal_physical_assignment (used only by the
// AMS pre-load optimizer, a later feature) are not implemented here.
namespace Slic3r {
struct FilamentInfo; // Slic3r::FilamentInfo (ProjectTask.hpp) — consumed by StaticNozzleGroupResult / the 3mf reader
namespace MultiNozzleUtils {
// Information about a single logical nozzle.
struct NozzleInfo
{
std::string diameter;
NozzleVolumeType volume_type;
int extruder_id{-1}; // logical extruder id
int group_id{-1}; // logical nozzle id
std::string serialize() const;
bool operator<(const NozzleInfo& other) const {
if(group_id != other.group_id) return group_id < other.group_id;
if(extruder_id != other.extruder_id) return extruder_id < other.extruder_id;
if(volume_type != other.volume_type) return volume_type < other.volume_type;
return diameter < other.diameter;
}
};
// A group of identical nozzles on one extruder (diameter + volume type + count).
struct NozzleGroupInfo
{
std::string diameter;
NozzleVolumeType volume_type;
int extruder_id;
int nozzle_count;
NozzleGroupInfo() = default;
NozzleGroupInfo(const std::string& nozzle_diameter_, const NozzleVolumeType volume_type_, const int extruder_id_, const int nozzle_count_)
: diameter(nozzle_diameter_), volume_type(volume_type_), extruder_id(extruder_id_), nozzle_count(nozzle_count_)
{}
inline bool operator<(const NozzleGroupInfo &rhs) const
{
if (extruder_id != rhs.extruder_id) return extruder_id < rhs.extruder_id;
if (diameter != rhs.diameter) return diameter < rhs.diameter;
if (volume_type != rhs.volume_type) return volume_type < rhs.volume_type;
return nozzle_count < rhs.nozzle_count;
}
bool is_same_type(const NozzleGroupInfo &rhs) const
{
return diameter == rhs.diameter && volume_type == rhs.volume_type && extruder_id == rhs.extruder_id;
}
inline bool operator==(const NozzleGroupInfo &rhs) const
{
return diameter == rhs.diameter && volume_type == rhs.volume_type && extruder_id == rhs.extruder_id && nozzle_count == rhs.nozzle_count;
}
std::string serialize() const;
static std::optional<NozzleGroupInfo> deserialize(const std::string& str);
};
// Load/unload time constants used by the filament-change-time model.
// Consumed by simulate_filament_change_time() below and carried by the grouping-context
// substrate (FilamentGroupContext::SpeedInfo).
struct FilamentChangeTimeParams
{
float selector_load_time{0.0f};
float selector_unload_time{0.0f};
float standard_load_time{0.0f};
float standard_unload_time{0.0f};
};
/**
* @brief Abstract base for a nozzle-grouping result.
*/
class NozzleGroupResultBase
{
protected:
bool support_dynamic_nozzle_map{false}; // whether dynamic (selector) mapping is used
public:
NozzleGroupResultBase(bool support_dynamic_map = false) : support_dynamic_nozzle_map(support_dynamic_map) {}
virtual ~NozzleGroupResultBase() = default;
virtual std::optional<NozzleInfo> get_nozzle_from_id(int nozzle_id) const = 0;
virtual std::optional<NozzleInfo> get_first_nozzle_for_filament(int filament_id) const = 0; // logical nozzle a filament first uses
virtual std::vector<NozzleInfo> get_nozzles_for_filament(int filament_id) const = 0; // every nozzle a filament may use (across all layers)
bool is_support_dynamic_nozzle_map() const { return support_dynamic_nozzle_map; }
virtual int get_extruder_count() const = 0;
virtual std::vector<NozzleInfo> get_used_nozzles_in_extruder(int extruder_id =-1) const = 0;
virtual std::vector<int> get_used_extruders() const = 0;
virtual std::vector<unsigned int> get_used_filaments() const = 0;
};
/**
* @brief Layer-aware nozzle-grouping result.
* Used by the back-end slicing code; supports per-layer nozzle mapping.
*/
class LayeredNozzleGroupResult : public NozzleGroupResultBase
{
private:
std::vector<std::vector<int>> _layer_filament_nozzle_maps; // per-layer filament -> nozzle map
std::vector<std::vector<unsigned int>> _layer_filament_sequences; // per-layer filament print order
std::vector<int> _default_filament_nozzle_map; // global filament -> nozzle map
std::vector<unsigned int> _used_filaments; // all used filament indices
std::vector<NozzleInfo> _nozzle_list; // global nozzle list
public:
LayeredNozzleGroupResult(bool support_dynamic_map = false) : NozzleGroupResultBase(support_dynamic_map) {}
// No selector: one global filament->nozzle map.
static std::optional<LayeredNozzleGroupResult> create(
const std::vector<int>& filament_nozzle_map,
const std::vector<NozzleInfo>& nozzle_list,
const std::vector<unsigned int>& used_filaments);
// Selector: built from per-layer maps (each layer may differ).
static std::optional<LayeredNozzleGroupResult> create(
const std::vector<std::vector<int>>& layer_filament_nozzle_maps,
const std::vector<NozzleInfo>& nozzle_list,
const std::vector<unsigned int>& used_filaments,
const std::vector<std::vector<unsigned int>>& layer_filament_sequences);
// Multi-nozzle without selector: resolve each requested logical nozzle to a physical nozzle.
static std::optional<LayeredNozzleGroupResult> create(
const std::vector<unsigned int>& used_filaments,
const std::vector<int>& filament_map,
const std::vector<int>& filament_volume_map,
const std::vector<int>& filament_nozzle_map,
const std::vector<std::map<NozzleVolumeType, int>>& nozzle_count,
float diameter);
bool are_filaments_same_extruder(int filament_id1, int filament_id2, int layer_id = -1) const;
bool are_filaments_same_nozzle(int filament_id1, int filament_id2, int layer_id = -1) const;
int get_extruder_count() const override;
std::vector<NozzleInfo> get_used_nozzles_in_extruder(int target_extruder_id = -1) const override;
std::vector<NozzleInfo> get_used_nozzles_in_extruder(int target_extruder_id, int layer_id) const; // layer_id=-1 uses default map
std::vector<int> get_used_extruders() const override;
std::vector<int> get_used_extruders(int layer_id) const; // layer_id=-1 returns global extruders
std::vector<int> get_extruder_map(bool zero_based = true, int layer_id = -1) const;
std::vector<int> get_nozzle_map(int layer_id = -1) const;
std::vector<int> get_volume_map(int layer_id = -1) const;
std::vector<unsigned int> get_used_filaments() const override { return _used_filaments; }
std::vector<unsigned int> get_used_filaments(int layer_id) const;
std::optional<NozzleInfo> get_nozzle_for_filament(int filament_id, int layer_id = -1) const;
std::vector<NozzleInfo> get_nozzles_for_filament(int filament_id) const override;
std::optional<NozzleInfo> get_nozzle_from_id(int nozzle_id) const override;
std::optional<NozzleInfo> get_first_nozzle_for_filament(int filament_id) const override;
int get_extruder_id(int filament_id, int layer_id = -1) const;
int get_nozzle_id(int filament_id, int layer_id = -1) const;
size_t get_layer_count() const { return _layer_filament_nozzle_maps.size(); }
const std::vector<int>& get_layer_filament_nozzle_map(int layer_id) const;
const std::vector<std::vector<int>> &get_layer_filament_nozzle_maps() const { return _layer_filament_nozzle_maps; }
const std::vector<std::vector<unsigned int>>& get_layer_filament_sequences() const { return _layer_filament_sequences; }
// Estimate the flush weight of a filament-change sequence given the per-extruder flush matrix
// (extruder -> from-filament -> to-filament).
int estimate_seq_flush_weight(const std::vector<std::vector<std::vector<float>>>& flush_matrix, const std::vector<int>& filament_change_seq) const;
};
/**
* @brief Layer-less nozzle-grouping result for the device side (static nozzle mapping only).
* Reconstructed from a loaded gcode.3mf together with the filament/nozzle change sequences.
*/
class StaticNozzleGroupResult : public NozzleGroupResultBase
{
private:
std::map<int, std::set<int>> _filament_to_nozzles; // every nozzle a filament may map to
std::map<int, NozzleInfo> _nozzle_list_map; // used nozzles, keyed by logical nozzle id
std::vector<int> _filament_change_seq; // filament sequence used to resolve first-use
std::vector<int> _nozzle_change_seq; // logical-nozzle sequence paired with the filament sequence
public:
StaticNozzleGroupResult(bool support_dynamic_map) : NozzleGroupResultBase(support_dynamic_map) {}
// Build from a loaded 3mf, with the filament/nozzle change sequences.
static std::optional<StaticNozzleGroupResult> create(
const std::vector<FilamentInfo>& filaments_info,
const std::vector<NozzleInfo>& nozzles_info,
const std::vector<int>& filament_change_seq,
const std::vector<int>& nozzle_change_seq,
bool support_dynamic_map);
int get_extruder_count() const override;
std::vector<NozzleInfo> get_used_nozzles_in_extruder(int extruder_id = -1) const override;
std::vector<int> get_used_extruders() const override;
std::vector<unsigned int> get_used_filaments() const override;
std::optional<NozzleInfo> get_nozzle_from_id(int nozzle_id) const override;
std::vector<NozzleInfo> get_nozzles_for_filament(int filament_id) const override;
std::optional<NozzleInfo> get_first_nozzle_for_filament(int filament_id) const override;
};
// Tracks, during the filament-change simulation, which filament sits in each physical nozzle
// and which nozzle each extruder currently carries.
class NozzleStatusRecorder
{
private:
std::unordered_map<int, int> nozzle_filament_status; // Track filament in each nozzle
std::unordered_map<int, int> extruder_nozzle_status; // Track nozzle for each extruder
int current_extruder_id_ = -1; // Track current extruder id
public:
NozzleStatusRecorder() = default;
bool is_nozzle_empty(int nozzle_id) const;
int get_filament_in_nozzle(int nozzle_id) const;
int get_nozzle_in_extruder(int extruder_id) const;
int get_current_extruder_id() const { return current_extruder_id_; }
void clear_nozzle_status(int nozzle_id);
void set_current_extruder_id(int extruder_id) { current_extruder_id_ = extruder_id; }
// Update the status of a nozzle with new filament and extruder information
void set_nozzle_status(int nozzle_id, int filament_id, int extruder_id = -1);
// key: nozzle id, value: filament id (-1 = the nozzle carries no filament)
const std::unordered_map<int, int>& get_nozzle_filament_map() const { return nozzle_filament_status; }
// key: extruder id, value: nozzle id (-1 = the extruder carries no nozzle)
const std::unordered_map<int, int>& get_extruder_nozzle_map() const { return extruder_nozzle_status; }
};
struct FilamentChangeSimResult {
double actual_time = 0.0;
double sliced_time = 0.0;
};
// Analytic filament-change-time model. Given the used filaments, the nozzle
// list, the filament/nozzle change sequences, each filament's AMS group and the load/unload time
// constants, it simulates AMS->selector->extruder transport (with optional AMS pre-load overlap)
// and returns the actual print time plus the slicer-estimated time. Self-contained: it never
// touches the g-code time estimator.
FilamentChangeSimResult simulate_filament_change_time(
const std::vector<int>& logical_filaments,
const std::vector<NozzleInfo>& nozzle_list,
const std::vector<int>& filament_change_seq,
const std::vector<int>& nozzle_change_seq,
const std::vector<int>& group_of_filament,
const FilamentChangeTimeParams& time_params,
const std::vector<bool>& ams_preload_enabled = {},
bool calc_sliced_time = false);
// ==================== tool functions ====================
// Make each filament's per-layer nozzle assignment gap-free: layers where a filament is not
// extruded inherit the nozzle it last used (forward carry); layers before its first use inherit
// the first nozzle it ever uses (back-fill). Entries on layers where the filament is actually
// used stay untouched. Needed for stitched sequential maps, where consumers indexing with an
// object-local layer id must resolve the same nozzle as global-id consumers except across a
// genuine mid-print reassignment.
void normalize_nozzle_map_per_layer(std::vector<std::vector<int>>& layer_filament_nozzle_maps,
const std::vector<std::vector<unsigned int>>& layer_filaments);
std::vector<NozzleInfo> build_nozzle_list(std::vector<NozzleGroupInfo> info);
std::vector<NozzleInfo> build_nozzle_list(double diameter, const std::vector<int>& filament_nozzle_map,
const std::vector<int>& filament_volume_map, const std::vector<int>& filament_map);
// Load nozzle infos from a gcode.3mf, handling backward compatibility with older 3mf that did not
// record standalone <nozzle> tags: falls back to the per-filament group_id/diameter/volume_type, and
// (for the oldest single-nozzle 3mf) to the filament_map + extruder volume types + nozzle diameters.
std::vector<NozzleInfo> load_nozzle_infos_with_compatibility(
const std::vector<NozzleInfo>& nozzle_infos,
const std::vector<FilamentInfo>& filament_infos,
const std::vector<int>& filament_map,
const std::vector<NozzleVolumeType>& extruder_volume_types,
const std::vector<double>& nozzle_diameter
);
} // namespace MultiNozzleUtils
} // namespace Slic3r
#endif // MULTI_NOZZLE_UTILS_HPP