add filament_extruder_map (#148)

This commit is contained in:
xiaoyeliu
2026-02-04 09:28:34 +08:00
committed by GitHub
parent 705909d54e
commit 9d423d0714
19 changed files with 6062 additions and 3615 deletions

View File

@@ -344,6 +344,9 @@ public:
// Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions.
// This function is useful to split values from multiple extrder / filament settings into separate configurations.
virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0;
// SM Orca: Copy a single element from source vector at src_idx to this vector at dst_idx
// This function is useful for applying physical extruder mapping to filament parameters
virtual void set_at(const ConfigOptionVectorBase* source, size_t dst_idx, size_t src_idx) = 0;
// Resize the vector of values, copy the newly added values from opt_default if provided.
virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0;
// Clear the values vector.
@@ -431,6 +434,26 @@ public:
throw ConfigurationError("ConfigOptionVector::set_at(): Assigning an incompatible type");
}
// SM Orca: Copy a single element from source vector at src_idx to this vector at dst_idx
// Used for applying physical extruder mapping to filament parameters
void set_at(const ConfigOptionVectorBase* source, size_t dst_idx, size_t src_idx) override
{
auto* src_typed = dynamic_cast<const ConfigOptionVector<T>*>(source);
if (!src_typed || src_idx >= src_typed->size() || dst_idx >= this->size())
return;
// Handle nullable vectors - only copy if source value is not nil
if (this->nullable() && src_typed->nullable()) {
if (!src_typed->is_nil(src_idx)) {
this->values[dst_idx] = src_typed->values[src_idx];
}
} else if (!src_typed->nullable()) {
// Source is not nullable, always copy
this->values[dst_idx] = src_typed->values[src_idx];
}
// If source is nullable and value is nil, don't copy (keep existing value)
}
const T& get_at(size_t i) const
{
assert(! this->values.empty());

View File

@@ -6,13 +6,14 @@ namespace Slic3r {
double Extruder::m_share_E = 0.;
double Extruder::m_share_retracted = 0.;
Extruder::Extruder(unsigned int id, GCodeConfig *config, bool share_extruder) :
Extruder::Extruder(unsigned int id, unsigned int physical_extruder_id, GCodeConfig *config, bool share_extruder) :
m_id(id),
m_physical_extruder_id(physical_extruder_id),
m_config(config),
m_share_extruder(share_extruder)
{
reset();
// cache values that are going to be called often
m_e_per_mm3 = this->filament_flow_ratio();
m_e_per_mm3 /= this->filament_crossection();
@@ -157,24 +158,25 @@ double Extruder::filament_flow_ratio() const
}
// Return a "retract_before_wipe" percentage as a factor clamped to <0, 1>
// SM Orca: 回抽相关参数是挤出机属性,使用 m_physical_extruder_id
double Extruder::retract_before_wipe() const
{
return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_id) * 0.01));
return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_physical_extruder_id) * 0.01));
}
double Extruder::retraction_length() const
{
return m_config->retraction_length.get_at(m_id);
return m_config->retraction_length.get_at(m_physical_extruder_id);
}
double Extruder::retract_lift() const
{
return m_config->z_hop.get_at(m_id);
return m_config->z_hop.get_at(m_physical_extruder_id);
}
int Extruder::retract_speed() const
{
return int(floor(m_config->retraction_speed.get_at(m_id)+0.5));
return int(floor(m_config->retraction_speed.get_at(m_physical_extruder_id)+0.5));
}
bool Extruder::use_firmware_retraction() const
@@ -184,28 +186,28 @@ bool Extruder::use_firmware_retraction() const
int Extruder::deretract_speed() const
{
int speed = int(floor(m_config->deretraction_speed.get_at(m_id)+0.5));
int speed = int(floor(m_config->deretraction_speed.get_at(m_physical_extruder_id)+0.5));
return (speed > 0) ? speed : this->retract_speed();
}
double Extruder::retract_restart_extra() const
{
return m_config->retract_restart_extra.get_at(m_id);
return m_config->retract_restart_extra.get_at(m_physical_extruder_id);
}
double Extruder::retract_length_toolchange() const
{
return m_config->retract_length_toolchange.get_at(m_id);
return m_config->retract_length_toolchange.get_at(m_physical_extruder_id);
}
double Extruder::retract_restart_extra_toolchange() const
{
return m_config->retract_restart_extra_toolchange.get_at(m_id);
return m_config->retract_restart_extra_toolchange.get_at(m_physical_extruder_id);
}
double Extruder::travel_slope() const
{
return m_config->travel_slope.get_at(m_id) * PI / 180;
return m_config->travel_slope.get_at(m_physical_extruder_id) * PI / 180;
}
}

View File

