Run a layer's regions in parallel where they are independent

detect_surfaces_type, process_external_surfaces and the vertical shells
each waited on their own heaviest layer in turn. The LOTR map plate
slices in ~10.5 min at 0.1 mm / 2000k, was ~11.5; ~87 s at normal
settings, was ~97.
This commit is contained in:
ExPikaPaka
2026-09-22 22:14:39 +02:00
parent 2c8296a9bc
commit ec29903e4c
+181 -137
View File
@@ -1666,7 +1666,9 @@ void PrintObject::detect_surfaces_type()
bool interface_shells = ! spiral_mode && m_config.interface_shells.value; bool interface_shells = ! spiral_mode && m_config.interface_shells.value;
size_t num_layers = spiral_mode ? std::min(size_t(this->printing_region(0).config().bottom_shell_layers), m_layers.size()) : m_layers.size(); size_t num_layers = spiral_mode ? std::min(size_t(this->printing_region(0).config().bottom_shell_layers), m_layers.size()) : m_layers.size();
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { // The regions of a layer do not see each other here, and a layer cut through a fine relief takes far longer than the
// others, so the regions run next to each other instead of one after another, each still over all layers.
tbb::parallel_for(size_t(0), this->num_printing_regions(), [&](size_t region_id) {
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " in parallel - start"; BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " in parallel - start";
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (Layer *layer : m_layers) for (Layer *layer : m_layers)
@@ -2086,29 +2088,31 @@ void PrintObject::detect_surfaces_type()
} }
} }
); );
// ==============================================================================================================
// === ORCA: Interim workaround - for now the new stInternalAfterExternalBridge surfaace is re-classified ==============
// === back to a bottom bridge. As a starting point, this improves bridging reliability as it extrudes ==========
// === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase ==========
// ==============================================================================================================
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
tbb::parallel_for( tbb::blocked_range<size_t>(0, m_layers.size()), [this, region_id](const tbb::blocked_range<size_t> &range) {
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) {
Surfaces &surfs = m_layers[idx_layer]->m_regions[region_id]->slices.surfaces;
for (Surface &s : surfs) {
if (s.surface_type == stInternalAfterExternalBridge) {
s.surface_type = stBottomBridge;
}
}
}
}
);
}
} }
// ============================================================================================================== // ==============================================================================================================
// === ORCA: End of second external bridge layer changes ======================================================= // === ORCA: End of second external bridge layer changes =======================================================
// ============================================================================================================== // ==============================================================================================================
}); // for each this->print->region_count
// ==============================================================================================================
// === ORCA: Interim workaround - for now the new stInternalAfterExternalBridge surfaace is re-classified ==============
// === back to a bottom bridge. As a starting point, this improves bridging reliability as it extrudes ==========
// === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase ==========
// ==============================================================================================================
// Once all the regions have their second bridge layer, and before their slices are trimmed into fill surfaces below.
if ((this->config().enable_extra_bridge_layer.value == eblApplyToAll) || (this->config().enable_extra_bridge_layer.value == eblExternalBridgeOnly)) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_layers.size()), [this](const tbb::blocked_range<size_t> &range) {
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer)
for (LayerRegion *layerm : m_layers[idx_layer]->regions())
for (Surface &s : layerm->slices.surfaces)
if (s.surface_type == stInternalAfterExternalBridge)
s.surface_type = stBottomBridge;
});
m_print->throw_if_canceled();
}
tbb::parallel_for(size_t(0), this->num_printing_regions(), [&](size_t region_id) {
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start"; BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start";
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
tbb::parallel_for( tbb::parallel_for(
@@ -2125,7 +2129,7 @@ void PrintObject::detect_surfaces_type()
}); });
m_print->throw_if_canceled(); m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - end"; BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - end";
} // for each this->print->region_count });
// Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.) // Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.)
m_typed_slices = true; m_typed_slices = true;
@@ -2192,8 +2196,10 @@ void PrintObject::process_external_surfaces()
BOOST_LOG_TRIVIAL(debug) << "Collecting surfaces covered with extrusions in parallel - end"; BOOST_LOG_TRIVIAL(debug) << "Collecting surfaces covered with extrusions in parallel - end";
} }
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - start";
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start"; // The regions of a layer do not see each other here, and a layer cut through a fine relief takes far longer than the
// others, so the regions run next to each other instead of one after another, each still over all layers.
tbb::parallel_for(size_t(0), this->num_printing_regions(), [this, &surfaces_covered](size_t region_id) {
tbb::parallel_for( tbb::parallel_for(
tbb::blocked_range<size_t>(0, m_layers.size()), tbb::blocked_range<size_t>(0, m_layers.size()),
[this, &surfaces_covered, region_id](const tbb::blocked_range<size_t>& range) { [this, &surfaces_covered, region_id](const tbb::blocked_range<size_t>& range) {
@@ -2208,9 +2214,9 @@ void PrintObject::process_external_surfaces()
} }
} }
); );
});
m_print->throw_if_canceled(); m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end"; BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - end";
}
} }
void PrintObject::discover_vertical_shells() void PrintObject::discover_vertical_shells()
@@ -2330,123 +2336,21 @@ void PrintObject::discover_vertical_shells()
// region-specific but the shell settings and the external perimeter spacing, so a region sharing them with an earlier // region-specific but the shell settings and the external perimeter spacing, so a region sharing them with an earlier
// one reuses its result instead of repeating it: that accumulation is a union over several layers of top/bottom // one reuses its result instead of repeating it: that accumulation is a union over several layers of top/bottom
// surfaces, and a multi-material print has a region per filament. // surfaces, and a multi-material print has a region per filament.
using AccumulationKey = std::array<double, 5>;
struct ShellAccumulation struct ShellAccumulation
{ {
std::array<double, 5> key; AccumulationKey key;
Polygons shell; Polygons shell;
Polygons holes; Polygons holes;
}; };
std::vector<std::vector<ShellAccumulation>> shell_accumulations(top_bottom_surfaces_all_regions ? num_layers : 0); const auto accumulation_key = [](const PrintRegionConfig &region_config, const LayerRegion *layerm) {
return AccumulationKey{ double(region_config.top_shell_layers.value), region_config.top_shell_thickness.value,
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
const PrintRegion &region = this->printing_region(region_id);
if (region.config().ensure_vertical_shell_thickness.value != evstAll )
// This region will be handled by discover_horizontal_shells().
continue;
//FIXME Improve the heuristics for a grain size.
size_t grain_size = std::max(num_layers / 16, size_t(1));
if (! top_bottom_surfaces_all_regions) {
// This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
// is calculated over a single material.
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : cache top / bottom";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
[this, region_id, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
m_print->throw_if_canceled();
Layer &layer = *m_layers[idx_layer];
LayerRegion &layerm = *layer.m_regions[region_id];
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
// Top surfaces.
auto &cache = cache_top_botom_regions[idx_layer];
cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion);
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
// Bottom surfaces.
cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion);
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
// Holes over all regions. Only collect them once, they are valid for all region_id iterations.
if (cache.holes.empty()) {
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id)
polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons));
}
}
});
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end : cache top / bottom";
}
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness";
grain_size = 1;
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
[this, region_id, &cache_top_botom_regions, &shell_accumulations]
(const tbb::blocked_range<size_t>& range) {
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
m_print->throw_if_canceled();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
static size_t debug_idx = 0;
++ debug_idx;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Layer *layer = m_layers[idx_layer];
LayerRegion *layerm = layer->m_regions[region_id];
const PrintRegionConfig &region_config = layerm->region().config();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-initial");
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Flow solid_infill_flow = layerm->flow(frSolidInfill);
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
// Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
Polygons shell;
Polygons holes;
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
ExPolygons shell_ex;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
#if 0
// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box());
for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) {
if (n < 0 || n >= (int)m_layers.size())
continue;
ExPolygons &expolys = m_layers[n]->perimeter_expolygons;
for (size_t i = 0; i < expolys.size(); ++ i) {
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i]));
svg.draw(expolys[i]);
svg.draw_outline(expolys[i].contour, "black", scale_(0.05));
svg.draw_outline(expolys[i].holes, "blue", scale_(0.05));
svg.Close();
svg_cummulative.draw(expolys[i]);
svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05));
svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
}
}
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
const std::array<double, 5> accumulation_key{ double(region_config.top_shell_layers.value), region_config.top_shell_thickness.value,
double(region_config.bottom_shell_layers.value), region_config.bottom_shell_thickness.value, double(region_config.bottom_shell_layers.value), region_config.bottom_shell_thickness.value,
double(layerm->flow(frExternalPerimeter).scaled_spacing()) }; double(layerm->flow(frExternalPerimeter).scaled_spacing()) };
std::vector<ShellAccumulation> *accumulations = shell_accumulations.empty() ? nullptr : &shell_accumulations[idx_layer]; };
const auto reused = accumulations == nullptr ? nullptr : const auto accumulate_shell = [this, &cache_top_botom_regions](size_t idx_layer, const PrintRegionConfig &region_config,
[&]() -> const ShellAccumulation * { const LayerRegion *layerm, Polygons &shell, Polygons &holes) {
for (const ShellAccumulation &a : *accumulations) const Layer *layer = m_layers[idx_layer];
if (a.key == accumulation_key)
return &a;
return nullptr;
}();
if (reused != nullptr) {
shell = reused->shell;
holes = reused->holes;
} else {
polygons_append(holes, cache_top_botom_regions[idx_layer].holes); polygons_append(holes, cache_top_botom_regions[idx_layer].holes);
auto combine_holes = [&holes](const Polygons &holes2) { auto combine_holes = [&holes](const Polygons &holes2) {
if (holes.empty() || holes2.empty()) if (holes.empty() || holes2.empty())
@@ -2521,9 +2425,141 @@ void PrintObject::discover_vertical_shells()
(i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON)) (i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON))
combine_holes(cache_top_botom_regions[i].holes); combine_holes(cache_top_botom_regions[i].holes);
} }
if (accumulations != nullptr) };
accumulations->push_back({ accumulation_key, shell, holes }); std::vector<std::vector<ShellAccumulation>> shell_accumulations(top_bottom_surfaces_all_regions ? num_layers : 0);
if (! shell_accumulations.empty()) {
// Every (layer, key) pair is accumulated once, before the regions, so that nothing in the loop below is shared
// between them and they can run next to each other.
std::vector<std::array<size_t, 3>> todo; // layer, its slot, a region holding the key
for (size_t idx_layer = 0; idx_layer < num_layers; ++ idx_layer) {
std::vector<ShellAccumulation> &accumulations = shell_accumulations[idx_layer];
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
if (this->printing_region(region_id).config().ensure_vertical_shell_thickness.value != evstAll)
continue;
const LayerRegion *layerm = m_layers[idx_layer]->m_regions[region_id];
const AccumulationKey key = accumulation_key(layerm->region().config(), layerm);
if (std::none_of(accumulations.begin(), accumulations.end(), [&key](const ShellAccumulation &a) { return a.key == key; })) {
todo.push_back({ idx_layer, accumulations.size(), region_id });
accumulations.push_back({ key, {}, {} });
} }
}
}
tbb::parallel_for(size_t(0), todo.size(), [this, &todo, &shell_accumulations, &accumulate_shell](size_t i) {
m_print->throw_if_canceled();
const LayerRegion *layerm = m_layers[todo[i][0]]->m_regions[todo[i][2]];
ShellAccumulation &out = shell_accumulations[todo[i][0]][todo[i][1]];
accumulate_shell(todo[i][0], layerm->region().config(), layerm, out.shell, out.holes);
});
m_print->throw_if_canceled();
}
const auto process_region = [&](size_t region_id) {
const PrintRegion &region = this->printing_region(region_id);
if (region.config().ensure_vertical_shell_thickness.value != evstAll )
// This region will be handled by discover_horizontal_shells().
return;
//FIXME Improve the heuristics for a grain size.
size_t grain_size = std::max(num_layers / 16, size_t(1));
if (! top_bottom_surfaces_all_regions) {
// This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
// is calculated over a single material.
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : cache top / bottom";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
[this, region_id, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
m_print->throw_if_canceled();
Layer &layer = *m_layers[idx_layer];
LayerRegion &layerm = *layer.m_regions[region_id];
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
// Top surfaces.
auto &cache = cache_top_botom_regions[idx_layer];
cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion);
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
// Bottom surfaces.
cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion);
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
// Holes over all regions. Only collect them once, they are valid for all region_id iterations.
if (cache.holes.empty()) {
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id)
polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons));
}
}
});
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end : cache top / bottom";
}
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness";
grain_size = 1;
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
[this, region_id, &shell_accumulations, &accumulation_key, &accumulate_shell]
(const tbb::blocked_range<size_t>& range) {
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
m_print->throw_if_canceled();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
static size_t debug_idx = 0;
++ debug_idx;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Layer *layer = m_layers[idx_layer];
LayerRegion *layerm = layer->m_regions[region_id];
const PrintRegionConfig &region_config = layerm->region().config();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-initial");
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Flow solid_infill_flow = layerm->flow(frSolidInfill);
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
// Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
Polygons shell;
Polygons holes;
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
ExPolygons shell_ex;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
#if 0
// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box());
for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) {
if (n < 0 || n >= (int)m_layers.size())
continue;
ExPolygons &expolys = m_layers[n]->perimeter_expolygons;
for (size_t i = 0; i < expolys.size(); ++ i) {
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i]));
svg.draw(expolys[i]);
svg.draw_outline(expolys[i].contour, "black", scale_(0.05));
svg.draw_outline(expolys[i].holes, "blue", scale_(0.05));
svg.Close();
svg_cummulative.draw(expolys[i]);
svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05));
svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
}
}
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
const AccumulationKey key = accumulation_key(region_config, layerm);
const ShellAccumulation *reused = shell_accumulations.empty() ? nullptr :
[&]() -> const ShellAccumulation * {
for (const ShellAccumulation &a : shell_accumulations[idx_layer])
if (a.key == key)
return &a;
return nullptr;
}();
if (reused != nullptr) {
shell = reused->shell;
holes = reused->holes;
} else
accumulate_shell(idx_layer, region_config, layerm, shell, holes);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{ {
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell)); Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell));
@@ -2707,7 +2743,15 @@ void PrintObject::discover_vertical_shells()
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-final"); layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-final");
} }
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
} // for each region }; // for each region
if (top_bottom_surfaces_all_regions)
// Nothing is shared between the regions, and a layer cut through a fine relief takes far longer than the others,
// so they run next to each other instead of one after another.
tbb::parallel_for(size_t(0), this->num_printing_regions(), process_region);
else
// Here every region fills the one top/bottom cache with its own surfaces first.
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id)
process_region(region_id);
} // void PrintObject::discover_vertical_shells() } // void PrintObject::discover_vertical_shells()
// #define DEBUG_BRIDGE_OVER_INFILL // #define DEBUG_BRIDGE_OVER_INFILL