mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-12 03:27:55 +00:00
Fixes from Full spectrum port
https://github.com/OrcaSlicer/OrcaSlicer/pull/14383
This commit is contained in:
@@ -3795,7 +3795,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
|||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
for (size_t i = 0; i < need_append_colors.size(); i++){
|
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;
|
break;
|
||||||
}
|
}
|
||||||
auto idx = get_idx_in_array(exist_filament_presets, exist_colors, need_append_colors[i].filament_preset, need_append_colors[i].filament_color);
|
auto idx = get_idx_in_array(exist_filament_presets, exist_colors, need_append_colors[i].filament_preset, need_append_colors[i].filament_color);
|
||||||
|
|||||||
@@ -1931,7 +1931,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
for (const ModelVolume *volume : volumes) {
|
for (const ModelVolume *volume : volumes) {
|
||||||
const std::vector<bool> &volume_used_facet_states = volume->mmu_segmentation_facets.get_data().used_states;
|
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)
|
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];
|
used_facet_states[state_idx] |= volume_used_facet_states[state_idx];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1736,13 +1736,22 @@ TriangleSelector::TriangleSplittingData TriangleSelector::serialize() const {
|
|||||||
data.used_states[n] = true;
|
data.used_states[n] = true;
|
||||||
|
|
||||||
if (n >= 3) {
|
if (n >= 3) {
|
||||||
assert(n <= 16);
|
assert(n <= int(EnforcerBlockerType::ExtruderMax));
|
||||||
if (n <= 16) {
|
// Store "11" plus 4 bits of (n-3), which covers states 3..17. State 18 and
|
||||||
// Store "11" plus 4 bits of (n-3).
|
// above set that nibble to 0b1111 and store (n-18) in a second nibble. This is
|
||||||
data.bitstream.insert(data.bitstream.end(), { true, true });
|
// the encoding the CONST_FILAMENTS table in Model.cpp already writes for
|
||||||
n -= 3;
|
// 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)
|
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 {
|
} else {
|
||||||
// Simple case, compatible with PrusaSlicer 2.3.1 and older for storing paint on supports and seams.
|
// 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;
|
n |= data.bitstream[ibit ++] << i;
|
||||||
return n;
|
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();
|
parents.clear();
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -1818,8 +1833,8 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data,
|
|||||||
int num_of_split_sides = code & 0b11;
|
int num_of_split_sides = code & 0b11;
|
||||||
int num_of_children = num_of_split_sides == 0 ? 0 : num_of_split_sides + 1;
|
int num_of_children = num_of_split_sides == 0 ? 0 : num_of_split_sides + 1;
|
||||||
bool is_split = num_of_children != 0;
|
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.
|
// Only valid if not is_split.
|
||||||
auto state = is_split ? EnforcerBlockerType::NONE : EnforcerBlockerType((code & 0b1100) == 0b1100 ? next_nibble() + 3 : code >> 2);
|
auto state = is_split ? EnforcerBlockerType::NONE : ((code & 0b1100) == 0b1100 ? decode_leaf_state() : EnforcerBlockerType(code >> 2));
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
if (state == to_delete_filament)
|
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)
|
if (const bool is_split = (code & 0b11) != 0; is_split)
|
||||||
continue;
|
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());
|
assert(facet_state < this->used_states.size());
|
||||||
if (facet_state >= this->used_states.size())
|
if (facet_state >= this->used_states.size())
|
||||||
continue;
|
continue;
|
||||||
@@ -1946,9 +1968,13 @@ bool TriangleSelector::has_facets(const TriangleSplittingData &data, const Enfor
|
|||||||
auto num_children_or_state = [&next_nibble]() -> int {
|
auto num_children_or_state = [&next_nibble]() -> int {
|
||||||
int code = next_nibble();
|
int code = next_nibble();
|
||||||
int num_of_split_sides = code & 0b11;
|
int num_of_split_sides = code & 0b11;
|
||||||
return num_of_split_sides == 0 ?
|
if (num_of_split_sides != 0)
|
||||||
((code & 0b1100) == 0b1100 ? next_nibble() + 3 : code >> 2) :
|
return - num_of_split_sides - 1;
|
||||||
- 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();
|
int state = num_children_or_state();
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ enum class EnforcerBlockerType : int8_t {
|
|||||||
BLOCKER = 2,
|
BLOCKER = 2,
|
||||||
// For the fuzzy skin, we use just two values (NONE and FUZZY_SKIN).
|
// For the fuzzy skin, we use just two values (NONE and FUZZY_SKIN).
|
||||||
FUZZY_SKIN = ENFORCER,
|
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,
|
Extruder1 = ENFORCER,
|
||||||
Extruder2 = BLOCKER,
|
Extruder2 = BLOCKER,
|
||||||
Extruder3,
|
Extruder3,
|
||||||
@@ -34,7 +36,23 @@ enum class EnforcerBlockerType : int8_t {
|
|||||||
Extruder14,
|
Extruder14,
|
||||||
Extruder15,
|
Extruder15,
|
||||||
Extruder16,
|
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
|
// Type alias for the state mapping array to improve code readability
|
||||||
|
|||||||
@@ -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.
|
// Orca: maximum number of extruders is 64. For SEMM printers, it defines maximum filament number.
|
||||||
static constexpr size_t MAXIMUM_EXTRUDER_NUMBER = 64;
|
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
|
// Orca: maximum line width is 5 times the nozzle diameter
|
||||||
static constexpr float MAX_LINE_WIDTH_MULTIPLIER = 5;
|
static constexpr float MAX_LINE_WIDTH_MULTIPLIER = 5;
|
||||||
|
|
||||||
|
|||||||
@@ -682,13 +682,19 @@ void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_obj
|
|||||||
if (shader) {
|
if (shader) {
|
||||||
if (idx == 0) {
|
if (idx == 0) {
|
||||||
int extruder_id = model_volume->extruder_id();
|
int extruder_id = model_volume->extruder_id();
|
||||||
//to make black not too hard too see
|
// ORCA: extruder_id may be 0 (unset) or point past the colour list after a
|
||||||
ColorRGBA new_color = adjust_color_for_rendering(extruder_colors[extruder_id - 1]);
|
// filament is deleted/remapped, so clamp the index instead of reading out of
|
||||||
if (ban_light) {
|
// bounds.
|
||||||
new_color[3] = (255 - (extruder_id - 1))/255.0f;
|
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 {
|
else {
|
||||||
if (idx <= extruder_colors.size()) {
|
if (idx <= extruder_colors.size()) {
|
||||||
|
|||||||
@@ -577,36 +577,40 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
// A per-role filament override must name a real, physical filament. Out-of-range values are
|
// A filament override naming a slot that no longer exists is stale and falls back to the
|
||||||
// stale; a mixed-color slot is virtual and cannot be driven directly by a role override, so
|
// plater's value. Support is additionally restricted to physical filaments: the support paths
|
||||||
// both are reset to 0 ("inherit the object's filament"). The object's own extruder assignment
|
// (ToolOrdering::collect_extruders, Print::validate) consume support_filament directly, with
|
||||||
// is what legitimately carries a mixed slot. Orca splits BBS's wall/solid_infill roles into
|
// no per-layer mixed resolution, so a virtual slot there would reach the G-code unresolved.
|
||||||
// six keys, so all of them are checked here.
|
// The per-feature keys have no such restriction — LayerTools::extruder() and its siblings
|
||||||
static const char* keys[] = { "support_filament", "support_interface_filament",
|
// resolve a mixed slot to the physical filament chosen for each layer.
|
||||||
"outer_wall_filament_id", "inner_wall_filament_id",
|
static const char* support_keys[] = { "support_filament", "support_interface_filament" };
|
||||||
"sparse_infill_filament_id", "internal_solid_filament_id",
|
static const char* feature_keys[] = { "outer_wall_filament_id", "inner_wall_filament_id",
|
||||||
"top_surface_filament_id", "bottom_surface_filament_id" };
|
"sparse_infill_filament_id", "internal_solid_filament_id",
|
||||||
for (int i = 0; i < sizeof(keys) / sizeof(keys[0]); i++) {
|
"top_surface_filament_id", "bottom_surface_filament_id" };
|
||||||
std::string key = std::string(keys[i]);
|
auto reset_invalid_filament = [this, config, filament_cnt](const char* key, bool allow_mixed) {
|
||||||
auto* opt = dynamic_cast<ConfigOptionInt*>(config->option(key, false));
|
auto* opt = dynamic_cast<ConfigOptionInt*>(config->option(key, false));
|
||||||
if (opt != nullptr) {
|
if (opt == nullptr)
|
||||||
int val = opt->getInt();
|
return;
|
||||||
bool out_of_range = val > filament_cnt;
|
const int val = opt->getInt();
|
||||||
bool is_mixed = (val > 0 && val <= filament_cnt &&
|
const bool out_of_range = val > filament_cnt;
|
||||||
wxGetApp().preset_bundle->is_mixed_filament(val - 1));
|
const bool is_mixed = !allow_mixed && val > 0 && val <= filament_cnt &&
|
||||||
if (out_of_range || is_mixed) {
|
wxGetApp().preset_bundle->is_mixed_filament(val - 1);
|
||||||
DynamicPrintConfig new_conf = *config;
|
if (!out_of_range && !is_mixed)
|
||||||
int new_value = 0;
|
return;
|
||||||
if (out_of_range) {
|
DynamicPrintConfig new_conf = *config;
|
||||||
const DynamicPrintConfig *conf_temp = wxGetApp().plater()->config();
|
int new_value = 0;
|
||||||
if (conf_temp != nullptr && conf_temp->has(key))
|
if (out_of_range) {
|
||||||
new_value = conf_temp->opt_int(key);
|
const DynamicPrintConfig *conf_temp = wxGetApp().plater()->config();
|
||||||
}
|
if (conf_temp != nullptr && conf_temp->has(key))
|
||||||
new_conf.set_key_value(key, new ConfigOptionInt(new_value));
|
new_value = conf_temp->opt_int(key);
|
||||||
apply(config, &new_conf);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
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
|
// 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.
|
// those sub-layer heights vary per layer, which degrades the blend. Warn once per enable.
|
||||||
|
|||||||
@@ -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();
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||||
const float canvas_w = float(get_canvas_size().get_width());
|
const float canvas_w = float(get_canvas_size().get_width());
|
||||||
|
|||||||
@@ -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);
|
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).
|
// ORCA: Remap filaments section (Border only, Title in border).
|
||||||
// Styled as a panel for visual grouping.
|
// Styled as a panel for visual grouping.
|
||||||
|
|||||||
@@ -73,11 +73,10 @@ public:
|
|||||||
|
|
||||||
void data_changed(bool is_serializing) override;
|
void data_changed(bool is_serializing) override;
|
||||||
|
|
||||||
// TriangleSelector::serialization/deserialization has a limit to store 19 different states.
|
// The paint material limit follows EnforcerBlockerType::ExtruderMax: TriangleSelector
|
||||||
// EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored.
|
// serialization covers the extended (17..32) range through an escape nibble. Mixed-color
|
||||||
// When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization
|
// filaments occupy ordinary slots, so they draw from the same budget as physical ones.
|
||||||
// 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 = static_cast<size_t>(EnforcerBlockerType::ExtruderMax);
|
||||||
static const constexpr size_t EXTRUDERS_LIMIT = 16;
|
|
||||||
|
|
||||||
// Endpoint colours for gradient mixed filaments, mirrored from Plater so the extruder
|
// 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.
|
// swatches below can be drawn as a two-tone fade instead of a single blended colour.
|
||||||
|
|||||||
@@ -998,16 +998,40 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
|
|||||||
keyCode = keyCode- WXK_NUMPAD0+'0';
|
keyCode = keyCode- WXK_NUMPAD0+'0';
|
||||||
}
|
}
|
||||||
if (keyCode >= '0' && keyCode <= '9') {
|
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);
|
m_timer_set_color.StartOnce(500);
|
||||||
processed = true;
|
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 {
|
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') {
|
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)
|
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());
|
GLGizmoMmuSegmentation* mmu_seg = dynamic_cast<GLGizmoMmuSegmentation*>(get_current());
|
||||||
mmu_seg->on_number_key_down(1);
|
if (mmu_seg != nullptr) {
|
||||||
m_parent.set_as_dirty();
|
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)
|
void GLGizmosManager::update_after_undo_redo(const UndoRedo::Snapshot& snapshot)
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ private:
|
|||||||
|
|
||||||
//When there are more than 9 colors, shortcut key coloring
|
//When there are more than 9 colors, shortcut key coloring
|
||||||
wxTimer m_timer_set_color;
|
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);
|
void on_set_color_timer(wxTimerEvent& evt);
|
||||||
|
|
||||||
// key MENU_ICON_NAME, value = ImtextureID
|
// key MENU_ICON_NAME, value = ImtextureID
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_mutable_priority_queue.cpp
|
test_mutable_priority_queue.cpp
|
||||||
test_nozzle_volume_type.cpp
|
test_nozzle_volume_type.cpp
|
||||||
test_stl.cpp
|
test_stl.cpp
|
||||||
|
test_triangle_selector.cpp
|
||||||
test_meshboolean.cpp
|
test_meshboolean.cpp
|
||||||
test_marchingsquares.cpp
|
test_marchingsquares.cpp
|
||||||
test_model.cpp
|
test_model.cpp
|
||||||
|
|||||||
124
tests/libslic3r/test_triangle_selector.cpp
Normal file
124
tests/libslic3r/test_triangle_selector.cpp
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include "libslic3r/TriangleSelector.hpp"
|
||||||
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
// A sphere gives well over ExtruderMax original facets, so every extruder state can be assigned
|
||||||
|
// to a facet of its own without any splitting getting in the way.
|
||||||
|
static TriangleMesh test_mesh() { return make_sphere(5., 2 * PI / 24); }
|
||||||
|
|
||||||
|
// Read the nibble_idx-th 4-bit group of a serialized bitstream, least significant bit first.
|
||||||
|
static int nibble_at(const std::vector<bool> &bitstream, size_t nibble_idx)
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
for (size_t bit = 0; bit < 4; ++bit)
|
||||||
|
n |= int(bitstream[nibble_idx * 4 + bit]) << bit;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Every extruder state survives a serialize/deserialize round trip", "[TriangleSelector]")
|
||||||
|
{
|
||||||
|
const TriangleMesh mesh = test_mesh();
|
||||||
|
const int max_state = int(EnforcerBlockerType::ExtruderMax);
|
||||||
|
REQUIRE(int(mesh.its.indices.size()) >= max_state);
|
||||||
|
|
||||||
|
TriangleSelector selector(mesh);
|
||||||
|
for (int state = 1; state <= max_state; ++state)
|
||||||
|
selector.set_facet(state - 1, EnforcerBlockerType(state));
|
||||||
|
|
||||||
|
TriangleSelector restored(mesh);
|
||||||
|
restored.deserialize(selector.serialize());
|
||||||
|
|
||||||
|
for (int state = 1; state <= max_state; ++state) {
|
||||||
|
INFO("Extruder " << state);
|
||||||
|
REQUIRE(restored.has_facets(EnforcerBlockerType(state)));
|
||||||
|
REQUIRE(restored.num_facets(EnforcerBlockerType(state)) == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Serialized data reports the extruder states it uses", "[TriangleSelector]")
|
||||||
|
{
|
||||||
|
const TriangleMesh mesh = test_mesh();
|
||||||
|
TriangleSelector selector(mesh);
|
||||||
|
selector.set_facet(0, EnforcerBlockerType::Extruder16);
|
||||||
|
selector.set_facet(1, EnforcerBlockerType::Extruder32);
|
||||||
|
|
||||||
|
const TriangleSelector::TriangleSplittingData data = selector.serialize();
|
||||||
|
|
||||||
|
REQUIRE(data.used_states.size() == size_t(EnforcerBlockerType::ExtruderMax) + 1);
|
||||||
|
REQUIRE(data.used_states[size_t(EnforcerBlockerType::Extruder16)]);
|
||||||
|
REQUIRE(data.used_states[size_t(EnforcerBlockerType::Extruder32)]);
|
||||||
|
REQUIRE_FALSE(data.used_states[size_t(EnforcerBlockerType::Extruder17)]);
|
||||||
|
|
||||||
|
SECTION("used_states recomputed from the bitstream agrees") {
|
||||||
|
TriangleSelector::TriangleSplittingData recomputed = data;
|
||||||
|
recomputed.reset_used_states();
|
||||||
|
recomputed.update_used_states(0);
|
||||||
|
REQUIRE(recomputed.used_states == data.used_states);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("has_facets on the raw data agrees") {
|
||||||
|
REQUIRE(TriangleSelector::has_facets(data, EnforcerBlockerType::Extruder32));
|
||||||
|
REQUIRE_FALSE(TriangleSelector::has_facets(data, EnforcerBlockerType::Extruder17));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// States 3..17 must keep the pre-existing encoding ("11" prefix plus one nibble of state-3) so
|
||||||
|
// projects written by older builds stay readable and newly written ones stay readable by them.
|
||||||
|
TEST_CASE("Extruder states up to 17 keep the single-nibble encoding", "[TriangleSelector]")
|
||||||
|
{
|
||||||
|
const int state = GENERATE(3, 8, 16, 17);
|
||||||
|
|
||||||
|
TriangleSelector selector(test_mesh());
|
||||||
|
selector.set_facet(0, EnforcerBlockerType(state));
|
||||||
|
const std::vector<bool> bitstream = selector.serialize().bitstream;
|
||||||
|
|
||||||
|
INFO("Extruder " << state);
|
||||||
|
// Two nibbles: the "11"-prefixed leaf code, then the state itself.
|
||||||
|
REQUIRE(bitstream.size() == 8);
|
||||||
|
REQUIRE(nibble_at(bitstream, 0) == 0b1100);
|
||||||
|
REQUIRE(nibble_at(bitstream, 1) == state - 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// States 18 and above set the state nibble to 0b1111 and carry (state-18) in one more nibble.
|
||||||
|
TEST_CASE("Extruder states above 17 are encoded in a second nibble", "[TriangleSelector]")
|
||||||
|
{
|
||||||
|
const int state = GENERATE(18, 25, 32);
|
||||||
|
|
||||||
|
TriangleSelector selector(test_mesh());
|
||||||
|
selector.set_facet(0, EnforcerBlockerType(state));
|
||||||
|
const std::vector<bool> bitstream = selector.serialize().bitstream;
|
||||||
|
|
||||||
|
INFO("Extruder " << state);
|
||||||
|
REQUIRE(bitstream.size() == 12);
|
||||||
|
REQUIRE(nibble_at(bitstream, 0) == 0b1100);
|
||||||
|
REQUIRE(nibble_at(bitstream, 1) == 0b1111);
|
||||||
|
REQUIRE(nibble_at(bitstream, 2) == state - 18);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model.cpp writes these hex strings into the 3MF for colored mesh imports; the selector must
|
||||||
|
// decode exactly the states that table assigns to them.
|
||||||
|
TEST_CASE("Extruder states match the CONST_FILAMENTS hex encoding", "[TriangleSelector]")
|
||||||
|
{
|
||||||
|
struct Case { const char *hex; int state; };
|
||||||
|
const auto c = GENERATE(values<Case>({
|
||||||
|
{"8", 2}, {"0C", 3}, {"DC", 16}, {"EC", 17}, {"0FC", 18}, {"EFC", 32},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// get_triangle_as_string emits the nibbles most significant first, so read the hex backwards.
|
||||||
|
std::vector<bool> bitstream;
|
||||||
|
for (auto it = std::string(c.hex).rbegin(); it != std::string(c.hex).rend(); ++it) {
|
||||||
|
const int nibble = *it >= 'A' ? (*it - 'A' + 10) : (*it - '0');
|
||||||
|
for (int bit = 0; bit < 4; ++bit)
|
||||||
|
bitstream.push_back((nibble >> bit) & 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TriangleSelector::TriangleSplittingData data;
|
||||||
|
data.triangles_to_split.emplace_back(0, 0);
|
||||||
|
data.bitstream = bitstream;
|
||||||
|
|
||||||
|
INFO("Hex " << c.hex << " -> extruder " << c.state);
|
||||||
|
REQUIRE(TriangleSelector::has_facets(data, EnforcerBlockerType(c.state)));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user