@@ -11,7 +11,8 @@ class GCodeConfig;
class Extruder
{
public:
Extruder(unsigned int id, GCodeConfig *config, bool share_extruder);
// SM Orca: 添加 physical_extruder_id 参数用于支持耗材-挤出机映射
Extruder(unsigned int id, unsigned int physical_extruder_id, GCodeConfig *config, bool share_extruder);
virtual ~Extruder() {}
void reset() {
@@ -28,6 +29,8 @@ public:
}
unsigned int id() const { return m_id; }
// SM Orca: 获取物理挤出机ID
unsigned int physical_extruder_id() const { return m_physical_extruder_id; }
double extrude(double dE);
double retract(double length, double restart_extra);
@@ -75,12 +78,14 @@ public:
private:
// Private constructor to create a key for a search in std::set.
Extruder(unsigned int id) : m_id(id) {}
Extruder(unsigned int id) : m_id(id), m_physical_extruder_id(id) {}
// Reference to GCodeWriter instance owned by GCodeWriter.
GCodeConfig *m_config;
// Print-wide global ID of this extruder.
// Print-wide global ID of this extruder (filament index).
unsigned int m_id;
// SM Orca: 物理挤出机ID用于查询挤出机属性温度、回抽等
unsigned int m_physical_extruder_id;
// Current state of the extruder axis, may be resetted if use_relative_e_distances.
double m_E;
// Current state of the extruder tachometer, used to output the extruded_volume() and used_filament() statistics.

View File

@@ -213,42 +213,93 @@ double Flow::mm3_per_mm() const
Flow support_material_flow(const PrintObject *object, float layer_height)
{
// SM Orca: 使用物理挤出机的喷嘴直径
int filament_idx = object->config().support_filament - 1;
int physical_extruder = object->print()->get_physical_extruder(filament_idx);
// SM Orca: 日志 - 配置数组访问边界检查
const auto& nozzle_diameter_config = object->print()->config().nozzle_diameter;
size_t array_size = nozzle_diameter_config.values.size();
BOOST_LOG_TRIVIAL(info) << "Flow::support_material_flow: filament_idx=" << filament_idx
<< " physical_extruder=" << physical_extruder
<< " nozzle_diameter array_size=" << array_size
<< " access_index=" << physical_extruder
<< (physical_extruder >= (int)array_size ? " [POTENTIAL_OUT_OF_BOUNDS!]" : " [OK]");
return Flow::new_from_config_width(
frSupportMaterial,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(object->config().support_line_width.value > 0) ? object->config().support_line_width : object->config().line_width,
// if object->config().support_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament-1)),
float(object->print()->config().nozzle_diameter.get_at(physical_extruder)),
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
}
//BBS
Flow support_transition_flow(const PrintObject* object)
{
//BBS: support transition of tree support is bridge flow
float dmr = float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament - 1));
// SM Orca: 使用物理挤出机的喷嘴直径
int filament_idx = object->config().support_filament - 1;
int physical_extruder = object->print()->get_physical_extruder(filament_idx);
// SM Orca: 日志 - 配置数组访问边界检查
const auto& nozzle_diameter_config = object->print()->config().nozzle_diameter;
size_t array_size = nozzle_diameter_config.values.size();
BOOST_LOG_TRIVIAL(info) << "Flow::support_transition_flow: filament_idx=" << filament_idx
<< " physical_extruder=" << physical_extruder
<< " nozzle_diameter array_size=" << array_size
<< " access_index=" << physical_extruder
<< (physical_extruder >= (int)array_size ? " [POTENTIAL_OUT_OF_BOUNDS!]" : " [OK]");
float dmr = float(object->print()->config().nozzle_diameter.get_at(physical_extruder));
return Flow::bridging_flow(dmr, dmr);
}
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
{
// SM Orca: 使用物理挤出机的喷嘴直径
int filament_idx = object->config().support_filament - 1;
int physical_extruder = object->print()->get_physical_extruder(filament_idx);
const PrintConfig &print_config = object->print()->config();
// SM Orca: 日志 - 配置数组访问边界检查
size_t array_size = print_config.nozzle_diameter.values.size();
BOOST_LOG_TRIVIAL(info) << "Flow::support_material_1st_layer_flow: filament_idx=" << filament_idx
<< " physical_extruder=" << physical_extruder
<< " nozzle_diameter array_size=" << array_size
<< " access_index=" << physical_extruder
<< (physical_extruder >= (int)array_size ? " [POTENTIAL_OUT_OF_BOUNDS!]" : " [OK]");
const auto &width = (print_config.initial_layer_line_width.value > 0) ? print_config.initial_layer_line_width : object->config().support_line_width;
return Flow::new_from_config_width(
frSupportMaterial,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(width.value > 0) ? width : object->config().line_width,
float(print_config.nozzle_diameter.get_at(object->config().support_filament-1)),
float(print_config.nozzle_diameter.get_at(physical_extruder)),
(layer_height > 0.f) ? layer_height : float(print_config.initial_layer_print_height.value));
}
Flow support_material_interface_flow(const PrintObject *object, float layer_height)
{
// SM Orca: 使用物理挤出机的喷嘴直径
int filament_idx = object->config().support_interface_filament - 1;
int physical_extruder = object->print()->get_physical_extruder(filament_idx);
// SM Orca: 日志 - 配置数组访问边界检查
const auto& nozzle_diameter_config = object->print()->config().nozzle_diameter;
size_t array_size = nozzle_diameter_config.values.size();
BOOST_LOG_TRIVIAL(info) << "Flow::support_material_interface_flow: filament_idx=" << filament_idx
<< " physical_extruder=" << physical_extruder
<< " nozzle_diameter array_size=" << array_size
<< " access_index=" << physical_extruder
<< (physical_extruder >= (int)array_size ? " [POTENTIAL_OUT_OF_BOUNDS!]" : " [OK]");
return Flow::new_from_config_width(
frSupportMaterialInterface,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(object->config().support_line_width > 0) ? object->config().support_line_width : object->config().line_width,
// if object->config().support_interface_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_interface_filament-1)),
float(object->print()->config().nozzle_diameter.get_at(physical_extruder)),
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
}

File diff suppressed because it is too large Load Diff

View File

@@ -114,7 +114,7 @@ private:
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
std::string post_process_wipe_tower_moves(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
// Left / right edges of the wipe tower, for the planning of wipe moves.
const float m_left;
const float m_right;
@@ -197,6 +197,9 @@ public:
//BBS: set offset for gcode writer
void set_gcode_offset(double x, double y) { m_writer.set_xy_offset(x, y); m_processor.set_xy_offset(x, y);}
// SM Orca: Set filament-extruder mapping
void set_filament_extruder_map(const std::unordered_map<int, int>& map) { m_writer.set_filament_extruder_map(map); m_processor.set_filament_extruder_map(map); }
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
const Vec2d& origin() const { return m_origin; }
void set_origin(const Vec2d &pointf);

View File

@@ -526,9 +526,18 @@ void GCodeProcessor::UsedFilaments::process_role_cache(GCodeProcessor* processor
if (role_cache != 0.0f) {
std::pair<double, double> filament = { 0.0f, 0.0f };
double s = PI * sqr(0.5 * processor->m_result.filament_diameters[processor->m_extruder_id]);
// SM Orca: 添加边界检查,与其他地方保持一致
float diameter = (static_cast<size_t>(processor->m_extruder_id) < processor->m_result.filament_diameters.size())
? processor->m_result.filament_diameters[processor->m_extruder_id]
: processor->m_result.filament_diameters.back();
float density = (static_cast<size_t>(processor->m_extruder_id) < processor->m_result.filament_densities.size())
? processor->m_result.filament_densities[processor->m_extruder_id]
: processor->m_result.filament_densities.back();
double s = PI * sqr(0.5 * diameter);
filament.first = role_cache / s * 0.001;
filament.second = role_cache * processor->m_result.filament_densities[processor->m_extruder_id] * 0.001;
filament.second = role_cache * density * 0.001;
ExtrusionRole active_role = processor->m_extrusion_role;
if (filaments_per_role.find(active_role) != filaments_per_role.end()) {
@@ -556,6 +565,25 @@ void GCodeProcessorResult::reset() {
//BBS: add mutex for protection of gcode result
lock();
// SM Orca: 保存当前的extruders_count并尝试从已有数组推断
size_t saved_count = extruders_count;
// SM Orca: 如果extruders_count不合理尝试从已有数组大小推断
if (saved_count == 0 || saved_count > 256) {
// 尝试从已有数组大小推断优先使用filament_diameters的大小
if (!filament_diameters.empty() && filament_diameters.size() <= 256) {
saved_count = filament_diameters.size();
BOOST_LOG_TRIVIAL(info) << "SM Orca: GCodeProcessorResult::reset() - Inferred extruders_count from existing array: "
<< saved_count;
} else {
// SM Orca: 使用16作为默认值覆盖U1等多耗材机型
// 对于只有少量耗材的用户,稍微多分配一些内存影响很小
saved_count = 16;
BOOST_LOG_TRIVIAL(info) << "SM Orca: GCodeProcessorResult::reset() - Using default extruders_count: "
<< saved_count << " (was " << extruders_count << ", array size was " << filament_diameters.size() << ")";
}
}
moves = std::vector<GCodeProcessorResult::MoveVertex>();
printable_area = Pointfs();
//BBS: add bed exclude area
@@ -567,10 +595,22 @@ void GCodeProcessorResult::reset() {
timelapse_warning_code = 0;
printable_height = 0.0f;
settings_ids.reset();
extruders_count = 0;
// SM Orca: 恢复extruders_count为合理的值
extruders_count = saved_count;
extruder_colors = std::vector<std::string>();
filament_diameters = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER);
filament_densities = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY);
// SM Orca: 使用推断的大小初始化所有耗材相关数组
filament_diameters = std::vector<float>(saved_count, DEFAULT_FILAMENT_DIAMETER);
filament_densities = std::vector<float>(saved_count, DEFAULT_FILAMENT_DENSITY);
filament_costs = std::vector<float>(saved_count, DEFAULT_FILAMENT_COST);
required_nozzle_HRC = std::vector<int>(saved_count, DEFAULT_FILAMENT_HRC);
filament_vitrification_temperature = std::vector<int>(saved_count, DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE);
BOOST_LOG_TRIVIAL(info) << "SM Orca: GCodeProcessorResult::reset() - Resized arrays to " << saved_count
<< " (original extruders_count=" << (saved_count == extruders_count ? "preserved" : "inferred")
<< ", this=" << this << ")";
custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>();
time = 0;
@@ -583,6 +623,25 @@ void GCodeProcessorResult::reset() {
//BBS: add mutex for protection of gcode result
lock();
// SM Orca: 保存当前的extruders_count并尝试从已有数组推断
size_t saved_count = extruders_count;
// SM Orca: 如果extruders_count不合理尝试从已有数组大小推断
if (saved_count == 0 || saved_count > 256) {
// 尝试从已有数组大小推断优先使用filament_diameters的大小
if (!filament_diameters.empty() && filament_diameters.size() <= 256) {
saved_count = filament_diameters.size();
BOOST_LOG_TRIVIAL(info) << "SM Orca: GCodeProcessorResult::reset() - Inferred extruders_count from existing array: "
<< saved_count;
} else {
// SM Orca: 使用16作为默认值覆盖U1等多耗材机型
// 对于只有少量耗材的用户,稍微多分配一些内存影响很小
saved_count = 16;
BOOST_LOG_TRIVIAL(info) << "SM Orca: GCodeProcessorResult::reset() - Using default extruders_count: "
<< saved_count << " (was " << extruders_count << ", array size was " << filament_diameters.size() << ")";
}
}
moves.clear();
lines_ends.clear();
printable_area = Pointfs();
@@ -596,13 +655,23 @@ void GCodeProcessorResult::reset() {
timelapse_warning_code = 0;
printable_height = 0.0f;
settings_ids.reset();
extruders_count = 0;
backtrace_enabled = false;
extruder_colors = std::vector<std::string>();
filament_diameters = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER);
required_nozzle_HRC = std::vector<int>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_HRC);
filament_densities = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY);
filament_costs = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST);
// SM Orca: 恢复extruders_count为合理的值
extruders_count = saved_count;
// SM Orca: 使用推断的大小初始化所有耗材相关数组
filament_diameters = std::vector<float>(saved_count, DEFAULT_FILAMENT_DIAMETER);
required_nozzle_HRC = std::vector<int>(saved_count, DEFAULT_FILAMENT_HRC);
filament_densities = std::vector<float>(saved_count, DEFAULT_FILAMENT_DENSITY);
filament_costs = std::vector<float>(saved_count, DEFAULT_FILAMENT_COST);
filament_vitrification_temperature = std::vector<int>(saved_count, DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE);
BOOST_LOG_TRIVIAL(info) << "SM Orca: GCodeProcessorResult::reset() - Resized arrays to " << saved_count
<< " (original extruders_count=" << (saved_count == extruders_count ? "preserved" : "inferred")
<< ", this=" << this << ")";
custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>();
bed_match_result = BedMatchResult(true);
@@ -719,6 +788,45 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
m_preheat_steps = 1;
m_result.backtrace_enabled = m_preheat_time > 0 && (m_is_XL_printer || (!m_single_extruder_multi_material && extruders_count > 1));
// SM Orca: 配置验证 - 在resize之前检查配置完整性
size_t physical_extruder_count = config.extruder_offset.values.size();
BOOST_LOG_TRIVIAL(info) << "SM Orca: Configuration validation:"
<< " filaments=" << extruders_count
<< ", physical_extruders=" << physical_extruder_count
<< ", mappings=" << m_filament_extruder_map.size();
// 验证各配置数组大小
if (config.filament_density.values.size() < extruders_count) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: filament_density has "
<< config.filament_density.values.size() << " values, expected " << extruders_count
<< " (will use fallback values)";
}
if (config.filament_cost.values.size() < extruders_count) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: filament_cost has "
<< config.filament_cost.values.size() << " values, expected " << extruders_count
<< " (will use fallback values)";
}
if (config.nozzle_temperature.values.size() < extruders_count) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: nozzle_temperature has "
<< config.nozzle_temperature.values.size() << " values, expected " << extruders_count
<< " (will use fallback values)";
}
// 验证映射表有效性
if (!m_filament_extruder_map.empty()) {
for (size_t i = 0; i < extruders_count; ++i) {
int physical_extruder = get_physical_extruder(i);
if (physical_extruder < 0 ||
physical_extruder >= static_cast<int>(physical_extruder_count)) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Filament " << i
<< " maps to invalid physical extruder " << physical_extruder
<< " (valid range: 0-" << (physical_extruder_count - 1) << ")";
}
}
}
m_extruder_offsets.resize(extruders_count);
m_extruder_colors.resize(extruders_count);
m_result.filament_diameters.resize(extruders_count);
@@ -731,20 +839,103 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
m_extruder_temps_first_layer_config.resize(extruders_count);
m_result.nozzle_hrc = static_cast<int>(config.nozzle_hrc.getInt());
m_result.nozzle_type = config.nozzle_type;
// SM Orca: 获取各配置数组的大小,用于边界检查
size_t diameter_count = config.filament_diameter.values.size();
size_t density_count = config.filament_density.values.size();
size_t cost_count = config.filament_cost.values.size();
size_t temp_initial_count = config.nozzle_temperature_initial_layer.values.size();
size_t temp_count = config.nozzle_temperature.values.size();
size_t hrc_count = config.required_nozzle_HRC.values.size();
size_t vitrification_count = config.temperature_vitrification.values.size();
for (size_t i = 0; i < extruders_count; ++ i) {
m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast<float>().eval(), 0.f);
// SM Orca: 使用物理挤出机ID来访问offset并确保不越界
int physical_extruder = get_physical_extruder(i);
// SM Orca: 边界检查 - 如果映射的物理挤出机超出范围回退到1:1映射
if (physical_extruder < 0 || physical_extruder >= static_cast<int>(physical_extruder_count)) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Filament " << i
<< " maps to invalid physical extruder " << physical_extruder
<< " (valid range: 0-" << (physical_extruder_count - 1) << ")";
// 使用filament index作为fallback1:1映射如果也越界则使用0
physical_extruder = (i < physical_extruder_count) ? static_cast<int>(i) : 0;
BOOST_LOG_TRIVIAL(error) << " SM Orca: Using fallback physical extruder " << physical_extruder;
}
m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(physical_extruder).cast<float>().eval(), 0.f);
m_extruder_colors[i] = static_cast<unsigned char>(i);
m_extruder_temps_first_layer_config[i] = static_cast<int>(config.nozzle_temperature_initial_layer.get_at(i));
m_extruder_temps_config[i] = static_cast<int>(config.nozzle_temperature.get_at(i));
// SM Orca: 安全读取温度配置(使用最后有效值作为回退)
// 温度是挤出机属性,使用 physical_extruder 而不是 filament index
if (physical_extruder < static_cast<int>(temp_initial_count)) {
m_extruder_temps_first_layer_config[i] = static_cast<int>(config.nozzle_temperature_initial_layer.get_at(physical_extruder));
} else {
int fallback = temp_initial_count > 0 ?
static_cast<int>(config.nozzle_temperature_initial_layer.get_at(temp_initial_count - 1)) : 210;
m_extruder_temps_first_layer_config[i] = fallback;
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " (physical extruder " << physical_extruder << ") initial layer temperature not configured, using " << fallback;
}
if (physical_extruder < static_cast<int>(temp_count)) {
m_extruder_temps_config[i] = static_cast<int>(config.nozzle_temperature.get_at(physical_extruder));
} else {
int fallback = temp_count > 0 ?
static_cast<int>(config.nozzle_temperature.get_at(temp_count - 1)) : 210;
m_extruder_temps_config[i] = fallback;
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " (physical extruder " << physical_extruder << ") temperature not configured, using " << fallback;
}
if (m_extruder_temps_config[i] == 0) {
// This means the value should be ignored and first layer temp should be used.
m_extruder_temps_config[i] = m_extruder_temps_first_layer_config[i];
}
m_result.filament_diameters[i] = static_cast<float>(config.filament_diameter.get_at(i));
m_result.required_nozzle_HRC[i] = static_cast<int>(config.required_nozzle_HRC.get_at(i));
m_result.filament_densities[i] = static_cast<float>(config.filament_density.get_at(i));
m_result.filament_vitrification_temperature[i] = static_cast<float>(config.temperature_vitrification.get_at(i));
m_result.filament_costs[i] = static_cast<float>(config.filament_cost.get_at(i));
// SM Orca: 安全读取直径(使用最后有效值作为回退)
if (i < diameter_count) {
m_result.filament_diameters[i] = static_cast<float>(config.filament_diameter.get_at(i));
} else {
float fallback = diameter_count > 0 ?
static_cast<float>(config.filament_diameter.get_at(diameter_count - 1)) : 1.75f;
m_result.filament_diameters[i] = fallback;
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " diameter not configured, using " << fallback << "mm";
}
// SM Orca: 安全读取HRC
if (i < hrc_count) {
m_result.required_nozzle_HRC[i] = static_cast<int>(config.required_nozzle_HRC.get_at(i));
} else {
int fallback = hrc_count > 0 ?
static_cast<int>(config.required_nozzle_HRC.get_at(hrc_count - 1)) : 0;
m_result.required_nozzle_HRC[i] = fallback;
}
// SM Orca: 安全读取密度(使用最后有效值作为回退)
if (i < density_count) {
m_result.filament_densities[i] = static_cast<float>(config.filament_density.get_at(i));
} else {
float fallback = density_count > 0 ?
static_cast<float>(config.filament_density.get_at(density_count - 1)) : 1.25f;
m_result.filament_densities[i] = fallback;
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " density not configured, using " << fallback << " g/cm³";
}
// SM Orca: 安全读取玻璃化温度
if (i < vitrification_count) {
m_result.filament_vitrification_temperature[i] = static_cast<int>(config.temperature_vitrification.get_at(i));
} else {
int fallback = vitrification_count > 0 ?
static_cast<int>(config.temperature_vitrification.get_at(vitrification_count - 1)) : 0;
m_result.filament_vitrification_temperature[i] = fallback;
}
// SM Orca: 安全读取成本使用0作为默认值
if (i < cost_count) {
m_result.filament_costs[i] = static_cast<float>(config.filament_cost.get_at(i));
} else {
m_result.filament_costs[i] = 0.0f;
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " cost not configured, using 0.0";
}
}
if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfKlipper || m_flavor == gcfRepRapFirmware) {
@@ -858,24 +1049,39 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
const ConfigOptionFloats* filament_diameters = config.option<ConfigOptionFloats>("filament_diameter");
if (filament_diameters != nullptr) {
m_result.filament_diameters.clear();
m_result.filament_diameters.resize(filament_diameters->values.size());
for (size_t i = 0; i < filament_diameters->values.size(); ++i) {
size_t config_size = filament_diameters->values.size();
// SM Orca: 确保数组大小等于extruders_count不要clear()
if (m_result.filament_diameters.size() < m_result.extruders_count) {
m_result.filament_diameters.resize(m_result.extruders_count, DEFAULT_FILAMENT_DIAMETER);
}
// SM Orca: 只更新配置中有的值
for (size_t i = 0; i < config_size && i < m_result.extruders_count; ++i) {
m_result.filament_diameters[i] = static_cast<float>(filament_diameters->values[i]);
}
// SM Orca: 对于配置中没有的值,使用最后一个有效值
if (config_size > 0 && config_size < m_result.extruders_count) {
float last_value = static_cast<float>(filament_diameters->values[config_size - 1]);
for (size_t i = config_size; i < m_result.extruders_count; ++i) {
m_result.filament_diameters[i] = last_value;
BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << i
<< " diameter not in config, using last value " << last_value << "mm";
}
}
}
// SM Orca: 确保数组大小正确(移除原有的回退逻辑,因为已经在上面处理了)
if (m_result.filament_diameters.size() < m_result.extruders_count) {
for (size_t i = m_result.filament_diameters.size(); i < m_result.extruders_count; ++i) {
m_result.filament_diameters.emplace_back(DEFAULT_FILAMENT_DIAMETER);
}
m_result.filament_diameters.resize(m_result.extruders_count, DEFAULT_FILAMENT_DIAMETER);
}
const ConfigOptionInts *filament_HRC = config.option<ConfigOptionInts>("required_nozzle_HRC");
if (filament_HRC != nullptr) {
m_result.required_nozzle_HRC.clear();
m_result.required_nozzle_HRC.resize(filament_HRC->values.size());
for (size_t i = 0; i < filament_HRC->values.size(); ++i) { m_result.required_nozzle_HRC[i] = static_cast<float>(filament_HRC->values[i]); }
for (size_t i = 0; i < filament_HRC->values.size(); ++i) { m_result.required_nozzle_HRC[i] = static_cast<int>(filament_HRC->values[i]); }
}
if (m_result.required_nozzle_HRC.size() < m_result.extruders_count) {
@@ -885,43 +1091,86 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
const ConfigOptionFloats* filament_densities = config.option<ConfigOptionFloats>("filament_density");
if (filament_densities != nullptr) {
m_result.filament_densities.clear();
m_result.filament_densities.resize(filament_densities->values.size());
for (size_t i = 0; i < filament_densities->values.size(); ++i) {
size_t config_size = filament_densities->values.size();
// SM Orca: 确保数组大小等于extruders_count不要clear()
if (m_result.filament_densities.size() < m_result.extruders_count) {
m_result.filament_densities.resize(m_result.extruders_count, DEFAULT_FILAMENT_DENSITY);
}
// SM Orca: 只更新配置中有的值
for (size_t i = 0; i < config_size && i < m_result.extruders_count; ++i) {
m_result.filament_densities[i] = static_cast<float>(filament_densities->values[i]);
}
// SM Orca: 对于配置中没有的值,使用最后一个有效值
if (config_size > 0 && config_size < m_result.extruders_count) {
float last_value = static_cast<float>(filament_densities->values[config_size - 1]);
for (size_t i = config_size; i < m_result.extruders_count; ++i) {
m_result.filament_densities[i] = last_value;
BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << i
<< " density not in config, using last value " << last_value << " g/cm³";
}
}
}
// SM Orca: 确保数组大小正确
if (m_result.filament_densities.size() < m_result.extruders_count) {
for (size_t i = m_result.filament_densities.size(); i < m_result.extruders_count; ++i) {
m_result.filament_densities.emplace_back(DEFAULT_FILAMENT_DENSITY);
}
m_result.filament_densities.resize(m_result.extruders_count, DEFAULT_FILAMENT_DENSITY);
}
//BBS
const ConfigOptionFloats* filament_costs = config.option<ConfigOptionFloats>("filament_cost");
if (filament_costs != nullptr) {
m_result.filament_costs.clear();
m_result.filament_costs.resize(filament_costs->values.size());
for (size_t i = 0; i < filament_costs->values.size(); ++i)
m_result.filament_costs[i]=static_cast<float>(filament_costs->values[i]);
size_t config_size = filament_costs->values.size();
// SM Orca: 确保数组大小等于extruders_count不要clear()
if (m_result.filament_costs.size() < m_result.extruders_count) {
m_result.filament_costs.resize(m_result.extruders_count, DEFAULT_FILAMENT_COST);
}
// SM Orca: 只更新配置中有的值
for (size_t i = 0; i < config_size && i < m_result.extruders_count; ++i)
m_result.filament_costs[i] = static_cast<float>(filament_costs->values[i]);
// SM Orca: 对于配置中没有的值使用0成本默认值
if (config_size < m_result.extruders_count) {
for (size_t i = config_size; i < m_result.extruders_count; ++i) {
m_result.filament_costs[i] = DEFAULT_FILAMENT_COST;
BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << i
<< " cost not in config, using default " << DEFAULT_FILAMENT_COST;
}
}
}
for (size_t i = m_result.filament_costs.size(); i < m_result.extruders_count; ++i) {
m_result.filament_costs.emplace_back(DEFAULT_FILAMENT_COST);
// SM Orca: 确保数组大小正确
if (m_result.filament_costs.size() < m_result.extruders_count) {
m_result.filament_costs.resize(m_result.extruders_count, DEFAULT_FILAMENT_COST);
}
//BBS
const ConfigOptionInts* filament_vitrification_temperature = config.option<ConfigOptionInts>("temperature_vitrification");
if (filament_vitrification_temperature != nullptr) {
m_result.filament_vitrification_temperature.clear();
m_result.filament_vitrification_temperature.resize(filament_vitrification_temperature->values.size());
for (size_t i = 0; i < filament_vitrification_temperature->values.size(); ++i) {
size_t config_size = filament_vitrification_temperature->values.size();
// SM Orca: 确保数组大小等于extruders_count不要clear()
if (m_result.filament_vitrification_temperature.size() < m_result.extruders_count) {
m_result.filament_vitrification_temperature.resize(m_result.extruders_count, DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE);
}
// SM Orca: 只更新配置中有的值
for (size_t i = 0; i < config_size && i < m_result.extruders_count; ++i) {
m_result.filament_vitrification_temperature[i] = static_cast<int>(filament_vitrification_temperature->values[i]);
}
}
if (m_result.filament_vitrification_temperature.size() < m_result.extruders_count) {
for (size_t i = m_result.filament_vitrification_temperature.size(); i < m_result.extruders_count; ++i) {
m_result.filament_vitrification_temperature.emplace_back(DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE);
// SM Orca: 对于配置中没有的值,使用最后一个有效值
if (config_size > 0 && config_size < m_result.extruders_count) {
int last_value = static_cast<int>(filament_vitrification_temperature->values[config_size - 1]);
for (size_t i = config_size; i < m_result.extruders_count; ++i) {
m_result.filament_vitrification_temperature[i] = last_value;
BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << i
<< " vitrification temperature not in config, using last value " << last_value;
}
}
}
@@ -937,17 +1186,34 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
}
}
else {
m_extruder_offsets.resize(extruder_offset->values.size());
for (size_t i = 0; i < extruder_offset->values.size(); ++i) {
// SM Orca: 先确保数组足够大,不要缩小已有的数组
size_t physical_count = extruder_offset->values.size();
if (m_extruder_offsets.size() < m_result.extruders_count) {
m_extruder_offsets.resize(m_result.extruders_count, DEFAULT_EXTRUDER_OFFSET);
}
// 只更新物理挤出机的offset
for (size_t i = 0; i < physical_count && i < m_extruder_offsets.size(); ++i) {
Vec2f offset = extruder_offset->values[i].cast<float>();
m_extruder_offsets[i] = { offset(0), offset(1), 0.0f };
}
}
}
if (m_extruder_offsets.size() < m_result.extruders_count) {
// SM Orca: 使用映射来填充剩余耗材的offset而不是使用默认值
size_t physical_count = m_extruder_offsets.size();
for (size_t i = m_extruder_offsets.size(); i < m_result.extruders_count; ++i) {
m_extruder_offsets.emplace_back(DEFAULT_EXTRUDER_OFFSET);
int physical_extruder = get_physical_extruder(i);
// 如果映射的物理挤出机索引在有效范围内复用它的offset
if (physical_extruder >= 0 && physical_extruder < static_cast<int>(physical_count)) {
m_extruder_offsets.emplace_back(m_extruder_offsets[physical_extruder]);
BOOST_LOG_TRIVIAL(debug) << "Filament " << i << " using offset from physical extruder " << physical_extruder;
} else {
// 否则使用默认offset
m_extruder_offsets.emplace_back(DEFAULT_EXTRUDER_OFFSET);
BOOST_LOG_TRIVIAL(warning) << "Filament " << i << " using default offset (physical extruder " << physical_extruder << " out of range)";
}
}
}
@@ -1117,6 +1383,34 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
const ConfigOptionFloat* z_offset = config.option<ConfigOptionFloat>("z_offset");
if (z_offset != nullptr)
m_z_offset = z_offset->value;
// SM Orca: 验证所有数组大小是否正确
bool arrays_valid = true;
if (m_result.filament_diameters.size() != m_result.extruders_count) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: CRITICAL - filament_diameters size mismatch: "
<< m_result.filament_diameters.size() << " != " << m_result.extruders_count;
arrays_valid = false;
}
if (m_result.filament_densities.size() != m_result.extruders_count) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: CRITICAL - filament_densities size mismatch: "
<< m_result.filament_densities.size() << " != " << m_result.extruders_count;
arrays_valid = false;
}
if (m_result.filament_costs.size() != m_result.extruders_count) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: CRITICAL - filament_costs size mismatch: "
<< m_result.filament_costs.size() << " != " << m_result.extruders_count;
arrays_valid = false;
}
if (m_result.filament_vitrification_temperature.size() != m_result.extruders_count) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: CRITICAL - filament_vitrification_temperature size mismatch: "
<< m_result.filament_vitrification_temperature.size() << " != " << m_result.extruders_count;
arrays_valid = false;
}
if (arrays_valid) {
BOOST_LOG_TRIVIAL(info) << "SM Orca: DynamicPrintConfig array validation passed - all arrays correctly sized to "
<< m_result.extruders_count;
}
}
void GCodeProcessor::enable_stealth_time_estimator(bool enabled)
@@ -1556,6 +1850,23 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool
// update start position
m_start_position = m_end_position;
// SM Orca: 防止NaN/inf从m_end_position传播到m_start_position
if (std::isnan(m_start_position[X]) || std::isinf(m_start_position[X]) ||
std::isnan(m_start_position[Y]) || std::isinf(m_start_position[Y]) ||
std::isnan(m_start_position[Z]) || std::isinf(m_start_position[Z])) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Detected invalid m_start_position at line " << m_line_id
<< " extruder=" << static_cast<int>(m_extruder_id)
<< " m_start_position=(" << m_start_position[X] << ", " << m_start_position[Y] << ", " << m_start_position[Z] << ")"
<< " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")";
// 重置为原点,防止污染继续传播
m_start_position[X] = std::isnan(m_start_position[X]) || std::isinf(m_start_position[X]) ? 0.0f : m_start_position[X];
m_start_position[Y] = std::isnan(m_start_position[Y]) || std::isinf(m_start_position[Y]) ? 0.0f : m_start_position[Y];
m_start_position[Z] = std::isnan(m_start_position[Z]) || std::isinf(m_start_position[Z]) ? 0.0f : m_start_position[Z];
m_end_position[X] = std::isnan(m_end_position[X]) || std::isinf(m_end_position[X]) ? 0.0f : m_end_position[X];
m_end_position[Y] = std::isnan(m_end_position[Y]) || std::isinf(m_end_position[Y]) ? 0.0f : m_end_position[Y];
m_end_position[Z] = std::isnan(m_end_position[Z]) || std::isinf(m_end_position[Z]) ? 0.0f : m_end_position[Z];
}
const std::string_view cmd = line.cmd();
if (m_flavor == gcfKlipper)
{
@@ -2635,6 +2946,22 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line, const std::o
m_end_position[a] = absolute_position((Axis)a, line);
}
// SM Orca: 验证position更新捕获NaN/inf的源头
if (std::isnan(m_end_position[X]) || std::isinf(m_end_position[X]) ||
std::isnan(m_end_position[Y]) || std::isinf(m_end_position[Y]) ||
std::isnan(m_end_position[Z]) || std::isinf(m_end_position[Z])) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid m_end_position after G1 processing for extruder " << static_cast<int>(m_extruder_id)
<< " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")"
<< " m_start_position=(" << m_start_position[X] << ", " << m_start_position[Y] << ", " << m_start_position[Z] << ")"
<< " m_origin=(" << m_origin[X] << ", " << m_origin[Y] << ", " << m_origin[Z] << ")"
<< " has_X=" << line.has(X) << " has_Y=" << line.has(Y) << " has_Z=" << line.has(Z)
<< " X_value=" << (line.has(X) ? line.value(X) : 0.0f)
<< " Y_value=" << (line.has(Y) ? line.value(Y) : 0.0f)
<< " Z_value=" << (line.has(Z) ? line.value(Z) : 0.0f)
<< " positioning=" << (m_global_positioning_type == EPositioningType::Relative ? "relative" : "absolute")
<< " units=" << (m_units == EUnits::Inches ? "inches" : "mm");
}
// updates feedrate from line, if present
if (line.has_f())
m_feedrate = line.f() * MMMIN_TO_MMSEC;
@@ -2698,13 +3025,35 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line, const std::o
else if (m_extrusion_role == erExternalPerimeter)
// cross section: rectangle
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height);
else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erInternalBridgeInfill || m_extrusion_role == erNone)
else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erInternalBridgeInfill || m_extrusion_role == erNone) {
// SM Orca: 添加边界检查
float diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size())
? m_result.filament_diameters[m_extruder_id]
: m_result.filament_diameters.back();
// SM Orca: 防止sqrt(负数)产生NaN
float ratio = delta_pos[E] / delta_xyz;
if (ratio < 0.0f) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Negative E/XYZ ratio (" << ratio
<< ") for extruder " << m_extruder_id << ", using absolute value";
ratio = std::abs(ratio);
}
// cross section: circle
m_width = static_cast<float>(m_result.filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz);
m_width = diameter * std::sqrt(ratio);
}
else
// cross section: rectangle + 2 semicircles
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast<float>(1.0 - 0.25 * M_PI) * m_height;
// SM Orca: 验证宽度计算结果防止NaN/inf传播
if (std::isnan(m_width) || std::isinf(m_width)) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid width calculated: " << m_width
<< " for extruder " << m_extruder_id
<< " (E=" << delta_pos[E] << ", XYZ=" << delta_xyz << ")";
m_width = DEFAULT_TOOLPATH_WIDTH;
}
if (m_width == 0.0f)
m_width = DEFAULT_TOOLPATH_WIDTH;
@@ -2960,6 +3309,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line, const std::o
// check for seam starting vertex
if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) {
//BBS: m_result.moves.back().position has plate offset, must minus plate offset before calculate the real seam position
// SM Orca: m_extruder_offsets[i] 已经在 apply_config 中映射过了,这里直接使用 m_extruder_id 作为索引
const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset;
if (!m_seams_detector.has_first_vertex()) {
m_seams_detector.set_first_vertex(new_pos);
@@ -2979,6 +3329,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line, const std::o
const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]);
//BBS: m_result.moves.back().position has plate offset, must minus plate offset before calculate the real seam position
// SM Orca: m_extruder_offsets[i] 已经在 apply_config 中映射过了,这里直接使用 m_extruder_id 作为索引
const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset;
const std::optional<Vec3f> first_vertex = m_seams_detector.get_first_vertex();
// the threshold value = 0.0625f == 0.25 * 0.25 is arbitrary, we may find some smarter condition later
@@ -2994,6 +3345,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line, const std::o
}
else if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) {
m_seams_detector.activate(true);
// SM Orca: m_extruder_offsets[i] 已经在 apply_config 中映射过了,这里直接使用 m_extruder_id 作为索引
m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset);
}
@@ -3086,6 +3438,23 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line)
for (unsigned char a = X; a <= E; ++a) {
m_end_position[a] = absolute_position((Axis)a, line);
}
// SM Orca: 验证position更新捕获NaN/inf的源头
if (std::isnan(m_end_position[X]) || std::isinf(m_end_position[X]) ||
std::isnan(m_end_position[Y]) || std::isinf(m_end_position[Y]) ||
std::isnan(m_end_position[Z]) || std::isinf(m_end_position[Z])) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid m_end_position after G2/G3 processing for extruder " << static_cast<int>(m_extruder_id)
<< " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")"
<< " m_start_position=(" << m_start_position[X] << ", " << m_start_position[Y] << ", " << m_start_position[Z] << ")"
<< " m_origin=(" << m_origin[X] << ", " << m_origin[Y] << ", " << m_origin[Z] << ")"
<< " has_X=" << line.has(X) << " has_Y=" << line.has(Y) << " has_Z=" << line.has(Z)
<< " X_value=" << (line.has(X) ? line.value(X) : 0.0f)
<< " Y_value=" << (line.has(Y) ? line.value(Y) : 0.0f)
<< " Z_value=" << (line.has(Z) ? line.value(Z) : 0.0f)
<< " positioning=" << (m_global_positioning_type == EPositioningType::Relative ? "relative" : "absolute")
<< " units=" << (m_units == EUnits::Inches ? "inches" : "mm");
}
//BBS: G2 G3 line but has no I and J axis, invalid G code format
if (!line.has(I) && !line.has(J))
return;
@@ -3178,13 +3547,35 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line)
else if (m_extrusion_role == erExternalPerimeter)
//BBS: cross section: rectangle
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height);
else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erInternalBridgeInfill || m_extrusion_role == erNone)
else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erInternalBridgeInfill || m_extrusion_role == erNone) {
// SM Orca: 添加边界检查
float diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size())
? m_result.filament_diameters[m_extruder_id]
: m_result.filament_diameters.back();
// SM Orca: 防止sqrt(负数)产生NaN
float ratio = delta_pos[E] / delta_xyz;
if (ratio < 0.0f) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Negative E/XYZ ratio (" << ratio
<< ") for extruder " << m_extruder_id << ", using absolute value";
ratio = std::abs(ratio);
}
//BBS: cross section: circle
m_width = static_cast<float>(m_result.filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz);
m_width = diameter * std::sqrt(ratio);
}
else
//BBS: cross section: rectangle + 2 semicircles
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast<float>(1.0 - 0.25 * M_PI) * m_height;
// SM Orca: 验证宽度计算结果防止NaN/inf传播
if (std::isnan(m_width) || std::isinf(m_width)) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid width calculated: " << m_width
<< " for extruder " << m_extruder_id
<< " (E=" << delta_pos[E] << ", XYZ=" << delta_xyz << ")";
m_width = DEFAULT_TOOLPATH_WIDTH;
}
if (m_width == 0.0f)
m_width = DEFAULT_TOOLPATH_WIDTH;
@@ -3395,6 +3786,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line)
if (m_seams_detector.is_active()) {
//BBS: check for seam starting vertex
if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) {
// SM Orca: m_extruder_offsets[i] 已经在 apply_config 中映射过了,这里直接使用 m_extruder_id 作为索引
const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset;
if (!m_seams_detector.has_first_vertex()) {
m_seams_detector.set_first_vertex(new_pos);
@@ -3412,6 +3804,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line)
m_end_position[X] = pos.x(); m_end_position[Y] = pos.y(); m_end_position[Z] = pos.z();
};
const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]);
// SM Orca: m_extruder_offsets[i] 已经在 apply_config 中映射过了,这里直接使用 m_extruder_id 作为索引
const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset;
const std::optional<Vec3f> first_vertex = m_seams_detector.get_first_vertex();
//BBS: the threshold value = 0.0625f == 0.25 * 0.25 is arbitrary, we may find some smarter condition later
@@ -3427,6 +3820,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line)
}
else if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) {
m_seams_detector.activate(true);
// SM Orca: m_extruder_offsets[i] 已经在 apply_config 中映射过了,这里直接使用 m_extruder_id 作为索引
m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset);
}
@@ -3472,17 +3866,21 @@ void GCodeProcessor::process_G29(const GCodeReader::GCodeLine& line)
void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line)
{
// SM Orca: 回抽参数是挤出机属性,使用 physical_extruder
int physical_extruder = get_physical_extruder(m_extruder_id);
GCodeReader::GCodeLine g10;
g10.set(Axis::E, -this->m_parser.config().retraction_length.get_at(m_extruder_id));
g10.set(Axis::F, this->m_parser.config().retraction_speed.get_at(m_extruder_id) * 60);
g10.set(Axis::E, -this->m_parser.config().retraction_length.get_at(physical_extruder));
g10.set(Axis::F, this->m_parser.config().retraction_speed.get_at(physical_extruder) * 60);
process_G1(g10);
}
void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line)
{
// SM Orca: 回抽参数是挤出机属性,使用 physical_extruder
int physical_extruder = get_physical_extruder(m_extruder_id);
GCodeReader::GCodeLine g11;
g11.set(Axis::E, this->m_parser.config().retraction_length.get_at(m_extruder_id) + this->m_parser.config().retract_restart_extra.get_at(m_extruder_id));
g11.set(Axis::F, this->m_parser.config().deretraction_speed.get_at(m_extruder_id) * 60);
g11.set(Axis::E, this->m_parser.config().retraction_length.get_at(physical_extruder) + this->m_parser.config().retract_restart_extra.get_at(physical_extruder));
g11.set(Axis::F, this->m_parser.config().deretraction_speed.get_at(physical_extruder) * 60);
process_G1(g11);
}
@@ -3574,12 +3972,26 @@ void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line)
simulate_st_synchronize();
if (!any_found && !line.has_unknown_axis()) {
// The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510,
// The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510,
// where G92 A0 B0 is called although the extruder axis is till E.
for (unsigned char a = X; a <= E; ++a) {
m_origin[a] = m_end_position[a];
}
}
// SM Orca: 验证m_origin防止NaN/inf传播
if (std::isnan(m_origin[X]) || std::isinf(m_origin[X]) ||
std::isnan(m_origin[Y]) || std::isinf(m_origin[Y]) ||
std::isnan(m_origin[Z]) || std::isinf(m_origin[Z])) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid m_origin after G92 processing"
<< " extruder=" << static_cast<int>(m_extruder_id)
<< " m_origin=(" << m_origin[X] << ", " << m_origin[Y] << ", " << m_origin[Z] << ")"
<< " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")";
// 重置为0防止污染继续传播
m_origin[X] = std::isnan(m_origin[X]) || std::isinf(m_origin[X]) ? 0.0f : m_origin[X];
m_origin[Y] = std::isnan(m_origin[Y]) || std::isinf(m_origin[Y]) ? 0.0f : m_origin[Y];
m_origin[Z] = std::isnan(m_origin[Z]) || std::isinf(m_origin[Z]) ? 0.0f : m_origin[Z];
}
}
void GCodeProcessor::process_M1(const GCodeReader::GCodeLine& line)
@@ -4045,12 +4457,54 @@ void GCodeProcessor::run_post_process()
double filament_total_cost = 0.0;
for (const auto& [id, volume] : m_result.print_statistics.total_volumes_per_extruder) {
filament_mm[id] = volume / (static_cast<double>(M_PI) * sqr(0.5 * m_result.filament_diameters[id]));
// SM Orca: 边界检查 - 确保id在有效范围内
if (id >= m_result.filament_diameters.size() ||
id >= m_result.filament_densities.size() ||
id >= m_result.filament_costs.size()) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Filament index " << id << " out of bounds (sizes: "
<< "diameter=" << m_result.filament_diameters.size()
<< ", density=" << m_result.filament_densities.size()
<< ", cost=" << m_result.filament_costs.size() << "), skipping cost calculation";
continue;
}
// SM Orca: 读取并验证配置值
double diameter = m_result.filament_diameters[id];
double density = m_result.filament_densities[id];
double cost = m_result.filament_costs[id];
// SM Orca: 防止除零和NaN传播
if (diameter <= 0.0 || std::isnan(diameter)) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Invalid filament diameter " << diameter
<< " for filament " << id << ", using default 1.75mm";
diameter = 1.75;
}
if (density <= 0.0 || std::isnan(density)) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Invalid filament density " << density
<< " for filament " << id << ", using default 1.25 g/cm³";
density = 1.25;
}
if (cost < 0.0 || std::isnan(cost)) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Invalid filament cost " << cost
<< " for filament " << id << ", using 0.0";
cost = 0.0;
}
// SM Orca: 执行成本计算
double cross_section = M_PI * sqr(0.5 * diameter);
filament_mm[id] = volume / cross_section;
filament_cm3[id] = volume * 0.001;
filament_g[id] = filament_cm3[id] * double(m_result.filament_densities[id]);
filament_cost[id] = filament_g[id] * double(m_result.filament_costs[id]) * 0.001;
filament_g[id] = filament_cm3[id] * density;
filament_cost[id] = filament_g[id] * cost * 0.001;
filament_total_g += filament_g[id];
filament_total_cost += filament_cost[id];
BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << id
<< " - volume: " << volume << "mm³, length: " << filament_mm[id]
<< "mm, weight: " << filament_g[id] << "g, cost: " << filament_cost[id];
}
double total_g_wipe_tower = m_print->print_statistics().total_wipe_tower_filament;
@@ -4771,6 +5225,11 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type)
m_line_id + 1 :
((type == EMoveType::Seam) ? m_last_line_id : m_line_id);
// SM Orca: 添加边界检查,防止访问越界
Vec3f extruder_offset = (static_cast<size_t>(m_extruder_id) < m_extruder_offsets.size())
? m_extruder_offsets[m_extruder_id]
: Vec3f(0.0f, 0.0f, 0.0f);
//BBS: apply plate's and extruder's offset to arc interpolation points
if (path_type == EMovePathType::Arc_move_cw ||
path_type == EMovePathType::Arc_move_ccw) {
@@ -4779,7 +5238,40 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type)
Vec3f(m_interpolation_points[i].x() + m_x_offset,
m_interpolation_points[i].y() + m_y_offset,
m_processing_start_custom_gcode ? m_first_layer_height : m_interpolation_points[i].z()) +
m_extruder_offsets[m_extruder_id];
extruder_offset;
}
// SM Orca: 最后一道防线防止无效值进入result
if (std::isnan(m_width) || std::isinf(m_width)) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Blocking invalid width: " << m_width
<< " (extruder " << m_extruder_id << ")";
m_width = DEFAULT_TOOLPATH_WIDTH;
}
if (std::isnan(m_height) || std::isinf(m_height)) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Blocking invalid height: " << m_height
<< " (extruder " << m_extruder_id << ")";
m_height = DEFAULT_TOOLPATH_HEIGHT;
}
// SM Orca: 验证position防止NaN/inf进入moves
Vec3f final_position = Vec3f(m_end_position[X] + m_x_offset,
m_end_position[Y] + m_y_offset,
m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z] - m_z_offset)
+ extruder_offset;
if (std::isnan(final_position.x()) || std::isinf(final_position.x()) ||
std::isnan(final_position.y()) || std::isinf(final_position.y()) ||
std::isnan(final_position.z()) || std::isinf(final_position.z())) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid position calculated for extruder " << static_cast<int>(m_extruder_id)
<< " position=(" << final_position.x() << ", " << final_position.y() << ", " << final_position.z() << ")"
<< " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")"
<< " offset=(" << m_x_offset << ", " << m_y_offset << ", " << m_z_offset << ")"
<< " extruder_offset=(" << extruder_offset.x() << ", " << extruder_offset.y() << ", " << extruder_offset.z() << ")";
// 使用不带offset的position作为fallback
final_position = Vec3f(m_end_position[X] + m_x_offset,
m_end_position[Y] + m_y_offset,
m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z] - m_z_offset);
}
m_result.moves.push_back({
@@ -4789,7 +5281,8 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type)
m_extruder_id,
m_cp_color.current,
//BBS: add plate's offset to the rendering vertices
Vec3f(m_end_position[X] + m_x_offset, m_end_position[Y] + m_y_offset, m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z]- m_z_offset) + m_extruder_offsets[m_extruder_id],
// SM Orca: 使用验证过的final_position
final_position,
static_cast<float>(m_end_position[E] - m_start_position[E]),
m_feedrate,
m_width,
@@ -4802,6 +5295,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type)
static_cast<float>(m_layer_id), //layer_duration: set later
//BBS: add arc move related data
path_type,
// SM Orca: m_extruder_offsets[i] 已经在 apply_config 中映射过了,这里直接使用 m_extruder_id 作为索引
Vec3f(m_arc_center(0, 0) + m_x_offset, m_arc_center(1, 0) + m_y_offset, m_arc_center(2, 0)) + m_extruder_offsets[m_extruder_id],
m_interpolation_points,
});

