Fixes from Full spectrum port

https://github.com/OrcaSlicer/OrcaSlicer/pull/14383
This commit is contained in:
Ian Bassi
2026-08-13 10:21:58 -03:00
committed by SoftFever
parent 86a7e93a48
commit 42f708bbb6
14 changed files with 288 additions and 65 deletions

View File

@@ -3795,7 +3795,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
return -1;
};
for (size_t i = 0; i < need_append_colors.size(); i++){
if (exist_filament_presets.size() >= size_t(EnforcerBlockerType::ExtruderMax)){
if (exist_filament_presets.size() >= MAXIMUM_AMS_SYNC_FILAMENT_NUMBER){
break;
}
auto idx = get_idx_in_array(exist_filament_presets, exist_colors, need_append_colors[i].filament_preset, need_append_colors[i].filament_color);

View File

@@ -1931,7 +1931,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
for (const ModelVolume *volume : volumes) {
const std::vector<bool> &volume_used_facet_states = volume->mmu_segmentation_facets.get_data().used_states;
assert(volume_used_facet_states.size() == used_facet_states.size());
// Sizes may legitimately differ: paint data stored before the state range was
// extended carries a shorter used_states vector. Merge over the common prefix.
for (size_t state_idx = 0; state_idx < std::min(volume_used_facet_states.size(), used_facet_states.size()); ++state_idx)
used_facet_states[state_idx] |= volume_used_facet_states[state_idx];
}

View File

@@ -1736,13 +1736,22 @@ TriangleSelector::TriangleSplittingData TriangleSelector::serialize() const {
data.used_states[n] = true;
if (n >= 3) {
assert(n <= 16);
if (n <= 16) {
// Store "11" plus 4 bits of (n-3).
data.bitstream.insert(data.bitstream.end(), { true, true });
n -= 3;
assert(n <= int(EnforcerBlockerType::ExtruderMax));
// Store "11" plus 4 bits of (n-3), which covers states 3..17. State 18 and
// above set that nibble to 0b1111 and store (n-18) in a second nibble. This is
// the encoding the CONST_FILAMENTS table in Model.cpp already writes for
// colored mesh imports.
data.bitstream.insert(data.bitstream.end(), { true, true });
auto &bitstream = data.bitstream;
auto push_nibble = [&bitstream](int value) {
for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx)
data.bitstream.push_back(n & (uint64_t(0b0001) << bit_idx));
bitstream.push_back(value & (uint64_t(0b0001) << bit_idx));
};
if (n <= 17) {
push_nibble(n - 3);
} else {
push_nibble(0b1111);
push_nibble(n - 18);
}
} else {
// Simple case, compatible with PrusaSlicer 2.3.1 and older for storing paint on supports and seams.
@@ -1810,6 +1819,12 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data,
n |= data.bitstream[ibit ++] << i;
return n;
};
// Decode a leaf state stored behind the "11" prefix: one nibble of (state-3) for states
// 3..17, or 0b1111 followed by a nibble of (state-18) above that.
auto decode_leaf_state = [&next_nibble]() {
const int nibble = next_nibble();
return EnforcerBlockerType(nibble == 0b1111 ? next_nibble() + 18 : nibble + 3);
};
parents.clear();
while (true) {
@@ -1818,8 +1833,8 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data,
int num_of_split_sides = code & 0b11;
int num_of_children = num_of_split_sides == 0 ? 0 : num_of_split_sides + 1;
bool is_split = num_of_children != 0;
// Only valid if not is_split. Value of the second nibble was subtracted by 3, so it is added back.
auto state = is_split ? EnforcerBlockerType::NONE : EnforcerBlockerType((code & 0b1100) == 0b1100 ? next_nibble() + 3 : code >> 2);
// Only valid if not is_split.
auto state = is_split ? EnforcerBlockerType::NONE : ((code & 0b1100) == 0b1100 ? decode_leaf_state() : EnforcerBlockerType(code >> 2));
// BBS
if (state == to_delete_filament)
@@ -1916,7 +1931,14 @@ void TriangleSelector::TriangleSplittingData::update_used_states(const size_t bi
if (const bool is_split = (code & 0b11) != 0; is_split)
continue;
const uint8_t facet_state = (code & 0b1100) == 0b1100 ? read_next_nibble() + 3 : code >> 2;
uint8_t facet_state;
if ((code & 0b1100) == 0b1100) {
// Leaf behind the "11" prefix: one nibble of (state-3), or 0b1111 + (state-18).
const uint8_t nibble = read_next_nibble();
facet_state = nibble == 0b1111 ? uint8_t(read_next_nibble() + 18) : uint8_t(nibble + 3);
} else {
facet_state = code >> 2;
}
assert(facet_state < this->used_states.size());
if (facet_state >= this->used_states.size())
continue;
@@ -1946,9 +1968,13 @@ bool TriangleSelector::has_facets(const TriangleSplittingData &data, const Enfor
auto num_children_or_state = [&next_nibble]() -> int {
int code = next_nibble();
int num_of_split_sides = code & 0b11;
return num_of_split_sides == 0 ?
((code & 0b1100) == 0b1100 ? next_nibble() + 3 : code >> 2) :
- num_of_split_sides - 1;
if (num_of_split_sides != 0)
return - num_of_split_sides - 1;
if ((code & 0b1100) != 0b1100)
return code >> 2;
// Leaf behind the "11" prefix: one nibble of (state-3), or 0b1111 + (state-18).
const int nibble = next_nibble();
return nibble == 0b1111 ? next_nibble() + 18 : nibble + 3;
};
int state = num_children_or_state();

View File

@@ -17,7 +17,9 @@ enum class EnforcerBlockerType : int8_t {
BLOCKER = 2,
// For the fuzzy skin, we use just two values (NONE and FUZZY_SKIN).
FUZZY_SKIN = ENFORCER,
// Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code.
// States 3..17 are serialized into 6 bits using a 2 bit prefix code; states 18 and above use
// one additional nibble (see TriangleSelector::serialize). ExtruderMax matches the last entry
// of CONST_FILAMENTS in Model.cpp, which encodes the same range for colored mesh imports.
Extruder1 = ENFORCER,
Extruder2 = BLOCKER,
Extruder3,
@@ -34,7 +36,23 @@ enum class EnforcerBlockerType : int8_t {
Extruder14,
Extruder15,
Extruder16,
ExtruderMax = Extruder16
Extruder17,
Extruder18,
Extruder19,
Extruder20,
Extruder21,
Extruder22,
Extruder23,
Extruder24,
Extruder25,
Extruder26,
Extruder27,
Extruder28,
Extruder29,
Extruder30,
Extruder31,
Extruder32,
ExtruderMax = Extruder32
};
// Type alias for the state mapping array to improve code readability

View File

@@ -64,6 +64,12 @@ static constexpr double LARGE_BED_THRESHOLD = 2147;
// Orca: maximum number of extruders is 64. For SEMM printers, it defines maximum filament number.
static constexpr size_t MAXIMUM_EXTRUDER_NUMBER = 64;
// Orca: how many filament slots syncing an AMS setup may create. This used to follow
// EnforcerBlockerType::ExtruderMax, which capped it at the number of paintable filaments; that
// limit has since been raised to 32, so the value is pinned here to keep AMS sync behaving as
// before for projects that use no mixed-colour filaments.
static constexpr size_t MAXIMUM_AMS_SYNC_FILAMENT_NUMBER = 16;
// Orca: maximum line width is 5 times the nozzle diameter
static constexpr float MAX_LINE_WIDTH_MULTIPLIER = 5;

View File

@@ -682,13 +682,19 @@ void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_obj
if (shader) {
if (idx == 0) {
int extruder_id = model_volume->extruder_id();
//to make black not too hard too see
ColorRGBA new_color = adjust_color_for_rendering(extruder_colors[extruder_id - 1]);
if (ban_light) {
new_color[3] = (255 - (extruder_id - 1))/255.0f;
// ORCA: extruder_id may be 0 (unset) or point past the colour list after a
// filament is deleted/remapped, so clamp the index instead of reading out of
// bounds.
if (!extruder_colors.empty()) {
int color_idx = std::clamp(extruder_id - 1, 0, int(extruder_colors.size()) - 1);
//to make black not too hard too see
ColorRGBA new_color = adjust_color_for_rendering(extruder_colors[color_idx]);
if (ban_light) {
new_color[3] = (255 - color_idx)/255.0f;
}
m.set_color(new_color);
// shader->set_uniform("uniform_color", new_color);
}
m.set_color(new_color);
// shader->set_uniform("uniform_color", new_color);
}
else {
if (idx <= extruder_colors.size()) {

View File

@@ -577,36 +577,40 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
}
// BBS
// A per-role filament override must name a real, physical filament. Out-of-range values are
// stale; a mixed-color slot is virtual and cannot be driven directly by a role override, so
// both are reset to 0 ("inherit the object's filament"). The object's own extruder assignment
// is what legitimately carries a mixed slot. Orca splits BBS's wall/solid_infill roles into
// six keys, so all of them are checked here.
static const char* keys[] = { "support_filament", "support_interface_filament",
"outer_wall_filament_id", "inner_wall_filament_id",
"sparse_infill_filament_id", "internal_solid_filament_id",
"top_surface_filament_id", "bottom_surface_filament_id" };
for (int i = 0; i < sizeof(keys) / sizeof(keys[0]); i++) {
std::string key = std::string(keys[i]);
// A filament override naming a slot that no longer exists is stale and falls back to the
// plater's value. Support is additionally restricted to physical filaments: the support paths
// (ToolOrdering::collect_extruders, Print::validate) consume support_filament directly, with
// no per-layer mixed resolution, so a virtual slot there would reach the G-code unresolved.
// The per-feature keys have no such restriction — LayerTools::extruder() and its siblings
// resolve a mixed slot to the physical filament chosen for each layer.
static const char* support_keys[] = { "support_filament", "support_interface_filament" };
static const char* feature_keys[] = { "outer_wall_filament_id", "inner_wall_filament_id",
"sparse_infill_filament_id", "internal_solid_filament_id",
"top_surface_filament_id", "bottom_surface_filament_id" };
auto reset_invalid_filament = [this, config, filament_cnt](const char* key, bool allow_mixed) {
auto* opt = dynamic_cast<ConfigOptionInt*>(config->option(key, false));
if (opt != nullptr) {
int val = opt->getInt();
bool out_of_range = val > filament_cnt;
bool is_mixed = (val > 0 && val <= filament_cnt &&
wxGetApp().preset_bundle->is_mixed_filament(val - 1));
if (out_of_range || is_mixed) {
DynamicPrintConfig new_conf = *config;
int new_value = 0;
if (out_of_range) {
const DynamicPrintConfig *conf_temp = wxGetApp().plater()->config();
if (conf_temp != nullptr && conf_temp->has(key))
new_value = conf_temp->opt_int(key);
}
new_conf.set_key_value(key, new ConfigOptionInt(new_value));
apply(config, &new_conf);
}
if (opt == nullptr)
return;
const int val = opt->getInt();
const bool out_of_range = val > filament_cnt;
const bool is_mixed = !allow_mixed && val > 0 && val <= filament_cnt &&
wxGetApp().preset_bundle->is_mixed_filament(val - 1);
if (!out_of_range && !is_mixed)
return;
DynamicPrintConfig new_conf = *config;
int new_value = 0;
if (out_of_range) {
const DynamicPrintConfig *conf_temp = wxGetApp().plater()->config();
if (conf_temp != nullptr && conf_temp->has(key))
new_value = conf_temp->opt_int(key);
}
}
new_conf.set_key_value(key, new ConfigOptionInt(new_value));
apply(config, &new_conf);
};
for (const char* key : support_keys)
reset_invalid_filament(key, false);
for (const char* key : feature_keys)
reset_invalid_filament(key, true);
// Sub-layer splitting divides each layer by the mix ratio; an adaptive layer profile makes
// those sub-layer heights vary per layer, which degrades the blend. Warn once per enable.

View File

@@ -9681,6 +9681,14 @@ void GLCanvas3D::_render_paint_toolbar() const
}
}
}
// ORCA: the loop above only produces a label for a slot whose preset is found in the preset
// collection, while the render loop below iterates extruder_num (= colour count). Pad the
// label arrays so a slot without a matching preset cannot index past them — reading a garbage
// std::string here crashes in ImGui::CalcTextSize (strlen).
while (int(filament_text_first_line.size()) < extruder_num) {
filament_text_first_line.emplace_back();
filament_text_second_line.emplace_back();
}
ImGuiWrapper& imgui = *wxGetApp().imgui();
const float canvas_w = float(get_canvas_size().get_width());

View File

@@ -454,7 +454,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGui::GetWindowDrawList()->AddRectFilledMultiColor(r_min, r_max, col_from, col_to, col_to, col_from);
}
if (extruder_idx < 16 && ImGui::IsItemHovered()) m_imgui->tooltip(_L("Shortcut Key ") + std::to_string(extruder_idx + 1), max_tooltip_width);
if (extruder_idx < int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT) && ImGui::IsItemHovered()) m_imgui->tooltip(_L("Shortcut Key ") + std::to_string(extruder_idx + 1), max_tooltip_width);
}
// ORCA: Remap filaments section (Border only, Title in border).
// Styled as a panel for visual grouping.

View File

@@ -73,11 +73,10 @@ public:
void data_changed(bool is_serializing) override;
// TriangleSelector::serialization/deserialization has a limit to store 19 different states.
// EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored.
// When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization
// will be also extended to support additional states, requiring at least one state to remain free out of 19 states.
static const constexpr size_t EXTRUDERS_LIMIT = 16;
// The paint material limit follows EnforcerBlockerType::ExtruderMax: TriangleSelector
// serialization covers the extended (17..32) range through an escape nibble. Mixed-color
// filaments occupy ordinary slots, so they draw from the same budget as physical ones.
static const constexpr size_t EXTRUDERS_LIMIT = static_cast<size_t>(EnforcerBlockerType::ExtruderMax);
// Endpoint colours for gradient mixed filaments, mirrored from Plater so the extruder
// swatches below can be drawn as a two-tone fade instead of a single blended colour.

View File

@@ -998,16 +998,40 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
keyCode = keyCode- WXK_NUMPAD0+'0';
}
if (keyCode >= '0' && keyCode <= '9') {
if (keyCode == '1' && !m_timer_set_color.IsRunning()) {
// The paint palette now reaches EXTRUDERS_LIMIT (mixed-color filaments share
// the same slots), so any leading digit that can start a valid two-digit
// number waits briefly for a second one.
const int digit = keyCode - '0';
const int shortcut_max = int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT);
auto can_start_two_digit = [shortcut_max](int d) { return d > 0 && d * 10 <= shortcut_max; };
auto select = [mmu_seg](int number) { return number > 0 && mmu_seg->on_number_key_down(number); };
if (m_timer_set_color.IsRunning() && m_pending_color_shortcut_tens > 0) {
const int two_digit = m_pending_color_shortcut_tens * 10 + digit;
const int pending = m_pending_color_shortcut_tens;
m_pending_color_shortcut_tens = 0;
m_timer_set_color.Stop();
if (two_digit <= shortcut_max) {
processed = select(two_digit);
} else {
// Out of range: commit the pending digit, then treat this one as new input.
processed = select(pending);
if (can_start_two_digit(digit)) {
m_pending_color_shortcut_tens = digit;
m_timer_set_color.StartOnce(500);
processed = true;
} else {
processed = select(digit) || processed;
}
}
}
else if (can_start_two_digit(digit)) {
m_pending_color_shortcut_tens = digit;
m_timer_set_color.StartOnce(500);
processed = true;
}
else if (keyCode < '7' && m_timer_set_color.IsRunning()) {
processed = mmu_seg->on_number_key_down(keyCode - '0'+10);
m_timer_set_color.Stop();
}
else {
processed = mmu_seg->on_number_key_down(keyCode - '0');
processed = select(digit);
}
}
else if (keyCode == 'F' || keyCode == 'T' || keyCode == 'S' || keyCode == 'C' || keyCode == 'H' || keyCode == 'G') {
@@ -1054,11 +1078,15 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
void GLGizmosManager::on_set_color_timer(wxTimerEvent& evt)
{
if (m_current == MmSegmentation) {
// No second digit arrived in time: commit the pending leading digit on its own.
if (m_current == MmSegmentation && m_pending_color_shortcut_tens > 0) {
GLGizmoMmuSegmentation* mmu_seg = dynamic_cast<GLGizmoMmuSegmentation*>(get_current());
mmu_seg->on_number_key_down(1);
m_parent.set_as_dirty();
if (mmu_seg != nullptr) {
mmu_seg->on_number_key_down(m_pending_color_shortcut_tens);
m_parent.set_as_dirty();
}
}
m_pending_color_shortcut_tens = 0;
}
void GLGizmosManager::update_after_undo_redo(const UndoRedo::Snapshot& snapshot)

View File

@@ -144,6 +144,8 @@ private:
//When there are more than 9 colors, shortcut key coloring
wxTimer m_timer_set_color;
// Leading digit of a two-digit color shortcut still waiting for its second digit.
int m_pending_color_shortcut_tens = 0;
void on_set_color_timer(wxTimerEvent& evt);
// key MENU_ICON_NAME, value = ImtextureID