sublayers and more

This commit is contained in:
Ian Bassi
2026-08-23 22:11:48 +08:00
committed by SoftFever
parent 76f23396ea
commit ccd34ab03a
11 changed files with 355 additions and 7 deletions
+162 -4
View File
@@ -5355,9 +5355,16 @@ void Sidebar::delete_filament(size_t filament_id, int replace_filament_id) {
p->editing_filament = -1;
}
// update_num_filaments() shrinks filament_is_mixed along with the other per-filament arrays,
// so snapshot it first — the paint cleanup below needs to know which slots were mixed
// *before* the delete to avoid discarding assignments to still-valid mixed slots.
std::vector<unsigned char> is_mixed_snapshot;
if (auto* opt = wxGetApp().preset_bundle->project_config.option<ConfigOptionBools>("filament_is_mixed"))
is_mixed_snapshot = opt->values;
wxGetApp().preset_bundle->update_num_filaments(filament_id);
wxGetApp().plater()->get_partplate_list().on_filament_deleted(filament_count, filament_id);
wxGetApp().plater()->on_filaments_delete(filament_count, filament_id, replace_filament_id > (int)filament_id ? (replace_filament_id - 1) : replace_filament_id);
wxGetApp().plater()->on_filaments_delete(filament_count, filament_id, replace_filament_id > (int)filament_id ? (replace_filament_id - 1) : replace_filament_id, is_mixed_snapshot);
wxGetApp().get_tab(Preset::TYPE_PRINT)->update();
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
@@ -5374,6 +5381,36 @@ void Sidebar::delete_filament(size_t filament_id, int replace_filament_id) {
void Sidebar::change_filament(size_t from_id, size_t to_id)
{
// Merging a physical filament into a mixed one that lists it as a component would delete
// the very filament the mix depends on, leaving it broken. Warn before doing so.
auto& pb = *wxGetApp().preset_bundle;
bool from_is_physical = !pb.is_mixed_filament(from_id);
bool to_is_mixed = pb.is_mixed_filament(to_id);
if (from_is_physical && to_is_mixed) {
auto* comp_opt = pb.project_config.option<ConfigOptionStrings>("filament_mixed_components");
if (comp_opt && to_id < comp_opt->values.size()) {
auto comps = Slic3r::parse_mixed_components(comp_opt->values[to_id]);
unsigned int from_1based = (unsigned int)from_id + 1;
bool target_uses_source = false;
for (unsigned int c : comps) {
if (c == from_1based) {
target_uses_source = true;
break;
}
}
if (target_uses_source) {
int ret = wxMessageBox(
_L("The target mixed filament uses this physical filament as a component. "
"Merging will remove this physical filament and may invalidate the mixed filament. Continue?"),
_L("Warning"),
wxOK | wxCANCEL | wxICON_WARNING);
if (ret != wxOK)
return;
}
}
}
delete_filament(from_id, int(to_id));
}
@@ -9744,7 +9781,11 @@ void Plater::priv::object_list_changed()
// BBS
//sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits && part_plate->has_printable_instances());
bool can_slice = !model.objects.empty() && !export_in_progress && model_fits && part_plate->has_printable_instances();
// A mixed filament with deleted or type-mismatched components cannot be resolved at slicing
// time, so block the slice buttons the same way MainFrame::get_enable_slice_status() does.
bool mixed_broken = sidebar->has_broken_mixed_filament();
bool can_slice = !model.objects.empty() && !export_in_progress && model_fits && part_plate->has_printable_instances()
&& !mixed_broken;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": can_slice %1%, model_fits= %2%, export_in_progress %3%, has_printable_instances %4% ")%can_slice %model_fits %export_in_progress %part_plate->has_printable_instances();
main_frame->update_slice_print_status(MainFrame::eEventObjectUpdate, can_slice);
@@ -13963,6 +14004,25 @@ bool Plater::priv::can_layers_editing() const
void Plater::priv::on_action_layersediting(SimpleEvent&)
{
// Sub-layer splitting divides each layer by the mix ratio, so an adaptive layer profile makes
// those sub-layer heights vary and degrades the blend. ConfigManipulation warns when the
// option is switched on with a variable profile already present; this is the other direction,
// warning when variable layer editing is switched on while the option is active. Both honour
// the same do-not-show-again flag.
if (!view3D->is_layers_editing_enabled()) {
const auto& print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
if (print_config.opt_bool("enable_mixed_color_sublayer")) {
if (wxGetApp().app_config->get("no_warn_mixed_sublayer_variable_layer") != "1") {
MessageDialog dlg(q,
_L("Using variable layer height together with mixed color sublayer may result in poor color mixing quality."),
_L("Warning"), wxICON_WARNING | wxOK);
dlg.show_dsa_button();
dlg.ShowModal();
if (dlg.get_checkbox_state())
wxGetApp().app_config->set("no_warn_mixed_sublayer_variable_layer", "1");
}
}
}
view3D->enable_layers_editing(!view3D->is_layers_editing_enabled());
notification_manager->set_move_from_overlay(view3D->is_layers_editing_enabled());
}
@@ -19385,7 +19445,7 @@ void Plater::on_filament_count_change(size_t num_filaments)
}
}
void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int replace_filament_id)
void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int replace_filament_id, const std::vector<unsigned char>& is_mixed_before_delete)
{
// only update elements in plater
update_filament_colors_in_full_config();
@@ -19399,9 +19459,15 @@ void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int r
}*/
// update mmu info
// A volume assigned to a mixed slot legitimately sits past the physical filament count, so
// the paint cleanup must know which slots were mixed. Callers that already shrank the arrays
// pass the pre-delete flags; otherwise read the current ones.
const auto &is_mixed = is_mixed_before_delete.empty()
? wxGetApp().preset_bundle->project_config.option<ConfigOptionBools>("filament_is_mixed")->values
: is_mixed_before_delete;
for (ModelObject *mo : wxGetApp().model().objects) {
for (ModelVolume *mv : mo->volumes) {
mv->update_extruder_count_when_delete_filament(num_filaments, filament_id + 1, replace_filament_id + 1); // this function is 1 base
mv->update_extruder_count_when_delete_filament(num_filaments, filament_id + 1, replace_filament_id + 1, is_mixed); // this function is 1 base
}
}
@@ -19740,6 +19806,63 @@ std::vector<std::string> Plater::get_extruder_colors_from_plater_config(const GC
}
}
namespace {
// A gradient mixed filament fades between its two components over Z, so the UI shows it as a
// two-tone swatch rather than one blended colour. Resolve each slot to its from/to endpoint
// colours; non-gradient slots are left untouched.
struct MixedGradientSlot {
bool is_gradient = false;
std::string color_from;
std::string color_to;
};
std::vector<MixedGradientSlot> parse_mixed_gradient_slots(const Slic3r::DynamicPrintConfig& config, size_t slot_count)
{
std::vector<MixedGradientSlot> result(slot_count);
const auto* is_mixed = config.option<ConfigOptionBools>("filament_is_mixed");
const auto* mixed_grad = config.option<ConfigOptionBools>("filament_mixed_gradient");
const auto* mixed_comp = config.option<ConfigOptionStrings>("filament_mixed_components");
const auto* grad_range = config.option<ConfigOptionStrings>("filament_mixed_gradient_range");
const auto* fil_colour = config.option<ConfigOptionStrings>("filament_colour");
if (!is_mixed || !mixed_grad || !mixed_comp || !fil_colour) return result;
for (size_t i = 0; i < slot_count && i < is_mixed->values.size(); ++i) {
if (!is_mixed->values[i]) continue;
if (i >= mixed_grad->values.size() || !mixed_grad->values[i]) continue;
if (i >= mixed_comp->values.size()) continue;
std::vector<unsigned int> comp_ids;
std::istringstream iss(mixed_comp->values[i]);
std::string tok;
while (std::getline(iss, tok, ',')) {
unsigned int v = 0;
if (std::sscanf(tok.c_str(), "%u", &v) == 1)
comp_ids.push_back(v);
}
if (comp_ids.size() != 2) continue;
int direction = 0;
if (grad_range && i < grad_range->values.size()) {
CNumericLocalesSetter c_locale_setter;
float v0 = 0, v1 = 0;
if (std::sscanf(grad_range->values[i].c_str(), "%f,%f", &v0, &v1) == 2)
direction = (v0 > v1) ? 0 : 1;
}
unsigned int from_id = (direction == 0) ? comp_ids[0] : comp_ids[1];
unsigned int to_id = (direction == 0) ? comp_ids[1] : comp_ids[0];
result[i].is_gradient = true;
result[i].color_from = (from_id >= 1 && from_id <= fil_colour->values.size())
? fil_colour->values[from_id - 1] : "#D9D9D9";
result[i].color_to = (to_id >= 1 && to_id <= fil_colour->values.size())
? fil_colour->values[to_id - 1] : "#D9D9D9";
}
return result;
}
} // anonymous namespace
std::vector<std::string> Plater::get_filament_colors_render_info() const
{
const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->project_config;
@@ -19747,6 +19870,13 @@ std::vector<std::string> Plater::get_filament_colors_render_info() const
if (!config->has("filament_multi_colour")) return color_packs;
color_packs = (config->option<ConfigOptionStrings>("filament_multi_colour"))->values;
auto slots = parse_mixed_gradient_slots(*config, color_packs.size());
for (size_t i = 0; i < color_packs.size(); ++i) {
if (slots[i].is_gradient)
color_packs[i] = slots[i].color_from + " " + slots[i].color_to;
}
return color_packs;
}
@@ -19757,9 +19887,37 @@ std::vector<std::string> Plater::get_filament_color_render_type() const
if (!config->has("filament_colour_type")) return ctype;
ctype = (config->option<ConfigOptionStrings>("filament_colour_type"))->values;
auto slots = parse_mixed_gradient_slots(*config, ctype.size());
while (ctype.size() < slots.size()) ctype.push_back("1");
for (size_t i = 0; i < ctype.size() && i < slots.size(); ++i) {
if (slots[i].is_gradient)
ctype[i] = "0";
}
return ctype;
}
std::vector<Plater::FilamentGradientInfo> Plater::get_filament_gradient_info() const
{
const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->project_config;
size_t n = get_extruder_colors_from_plater_config().size();
std::vector<FilamentGradientInfo> info(n);
auto slots = parse_mixed_gradient_slots(*config, n);
unsigned char rgba[4] = {};
for (size_t i = 0; i < n; ++i) {
if (!slots[i].is_gradient) continue;
info[i].is_gradient = true;
Slic3r::GUI::BitmapCache::parse_color4(slots[i].color_from, rgba);
info[i].color_from = {rgba[0] / 255.f, rgba[1] / 255.f, rgba[2] / 255.f, rgba[3] / 255.f};
Slic3r::GUI::BitmapCache::parse_color4(slots[i].color_to, rgba);
info[i].color_to = {rgba[0] / 255.f, rgba[1] / 255.f, rgba[2] / 255.f, rgba[3] / 255.f};
}
return info;
}
/* Get vector of colors used for rendering of a Preview scene in "Color print" mode
* It consists of extruder colors and colors, saved in model.custom_gcode_per_print_z
*/