View File

@@ -14,6 +14,7 @@
#include <string>
#include <string_view>
#include <optional>
#include <unordered_map>
namespace Slic3r {
@@ -677,6 +678,8 @@ class Print;
EPositioningType m_global_positioning_type;
EPositioningType m_e_local_positioning_type;
std::vector<Vec3f> m_extruder_offsets;
// SM Orca: 耗材到物理挤出机的映射
std::unordered_map<int, int> m_filament_extruder_map;
GCodeFlavor m_flavor;
float m_nozzle_volume;
AxisCoords m_start_position; // mm
@@ -776,6 +779,21 @@ class Print;
void apply_config(const PrintConfig& config);
void set_print(Print* print) { m_print = print; }
// SM Orca: 设置耗材到物理挤出机的映射
void set_filament_extruder_map(const std::unordered_map<int, int>& map) {
m_filament_extruder_map = map;
}
// SM Orca: 获取物理挤出机ID根据耗材索引
int get_physical_extruder(int filament_idx) const {
auto it = m_filament_extruder_map.find(filament_idx);
int physical_extruder_id = (it != m_filament_extruder_map.end()) ? it->second : filament_idx;
// SM Orca: 日志 - 映射查询
BOOST_LOG_TRIVIAL(info) << "GCodeProcessor::get_physical_extruder: filament_id=" << filament_idx
<< " -> physical_extruder_id=" << physical_extruder_id
<< " (map_size=" << m_filament_extruder_map.size() << ")"
<< (it != m_filament_extruder_map.end() ? " [from_map]" : " [default_identity]");
return physical_extruder_id;
}
void enable_stealth_time_estimator(bool enabled);
bool is_stealth_time_estimator_enabled() const {
return m_time_processor.machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled;

View File

@@ -668,18 +668,20 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
void WipeTower::set_extruder(size_t idx, int physical_extruder, const PrintConfig& config)
{
//while (m_filpar.size() < idx+1) // makes sure the required element is in the vector
m_filpar.push_back(FilamentParameters());
// SM Orca: 耗材属性使用 idx (filament index)
m_filpar[idx].material = config.filament_type.get_at(idx);
// m_filpar[idx].is_soluble = config.filament_soluble.get_at(idx);
m_filpar[idx].is_soluble = config.wipe_tower_filament == 0 ? config.filament_soluble.get_at(idx) : (idx != size_t(config.wipe_tower_filament - 1));
// BBS
m_filpar[idx].is_support = config.filament_is_support.get_at(idx);
m_filpar[idx].nozzle_temperature = config.nozzle_temperature.get_at(idx);
m_filpar[idx].nozzle_temperature_initial_layer = config.nozzle_temperature_initial_layer.get_at(idx);
// SM Orca: 温度是挤出机属性,使用 physical_extruder
m_filpar[idx].nozzle_temperature = config.nozzle_temperature.get_at(physical_extruder);
m_filpar[idx].nozzle_temperature_initial_layer = config.nozzle_temperature_initial_layer.get_at(physical_extruder);
// If this is a single extruder MM printer, we will use all the SE-specific config values.
// Otherwise, the defaults will be used to turn off the SE stuff.
@@ -698,14 +700,19 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
#endif
m_filpar[idx].filament_area = float((M_PI/4.f) * pow(config.filament_diameter.get_at(idx), 2)); // all extruders are assumed to have the same filament diameter at this point
float nozzle_diameter = float(config.nozzle_diameter.get_at(idx));
// SM Orca: 喷嘴直径是挤出机属性,使用 physical_extruder
float nozzle_diameter = float(config.nozzle_diameter.get_at(physical_extruder));
m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
float max_vol_speed = float(config.filament_max_volumetric_speed.get_at(idx));
if (max_vol_speed!= 0.f)
m_filpar[idx].max_e_speed = (max_vol_speed / filament_area());
m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
// SM Orca: Store per-filament perimeter width and also set the global one
// Note: m_perimeter_width gets overwritten with each set_extruder() call
// The brim should use m_filpar[0].perimeter_width for consistency
m_filpar[idx].perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio;
m_perimeter_width = m_filpar[idx].perimeter_width; // all extruders are now assumed to have the same diameter
// BBS: remove useless config
#if 0
if (m_semm) {
@@ -1305,7 +1312,10 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
}
// brim chamfer
float spacing = m_perimeter_width - m_layer_height * float(1. - M_PI_4);
// SM Orca: Use first filament's perimeter width for consistent brim spacing
// The brim is generated once for the entire wipe tower and should use a consistent spacing
float brim_perimeter_width = m_filpar.empty() ? m_perimeter_width : m_filpar[0].perimeter_width;
float spacing = brim_perimeter_width - m_layer_height * float(1. - M_PI_4);
// How many perimeters shall the brim have?
int loops_num = (m_wipe_tower_brim_width + spacing / 2.f) / spacing;
const float max_chamfer_width = 3.f;

View File

@@ -144,7 +144,8 @@ public:
// Set the extruder properties.
void set_extruder(size_t idx, const PrintConfig& config);
// SM Orca: 添加 physical_extruder 参数,用于支持耗材-挤出机映射
void set_extruder(size_t idx, int physical_extruder, const PrintConfig& config);
// Appends into internal structure m_plan containing info about the future wipe tower
// to be used before building begins. The entries must be added ordered in z.
@@ -269,6 +270,8 @@ public:
std::vector<float> ramming_speed;
float nozzle_diameter;
float filament_area;
// SM Orca: Store per-filament perimeter width for correct brim generation
float perimeter_width = 0.f;
};
private:

File diff suppressed because it is too large Load Diff

View File

@@ -45,7 +45,9 @@ public:
// Set the extruder properties.
void set_extruder(size_t idx, const PrintConfig& config);
// SM Orca: 添加 physical_extruder 参数,用于支持耗材-挤出机映射
// idx: 耗材索引, physical_extruder: 物理挤出机索引
void set_extruder(size_t idx, int physical_extruder, const PrintConfig& config);
// Appends into internal structure m_plan containing info about the future wipe tower
// to be used before building begins. The entries must be added ordered in z.
@@ -160,6 +162,8 @@ public:
float filament_minimal_purge_on_wipe_tower = 0.f;
float retract_length;
float retract_speed;
// SM Orca: Store per-filament perimeter width for correct brim generation
float perimeter_width = 0.f;
};
private:

View File

@@ -45,17 +45,26 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config)
void GCodeWriter::set_extruders(std::vector<unsigned int> extruder_ids)
{
BOOST_LOG_TRIVIAL(info) << "GCodeWriter::set_extruders: START - Creating Extruder objects for " << extruder_ids.size() << " extruders";
BOOST_LOG_TRIVIAL(info) << "GCodeWriter::set_extruders: filament_extruder_map size=" << m_filament_extruder_map.size();
std::sort(extruder_ids.begin(), extruder_ids.end());
m_extruder = nullptr; // this points to object inside `m_extruders`, so should be cleared too
m_extruders.clear();
m_extruders.reserve(extruder_ids.size());
for (unsigned int extruder_id : extruder_ids)
m_extruders.emplace_back(Extruder(extruder_id, &this->config, config.single_extruder_multi_material.value));
// SM Orca: 创建 Extruder 对象时传递物理挤出机ID
for (unsigned int extruder_id : extruder_ids) {
int physical_extruder_id = get_physical_extruder(extruder_id);
BOOST_LOG_TRIVIAL(info) << "GCodeWriter::set_extruders: Creating Extruder - filament_id=" << extruder_id
<< " -> physical_extruder_id=" << physical_extruder_id;
m_extruders.emplace_back(Extruder(extruder_id, physical_extruder_id, &this->config, config.single_extruder_multi_material.value));
}
/* we enable support for multiple extruder if any extruder greater than 0 is used
(even if prints only uses that one) since we need to output Tx commands
first extruder has index 0 */
this->multiple_extruders = (*std::max_element(extruder_ids.begin(), extruder_ids.end())) > 0;
BOOST_LOG_TRIVIAL(info) << "GCodeWriter::set_extruders: END - multiple_extruders=" << this->multiple_extruders;
}
std::string GCodeWriter::preamble()
@@ -454,6 +463,12 @@ std::string GCodeWriter::toolchange_prefix() const
std::string GCodeWriter::toolchange(unsigned int extruder_id)
{
BOOST_LOG_TRIVIAL(info) << "SM Orca: GCodeWriter::toolchange(" << extruder_id << ") called";
// SM Orca: 记录映射信息
int physical_extruder = get_physical_extruder(extruder_id);
BOOST_LOG_TRIVIAL(info) << "SM Orca: Toolchange - filament " << extruder_id << " maps to physical extruder " << physical_extruder << " (mapping table size: " << m_filament_extruder_map.size() << ")";
// set the new extruder
auto it_extruder = Slic3r::lower_bound_by_predicate(m_extruders.begin(), m_extruders.end(), [extruder_id](const Extruder &e) { return e.id() < extruder_id; });
assert(it_extruder != m_extruders.end() && it_extruder->id() == extruder_id);
@@ -463,12 +478,16 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id)
// if we are running a single-extruder setup, just set the extruder and return nothing
std::ostringstream gcode;
if (this->multiple_extruders || (this->config.filament_diameter.values.size() > 1 && !is_bbl_printers())) {
// SM Orca: T命令使用耗材序号extruder_id物理换头由固件处理
BOOST_LOG_TRIVIAL(info) << "SM Orca: Generating T command: T" << extruder_id;
gcode << this->toolchange_prefix() << extruder_id;
//BBS
if (GCodeWriter::full_gcode_comment)
gcode << " ; change extruder";
gcode << "\n";
gcode << this->reset_e(true);
} else {
BOOST_LOG_TRIVIAL(info) << "SM Orca: Toolchange - Single extruder mode, no T command generated";
}
return gcode.str();
}
@@ -489,13 +508,21 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment)
{
// SM Orca: 诊断NaN输入
if (std::isnan(point(0)) || std::isinf(point(0)) || std::isnan(point(1)) || std::isinf(point(1))) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: travel_to_xy received NaN/inf point"
<< " extruder=" << (m_extruder ? m_extruder->id() : -1)
<< " point=(" << point(0) << ", " << point(1) << ")"
<< " comment=" << comment;
}
m_pos(0) = point(0);
m_pos(1) = point(1);
this->set_current_position_clear(true);
//BBS: take plate offset into consider
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
GCodeG1Formatter w;
w.emit_xy(point_on_plate);
auto speed = m_is_first_layer
@@ -709,11 +736,20 @@ bool GCodeWriter::will_move_z(double z) const
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment, bool force_no_extrusion)
{
// SM Orca: 诊断NaN输入
if (std::isnan(point(0)) || std::isinf(point(0)) || std::isnan(point(1)) || std::isinf(point(1))) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: extrude_to_xy received NaN/inf point"
<< " extruder=" << (m_extruder ? m_extruder->id() : -1)
<< " point=(" << point(0) << ", " << point(1) << ")"
<< " dE=" << dE
<< " comment=" << comment;
}
m_pos(0) = point(0);
m_pos(1) = point(1);
if(std::abs(dE) <= std::numeric_limits<double>::epsilon())
force_no_extrusion = true;
if (!force_no_extrusion)
m_extruder->extrude(dE);

