BBL Port Color Mix Base

This commit is contained in:
Ian Bassi
2026-08-23 22:11:48 +08:00
committed by SoftFever
parent 550e234a37
commit 8fea099d99
75 changed files with 34174 additions and 51 deletions
+323 -2
View File
@@ -6557,6 +6557,318 @@ LayerResult GCode::process_layer(
}
}
}
// Mixed-color sublayer extrusion: if this extruder is a component of a mixed sublayer
// group, extrude the mixed slot's geometry at the appropriate sub-Z with scaled flow.
// Ported from BambuStudio's 混色耗材 feature; adapted to Orca's InstanceVisit-based
// instance loop and its finer-grained per-role region filament options.
for (const auto &grp : layer_tools.mixed_sub_layer_groups) {
int sub_idx = -1;
for (size_t k = 0; k < grp.components_0based.size(); ++k) {
if (grp.components_0based[k] == extruder_id) {
sub_idx = static_cast<int>(k);
break;
}
}
if (sub_idx < 0)
continue;
auto mixed_instances_it = filament_to_print_instances.find(grp.mixed_slot_0based);
if (mixed_instances_it == filament_to_print_instances.end() || mixed_instances_it->second.first.empty())
continue;
double lh = grp.layer_height > 0. ? grp.layer_height : static_cast<double>(height);
double cumulative_h = 0.0;
for (int i = 0; i < sub_idx; ++i)
cumulative_h += grp.sub_heights[i];
double default_sub_h = grp.sub_heights[sub_idx];
double default_sub_z = print_z - lh + cumulative_h + default_sub_h;
m_sub_layer_flow_ratio = default_sub_h / lh;
m_sub_layer_height = default_sub_h;
m_nominal_z = default_sub_z;
gcode += this->set_extruder(extruder_id, default_sub_z);
for (InstanceToPrint &instance_to_print : mixed_instances_it->second.first) {
const bool use_per_volume = grp.is_gradient
&& !grp.per_volume_gradient.empty()
&& std::any_of(grp.per_volume_gradient.begin(), grp.per_volume_gradient.end(),
[&](const auto &kv) { return kv.first.obj == &instance_to_print.print_object; });
// --- Shared instance preamble (mirrors Orca's main instance loop) ---
const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id];
const auto &inst = instance_to_print.print_object.instances()[instance_to_print.instance_id];
bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 &&
instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id();
m_config.apply(print.default_region_config());
m_config.apply(instance_to_print.print_object.config(), true);
m_layer = layer_to_print.layer();
m_object_layer_over_raft = object_layer_over_raft;
if (m_config.reduce_crossing_wall)
m_avoid_crossing_perimeters.init_layer(*m_layer);
if (this->config().gcode_label_objects) {
gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name +
" id:" + std::to_string(instance_to_print.print_object.get_id()) + " copy " +
std::to_string(inst.id) + "\n";
}
if (m_enable_exclude_object) {
if (is_BBL_Printer()) {
m_writer.set_object_start_str(
std::string("; start printing object, unique label id: ") +
std::to_string(instance_to_print.label_object_id) + "\n" + "M624 " +
_encode_label_ids_to_base64({instance_to_print.label_object_id}) + "\n");
} else {
const auto gflavor = print.config().gcode_flavor.value;
if (gflavor == gcfKlipper) {
m_writer.set_object_start_str(std::string("EXCLUDE_OBJECT_START NAME=") +
get_instance_name(&instance_to_print.print_object, inst.id) + "\n");
} else if (gflavor == gcfMarlinLegacy || gflavor == gcfMarlinFirmware || gflavor == gcfRepRapFirmware) {
m_writer.set_object_start_str(std::string("M486 S") + std::to_string(inst.unique_id) + "\n");
}
}
}
m_extrusion_quality_estimator.set_current_object(&instance_to_print.print_object);
const Point &offset = inst.shift;
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
if (m_last_obj_copy != this_object_copy)
m_avoid_crossing_perimeters.use_external_mp_once();
m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset));
// --- Build emission plan ---
// Each entry represents one travel_to_z + extrude pass. Per-object mode produces
// exactly 1 entry (all regions, single sub_z); per-volume mode produces N entries
// for tagged volumes plus an optional entry for untagged residue.
struct SubLayerEmitEntry {
double sub_h;
double sub_z;
std::function<bool(size_t region_idx)> region_filter;
bool skip = false;
};
std::vector<SubLayerEmitEntry> emit_plan;
auto compute_sub_zh = [&](double r1, double r2, double &out_sub_h, double &out_sub_z) {
std::vector<double> sub_heights_local(grp.components_0based.size());
for (size_t ci = 0; ci < grp.components_0based.size(); ++ci)
sub_heights_local[ci] = (static_cast<int>(ci) == grp.gradient_first_sorted_idx) ? r1 * lh : r2 * lh;
double cum = 0.0;
for (int ci = 0; ci < sub_idx; ++ci)
cum += sub_heights_local[ci];
out_sub_h = sub_heights_local[sub_idx];
out_sub_z = print_z - lh + cum + out_sub_h;
};
auto gradient_ratios = [](const auto &g) -> std::pair<double, double> {
double t = (g.total_layers > 0) ? (2.0 * g.current_idx + 1.0) / (2.0 * g.total_layers) : 0.5;
// Custom curve wins over linear range when present; OFF path stays bit-identical.
double r1 = g.curve.empty()
? (g.gradient_start + (g.gradient_end - g.gradient_start) * t)
: sample_gradient_curve(g.curve, t);
return {r1, 1.0 - r1};
};
// Orca splits BBS's three role filaments into five; a region belongs to the slot
// when any of its roles is assigned to it.
auto region_uses_slot = [](const PrintRegionConfig &rcfg, unsigned int slot_1b) {
return (unsigned int)rcfg.outer_wall_filament_id.value == slot_1b
|| (unsigned int)rcfg.inner_wall_filament_id.value == slot_1b
|| (unsigned int)rcfg.sparse_infill_filament_id.value == slot_1b
|| (unsigned int)rcfg.internal_solid_filament_id.value == slot_1b
|| (unsigned int)rcfg.top_surface_filament_id.value == slot_1b
|| (unsigned int)rcfg.bottom_surface_filament_id.value == slot_1b;
};
double obj_sub_z = default_sub_z;
if (use_per_volume) {
const PrintObject *po = &instance_to_print.print_object;
const unsigned int slot_1b = grp.mixed_slot_0based + 1;
// Discover tagged volumes and untagged presence for this instance.
std::set<ObjectID> tagged_volumes_present;
bool has_untagged_for_slot = false;
for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
for (size_t r = 0; r < island.by_region.size(); ++r) {
const auto &region = island.by_region[r];
if (region.perimeters.empty() && region.infills.empty())
continue;
const PrintRegion &pr = print.get_print_region(r);
if (!region_uses_slot(pr.config(), slot_1b))
continue;
ObjectID vid = pr.gradient_volume_id();
if (vid.valid())
tagged_volumes_present.insert(vid);
else
has_untagged_for_slot = true;
}
}
// One entry per tagged volume.
for (const ObjectID &target_vid : tagged_volumes_present) {
auto vg_it = grp.per_volume_gradient.find({po, target_vid});
if (vg_it == grp.per_volume_gradient.end())
continue;
const auto &vg = vg_it->second;
auto [r1, r2] = gradient_ratios(vg);
bool vol_no_split = false;
bool skip_entry = false;
const size_t n = grp.components_0based.size();
if (n == 2 && vg.current_idx + 1 == vg.total_layers) {
const size_t dom_idx = (r1 >= r2) ? 0 : 1;
const unsigned int first_sorted_comp = grp.components_0based[grp.gradient_first_sorted_idx];
const unsigned int other_comp = grp.components_0based[1 - grp.gradient_first_sorted_idx];
const unsigned int dom_0b = (dom_idx == 0) ? first_sorted_comp : other_comp;
const unsigned int oth_0b = (dom_idx == 0) ? other_comp : first_sorted_comp;
if (dom_0b < oth_0b) {
vol_no_split = true;
if (extruder_id != dom_0b)
skip_entry = true;
}
}
double vol_sub_h = default_sub_h;
double vol_sub_z = default_sub_z;
if (vol_no_split) {
vol_sub_h = lh;
vol_sub_z = print_z;
} else {
compute_sub_zh(r1, r2, vol_sub_h, vol_sub_z);
}
emit_plan.push_back({vol_sub_h, vol_sub_z,
[target_vid, &print](size_t r) {
return print.get_print_region(r).gradient_volume_id() == target_vid;
},
skip_entry});
}
// Optional entry for untagged regions (modifier / painted / fuzzy_skin).
if (has_untagged_for_slot) {
double obj_sub_h = default_sub_h;
auto og_it = grp.per_object_gradient.find(po);
if (og_it != grp.per_object_gradient.end()) {
auto [r1, r2] = gradient_ratios(og_it->second);
compute_sub_zh(r1, r2, obj_sub_h, obj_sub_z);
}
emit_plan.push_back({obj_sub_h, obj_sub_z,
[&print](size_t r) {
return !print.get_print_region(r).gradient_volume_id().valid();
},
false});
}
} else {
// Legacy per-object path: single entry, no region filter.
double legacy_sub_h = default_sub_h;
obj_sub_z = default_sub_z;
if (grp.is_gradient) {
auto og_it = grp.per_object_gradient.find(&instance_to_print.print_object);
if (og_it != grp.per_object_gradient.end()) {
auto [r1, r2] = gradient_ratios(og_it->second);
compute_sub_zh(r1, r2, legacy_sub_h, obj_sub_z);
}
}
emit_plan.push_back({legacy_sub_h, obj_sub_z, nullptr, false});
}
// --- Unified emission loop ---
auto plan_has_infill = [](const std::vector<ObjectByExtruder::Island::Region> &by_region) {
for (const auto &r : by_region)
if (!r.infills.empty())
return true;
return false;
};
for (auto &entry : emit_plan) {
if (entry.skip)
continue;
m_sub_layer_flow_ratio = entry.sub_h / lh;
m_sub_layer_height = entry.sub_h;
m_nominal_z = entry.sub_z;
// Use the same lazy-Z mechanism as change_layer(): set the flag so travel_to
// fires even when m_last_pos coincides with the first extrusion point,
// ensuring Z reaches sub_z via the combined XY+Z move.
m_need_change_layer_lift_z = true;
for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
const auto &src = island.by_region;
std::vector<ObjectByExtruder::Island::Region> subset_storage;
if (entry.region_filter) {
subset_storage.resize(src.size());
for (size_t r = 0; r < src.size(); ++r)
if (entry.region_filter(r))
subset_storage[r] = src[r];
}
const auto &by_region_specific = entry.region_filter ? subset_storage : src;
// Orca resolves infill-first per region inside extrude_perimeters()
// (unlike BBS, which branches on a single global flag), so mirror the
// main instance loop's ordering exactly.
gcode += this->extrude_perimeters(print, by_region_specific, first_layer, false);
if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional
&& printer_structure == PrinterStructure::psI3
&& !has_insert_timelapse_gcode && plan_has_infill(by_region_specific)) {
gcode += this->retract(false, false, auto_lift_type, true);
gcode += insert_timelapse_gcode();
has_insert_timelapse_gcode = true;
}
gcode += this->extrude_infill(print, by_region_specific, false);
gcode += this->extrude_perimeters(print, by_region_specific, first_layer, true);
// ironing
gcode += this->extrude_infill(print, by_region_specific, true);
}
}
// --- Shared support ---
if (instance_to_print.object_by_extruder.support && !instance_to_print.object_by_extruder.support->empty()) {
if (use_per_volume) {
m_nominal_z = obj_sub_z;
gcode += m_writer.travel_to_z(obj_sub_z, "restore Z for support");
}
ExtrusionRole support_role = instance_to_print.object_by_extruder.support_extrusion_role;
gcode += this->extrude_support(*instance_to_print.object_by_extruder.support, support_role);
// Make sure ironing is the last (Orca names this role erIroning, not erSupportIroning).
if (support_role == erMixed || support_role == erSupportMaterialInterface)
gcode += this->extrude_support(*instance_to_print.object_by_extruder.support, erIroning);
}
// --- Shared instance footer (mirrors Orca's main instance loop) ---
if (!m_writer.is_object_start_str_empty()) {
m_writer.set_object_start_str("");
} else if (m_enable_exclude_object) {
if (is_BBL_Printer()) {
m_writer.set_object_end_str(std::string("; stop printing object, unique label id: ") +
std::to_string(instance_to_print.label_object_id) + "\n" +
"M625\n");
} else {
const auto gflavor = print.config().gcode_flavor.value;
if (gflavor == gcfKlipper) {
m_writer.set_object_end_str(std::string("EXCLUDE_OBJECT_END NAME=") +
get_instance_name(&instance_to_print.print_object, inst.id) + "\n");
} else if (gflavor == gcfMarlinLegacy || gflavor == gcfMarlinFirmware || gflavor == gcfRepRapFirmware) {
m_writer.set_object_end_str(std::string("M486 S-1\n"));
}
}
}
}
m_sub_layer_flow_ratio = 0.0;
m_sub_layer_height = 0.0;
}
// Flush any pending object end label before leaving the sublayer block, otherwise the
// wipe tower's add_object_end_labels may consume it into a local temp string and the
// M625 would be lost for BBL printers.
if (!layer_tools.mixed_sub_layer_groups.empty()) {
m_writer.add_object_end_labels(gcode);
m_nominal_z = print_z;
gcode += m_writer.travel_to_z(print_z, "restore Z after sublayers");
}
}
if (first_layer) {
for (auto iter = by_extruder.begin(); iter != by_extruder.end(); ++iter) {
@@ -7634,6 +7946,15 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
}
// Mixed-color sublayer: this path belongs to one sub-layer of a split layer, so scale the
// flow down to that sub-layer's share of the nominal layer height and report the sub-height
// as the effective extrusion height. Inert (ratio == 0) outside the sublayer emission block.
float effective_height = path.height;
if (m_sub_layer_flow_ratio > 0.0) {
_mm3_per_mm *= m_sub_layer_flow_ratio;
effective_height = static_cast<float>(m_sub_layer_height);
}
// Effective extrusion length per distance unit = (filament_flow_ratio/cross_section) * mm3_per_mm / print flow ratio
// m_writer.extruder()->e_per_mm3() below is (filament flow ratio / cross-sectional area)
double e_per_mm = m_writer.filament()->e_per_mm3() * _mm3_per_mm;
@@ -7933,8 +8254,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
gcode += buf;
}
if (last_was_wipe_tower || std::abs(m_last_height - path.height) > EPSILON) {
m_last_height = path.height;
if (last_was_wipe_tower || std::abs(m_last_height - effective_height) > EPSILON) {
m_last_height = effective_height;
sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height).c_str(), m_last_height);
gcode += buf;
}