mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-12 11:37:42 +00:00
Keep Infill Off the Shell and Give Grown Segments a Flat Profile
On a plate of nine tall cubes, sparse infill is 1.36 M of the 1.57 M segments and the geometric shell test kept 356 k of them: short gyroid segments hugging a wall have most of their cells on the boundary ring. Infill can never be a visible surface whatever its geometry, so the interior roles and gap fill are now off the shell before the geometry is asked. The set drawn at rest on that plate falls from 255 k to 76 k segments at the default view. The dragging set kept the surfaces exposed to the neighbouring layer in the layers it skips, which on those cubes was 171 k segments of footprint jitter and made a drag slower than resting once layers were merged. It now keeps what either side of the print can see, from the occupant maps, and the smaller of the two sets stays bound through a drag: 43 k instead of 262 k here. Grown segments showed bright stripes on the sides of tall parts. The segment cross-section is a diamond, full width at mid-height and a point at top and bottom, so a box grown four layers tall left a notch that deep between itself and the next one, and its corner normals lit a bright edge every few layers where the real print has one on every layer. When grown, the four profile points now become the corners of a rectangle, a flat top and a flat camera- facing side lit through their own normals, in both of the shader's view branches. Preference off is pixel identical to before.
This commit is contained in:
@@ -114,6 +114,17 @@ static const char* Segments_Vertex_Shader =
|
|||||||
"#endif\n"
|
"#endif\n"
|
||||||
" float view_right_sign = sign(dot(-camera_view_dir, line_right_dir));\n"
|
" float view_right_sign = sign(dot(-camera_view_dir, line_right_dir));\n"
|
||||||
" float view_top_sign = sign(dot(-camera_view_dir, line_up_dir));\n"
|
" float view_top_sign = sign(dot(-camera_view_dir, line_up_dir));\n"
|
||||||
|
" // ORCA: the cross-section is a diamond, full width at mid-height and a point at top and bottom.\n"
|
||||||
|
" // A segment grown to stand in for skipped layers would leave a notch that deep between itself\n"
|
||||||
|
" // and the next one, so its four profile points become the corners of a rectangle instead:\n"
|
||||||
|
" // top to top-left, right to top-right, bottom to bottom-right, left to bottom-left, which keeps\n"
|
||||||
|
" // the winding and turns the two drawn faces into a flat top and a flat camera-facing side.\n"
|
||||||
|
" if (height_scale > 1.0) {\n"
|
||||||
|
" if (signs.y > 0.0) signs = vec2(-1.0, 1.0);\n"
|
||||||
|
" else if (signs.y < 0.0) signs = vec2(1.0, -1.0);\n"
|
||||||
|
" else if (signs.x > 0.0) signs = vec2(1.0, 1.0);\n"
|
||||||
|
" else signs = vec2(-1.0, -1.0);\n"
|
||||||
|
" }\n"
|
||||||
" // ORCA: a segment standing in for the skipped layers below it grows downward to cover them\n"
|
" // ORCA: a segment standing in for the skipped layers below it grows downward to cover them\n"
|
||||||
" endpoint_pos -= (height_scale - 1.0) * 0.5 * height_width_angle.x * line_up_dir;\n"
|
" endpoint_pos -= (height_scale - 1.0) * 0.5 * height_width_angle.x * line_up_dir;\n"
|
||||||
" float half_height = 0.5 * height_scale * height_width_angle.x;\n"
|
" float half_height = 0.5 * height_scale * height_width_angle.x;\n"
|
||||||
@@ -143,7 +154,13 @@ static const char* Segments_Vertex_Shader =
|
|||||||
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
|
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
|
||||||
" // ORCA: Apply bias to z-position to avoid z-fighting\n"
|
" // ORCA: Apply bias to z-position to avoid z-fighting\n"
|
||||||
" eye_position.z += bias;\n"
|
" eye_position.z += bias;\n"
|
||||||
" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n"
|
" vec3 normal_dir = normalize(pos - endpoint_pos);\n"
|
||||||
|
" // ORCA: a grown box is lit flat: its camera-facing side through the side normal, its top-left\n"
|
||||||
|
" // corner through the up normal, so the side carries no bright edge every few layers\n"
|
||||||
|
" if (height_scale > 1.0)\n"
|
||||||
|
" normal_dir = (signs.x > 0.0) ? horizontal_sign * line_right_dir :\n"
|
||||||
|
" (signs.y > 0.0) ? vertical_sign * line_up_dir : -horizontal_sign * line_right_dir;\n"
|
||||||
|
" vec3 eye_normal = (view_matrix * vec4(normal_dir, 0.0)).xyz;\n"
|
||||||
" vec3 color_base = decode_color(texelFetch(color_tex, id).r);\n"
|
" vec3 color_base = decode_color(texelFetch(color_tex, id).r);\n"
|
||||||
" color = color_base * lighting(eye_position, eye_normal);\n"
|
" color = color_base * lighting(eye_position, eye_normal);\n"
|
||||||
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
|
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
|
||||||
|
|||||||
@@ -899,7 +899,6 @@ void ViewerImpl::reset()
|
|||||||
m_enabled_options_reduced_count = 0;
|
m_enabled_options_reduced_count = 0;
|
||||||
m_enabled_segments_rest_count = 0;
|
m_enabled_segments_rest_count = 0;
|
||||||
m_shell_bitset = BitSet<>();
|
m_shell_bitset = BitSet<>();
|
||||||
m_exposed_bitset = BitSet<>();
|
|
||||||
m_top_visible_bitset = BitSet<>();
|
m_top_visible_bitset = BitSet<>();
|
||||||
m_bottom_visible_bitset = BitSet<>();
|
m_bottom_visible_bitset = BitSet<>();
|
||||||
|
|
||||||
@@ -1194,11 +1193,19 @@ static bool is_interior_infill(EGCodeExtrusionRole role)
|
|||||||
role == EGCodeExtrusionRole::InternalBridgeInfill;
|
role == EGCodeExtrusionRole::InternalBridgeInfill;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ORCA: what can never be a visible surface whatever the geometry says: the interior roles, and
|
||||||
|
// gap fill, which sits between walls. Short sparse-infill segments hugging a wall would otherwise
|
||||||
|
// pass the geometric test by the thousand.
|
||||||
|
static bool is_hidden_in_shell(EGCodeExtrusionRole role)
|
||||||
|
{
|
||||||
|
return is_interior_infill(role) || role == EGCodeExtrusionRole::GapFill;
|
||||||
|
}
|
||||||
|
|
||||||
bool ViewerImpl::reduced_set_keeps(EReducedDetailMode mode, size_t i, const PathVertex& v) const
|
bool ViewerImpl::reduced_set_keeps(EReducedDetailMode mode, size_t i, const PathVertex& v) const
|
||||||
{
|
{
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case EReducedDetailMode::NoInternalInfill: return !is_interior_infill(v.role);
|
case EReducedDetailMode::NoInternalInfill: return !is_interior_infill(v.role);
|
||||||
case EReducedDetailMode::ShellOnly: return m_shell_bitset[i];
|
case EReducedDetailMode::ShellOnly: return !is_hidden_in_shell(v.role) && m_shell_bitset[i];
|
||||||
default: return true;
|
default: return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1401,18 +1408,18 @@ static void close_gaps(OccupancyGrid& grid, int radius, ClosingScratch& scratch)
|
|||||||
// EReducedDetailMode::ShellOnly can leave out everything the walls hide. Each layer is rasterized
|
// EReducedDetailMode::ShellOnly can leave out everything the walls hide. Each layer is rasterized
|
||||||
// into a coarse occupancy grid and closed, so that its footprint is solid whatever the infill;
|
// into a coarse occupancy grid and closed, so that its footprint is solid whatever the infill;
|
||||||
// a cell is then on the shell when it is filled and any of its six neighbours (four in the layer,
|
// a cell is then on the shell when it is filled and any of its six neighbours (four in the layer,
|
||||||
// the layer below, the layer above) is not, and exposed when it is the layer below or above that
|
// the layer below, the layer above) is not. A segment is kept when at least half of the cells it
|
||||||
// is missing. A segment is kept when at least half of the cells it crosses are shell cells: walls
|
// crosses are shell cells: walls run along the shell, infill only touches it at the ends. Purely
|
||||||
// run along the shell, infill only touches it at the ends. Purely geometric, so it works as well
|
// geometric, so it works as well for the wipe tower, whose every segment shares one role, as for
|
||||||
// for the wipe tower, whose every segment shares one role, as for the objects.
|
// the objects.
|
||||||
// The same pass records the highest and lowest layer occupying each cell over the whole print,
|
// The same pass records the highest and lowest layer occupying each cell over the whole print,
|
||||||
// which tells the segments that are the topmost or bottommost thing at their place: exposure to the
|
// which tells the segments that are the topmost or bottommost thing at their place, the only ones
|
||||||
// next layer alone would also keep whatever sits under an overhang, and the edge of a tower whose
|
// a view from above or below sees of a layer. Exposure to the neighbouring layer alone would also
|
||||||
// footprint lands a cell differently from one layer to the next.
|
// count whatever sits under an overhang, and the edge of a tower whose footprint lands a cell
|
||||||
|
// differently from one layer to the next.
|
||||||
void ViewerImpl::update_shell_bitset()
|
void ViewerImpl::update_shell_bitset()
|
||||||
{
|
{
|
||||||
m_shell_bitset = BitSet<>(m_vertices.size());
|
m_shell_bitset = BitSet<>(m_vertices.size());
|
||||||
m_exposed_bitset = BitSet<>(m_vertices.size());
|
|
||||||
m_top_visible_bitset = BitSet<>(m_vertices.size());
|
m_top_visible_bitset = BitSet<>(m_vertices.size());
|
||||||
m_bottom_visible_bitset = BitSet<>(m_vertices.size());
|
m_bottom_visible_bitset = BitSet<>(m_vertices.size());
|
||||||
if (m_vertices.size() < 2 || m_layers.empty())
|
if (m_vertices.size() < 2 || m_layers.empty())
|
||||||
@@ -1484,13 +1491,12 @@ void ViewerImpl::update_shell_bitset()
|
|||||||
|
|
||||||
const OccupancyGrid nothing(nx, ny);
|
const OccupancyGrid nothing(nx, ny);
|
||||||
|
|
||||||
// Classifies the layers in [first_layer, last_layer) and returns the segments kept, and among
|
// Classifies the layers in [first_layer, last_layer) and returns the segments kept, plus the
|
||||||
// them the exposed ones, plus the highest and lowest of these layers occupying each cell. Each
|
// highest and lowest of these layers occupying each cell. Each call owns its grids, so the layer
|
||||||
// call owns its grids, so the layer range can be split across threads.
|
// range can be split across threads.
|
||||||
static constexpr int32_t NO_LAYER = -1;
|
static constexpr int32_t NO_LAYER = -1;
|
||||||
struct Kept {
|
struct Kept {
|
||||||
std::vector<uint32_t> shell;
|
std::vector<uint32_t> shell;
|
||||||
std::vector<uint32_t> exposed;
|
|
||||||
std::vector<int32_t> top;
|
std::vector<int32_t> top;
|
||||||
std::vector<int32_t> bottom;
|
std::vector<int32_t> bottom;
|
||||||
// the rectangle of cells these layers touched, inclusive; empty while min > max
|
// the rectangle of cells these layers touched, inclusive; empty while min > max
|
||||||
@@ -1506,7 +1512,6 @@ void ViewerImpl::update_shell_bitset()
|
|||||||
kept.bottom.assign(cells_count, NO_LAYER);
|
kept.bottom.assign(cells_count, NO_LAYER);
|
||||||
std::vector<OccupancyGrid> footprints(3, OccupancyGrid(nx, ny));
|
std::vector<OccupancyGrid> footprints(3, OccupancyGrid(nx, ny));
|
||||||
OccupancyGrid shell_cells(nx, ny);
|
OccupancyGrid shell_cells(nx, ny);
|
||||||
OccupancyGrid exposed_cells(nx, ny);
|
|
||||||
ClosingScratch scratch;
|
ClosingScratch scratch;
|
||||||
const auto footprint = [&](size_t layer) -> OccupancyGrid& { return footprints[layer % 3]; };
|
const auto footprint = [&](size_t layer) -> OccupancyGrid& { return footprints[layer % 3]; };
|
||||||
const auto prepare = [&](size_t layer) {
|
const auto prepare = [&](size_t layer) {
|
||||||
@@ -1531,7 +1536,6 @@ void ViewerImpl::update_shell_bitset()
|
|||||||
const OccupancyGrid& above = (layer + 1 < layers_count) ? footprint(layer + 1) : nothing;
|
const OccupancyGrid& above = (layer + 1 < layers_count) ? footprint(layer + 1) : nothing;
|
||||||
|
|
||||||
shell_cells.clear();
|
shell_cells.clear();
|
||||||
exposed_cells.clear();
|
|
||||||
if (!cur.empty()) {
|
if (!cur.empty()) {
|
||||||
kept.min_x = (kept.max_x < kept.min_x) ? cur.min_x : std::min(kept.min_x, cur.min_x);
|
kept.min_x = (kept.max_x < kept.min_x) ? cur.min_x : std::min(kept.min_x, cur.min_x);
|
||||||
kept.min_y = (kept.max_y < kept.min_y) ? cur.min_y : std::min(kept.min_y, cur.min_y);
|
kept.min_y = (kept.max_y < kept.min_y) ? cur.min_y : std::min(kept.min_y, cur.min_y);
|
||||||
@@ -1548,11 +1552,9 @@ void ViewerImpl::update_shell_bitset()
|
|||||||
top = static_cast<int32_t>(layer);
|
top = static_cast<int32_t>(layer);
|
||||||
if (bottom == NO_LAYER)
|
if (bottom == NO_LAYER)
|
||||||
bottom = static_cast<int32_t>(layer);
|
bottom = static_cast<int32_t>(layer);
|
||||||
const bool exposed = !below.at(x, y) || !above.at(x, y);
|
if (!below.at(x, y) || !above.at(x, y) ||
|
||||||
if (exposed || !cur.at(x - 1, y) || !cur.at(x + 1, y) || !cur.at(x, y - 1) || !cur.at(x, y + 1))
|
!cur.at(x - 1, y) || !cur.at(x + 1, y) || !cur.at(x, y - 1) || !cur.at(x, y + 1))
|
||||||
shell_cells.set(x, y);
|
shell_cells.set(x, y);
|
||||||
if (exposed)
|
|
||||||
exposed_cells.set(x, y);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1562,16 +1564,12 @@ void ViewerImpl::update_shell_bitset()
|
|||||||
continue;
|
continue;
|
||||||
int total = 0;
|
int total = 0;
|
||||||
int on_shell = 0;
|
int on_shell = 0;
|
||||||
int on_exposed = 0;
|
|
||||||
for_each_cell(i, [&](int x, int y) {
|
for_each_cell(i, [&](int x, int y) {
|
||||||
++total;
|
++total;
|
||||||
on_shell += shell_cells.at(x, y);
|
on_shell += shell_cells.at(x, y);
|
||||||
on_exposed += exposed_cells.at(x, y);
|
|
||||||
});
|
});
|
||||||
if (2 * on_shell >= total)
|
if (2 * on_shell >= total)
|
||||||
kept.shell.push_back(static_cast<uint32_t>(i));
|
kept.shell.push_back(static_cast<uint32_t>(i));
|
||||||
if (2 * on_exposed >= total)
|
|
||||||
kept.exposed.push_back(static_cast<uint32_t>(i));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return kept;
|
return kept;
|
||||||
@@ -1588,8 +1586,6 @@ void ViewerImpl::update_shell_bitset()
|
|||||||
const Kept kept = f.get();
|
const Kept kept = f.get();
|
||||||
for (uint32_t i : kept.shell)
|
for (uint32_t i : kept.shell)
|
||||||
m_shell_bitset.set(i);
|
m_shell_bitset.set(i);
|
||||||
for (uint32_t i : kept.exposed)
|
|
||||||
m_exposed_bitset.set(i);
|
|
||||||
for (int y = kept.min_y; y <= kept.max_y; ++y) {
|
for (int y = kept.min_y; y <= kept.max_y; ++y) {
|
||||||
for (int x = kept.min_x; x <= kept.max_x; ++x) {
|
for (int x = kept.min_x; x <= kept.max_x; ++x) {
|
||||||
const size_t c = cell_index(x, y);
|
const size_t c = cell_index(x, y);
|
||||||
@@ -1718,20 +1714,21 @@ void ViewerImpl::update_enabled_entities()
|
|||||||
#ifndef ENABLE_OPENGL_ES
|
#ifndef ENABLE_OPENGL_ES
|
||||||
const bool whole_layer = v.layer_id == layers_range[0] || v.layer_id == layers_range[1];
|
const bool whole_layer = v.layer_id == layers_range[0] || v.layer_id == layers_range[1];
|
||||||
const bool keep_anyway = whole_layer || !v.is_extrusion();
|
const bool keep_anyway = whole_layer || !v.is_extrusion();
|
||||||
const bool classified = v.is_extrusion() && m_exposed_bitset.size == m_vertices.size();
|
const bool classified = v.is_extrusion() && !is_hidden_in_shell(v.role) && m_top_visible_bitset.size == m_vertices.size();
|
||||||
const bool exposed = classified && m_exposed_bitset[i];
|
const bool visible_from_above = classified && m_top_visible_bitset[i];
|
||||||
|
const bool visible_from_below = classified && m_bottom_visible_bitset[i];
|
||||||
if (build_rest && !v.is_option()) {
|
if (build_rest && !v.is_option()) {
|
||||||
const bool skipped = !whole_layer && rest_stride > 1 && (v.layer_id % rest_stride) != 0;
|
const bool skipped = !whole_layer && rest_stride > 1 && (v.layer_id % rest_stride) != 0;
|
||||||
// a skipped layer keeps only what the camera's side of the print can see of it
|
// a skipped layer keeps only what the camera's side of the print can see of it
|
||||||
const bool visible = classified && (m_settings.rest_view_from_above ? m_top_visible_bitset[i] : m_bottom_visible_bitset[i]);
|
const bool visible = m_settings.rest_view_from_above ? visible_from_above : visible_from_below;
|
||||||
if (skipped ? visible : (keep_anyway || reduced_set_keeps(m_settings.rest_detail_mode, i, v)))
|
if (skipped ? visible : (keep_anyway || reduced_set_keeps(m_settings.rest_detail_mode, i, v)))
|
||||||
enabled_segments_rest.push_back(static_cast<uint32_t>(i));
|
enabled_segments_rest.push_back(static_cast<uint32_t>(i));
|
||||||
}
|
}
|
||||||
if (!build_reduced)
|
if (!build_reduced)
|
||||||
continue;
|
continue;
|
||||||
if (!whole_layer && (v.layer_id % layer_stride) != 0) {
|
if (!whole_layer && (v.layer_id % layer_stride) != 0) {
|
||||||
// the exposed surfaces of a skipped layer stay, so that a step does not vanish
|
// the surfaces of a skipped layer that either side can see stay, so that a step does not vanish
|
||||||
if (shell_reduced && exposed)
|
if (shell_reduced && (visible_from_above || visible_from_below))
|
||||||
enabled_segments_reduced.push_back(static_cast<uint32_t>(i));
|
enabled_segments_reduced.push_back(static_cast<uint32_t>(i));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -331,11 +331,8 @@ private:
|
|||||||
// computed on demand by update_shell_bitset() for EReducedDetailMode::ShellOnly
|
// computed on demand by update_shell_bitset() for EReducedDetailMode::ShellOnly
|
||||||
//
|
//
|
||||||
BitSet<> m_shell_bitset;
|
BitSet<> m_shell_bitset;
|
||||||
// the subset of those that are exposed from above or below: the surfaces a view from the top
|
// the segments that are the topmost, or the bottommost, thing at their place in the whole
|
||||||
// or bottom sees, kept in every layer even while layers are being skipped
|
// print: what a view from above, or below, sees of a layer, kept even while layers are skipped
|
||||||
BitSet<> m_exposed_bitset;
|
|
||||||
// narrower still: the segments that are the topmost, or the bottommost, thing at their place
|
|
||||||
// in the whole print, which is all a view from straight above, or below, can see
|
|
||||||
BitSet<> m_top_visible_bitset;
|
BitSet<> m_top_visible_bitset;
|
||||||
BitSet<> m_bottom_visible_bitset;
|
BitSet<> m_bottom_visible_bitset;
|
||||||
#endif // ENABLE_OPENGL_ES
|
#endif // ENABLE_OPENGL_ES
|
||||||
@@ -537,13 +534,16 @@ private:
|
|||||||
size_t m_enabled_options_tex_size{ 0 };
|
size_t m_enabled_options_tex_size{ 0 };
|
||||||
|
|
||||||
// The set the next draw reads from: the reduced one only while the user is dragging, and only
|
// The set the next draw reads from: the reduced one only while the user is dragging, and only
|
||||||
// if a reduced set is being built at all; otherwise the rest set, if one is being built.
|
// if a reduced set is being built at all; otherwise the rest set, if one is being built. A rest
|
||||||
bool use_reduced_set() const {
|
// set already smaller than the reduced one, as it is with layers merged looking from above,
|
||||||
return m_settings.reduced_detail && m_settings.reduced_detail_mode != EReducedDetailMode::Off;
|
// stays bound through the drag: it was right for the camera the drag started from.
|
||||||
}
|
|
||||||
bool build_rest_set() const {
|
bool build_rest_set() const {
|
||||||
return m_settings.rest_detail_mode != EReducedDetailMode::Off && m_settings.rest_detail_mode != EReducedDetailMode::LayersOnly;
|
return m_settings.rest_detail_mode != EReducedDetailMode::Off && m_settings.rest_detail_mode != EReducedDetailMode::LayersOnly;
|
||||||
}
|
}
|
||||||
|
bool use_reduced_set() const {
|
||||||
|
return m_settings.reduced_detail && m_settings.reduced_detail_mode != EReducedDetailMode::Off &&
|
||||||
|
!(build_rest_set() && m_enabled_segments_rest_count < m_enabled_segments_reduced_count);
|
||||||
|
}
|
||||||
bool use_rest_set() const { return !use_reduced_set() && build_rest_set(); }
|
bool use_rest_set() const { return !use_reduced_set() && build_rest_set(); }
|
||||||
// how many layers each drawn segment of the bound set stands in for
|
// how many layers each drawn segment of the bound set stands in for
|
||||||
float active_height_scale() const {
|
float active_height_scale() const {
|
||||||
|
|||||||
Reference in New Issue
Block a user