View File

@@ -4,6 +4,7 @@
#include "libslic3r.h"
#include <string>
#include <charconv>
#include <unordered_map>
#include "Extruder.hpp"
#include "Point.hpp"
#include "PrintConfig.hpp"
@@ -119,6 +120,21 @@ public:
void set_is_first_layer(bool bval) { m_is_first_layer = bval; }
GCodeFlavor get_gcode_flavor() const { return config.gcode_flavor; }
// SM Orca: 设置耗材-挤出机映射
void set_filament_extruder_map(const std::unordered_map<int, int>& map) { m_filament_extruder_map = map; }
const std::unordered_map<int, int>& get_filament_extruder_map() const { return m_filament_extruder_map; }
// SM Orca: 获取物理挤出机ID如果没有映射返回耗材ID本身
int get_physical_extruder(int filament_idx) const {
auto it = m_filament_extruder_map.find(filament_idx);
int physical_extruder_id = (it != m_filament_extruder_map.end()) ? it->second : filament_idx;
// SM Orca: 日志 - 映射查询
BOOST_LOG_TRIVIAL(info) << "GCodeWriter::get_physical_extruder: filament_id=" << filament_idx
<< " -> physical_extruder_id=" << physical_extruder_id
<< " (map_size=" << m_filament_extruder_map.size() << ")"
<< (it != m_filament_extruder_map.end() ? " [from_map]" : " [default_identity]");
return physical_extruder_id;
}
// Returns whether this flavor supports separate print and travel acceleration.
static bool supports_separate_travel_acceleration(GCodeFlavor flavor);
private:
@@ -170,6 +186,9 @@ public:
double m_current_speed;
bool m_is_first_layer = true;
// SM Orca: 耗材到物理挤出机的映射表filament_idx -> physical_extruder_id
std::unordered_map<int, int> m_filament_extruder_map;
enum class Acceleration {
Travel,
Print

View File

@@ -293,8 +293,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "wipe_tower_no_sparse_layers"
|| opt_key == "flush_volumes_matrix"
|| opt_key == "prime_volume"
|| opt_key == "prime_tower_brim_chamfer"
|| opt_key == "prime_tower_brim_chamfer_max_width"
|| opt_key == "flush_into_infill"
|| opt_key == "flush_into_support"
|| opt_key == "initial_layer_infill_speed"
@@ -495,6 +493,44 @@ std::vector<unsigned int> Print::extruders(bool conside_custom_gcode) const
return extruders;
}
// SM Orca: Initialize filament-to-physical-extruder mapping
// This must be called before the mapping is used (e.g., before export_gcode)
void Print::initialize_filament_extruder_map()
{
m_filament_extruder_map.clear();
// Get the number of physical extruders (number of nozzle_diameter entries)
size_t physical_extruder_count = m_config.nozzle_diameter.values.size();
if (physical_extruder_count == 0) {
BOOST_LOG_TRIVIAL(warning) << "Print::initialize_filament_extruder_map: No physical extruders configured!";
return;
}
// Get all filament indices that will be used
std::vector<unsigned int> filament_extruders = this->extruders();
BOOST_LOG_TRIVIAL(info) << "Print::initialize_filament_extruder_map: Initializing with "
<< physical_extruder_count << " physical extruders and "
<< filament_extruders.size() << " filaments to map";
// Create mapping: filament_id -> physical_extruder_id
// Mapping formula: physical_extruder = filament_id % physical_extruder_count
// This allows using 8 filaments with 4 physical extruders:
// filament 0,1,2,3 -> extruder 0,1,2,3
// filament 4,5,6,7 -> extruder 0,1,2,3
for (unsigned int filament_idx : filament_extruders) {
int physical_extruder = filament_idx % physical_extruder_count;
m_filament_extruder_map[filament_idx] = physical_extruder;
BOOST_LOG_TRIVIAL(info) << "Print::initialize_filament_extruder_map: filament "
<< filament_idx << " -> physical_extruder " << physical_extruder;
}
BOOST_LOG_TRIVIAL(info) << "Print::initialize_filament_extruder_map: Map initialized with "
<< m_filament_extruder_map.size() << " entries";
}
unsigned int Print::num_object_instances() const
{
unsigned int instances = 0;
@@ -506,8 +542,11 @@ unsigned int Print::num_object_instances() const
double Print::max_allowed_layer_height() const
{
double nozzle_diameter_max = 0.;
for (unsigned int extruder_id : this->extruders())
nozzle_diameter_max = std::max(nozzle_diameter_max, m_config.nozzle_diameter.get_at(extruder_id));
for (unsigned int extruder_id : this->extruders()) {
// SM Orca: 使用物理挤出机的喷嘴直径
int physical_extruder = get_physical_extruder(extruder_id);
nozzle_diameter_max = std::max(nozzle_diameter_max, m_config.nozzle_diameter.get_at(physical_extruder));
}
return nozzle_diameter_max;
}
@@ -1185,10 +1224,13 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
if (this->has_wipe_tower() && ! m_objects.empty()) {
// Make sure all extruders use same diameter filament and have the same nozzle diameter
// EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments
double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders.front());
// SM Orca: 使用物理挤出机的喷嘴直径
int first_physical = get_physical_extruder(extruders.front());
double first_nozzle_diam = m_config.nozzle_diameter.get_at(first_physical);
double first_filament_diam = m_config.filament_diameter.get_at(extruders.front());
for (const auto& extruder_idx : extruders) {
double nozzle_diam = m_config.nozzle_diameter.get_at(extruder_idx);
int physical_extruder = get_physical_extruder(extruder_idx);
double nozzle_diam = m_config.nozzle_diameter.get_at(physical_extruder);
double filament_diam = m_config.filament_diameter.get_at(extruder_idx);
if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam
|| std::abs((filament_diam - first_filament_diam) / first_filament_diam) > 0.1) {
@@ -1294,7 +1336,9 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
double min_nozzle_diameter = std::numeric_limits<double>::max();
double max_nozzle_diameter = 0;
for (unsigned int extruder_id : extruders) {
double dmr = m_config.nozzle_diameter.get_at(extruder_id);
// SM Orca: 使用物理挤出机的喷嘴直径
int physical_extruder = get_physical_extruder(extruder_id);
double dmr = m_config.nozzle_diameter.get_at(physical_extruder);
min_nozzle_diameter = std::min(min_nozzle_diameter, dmr);
max_nozzle_diameter = std::max(max_nozzle_diameter, dmr);
}
@@ -1382,9 +1426,11 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
size_t first_layer_extruder = object->config().raft_layers == 1
? object->config().support_interface_filament-1
: object->config().support_filament-1;
// SM Orca: 使用物理挤出机的喷嘴直径
int physical_extruder = get_physical_extruder(first_layer_extruder);
first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ?
min_nozzle_diameter :
m_config.nozzle_diameter.get_at(first_layer_extruder);
m_config.nozzle_diameter.get_at(physical_extruder);
} else {
// if we don't have raft layers, any nozzle diameter is potentially used in first layer
first_layer_min_nozzle_diameter = min_nozzle_diameter;
@@ -1702,11 +1748,14 @@ Flow Print::brim_flow() const
extruders and take the one with, say, the smallest index.
The same logic should be applied to the code that selects the extruder during G-code
generation as well. */
// SM Orca: 使用物理挤出机的喷嘴直径
int filament_idx = m_print_regions.front()->config().wall_filament - 1;
int physical_extruder = get_physical_extruder(filament_idx);
return Flow::new_from_config_width(
frPerimeter,
// Flow::new_from_config_width takes care of the percent to value substitution
width,
(float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().wall_filament-1),
(float)m_config.nozzle_diameter.get_at(physical_extruder),
(float)this->skirt_first_layer_height());
}
@@ -1721,11 +1770,14 @@ Flow Print::skirt_flow() const
extruders and take the one with, say, the smallest index;
The same logic should be applied to the code that selects the extruder during G-code
generation as well. */
// SM Orca: 使用物理挤出机的喷嘴直径
int filament_idx = m_objects.front()->config().support_filament - 1;
int physical_extruder = get_physical_extruder(filament_idx);
return Flow::new_from_config_width(
frPerimeter,
// Flow::new_from_config_width takes care of the percent to value substitution
width,
(float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_filament-1),
(float)m_config.nozzle_diameter.get_at(physical_extruder),
(float)this->skirt_first_layer_height());
}
@@ -2243,6 +2295,17 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
//BBS: compute plate offset for gcode-generator
const Vec3d origin = this->get_plate_origin();
gcode.set_gcode_offset(origin(0), origin(1));
// SM Orca: Initialize filament-to-extruder mapping before it's used
this->initialize_filament_extruder_map();
// SM Orca: 设置耗材-挤出机映射
BOOST_LOG_TRIVIAL(info) << "SM Orca: Print::export_gcode - Setting filament_extruder_map to GCode, mapping size: " << m_filament_extruder_map.size();
for (const auto& pair : m_filament_extruder_map) {
BOOST_LOG_TRIVIAL(info) << " SM Orca: Print mapping: filament " << pair.first << " -> extruder " << pair.second;
}
gcode.set_filament_extruder_map(m_filament_extruder_map);
BOOST_LOG_TRIVIAL(info) << "SM Orca: Print::export_gcode - Mapping set to GCode object, calling do_export";
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
//BBS
@@ -2333,7 +2396,9 @@ void Print::_make_skirt()
extruders_e_per_mm.reserve(set_extruders.size());
for (auto &extruder_id : set_extruders) {
extruders.push_back(extruder_id);
extruders_e_per_mm.push_back(Extruder((unsigned int)extruder_id, &m_config, m_config.single_extruder_multi_material).e_per_mm(mm3_per_mm));
// SM Orca: 创建 Extruder 对象时传递物理挤出机ID
int physical_extruder_id = get_physical_extruder(extruder_id);
extruders_e_per_mm.push_back(Extruder((unsigned int)extruder_id, physical_extruder_id, &m_config, m_config.single_extruder_multi_material).e_per_mm(mm3_per_mm));
}
}
@@ -2733,8 +2798,11 @@ void Print::_make_wipe_tower()
// wipe_tower.set_zhop();
// Set the extruder & material properties at the wipe tower object.
for (size_t i = 0; i < number_of_extruders; ++i)
wipe_tower.set_extruder(i, m_config);
// SM Orca: 传递物理挤出机ID以支持耗材-挤出机映射
for (size_t i = 0; i < number_of_extruders; ++i) {
int physical_extruder = get_physical_extruder(i);
wipe_tower.set_extruder(i, physical_extruder, m_config);
}
// BBS: remove priming logic
// m_wipe_tower_data.priming = Slic3r::make_unique<std::vector<WipeTower::ToolChangeResult>>(
@@ -2829,8 +2897,11 @@ void Print::_make_wipe_tower()
// wipe_tower.set_zhop();
// Set the extruder & material properties at the wipe tower object.
for (size_t i = 0; i < number_of_extruders; ++i)
wipe_tower.set_extruder(i, m_config);
// SM Orca: 传递物理挤出机ID以支持耗材-挤出机映射
for (size_t i = 0; i < number_of_extruders; ++i) {
int physical_extruder = get_physical_extruder(i);
wipe_tower.set_extruder(i, physical_extruder, m_config);
}
m_wipe_tower_data.priming = Slic3r::make_unique<std::vector<WipeTower::ToolChangeResult>>(
wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false));
@@ -2970,6 +3041,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
GCodeProcessor::s_IsBBLPrinter = is_BBL_printer();
const Vec3d origin = this->get_plate_origin();
processor.set_xy_offset(origin(0), origin(1));
// SM Orca: 设置耗材到物理挤出机的映射
processor.set_filament_extruder_map(m_filament_extruder_map);
//processor.enable_producers(true);
processor.process_file(file);

View File

@@ -23,6 +23,7 @@
#include <functional>
#include <set>
#include <unordered_map>
#include "calib.hpp"
@@ -887,6 +888,24 @@ public:
std::vector<unsigned int> object_extruders() const;
std::vector<unsigned int> support_material_extruders() const;
// SM Orca: 设置耗材-挤出机映射
void set_filament_extruder_map(const std::unordered_map<int, int>& map) { m_filament_extruder_map = map; }
// SM Orca: 获取耗材-挤出机映射表
const std::unordered_map<int, int>& get_filament_extruder_map() const { return m_filament_extruder_map; }
// SM Orca: 获取物理挤出机ID根据耗材索引
int get_physical_extruder(int filament_idx) const {
auto it = m_filament_extruder_map.find(filament_idx);
int physical_extruder_id = (it != m_filament_extruder_map.end()) ? it->second : filament_idx;
// SM Orca: 日志 - 映射查询
BOOST_LOG_TRIVIAL(info) << "Print::get_physical_extruder: filament_id=" << filament_idx
<< " -> physical_extruder_id=" << physical_extruder_id
<< " (map_size=" << m_filament_extruder_map.size() << ")"
<< (it != m_filament_extruder_map.end() ? " [from_map]" : " [default_identity]");
return physical_extruder_id;
}
// SM Orca: Initialize filament-to-physical-extruder mapping table
void initialize_filament_extruder_map();
std::vector<unsigned int> extruders(bool conside_custom_gcode = false) const;
double max_allowed_layer_height() const;
bool has_support_material() const;
@@ -1066,6 +1085,9 @@ private:
//SoftFever: calibration
Calib_Params m_calib_params;
// SM Orca: 耗材到物理挤出机的映射表
std::unordered_map<int, int> m_filament_extruder_map;
// To allow GCode to set the Print's GCodeExport step status.
friend class GCode;
// Allow PrintObject to access m_mutex and m_cancel_callback.

View File

@@ -216,13 +216,56 @@ static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector<Custo
return false;
}
// SM Orca: Apply physical extruder mapping to filament parameters without overrides
// For each filament slot, if no override is provided, inherit from the mapped physical extruder
static void apply_physical_extruder_defaults(
ConfigOption* target,
const ConfigOption* filament_overrides,
const ConfigOption* extruder_defaults,
const std::unordered_map<int, int>& filament_extruder_map)
{
if (!target->is_vector() || !extruder_defaults->is_vector())
return;
auto* target_vec = dynamic_cast<ConfigOptionVectorBase*>(target);
auto* extruder_vec = dynamic_cast<const ConfigOptionVectorBase*>(extruder_defaults);
const ConfigOptionVectorBase* override_vec = filament_overrides ?
dynamic_cast<const ConfigOptionVectorBase*>(filament_overrides) : nullptr;
if (!target_vec || !extruder_vec)
return;
size_t num_filaments = target_vec->size();
for (size_t filament_idx = 0; filament_idx < num_filaments; ++filament_idx) {
// Check if this filament has an override
bool has_override = false;
if (override_vec && filament_idx < override_vec->size()) {
has_override = override_vec->nullable() ? !override_vec->is_nil(filament_idx) : true;
}
// If no override, inherit from the mapped physical extruder
if (!has_override) {
auto map_it = filament_extruder_map.find(filament_idx);
int physical_extruder_idx = (map_it != filament_extruder_map.end()) ?
map_it->second : filament_idx;
if (physical_extruder_idx < extruder_vec->size()) {
target_vec->set_at(extruder_vec, filament_idx, physical_extruder_idx);
}
}
}
}
// Collect changes to print config, account for overrides of extruder retract values by filament presets.
//BBS: add plate index
// SM Orca: add filament_extruder_map for physical extruder mapping
static t_config_option_keys print_config_diffs(
const PrintConfig &current_config,
const DynamicPrintConfig &new_full_config,
DynamicPrintConfig &filament_overrides,
int plate_index)
int plate_index,
const std::unordered_map<int, int> &filament_extruder_map)
{
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
const std::string filament_prefix = "filament_";
@@ -249,6 +292,13 @@ static t_config_option_keys print_config_diffs(
if (!((opt_key == "long_retractions_when_cut" || opt_key == "retraction_distances_when_cut")
&& new_full_config.option<ConfigOptionInt>("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament)) // ugly code, remove it later if firmware supports
opt_copy->apply_override(opt_new_filament);
// SM Orca: Apply physical extruder mapping for slots without overrides
bool is_extruder_retract_param = (iter != extruder_retract_keys.end());
if (is_extruder_retract_param && !filament_extruder_map.empty()) {
apply_physical_extruder_defaults(opt_copy, opt_new_filament, opt_new, filament_extruder_map);
}
bool changed = *opt_old != *opt_copy;
if (changed)
print_diff.emplace_back(opt_key);
@@ -261,6 +311,19 @@ static t_config_option_keys print_config_diffs(
} else
delete opt_copy;
}
} else if (iter != extruder_retract_keys.end() && !filament_extruder_map.empty()) {
// SM Orca: No filament override exists, but this is an extruder retract parameter
// Apply physical extruder mapping to inherit from correct extruders
auto opt_copy = opt_new->clone();
apply_physical_extruder_defaults(opt_copy, nullptr, opt_new, filament_extruder_map);
bool changed = *opt_old != *opt_copy;
if (changed) {
print_diff.emplace_back(opt_key);
filament_overrides.set_key_value(opt_key, opt_copy);
} else {
delete opt_copy;
}
} else if (*opt_new != *opt_old) {
//BBS: add plate_index logic for wipe_tower_x/wipe_tower_y
if (!opt_key.compare("wipe_tower_x") || !opt_key.compare("wipe_tower_y")) {
@@ -1131,7 +1194,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles.
DynamicPrintConfig filament_overrides;
//BBS: add plate index
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index);
// SM Orca: Pass filament_extruder_map to apply physical extruder mapping
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, m_filament_extruder_map);
t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config, this->m_plate_index);
// Collect changes to object and region configs.
t_config_option_keys object_diff = m_default_object_config.diff(new_full_config);