Fix support interface semantics, gap handling, behavior; fix bottom interface generation for organic trees; fix non organic tree interfaces (#11812)

* Fix support interface semantics and gap handling

Fix zero-gap interface detection and gap initialization for supports and raft.
Ensures correct top/bottom contact semantics and avoids relying on default zero gaps.

* Additional fixes and robustness improvements

Fix incorrect coupling between top and bottom support interface spacing and density, ensuring bottom interfaces use their own parameters for smoothing and toolpath generation.
Restore correct bottom interface generation for organic (tree) supports when a non-zero bottom Z gap is used, and preserve contacts even when base polygons are empty.
Improve robustness of organic support slicing by fixing layer index drift and guarding against degenerate polygon boolean operations.

* Typo and semantics fix

differnt_support_interface_filament -> different_support_interface_filament

soluble -> zero_top_z_gap

* Fix non organic tree bottom support interface generation

Slim tree bottom interface layer numbers were capped by the object's layer number beneath it.
Fixed by refactoring the generation algorithm.

* Fix non organic tree interlaced support generation

Deterministic local interlaced support layers generation for non-organic tree support

* Enable support interface multimaterial for non organic tree

Enables mixed-material support interface behavior for non organic tree support type.

* Fix tree support interface layer counts and contact handling

- Correct non‑organic tree top interface layer budgeting so gaps don’t consume a layer (N stays N).
- Remove the extra roof interface pass that was duplicating the 2nd layer.
- Organic tree: use only the lowest contact footprint and avoid extra bottom‑contact extrusion so interface count matches the user setting.

* Bugfixes (a lot)

Many, many bugs fixed, majority edge-case but still not  playing by the rule.
Some of them:
- on multilevel base, on very small level variations the support was capped to the topmost level height
- non-organic tree had gaps for supports in a multilevel base situiation
- independent support layer height had issues with support interfaces (base support beneath bottom interface, top contact layer sometimes missing)
- organic tree had issues with small variation multi-level base
- organic tree support with zero top Z distance could overlap support-material and interface-material paths when separate materials were used
- many, many others (I lost track of them)
This commit is contained in:
Kiss Lorand
2026-05-10 10:12:56 +03:00
committed by GitHub
parent 5108b721a9
commit 4e507fffb2
33 changed files with 1098 additions and 447 deletions

View File

@@ -322,6 +322,7 @@ protected:
ExPolygon *area;
int type;
int interface_id = 0;
bool interface_as_base = false;
coordf_t dist_to_top; // mm dist to top
bool need_infill = false;
bool need_extra_wall = false;

View File

@@ -4854,7 +4854,9 @@ void PrintConfigDef::init_fff_params()
def = this->add("raft_contact_distance", coFloat);
def->label = L("Raft contact Z distance");
def->category = L("Support");
def->tooltip = L("Z gap between object and raft. Ignored for soluble interface.");
def->tooltip = L("Z gap between raft and object. "
"If Support Top Z Distance is 0, this value is ignored and "
"the object is printed in direct contact with the raft (no gap).");
def->sidetext = L("mm"); // millimeters, CIS languages need translation
def->min = 0;
def->mode = comAdvanced;
@@ -5839,7 +5841,7 @@ void PrintConfigDef::init_fff_params()
def->label = L("Top Z distance");
def->min = 0;
def->category = L("Support");
def->tooltip = L("The Z gap between the top support interface and object.");
def->tooltip = L("Z gap between the support's top and object.");
def->sidetext = L("mm"); // millimeters, CIS languages need translation
// def->min = 0;
#if 0
@@ -5856,7 +5858,9 @@ void PrintConfigDef::init_fff_params()
def = this->add("support_bottom_z_distance", coFloat);
def->label = L("Bottom Z distance");
def->category = L("Support");
def->tooltip = L("The Z gap between the bottom support interface and object.");
def->tooltip = L("Z gap between the object and the support bottom. "
"If Support Top Z Distance is 0 and the bottom has interface layers, this value "
"is ignored and the support is printed in direct contact with the object (no gap).");
def->sidetext = L("mm"); // millimeters, CIS languages need translation
def->min = 0;
def->mode = comAdvanced;

View File

@@ -60,14 +60,15 @@ coordf_t Slicing::max_layer_height_from_nozzle(const DynamicPrintConfig &print_c
}
SlicingParameters SlicingParameters::create_from_config(
const PrintConfig &print_config,
const PrintObjectConfig &object_config,
coordf_t object_height,
const std::vector<unsigned int> &object_extruders,
const Vec3d &object_shrinkage_compensation)
const PrintConfig &print_config,
const PrintObjectConfig &object_config,
coordf_t object_height,
const std::vector<unsigned int> &object_extruders,
const Vec3d &object_shrinkage_compensation)
{
coordf_t initial_layer_print_height = (print_config.initial_layer_print_height.value <= 0) ?
object_config.layer_height.value : print_config.initial_layer_print_height.value;
// If object_config.support_filament == 0 resp. object_config.support_interface_filament == 0,
// print_config.nozzle_diameter.get_at(size_t(-1)) returns the 0th nozzle diameter,
// which is consistent with the requirement that if support_filament == 0 resp. support_interface_filament == 0,
@@ -75,19 +76,47 @@ SlicingParameters SlicingParameters::create_from_config(
// In that case all the nozzles have to be of the same diameter.
coordf_t support_material_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_filament.value - 1);
coordf_t support_material_interface_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_interface_filament.value - 1);
bool soluble_interface = object_config.support_top_z_distance.value == 0.;
// ORCA: store Z distance
const coordf_t support_top_z_gap = object_config.support_top_z_distance.value;
const coordf_t support_bottom_z_gap = object_config.support_bottom_z_distance.value;
const coordf_t raft_z_gap = object_config.raft_contact_distance.value;
/* -------------------------------------------------- */
/* ORCA: Zero-gap interface detection (asymmetric) */
/* -------------------------------------------------- */
const bool zero_topZ_contact =
support_top_z_gap == 0.0;
const bool zero_gap_interface_top =
object_config.support_interface_top_layers.value > 0 && // Has some top interface layers
zero_topZ_contact;
const bool zero_gap_interface_bottom =
(object_config.support_interface_bottom_layers.value < 0 // Negative value means "use same as top"
? object_config.support_interface_top_layers.value
: object_config.support_interface_bottom_layers.value) > 0 && // Has some bottom interface layers
(support_bottom_z_gap == 0.0 || zero_topZ_contact);
const bool zero_gap_interface_raft =
raft_z_gap == 0.0 || zero_topZ_contact;
SlicingParameters params;
params.layer_height = object_config.layer_height.value;
params.first_print_layer_height = initial_layer_print_height;
params.first_object_layer_height = initial_layer_print_height;
params.object_print_z_min = 0.;
params.layer_height = object_config.layer_height.value;
params.first_print_layer_height = initial_layer_print_height;
params.first_object_layer_height = initial_layer_print_height;
params.object_print_z_min = 0.0;
// Orca: XYZ filament compensation
params.object_print_z_max = object_height * object_shrinkage_compensation.z();
params.object_print_z_max = object_height * object_shrinkage_compensation.z();
params.object_print_z_uncompensated_max = object_height;
params.object_shrinkage_compensation_z = object_shrinkage_compensation.z();
params.base_raft_layers = object_config.raft_layers.value;
params.soluble_interface = soluble_interface;
params.object_shrinkage_compensation_z = object_shrinkage_compensation.z();
params.base_raft_layers = object_config.raft_layers.value;
params.zero_gap_interface_top = zero_gap_interface_top;
params.zero_gap_interface_bottom = zero_gap_interface_bottom;
params.zero_gap_interface_raft = zero_gap_interface_raft;
// Miniumum/maximum of the minimum layer height over all extruders.
params.min_layer_height = MIN_LAYER_HEIGHT;
@@ -102,6 +131,7 @@ SlicingParameters SlicingParameters::create_from_config(
max_layer_height_from_nozzle(print_config, object_config.support_interface_filament));
params.max_suport_layer_height = params.max_layer_height;
}
if (object_extruders.empty()) {
params.min_layer_height = std::max(params.min_layer_height, min_layer_height_from_nozzle(print_config, 0));
params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, 0));
@@ -111,24 +141,58 @@ SlicingParameters SlicingParameters::create_from_config(
params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, extruder_id));
}
}
params.min_layer_height = std::min(params.min_layer_height, params.layer_height);
params.max_layer_height = std::max(params.max_layer_height, params.layer_height);
if (! soluble_interface) {
params.gap_raft_object = object_config.raft_contact_distance.value;
//BBS
params.gap_object_support = object_config.support_bottom_z_distance.value;
params.gap_support_object = object_config.support_top_z_distance.value;
/* -------------------------------------------------- */
/* ORCA: Gap assignment */
/* -------------------------------------------------- */
// ORCA: Raft contact (raft -> object)
if (zero_gap_interface_raft) {
params.gap_raft_object = 0.0;
} else {
params.gap_raft_object = raft_z_gap;
if (!print_config.independent_support_layer_height) {
params.gap_raft_object = std::round(params.gap_raft_object / object_config.layer_height + EPSILON) * object_config.layer_height;
params.gap_object_support = std::round(params.gap_object_support / object_config.layer_height + EPSILON) * object_config.layer_height;
params.gap_support_object = std::round(params.gap_support_object / object_config.layer_height + EPSILON) * object_config.layer_height;
params.gap_raft_object =
std::round(params.gap_raft_object / object_config.layer_height + EPSILON)
* object_config.layer_height;
}
}
// ORCA: BOTTOM contact (object -> support)
if (zero_gap_interface_bottom) {
params.gap_object_support = 0.0;
} else {
params.gap_object_support = support_bottom_z_gap;
if (!print_config.independent_support_layer_height) {
params.gap_object_support =
std::round(params.gap_object_support / object_config.layer_height + EPSILON)
* object_config.layer_height;
}
}
// ORCA: TOP contact (support -> object)
if (zero_gap_interface_top) {
params.gap_support_object = 0.0;
} else {
params.gap_support_object = support_top_z_gap;
if (!print_config.independent_support_layer_height) {
params.gap_support_object =
std::round(params.gap_support_object / object_config.layer_height + EPSILON)
* object_config.layer_height;
}
}
/* -------------------------------------------------- */
/* Raft logic */
/* -------------------------------------------------- */
if (params.base_raft_layers > 0) {
params.interface_raft_layers = (params.base_raft_layers + 1) / 2;
params.interface_raft_layers = (params.base_raft_layers + 1) / 2;
params.base_raft_layers -= params.interface_raft_layers;
// Use as large as possible layer height for the intermediate raft layers.
params.base_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_extruder_dmr);
@@ -141,11 +205,11 @@ SlicingParameters SlicingParameters::create_from_config(
if (params.has_raft()) {
// Raise first object layer Z by the thickness of the raft itself plus the extra distance required by the support material logic.
//FIXME The last raft layer is the contact layer, which shall be printed with a bridging flow for ease of separation. Currently it is not the case.
if (params.raft_layers() == 1) {
if (params.raft_layers() == 1) {
// There is only the contact layer.
params.contact_raft_layer_height = initial_layer_print_height;
params.raft_contact_top_z = initial_layer_print_height;
} else {
} else {
assert(params.base_raft_layers > 0);
assert(params.interface_raft_layers > 0);
// Number of the base raft layers is decreased by the first layer.
@@ -153,7 +217,8 @@ SlicingParameters SlicingParameters::create_from_config(
// Number of the interface raft layers is decreased by the contact layer.
params.raft_interface_top_z = params.raft_base_top_z + coordf_t(params.interface_raft_layers - 1) * params.interface_raft_layer_height;
params.raft_contact_top_z = params.raft_interface_top_z + params.contact_raft_layer_height;
}
}
coordf_t print_z = params.raft_contact_top_z + params.gap_raft_object;
params.object_print_z_min = print_z;
params.object_print_z_max += print_z;

View File

@@ -84,9 +84,10 @@ struct SlicingParameters
// If the object is printed over a non-soluble raft, the first layer may be printed with a briding flow.
bool first_object_layer_bridging { false };
// Soluble interface? (PLA soluble in water, HIPS soluble in lemonen)
// otherwise the interface must be broken off.
bool soluble_interface { false };
// Zero-gap interface flags for top / bottom / raft contact.
bool zero_gap_interface_top { false };
bool zero_gap_interface_bottom { false };
bool zero_gap_interface_raft { false };
// Gap when placing object over raft.
coordf_t gap_raft_object { 0 };
// Gap when placing support over object.
@@ -100,7 +101,7 @@ struct SlicingParameters
coordf_t raft_base_top_z { 0 };
coordf_t raft_interface_top_z { 0 };
coordf_t raft_contact_top_z { 0 };
// In case of a soluble interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer.
// In case of a zero-gap raft interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer.
coordf_t object_print_z_min { 0 };
// This value of maximum print Z is scaled by shrinkage compensation in the Z-axis.
coordf_t object_print_z_max { 0 };
@@ -133,7 +134,9 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters
// BBS: following are not required for equal layer height.
// Since the z-gap diff may be multiple of layer height.
#if 0
sp1.soluble_interface == sp2.soluble_interface &&
sp1.zero_gap_interface_top == sp2.zero_gap_interface_top &&
sp1.zero_gap_interface_bottom == sp2.zero_gap_interface_bottom &&
sp1.zero_gap_interface_raft == sp2.zero_gap_interface_raft &&
sp1.gap_raft_object == sp2.gap_raft_object &&
sp1.gap_object_support == sp2.gap_object_support &&
sp1.gap_support_object == sp2.gap_support_object &&

View File

@@ -13,6 +13,7 @@
#include <boost/container/static_vector.hpp>
#include <boost/log/trivial.hpp>
#include <algorithm>
#include <tbb/parallel_for.h>
#include "SupportCommon.hpp"
@@ -69,22 +70,30 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
if (support_params.has_base_interfaces())
base_interface_layers.assign(intermediate_layers.size(), nullptr);
const auto smoothing_distance = support_params.support_material_interface_flow.scaled_spacing() * 1.5;
const auto minimum_island_radius = support_params.support_material_interface_flow.scaled_spacing() / support_params.interface_density;
// ORCA: use top/bottom interface densities for smoothing.
const auto minimum_island_radius_top = support_params.support_material_interface_flow.scaled_spacing() / support_params.top_interface_density;
const auto minimum_island_radius_bottom = support_params.support_material_interface_flow.scaled_spacing() / support_params.bottom_interface_density;
const auto closing_distance = smoothing_distance; // scaled<float>(config.support_material_closing_radius.value);
// Insert a new layer into base_interface_layers, if intersection with base exists.
auto insert_layer = [&layer_storage, smooth_supports, closing_distance, smoothing_distance, minimum_island_radius](
// ORCA: regularize top and bottom interfaces with separate minimum island radii.
auto insert_layer = [&layer_storage, smooth_supports, closing_distance, smoothing_distance, minimum_island_radius_top, minimum_island_radius_bottom](
SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, SupportGeneratorLayer *top_interface_layer,
const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* {
bool has_top_interface = top_interface_layer && ! top_interface_layer->polygons.empty();
assert(! bottom.empty() || ! top.empty() || has_top_interface);
// Merge top into bottom, unite them with a safety offset.
append(bottom, std::move(top));
// Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners).
bottom = intersection(
smooth_supports ?
smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) :
union_safety_offset(std::move(bottom)),
intermediate_layer.polygons);
// ORCA: regularize interfaces using the top/bottom radii.
auto regularize = [&](Polygons polys, coordf_t minimum_island_radius) -> Polygons {
if (polys.empty())
return polys;
return smooth_supports ?
smooth_outward(closing(std::move(polys), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) :
union_safety_offset(std::move(polys));
};
// ORCA: apply independent smoothing to bottom vs top.
Polygons bottom_polys = regularize(std::move(bottom), minimum_island_radius_bottom);
Polygons top_polys = regularize(std::move(top), minimum_island_radius_top);
append(bottom_polys, std::move(top_polys));
bottom = intersection(std::move(bottom_polys), intermediate_layer.polygons);
if (has_top_interface) {
// Don't trim the precomputed Organic supports top interface with base layer
// as the precomputed top interface likely expands over multiple tree tips.
@@ -1365,7 +1374,8 @@ SupportGeneratorLayersPtr generate_support_layers(
SupportGeneratorLayer &layer = *layers_sorted[u];
if (! layer.polygons.empty()) {
empty = false;
num_interfaces += one_of(layer.layer_type, support_types_interface);
const bool is_base_interface = std::find(base_interface_layers.begin(), base_interface_layers.end(), &layer) != base_interface_layers.end();
num_interfaces += one_of(layer.layer_type, support_types_interface) || is_base_interface;
if (layer.layer_type == SupporLayerType::TopContact) {
++ num_top_contacts;
assert(num_top_contacts <= 1);
@@ -1562,7 +1572,7 @@ void generate_support_toolpaths(
auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get();
// Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer).
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr :
Fill::new_from_type(support_params.interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
Fill::new_from_type(support_params.top_interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(support_params.base_fill_pattern));
filler_interface->set_bounding_box(bbox_object);
if (filler_first_layer_ptr)
@@ -1610,6 +1620,8 @@ void generate_support_toolpaths(
// This layer is a raft contact layer. Any contact polygons at this layer are raft contacts.
bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z);
// ORCA: Organic tree uses projected contacts to build the interface stack; avoid extra bottom-contact extrusion.
const bool organic_tree = support_params.support_style == SupportMaterialStyle::smsTreeOrganic;
if (config.support_interface_top_layers == 0) {
// If no top interface layers were requested, we treat the contact layer exactly as a generic base layer.
// Don't merge the raft contact layer though.
@@ -1638,10 +1650,34 @@ void generate_support_toolpaths(
base_layer.merge(std::move(bottom_contact_layer));
else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging)
base_layer = std::move(bottom_contact_layer);
} else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer)
} else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) {
top_contact_layer.merge(std::move(bottom_contact_layer));
else if (bottom_contact_layer.could_merge(interface_layer))
} else if (bottom_contact_layer.could_merge(interface_layer) && ! organic_tree) {
bottom_contact_layer.merge(std::move(interface_layer));
}
// Orca: For organic trees the support-material regions are generated from
// expanded wall polygons. With zero top Z gap and separate interface material,
// that expansion can overlap same-layer interface-material regions, so trim
// the support-material regions from those interface footprints here.
if (organic_tree && support_params.zero_gap_interface_top && !support_params.can_merge_support_regions &&
(!base_layer.empty() || !base_interface_layer.empty())) {
Polygons interface_polygons;
if (!top_contact_layer.empty())
polygons_append(interface_polygons, top_contact_layer.polygons_to_extrude());
if (!interface_layer.empty())
polygons_append(interface_polygons, interface_layer.polygons_to_extrude());
if (!interface_polygons.empty()) {
const coord_t trim_margin = std::max(
support_params.support_material_flow.scaled_width(),
support_params.support_material_interface_flow.scaled_width());
Polygons interface_keepout = offset(interface_polygons, trim_margin);
if (!base_layer.empty())
base_layer.set_polygons_to_extrude(diff(base_layer.polygons_to_extrude(), interface_keepout));
if (!base_interface_layer.empty())
base_interface_layer.set_polygons_to_extrude(diff(base_interface_layer.polygons_to_extrude(), interface_keepout));
}
}
#if 0
if ( ! interface_layer.empty() && ! base_layer.empty()) {
@@ -1661,6 +1697,9 @@ void generate_support_toolpaths(
if (! layer_ex.empty() && ! layer_ex.polygons_to_extrude().empty()) {
bool interface_as_base = interface_layer_type == InterfaceLayerType::InterfaceAsBase;
bool raft_contact = interface_layer_type == InterfaceLayerType::RaftContact;
// ORCA: detect bottom interface layers for density selection.
bool bottom_interface = interface_layer_type == InterfaceLayerType::BottomContact ||
(interface_layer_type == InterfaceLayerType::Interface && layer_ex.layer->layer_type == SupporLayerType::BottomInterface);
//FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
auto *filler = raft_contact ? filler_raft_contact : filler_interface.get();
@@ -1676,7 +1715,10 @@ void generate_support_toolpaths(
raft_contact ?
support_params.raft_interface_angle(support_layer.interface_id()) :
support_interface_angle;
double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density;
// ORCA: pick density based on interface type.
double density = raft_contact ? support_params.raft_interface_density :
interface_as_base ? support_params.support_density :
bottom_interface ? support_params.bottom_interface_density : support_params.top_interface_density;
filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() :
interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
@@ -1694,9 +1736,9 @@ void generate_support_toolpaths(
const bool top_interfaces = config.support_interface_top_layers.value != 0;
const bool bottom_interfaces = top_interfaces && config.support_interface_bottom_layers != 0;
extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase);
extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase);
if (!organic_tree)
extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase);
extrude_interface(interface_layer, top_interfaces ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase);
// Base interface layers under soluble interfaces
if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) {
Fill *filler = filler_base_interface.get();
@@ -1706,7 +1748,7 @@ void generate_support_toolpaths(
Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
filler->angle = support_interface_angle;
filler->spacing = support_params.support_material_interface_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density));
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.top_interface_density));
fill_expolygons_generate_paths(
// Destination
base_interface_layer.extrusions,
@@ -1714,7 +1756,7 @@ void generate_support_toolpaths(
// Regions to fill
union_safety_offset_ex(base_interface_layer.polygons_to_extrude()),
// Filler and its parameters
filler, float(support_params.interface_density),
filler, float(support_params.top_interface_density),
// Extrusion parameters
ExtrusionRole::erSupportMaterial, interface_flow);
}

View File

@@ -1739,7 +1739,7 @@ static inline std::pair<SupportGeneratorLayer*, SupportGeneratorLayer*> new_cont
print_z = slicing_params.raft_contact_top_z;
bottom_z = slicing_params.raft_interface_top_z;
height = slicing_params.contact_raft_layer_height;
} else if (slicing_params.soluble_interface) {
} else if (slicing_params.zero_gap_interface_top) {
// Align the contact surface height with a layer immediately below the supported layer.
// Interface layer will be synchronized with the object.
print_z = layer.bottom_z();
@@ -1862,7 +1862,7 @@ static inline void fill_contact_layer(
#endif // SLIC3R_DEBUG
));
// 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra.
bool reduce_interfaces = object_config.support_style.value != smsSnug && layer_id > 0 && !slicing_params.soluble_interface;
bool reduce_interfaces = object_config.support_style.value != smsSnug && layer_id > 0 && !slicing_params.zero_gap_interface_top;
if (reduce_interfaces) {
// Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions.
Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface());
@@ -2421,12 +2421,12 @@ static inline SupportGeneratorLayer* detect_bottom_contacts(
Layer* upper_layer = layer.upper_layer;
if (object.print()->config().independent_support_layer_height) {
// If the layer is extruded with no bridging flow, support just the normal extrusions.
layer_new.height = slicing_params.soluble_interface ?
layer_new.height = slicing_params.zero_gap_interface_bottom ?
// Align the interface layer with the object's layer height.
upper_layer->height :
// Place a bridge flow interface layer or the normal flow interface layer over the top surface.
support_params.support_material_bottom_interface_flow.height();
layer_new.print_z = slicing_params.soluble_interface ? upper_layer->print_z :
layer_new.print_z = slicing_params.zero_gap_interface_bottom ? upper_layer->print_z :
layer.print_z + layer_new.height + slicing_params.gap_object_support;
}
else {
@@ -2436,11 +2436,11 @@ static inline SupportGeneratorLayer* detect_bottom_contacts(
}
layer_new.bottom_z = layer.print_z;
layer_new.idx_object_layer_below = layer_id;
layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges;
layer_new.bridging = !slicing_params.zero_gap_interface_bottom && object.config().thick_bridges;
//FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow.
layer_new.polygons = expand(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (! slicing_params.soluble_interface) {
if (!slicing_params.zero_gap_interface_bottom) {
// Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface,
// so there will be no support surfaces generated with thickness lower than m_support_layer_height_min.
for (size_t top_idx = size_t(std::max<int>(0, contact_idx));
@@ -2909,7 +2909,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp
// Continue printing the other layers up to extr2z.
step = dist / coordf_t(n_layers_extra);
}
if (! m_slicing_params.soluble_interface && extr2->layer_type == SupporLayerType::TopContact) {
if (!m_slicing_params.zero_gap_interface_top && extr2->layer_type == SupporLayerType::TopContact) {
// This is a top interface layer, which does not have a height assigned yet. Do it now.
assert(extr2->height == 0.);
assert(extr1z > m_slicing_params.first_print_layer_height - EPSILON);
@@ -3170,7 +3170,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
polygons_append(polygons_trimming, offset({ expoly }, trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
}
}
if (! m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
if (!m_slicing_params.zero_gap_interface_top && m_object_config->thick_bridges) {
// Collect all bottom surfaces, which will be extruded with a bridging flow.
for (; i < object.layers().size(); ++ i) {
const Layer &object_layer = *object.layers()[i];

View File

@@ -28,7 +28,7 @@ public:
bool has_support() const { return m_object_config->enable_support.value || m_object_config->enforce_support_layers; }
bool build_plate_only() const { return this->has_support() && m_object_config->support_on_build_plate_only.value; }
// BBS
bool synchronize_layers() const { return /*m_slicing_params.soluble_interface && */!m_print_config->independent_support_layer_height.value; }
bool synchronize_layers() const { return /*m_slicing_params.zero_gap_interface_top && */!m_print_config->independent_support_layer_height.value; }
bool has_contact_loops() const { return m_object_config->support_interface_loop_pattern.value; }
// Generate support material for the object.

View File

@@ -14,14 +14,15 @@ struct SupportParameters {
const PrintObjectConfig& object_config = object.config();
const SlicingParameters& slicing_params = object.slicing_parameters();
this->soluble_interface = slicing_params.soluble_interface;
this->soluble_interface_non_soluble_base =
// Zero z-gap between the overhangs and the support interface.
slicing_params.soluble_interface &&
// Interface extruder soluble.
object_config.support_interface_filament.value > 0 && print_config.filament_soluble.get_at(object_config.support_interface_filament.value - 1) &&
// Base extruder: Either "print with active extruder" not soluble.
(object_config.support_filament.value == 0 || ! print_config.filament_soluble.get_at(object_config.support_filament.value - 1));
this->zero_gap_interface_top = slicing_params.zero_gap_interface_top;
this->zero_gap_interface_bottom = slicing_params.zero_gap_interface_bottom;
const bool soluble_interface_non_soluble_base =
// Interface extruder soluble.
object_config.support_interface_filament.value > 0 && print_config.filament_soluble.get_at(object_config.support_interface_filament.value - 1) &&
// Base extruder: Either "print with active extruder" not soluble.
(object_config.support_filament.value == 0 || ! print_config.filament_soluble.get_at(object_config.support_filament.value - 1));
const bool non_soluble_base_top = this->zero_gap_interface_top && soluble_interface_non_soluble_base;
const bool non_soluble_base_bottom = this->zero_gap_interface_bottom && soluble_interface_non_soluble_base;
{
this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value);
@@ -29,19 +30,25 @@ struct SupportParameters {
num_top_interface_layers : object_config.support_interface_bottom_layers;
this->has_top_contacts = num_top_interface_layers > 0;
this->has_bottom_contacts = num_bottom_interface_layers > 0;
if (this->soluble_interface_non_soluble_base) {
// Try to support soluble dense interfaces with non-soluble dense interfaces.
this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2));
this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2));
} else {
// BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion
// Note: support materials (such as Supp.W) can't be used as support base now, so support interface and base are still using different filaments even if
// support_filament==0
bool differnt_support_interface_filament = object_config.support_interface_filament != 0 &&
object_config.support_interface_filament != object_config.support_filament;
this->num_top_base_interface_layers = differnt_support_interface_filament ? 1 : 0;
this->num_bottom_base_interface_layers = differnt_support_interface_filament ? 1 : 0;
}
// BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion
// Note: support materials (such as Supp.W) can't be used as support base now, so support interface and base are still using different filaments even if
// support_filament==0
bool different_support_interface_filament = object_config.support_interface_filament != 0 &&
object_config.support_interface_filament != object_config.support_filament;
if (non_soluble_base_top) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2));
} else {
this->num_top_base_interface_layers =
(different_support_interface_filament && this->zero_gap_interface_top) ? 1 : 0;
}
if (non_soluble_base_bottom) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2));
} else {
this->num_bottom_base_interface_layers =
(different_support_interface_filament && this->zero_gap_interface_bottom) ? 1 : 0;
}
}
this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height));
this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height));
@@ -78,7 +85,7 @@ struct SupportParameters {
this->gap_xy_first_layer = object_config.support_object_first_layer_gap.value;
bridge_flow_ratio /= object.num_printing_regions();
this->support_material_bottom_interface_flow = slicing_params.soluble_interface || !object_config.thick_bridges ?
this->support_material_bottom_interface_flow = this->zero_gap_interface_bottom || !object_config.thick_bridges ?
this->support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) :
Flow::bridging_flow(bridge_flow_ratio * this->support_material_interface_flow.nozzle_diameter(), this->support_material_interface_flow.nozzle_diameter());
@@ -95,18 +102,21 @@ struct SupportParameters {
this->base_angle = Geometry::deg2rad(float(object_config.support_angle.value));
this->interface_angle = Geometry::deg2rad(float(object_config.support_angle.value + 90.));
// Orca: Force solid support interface when using support ironing
this->interface_spacing = (this->ironing ? 0 : object_config.support_interface_spacing.value) + this->support_material_interface_flow.spacing();
this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / this->interface_spacing);
// Orca: Force solid support interface when using support ironing
// ORCA: split top/bottom interface spacing and density, and force solid top when ironing.
this->top_interface_spacing = (this->ironing ? 0 : object_config.support_interface_spacing.value) + this->support_material_interface_flow.spacing();
this->top_interface_density = std::min(1., this->support_material_interface_flow.spacing() / this->top_interface_spacing);
// ORCA: bottom interface spacing/density separated from top settings.
this->bottom_interface_spacing = object_config.support_bottom_interface_spacing.value + this->support_material_interface_flow.spacing();
this->bottom_interface_density = std::min(1., this->support_material_interface_flow.spacing() / this->bottom_interface_spacing);
// ORCA: force solid raft interface when ironing (top spacing).
double raft_interface_spacing = (this->ironing ? 0 : object_config.support_interface_spacing.value) + this->raft_interface_flow.spacing();
this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing);
this->support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing();
this->support_density = std::min(1., this->support_material_flow.spacing() / this->support_spacing);
if (object_config.support_interface_top_layers.value == 0) {
// No interface layers allowed, print everything with the base support pattern.
this->interface_spacing = this->support_spacing;
this->interface_density = this->support_density;
this->top_interface_spacing = this->support_spacing;
this->top_interface_density = this->support_density;
}
SupportMaterialPattern support_pattern = object_config.support_base_pattern;
@@ -114,7 +124,7 @@ struct SupportParameters {
this->base_fill_pattern =
support_pattern == smpHoneycomb ? ipHoneycomb :
this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase;
this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase);
this->interface_fill_pattern = (this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase;
if (object_config.support_interface_pattern == smipGrid)
this->contact_fill_pattern = ipGrid;
@@ -122,10 +132,10 @@ struct SupportParameters {
this->contact_fill_pattern = ipRectilinear;
else
this->contact_fill_pattern =
(object_config.support_interface_pattern == smipAuto && slicing_params.soluble_interface) ||
(object_config.support_interface_pattern == smipAuto && this->zero_gap_interface_top) ||
object_config.support_interface_pattern == smipConcentric ?
ipConcentric :
(this->interface_density > 0.95 ? ipRectilinear : ipSupportBase);
(this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
this->raft_angle_1st_layer = 0.f;
this->raft_angle_base = 0.f;
@@ -186,10 +196,9 @@ struct SupportParameters {
}
}
}
// Both top / bottom contacts and interfaces are soluble.
bool soluble_interface;
// Support contact & interface are soluble, but support base is non-soluble.
bool soluble_interface_non_soluble_base;
// Zero-gap interface flags for top / bottom contact.
bool zero_gap_interface_top;
bool zero_gap_interface_bottom;
// Is there at least a top contact layer extruded above support base?
bool has_top_contacts;
@@ -199,9 +208,9 @@ struct SupportParameters {
size_t num_top_interface_layers;
// Number of bottom interface layers without counting the contact layer.
size_t num_bottom_interface_layers;
// Number of top base interface layers. Zero if not soluble_interface_non_soluble_base.
// Number of top base interface layers.
size_t num_top_base_interface_layers;
// Number of bottom base interface layers. Zero if not soluble_interface_non_soluble_base.
// Number of bottom base interface layers.
size_t num_bottom_base_interface_layers;
bool has_contacts() const { return this->has_top_contacts || this->has_bottom_contacts; }
@@ -233,10 +242,13 @@ struct SupportParameters {
float base_angle;
float interface_angle;
coordf_t interface_spacing;
coordf_t top_interface_spacing;
coordf_t bottom_interface_spacing;
coordf_t support_expansion=0;
// Density of the top / bottom interface and contact layers.
coordf_t interface_density;
// Density of the top interface and contact layers.
coordf_t top_interface_density;
// Density of the bottom interface and contact layers.
coordf_t bottom_interface_density;
// Density of the raft interface and contact layers.
coordf_t raft_interface_density;
coordf_t support_spacing;

View File

@@ -25,6 +25,7 @@
#include <tbb/parallel_for_each.h>
#include <boost/log/trivial.hpp>
#include <algorithm>
#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
@@ -73,6 +74,32 @@ inline Point normal(Point pt, double scale)
return pt * (scale / length);
}
// ORCA:
// Collect all polygons of a given SurfaceType from all regions of a layer.
// Used for top-contact probing across region/modifier boundaries.
static Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type)
{
size_t n_polygons_new = 0;
for (const LayerRegion *region : layer.regions()) {
for (const Surface &surface : region->slices.surfaces) {
if (surface.surface_type == surface_type)
n_polygons_new += surface.expolygon.holes.size() + 1;
}
}
Polygons out;
out.reserve(n_polygons_new);
for (const LayerRegion *region : layer.regions()) {
for (const Surface &surface : region->slices.surfaces) {
if (surface.surface_type == surface_type)
polygons_append(out, surface.expolygon);
}
}
return out;
}
enum TreeSupportStage {
STAGE_DETECT_OVERHANGS,
@@ -1416,7 +1443,7 @@ void TreeSupport::generate_toolpaths()
Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter);
Fill* filler_interface = Fill::new_from_type(ipRectilinear);
filler_interface->angle = PI / 2; // interface should be perpendicular to base
filler_interface->angle = M_PI_2; // interface should be perpendicular to base
filler_interface->spacing = support_flow.spacing();
FillParams fill_params;
@@ -1436,7 +1463,7 @@ void TreeSupport::generate_toolpaths()
SupportLayer *ts_layer = m_object->get_support_layer(layer_nr);
Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter);
Fill* filler_raft = Fill::new_from_type(ipRectilinear);
filler_raft->angle = PI / 2;
filler_raft->angle = M_PI_2;
filler_raft->spacing = support_flow.spacing();
for (auto& poly : first_non_raft_base)
make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::min(size_t(1), wall_count), support_flow, erSupportMaterial, filler_raft, interface_density, false);
@@ -1446,13 +1473,8 @@ void TreeSupport::generate_toolpaths()
return;
BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.)));
std::shared_ptr<Fill> filler_interface = std::shared_ptr<Fill>(Fill::new_from_type(m_support_params.contact_fill_pattern));
std::shared_ptr<Fill> filler_Roof1stLayer = std::shared_ptr<Fill>(Fill::new_from_type(ipRectilinear));
filler_interface->set_bounding_box(bbox_object);
filler_Roof1stLayer->set_bounding_box(bbox_object);
filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value + 90.);
filler_Roof1stLayer->angle = Geometry::deg2rad(object_config.support_angle.value + 90.);
// ORCA: base angle used for explicit interlaced interface orientation.
const float base_support_angle = Geometry::deg2rad(object_config.support_angle.value);
// generate tree support tool paths
tbb::parallel_for(
@@ -1471,17 +1493,28 @@ void TreeSupport::generate_toolpaths()
coordf_t support_spacing = object_config.support_base_pattern_spacing.value + support_flow.spacing();
coordf_t support_density = std::min(1., support_flow.spacing() / support_spacing);
ts_layer->support_fills.no_sort = false;
// ORCA: per-layer Fill instances to avoid shared-state races during interlaced interfaces.
std::shared_ptr<Fill> filler_interface = std::shared_ptr<Fill>(Fill::new_from_type(m_support_params.contact_fill_pattern));
std::shared_ptr<Fill> filler_Roof1stLayer = std::shared_ptr<Fill>(Fill::new_from_type(ipRectilinear));
filler_interface->set_bounding_box(bbox_object);
filler_Roof1stLayer->set_bounding_box(bbox_object);
for (auto& area_group : ts_layer->area_groups) {
ExPolygon& poly = *area_group.area;
ExPolygons polys;
FillParams fill_params;
// ORCA: reset interface Fill state per area group to keep angles deterministic.
filler_interface->fixed_angle = false;
filler_interface->layer_id = size_t(-1);
filler_interface->angle = base_support_angle + M_PI_2; // default interface angle is perpendicular to support angle
if (area_group.type != SupportLayer::BaseType) {
// interface
if (layer_id == 0) {
Flow flow = m_raft_layers == 0 ? m_object->print()->brim_flow() : support_flow;
ExtrusionRole brim_role = (area_group.type == SupportLayer::RoofType && !area_group.interface_as_base) ?
erSupportMaterialInterface : erSupportMaterial;
make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, wall_count, flow,
area_group.type == SupportLayer::RoofType ? erSupportMaterialInterface : erSupportMaterial);
brim_role);
polys = std::move(offset_ex(poly, -flow.scaled_spacing()));
} else if (area_group.type == SupportLayer::Roof1stLayer) {
polys = std::move(offset_ex(poly, 0.5*support_flow.scaled_width()));
@@ -1494,12 +1527,18 @@ void TreeSupport::generate_toolpaths()
}
if (area_group.type == SupportLayer::Roof1stLayer) {
// roof_1st_layer
// ORCA: Roof1stLayer may be printed with base material when it acts as a contact layer.
bool interface_as_base = area_group.interface_as_base;
fill_params.density = interface_density;
// Note: spacing means the separation between two lines as if they are tightly extruded
filler_Roof1stLayer->spacing = interface_flow.spacing();
filler_Roof1stLayer->angle = base_support_angle;
fill_params.dont_sort = true;
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
// generate a perimeter first to support interface better
ExtrusionEntityCollection* temp_support_fills = new ExtrusionEntityCollection();
make_perimeter_and_infill(temp_support_fills->entities, poly, 1, interface_flow, erSupportMaterial,
make_perimeter_and_infill(temp_support_fills->entities, poly, 1, interface_base_flow, interface_role,
filler_Roof1stLayer.get(), interface_density, false);
temp_support_fills->no_sort = true; // make sure loops are first
if (!temp_support_fills->entities.empty())
@@ -1508,23 +1547,49 @@ void TreeSupport::generate_toolpaths()
delete temp_support_fills;
} else if (area_group.type == SupportLayer::FloorType) {
// floor_areas
bool interface_as_base = area_group.interface_as_base;
fill_params.density = bottom_interface_density;
filler_interface->spacing = interface_flow.spacing();
fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys,
filler_interface.get(), fill_params, erSupportMaterialInterface, interface_flow);
} else if (area_group.type == SupportLayer::RoofType) {
// roof_areas
fill_params.density = interface_density;
filler_interface->spacing = interface_flow.spacing();
if (m_object_config->support_interface_pattern == smipGrid) {
filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value);
filler_interface->angle = base_support_angle;
fill_params.dont_sort = true;
}
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced)
filler_interface->layer_id = area_group.interface_id;
fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys, filler_interface.get(), fill_params, erSupportMaterialInterface,
interface_flow);
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
filler_interface->fixed_angle = true;
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
fill_params.dont_sort = true;
}
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys,
filler_interface.get(), fill_params, interface_role, interface_base_flow);
} else if (area_group.type == SupportLayer::RoofType) {
// roof_areas
bool interface_as_base = area_group.interface_as_base;
fill_params.density = interface_density;
filler_interface->spacing = interface_flow.spacing();
if (m_object_config->support_interface_pattern == smipGrid) {
filler_interface->angle = base_support_angle;
fill_params.dont_sort = true;
}
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
filler_interface->fixed_angle = true;
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
fill_params.dont_sort = true;
}
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys, filler_interface.get(), fill_params, interface_role,
interface_base_flow);
}
else {
// base_areas
@@ -1890,7 +1955,7 @@ Polygons TreeSupport::get_trim_support_regions(
polygons_append(polygons_trimming, offset({ expoly }, trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
}
}
if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
if (!m_slicing_params.zero_gap_interface_top && m_object_config->thick_bridges) {
// Collect all bottom surfaces, which will be extruded with a bridging flow.
for (; i < object.layers().size(); ++i) {
const Layer& object_layer = *object.layers()[i];
@@ -1919,7 +1984,7 @@ void TreeSupport::draw_circles()
const PrintObjectConfig &config = m_object->config();
const Print* print = m_object->print();
bool has_brim = print->has_brim();
int bottom_gap_layers = round(m_slicing_params.gap_object_support / m_slicing_params.layer_height);
const coordf_t bottom_gap_height = m_slicing_params.gap_object_support;
const coordf_t branch_radius = config.tree_support_branch_diameter.value / 2;
const coordf_t branch_radius_scaled = scale_(branch_radius);
bool on_buildplate_only = m_object_config->support_on_build_plate_only.value;
@@ -1935,7 +2000,7 @@ void TreeSupport::draw_circles()
{
double angle;
if (SQUARE_SUPPORT)
angle = (double) i / CIRCLE_RESOLUTION * TAU + PI / 4.0 + nodes_angle;
angle = (double) i / CIRCLE_RESOLUTION * TAU + M_PI_4 + nodes_angle;
else
angle = (double) i / CIRCLE_RESOLUTION * TAU;
branch_circle.append(Point(cos(angle) * branch_radius_scaled, sin(angle) * branch_radius_scaled));
@@ -1999,7 +2064,7 @@ void TreeSupport::draw_circles()
coordf_t max_layers_above_base = 0;
coordf_t max_layers_above_roof = 0;
coordf_t max_layers_above_roof1 = 0;
int interface_id = 0;
bool floor_interface_as_base = false;
bool has_circle_node = false;
bool need_extra_wall = false;
ExPolygons collision_sharp_tails;
@@ -2033,6 +2098,8 @@ void TreeSupport::draw_circles()
break;
const SupportNode& node = *p_node;
// ORCA: Cap top interface height in mm based on per-node support layer height.
const coordf_t top_interface_height = coordf_t(top_interface_layers) * node.height;
ExPolygons area;
// Generate directly from overhang polygon if one of the following is true:
// 1) node is a normal part of hybrid support
@@ -2084,7 +2151,10 @@ void TreeSupport::draw_circles()
// Merge the overhang into the roof area so tree tips can still produce
// a continuous support interface. Suppressing this for build-plate-only
// support drops the roof polygons entirely in valid tree branches.
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !node.is_sharp_tail) {
// ORCA: Only keep top interface polygons that fully fit in the mm height cap.
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 &&
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON &&
!node.is_sharp_tail) {
ExPolygons overhang_expanded;
if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1)
overhang_expanded.emplace_back(node.overhang);
@@ -2097,16 +2167,19 @@ void TreeSupport::draw_circles()
if (obj_layer_nr>0 && node.distance_to_top < 0)
append(roof_gap_areas, area);
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 && node.is_sharp_tail==false)
// ORCA: Roof1stLayer must also fit inside the mm cap.
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 &&
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail==false)
{
append(roof_1st_layer, area);
max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top);
}
else if (obj_layer_nr > 0 && node.support_roof_layers_below > 0 && node.is_sharp_tail == false)
// ORCA: Roof layers must also fit inside the mm cap.
else if (obj_layer_nr > 0 && node.support_roof_layers_below > 1 &&
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail == false)
{
append(roof_areas, area);
max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top);
interface_id = node.obj_layer_nr % top_interface_layers;
}
else
{
@@ -2135,7 +2208,6 @@ void TreeSupport::draw_circles()
roof_1st_layer.clear();
max_layers_above_roof = std::max(max_layers_above_roof, max_layers_above_roof1);
max_layers_above_roof1 = 0;
interface_id = obj_layer_nr % top_interface_layers;
}
ExPolygons roofs; append(roofs, roof_1st_layer); append(roofs, roof_areas);append(roofs, roof_gap_areas);
@@ -2148,37 +2220,130 @@ void TreeSupport::draw_circles()
for (auto &area : base_areas) { area.simplify(scale_(line_width / 2), &base_areas_simplified); }
base_areas = std::move(base_areas_simplified);
}
//Subtract support floors. We can only compute floor_areas here instead of with roof_areas,
// or we'll get much wider floor than necessary.
if (bottom_interface_layers + bottom_gap_layers > 0)
// ORCA:
// Bottom interface / bottom gap must be anchored to the *true* support-to-model contact surface.
// Do NOT window the contact search by gap or interface height.
// First find the real contact below, then enforce:
// - an empty gap below (contact_z + gap)
// - exactly N interface layers above that
if (!base_areas.empty() && !m_object_config->support_on_build_plate_only.value &&
(bottom_gap_height > EPSILON || bottom_interface_layers > 0))
{
if (layer_nr >= bottom_interface_layers + bottom_gap_layers)
{
// find the lowest interface layer
// TODO the gap may not be exact when "independent support layer height" is enabled
size_t layer_nr_next = layer_nr - bottom_interface_layers;
size_t obj_layer_nr_next = m_ts_data->layer_heights[layer_nr_next].obj_layer_nr;
for (size_t i = 0; i <= bottom_gap_layers && i <= obj_layer_nr_next; i++)
{
const Layer *below_layer = m_object->get_layer(obj_layer_nr_next - i);
ExPolygons bottom_interface = intersection_ex(base_areas, below_layer->lslices);
floor_areas.insert(floor_areas.end(), bottom_interface.begin(), bottom_interface.end());
const coordf_t interface_height =
bottom_interface_layers > 0 ? coordf_t(bottom_interface_layers) * m_slicing_params.layer_height : 0.0;
const coordf_t layer_top_z = ts_layer->print_z;
const coordf_t layer_bottom_z = ts_layer->bottom_z();
ExPolygons new_base_areas;
ExPolygons new_floor_areas;
struct ContactBand {
coordf_t z = 0.0;
Polygons surfaces;
};
for (const ExPolygon& comp : base_areas) {
ExPolygons comp_poly { comp };
bool found_contact = false;
std::vector<ContactBand> bands;
// Search downward for object layers whose TOP/BOTTOM surfaces intersect this component.
for (size_t idx = obj_layer_nr + 1; idx-- > 0;) {
const Layer* below_layer = m_object->get_layer(idx);
Polygons top_surfaces = collect_region_slices_by_type(*below_layer, stTop);
Polygons bottom_surfaces = collect_region_slices_by_type(*below_layer, stBottom);
Polygons surf_union = top_surfaces;
polygons_append(surf_union, bottom_surfaces);
if (surf_union.empty())
continue;
ExPolygons inter = intersection_ex(comp_poly, surf_union);
if (!inter.empty()) {
bands.push_back(ContactBand{ below_layer->print_z, std::move(surf_union) });
found_contact = true;
}
}
if (found_contact) {
std::sort(bands.begin(), bands.end(), [](const ContactBand &a, const ContactBand &b) {
return a.z < b.z;
});
}
if (!found_contact) {
append(new_base_areas, comp_poly);
continue;
}
bool interface_id_set = false;
bool any_gap_cleared = false;
for (const ContactBand &band : bands) {
const coordf_t band_gap_top = band.z + bottom_gap_height;
const coordf_t band_iface_start = band_gap_top;
const bool band_applies = layer_top_z >= band.z - EPSILON;
if (!band_applies)
continue;
// Inside the gap: remove only the part overlapping the contact surface, keep the rest.
if (bottom_gap_height > EPSILON && layer_bottom_z < band_gap_top - EPSILON) {
any_gap_cleared = true;
comp_poly = std::move(diff_ex(comp_poly, band.surfaces));
}
// Overlaps interface band
if (bottom_interface_layers > 0 &&
layer_bottom_z >= band_iface_start - EPSILON &&
layer_bottom_z < band_iface_start + interface_height - EPSILON) {
if (!interface_id_set) {
size_t first_interface_layer = layer_nr;
while (first_interface_layer > 0) {
if (m_ts_data->layer_heights[first_interface_layer - 1].print_z <= band_iface_start + EPSILON)
break;
--first_interface_layer;
}
// ORCA: Use support-layer index for base-interface selection (robust with independent heights).
if (m_support_params.num_bottom_base_interface_layers > 0) {
const int bottom_interface_idx =
std::max(0, int(layer_nr) - int(first_interface_layer));
const int bottom_base_start_idx =
std::max(0, int(bottom_interface_layers) - int(m_support_params.num_bottom_base_interface_layers));
floor_interface_as_base = bottom_interface_idx >= bottom_base_start_idx;
}
interface_id_set = true;
}
ExPolygons band_ex = union_ex(band.surfaces);
if (!band_ex.empty()) {
const coordf_t margin = scale_(m_support_params.support_extrusion_width);
ExPolygons comp_margin = offset_ex(comp_poly, margin);
ExPolygons band_clipped = intersection_ex(band_ex, comp_margin);
band_ex = std::move(band_clipped);
}
ExPolygons comp_interface = band_ex.empty() ? ExPolygons {} : intersection_ex(comp_poly, band_ex);
if (!comp_interface.empty()) {
append(new_floor_areas, comp_interface);
comp_poly = std::move(diff_ex(comp_poly, offset_ex(comp_interface, 10)));
}
}
}
if (any_gap_cleared && comp_poly.empty()) {
continue;
}
if (!comp_poly.empty())
append(new_base_areas, comp_poly);
}
if (floor_areas.empty() == false) {
//floor_areas = std::move(diff_ex(floor_areas, avoid_region_interface));
//floor_areas = std::move(offset2_ex(floor_areas, contact_dist_scaled, -contact_dist_scaled));
base_areas = std::move(diff_ex(base_areas, offset_ex(floor_areas, 10)));
}
}
if (bottom_gap_layers > 0 && m_ts_data->layer_heights[layer_nr].obj_layer_nr > bottom_gap_layers) {
const Layer* below_layer = m_object->get_layer(m_ts_data->layer_heights[layer_nr].obj_layer_nr - bottom_gap_layers);
ExPolygons bottom_gap_area = intersection_ex(floor_areas, below_layer->lslices);
if (!bottom_gap_area.empty()) {
floor_areas = std::move(diff_ex(floor_areas, bottom_gap_area));
}
base_areas = std::move(new_base_areas);
floor_areas = std::move(new_floor_areas);
}
auto &area_groups = ts_layer->area_groups;
for (auto& expoly : ts_layer->base_areas) {
//if (area(expoly) < SQ(scale_(1))) continue;
area_groups.emplace_back(&expoly, SupportLayer::BaseType, max_layers_above_base);
@@ -2188,11 +2353,11 @@ void TreeSupport::draw_circles()
for (auto& expoly : ts_layer->roof_areas) {
//if (area(expoly) < SQ(scale_(1))) continue;
area_groups.emplace_back(&expoly, SupportLayer::RoofType, max_layers_above_roof);
area_groups.back().interface_id = interface_id;
}
for (auto &expoly : ts_layer->floor_areas) {
//if (area(expoly) < SQ(scale_(1))) continue;
area_groups.emplace_back(&expoly, SupportLayer::FloorType, 10000);
area_groups.back().interface_as_base = floor_interface_as_base;
}
for (auto &expoly : ts_layer->roof_1st_layer) {
//if (area(expoly) < SQ(scale_(1))) continue;
@@ -2216,13 +2381,49 @@ void TreeSupport::draw_circles()
//Must update bounding box which is used in avoid crossing perimeter
ts_layer->lslices_bboxes.clear();
ts_layer->lslices_bboxes.reserve(ts_layer->lslices.size());
for (const ExPolygon& expoly : ts_layer->lslices)
ts_layer->lslices_bboxes.emplace_back(get_extents(expoly));
ts_layer->backup_untyped_slices();
}
});
// ORCA: normalize interface_id sequencing to follow printed interface layers only.
const int top_base_layers = int(m_support_params.num_top_base_interface_layers);
const bool interlaced = m_object_config->support_interface_pattern == smipRectilinearInterlaced;
int roof_interface_id = 0;
int floor_interface_id = 0;
bool has_roof_interface;
bool has_floor_interface;
for (size_t layer_nr = 0; layer_nr < m_ts_data->layer_heights.size(); ++layer_nr) {
SupportLayer *ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
if (ts_layer == nullptr)
continue;
has_roof_interface = false;
has_floor_interface = false;
for (auto &area_group : ts_layer->area_groups) {
if (area_group.type == SupportLayer::RoofType || area_group.type == SupportLayer::Roof1stLayer) {
if (interlaced)
area_group.interface_id = roof_interface_id;
area_group.interface_as_base = top_base_layers > 0 && roof_interface_id < top_base_layers;
has_roof_interface = true;
} else if (area_group.type == SupportLayer::FloorType) {
if (interlaced)
area_group.interface_id = floor_interface_id;
has_floor_interface = true;
}
}
if (has_roof_interface)
++roof_interface_id;
if (has_floor_interface)
++floor_interface_id;
}
if (with_lightning_infill)
{
@@ -2488,6 +2689,7 @@ void TreeSupport::drop_nodes()
layer_radius.emplace(calc_radius(node_dist));
}
}
// parallel pre-compute avoidance
tbb::parallel_for(tbb::blocked_range<size_t>(0, contact_nodes.size() - 1), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) {
@@ -2679,8 +2881,9 @@ void TreeSupport::drop_nodes()
// Make sure the next pass doesn't drop down either of these (since that already happened).
node_parent->merged_neighbours.push_front(node_parent == p_node ? neighbour : p_node);
const bool to_buildplate = !is_inside_ex(get_collision(0, obj_layer_nr_next), next_position);
SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next, node_parent->support_roof_layers_below - 1, to_buildplate, node_parent,
print_z_next, height_next);
SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next,
node_parent->support_roof_layers_below - (node_parent->distance_to_top > 0 ? 1 : 0),
to_buildplate, node_parent, print_z_next, height_next);
get_max_move_dist(next_node);
m_ts_data->m_mutex.lock();
contact_nodes[layer_nr_next].push_back(next_node);
@@ -2730,7 +2933,8 @@ void TreeSupport::drop_nodes()
ExPolygons overhangs_next = diff_clipped({ node.overhang }, get_collision(0, obj_layer_nr_next));
for(auto& overhang:overhangs_next) {
Point next_pt = overhang.contour.centroid();
SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next, p_node->support_roof_layers_below - 1,
SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next,
p_node->support_roof_layers_below - (p_node->distance_to_top > 0 ? 1 : 0),
to_buildplate, p_node, print_z_next, height_next);
next_node->max_move_dist = 0;
next_node->overhang = std::move(overhang);
@@ -2876,8 +3080,9 @@ void TreeSupport::drop_nodes()
}
auto next_collision = get_collision(0, obj_layer_nr_next);
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex);
SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next, node.support_roof_layers_below - 1, to_buildplate, p_node,
print_z_next, height_next);
SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next,
node.support_roof_layers_below - (node.distance_to_top > 0 ? 1 : 0),
to_buildplate, p_node, print_z_next, height_next);
// don't increase radius if next node will collide partially with the object (STUDIO-7883)
to_outside = projection_onto(next_collision, next_node->position);
direction_to_outer = to_outside - node.position;
@@ -3098,7 +3303,12 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights()
// add support layers according to layer_heights
int support_layer_nr = m_raft_layers;
for (size_t i = 0; i < layer_heights.size(); i++, support_layer_nr++) {
SupportLayer *ts_layer = m_object->add_tree_support_layer(support_layer_nr, layer_heights[i].print_z, layer_heights[i].height, layer_heights[i].print_z);
// SupportLayer *ts_layer = m_object->add_tree_support_layer(support_layer_nr, layer_heights[i].print_z, layer_heights[i].height, layer_heights[i].print_z);
// ORCA: add_tree_support_layer() argument order is (id, height, print_z, slice_z).
// Passing print_z as height breaks support layer geometry.
SupportLayer *ts_layer = m_object->add_tree_support_layer(support_layer_nr, layer_heights[i].height, layer_heights[i].print_z, layer_heights[i].print_z);
if (ts_layer->id() > m_raft_layers) {
SupportLayer *lower_layer = m_object->get_support_layer(ts_layer->id() - 1);
if (lower_layer) {
@@ -3147,7 +3357,21 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights()
for (SupportNode *node : contact_nodes[layer_nr]) {
node->height = new_height;
node->distance_to_top = -num_layers;
node->support_roof_layers_below += num_layers - 1;
}
}
// ORCA: Recompute support_roof_layers_below from remaining interface height (independent heights).
const int top_layers = m_object->config().support_interface_top_layers.value;
if (m_support_params.independent_layer_height && top_layers > 0) {
const coordf_t interface_height_mm = coordf_t(top_layers) * m_slicing_params.layer_height;
for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) {
if (contact_nodes[layer_nr].empty()) continue;
for (SupportNode *node : contact_nodes[layer_nr]) {
if (node->height <= EPSILON) continue;
const coordf_t remaining_mm = interface_height_mm - (node->dist_mm_to_top - this->top_z_distance);
const int layers_fit = remaining_mm < -EPSILON ? 0 : int(std::floor((remaining_mm + EPSILON) / node->height));
node->support_roof_layers_below = std::min(layers_fit, top_layers);
}
}
}
@@ -3166,9 +3390,6 @@ void TreeSupport::generate_contact_points()
const coordf_t max_bridge_length = scale_(config.max_bridge_length.value);
coord_t radius_scaled = scale_(base_radius);
bool on_buildplate_only = m_object_config->support_on_build_plate_only.value;
const bool roof_enabled = config.support_interface_top_layers.value > 0;
const bool force_tip_to_roof = roof_enabled && m_support_params.soluble_interface;
//First generate grid points to cover the entire area of the print.
BoundingBox bounding_box = m_object->bounding_box();
const Point bounding_box_size = bounding_box.max - bounding_box.min;
@@ -3200,7 +3421,7 @@ void TreeSupport::generate_contact_points()
// z_distance_top = round(z_distance_top / layer_height) * layer_height;
// // BBS: add extra distance if thick bridge is enabled
// // Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height
// if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
// if (!m_slicing_params.zero_gap_interface_top && m_object_config->thick_bridges) {
// z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height;
//}
// }
@@ -3208,8 +3429,6 @@ void TreeSupport::generate_contact_points()
int gap_layers = z_distance_top == 0 ? 0 : 1;
size_t support_roof_layers = config.support_interface_top_layers.value;
if (support_roof_layers > 0)
support_roof_layers += 1; // BBS: add a normal support layer below interface (if we have interface)
coordf_t thresh_angle = std::min(89.f, config.support_threshold_angle.value < EPSILON ? 30.f : config.support_threshold_angle.value);
coordf_t half_overhang_distance = scale_(tan(thresh_angle * M_PI / 180.0) * layer_height / 2);
@@ -3263,13 +3482,13 @@ void TreeSupport::generate_contact_points()
if (force_add || !already_inserted.count(hash_pos)) {
already_inserted.emplace(hash_pos);
bool to_buildplate = true;
size_t roof_layers = add_interface ? support_roof_layers : 0;
size_t roof_layers = add_interface ? (support_roof_layers > 0 ? support_roof_layers - 1 : 0) : 0; // subtract 1 because the contact node itself counts as one layer
// add a new node as a virtual node which acts as the invisible gap between support and object
// distance_to_top=-1: it's virtual
// print_z=object_layer->bottom_z: it directly contacts the bottom
// height=z_distance_top: it's height is exactly the gap distance
// dist_mm_to_top=0: it directly contacts the bottom
contact_node = m_ts_data->create_node(pt, -gap_layers, layer_nr-1, roof_layers + 1, to_buildplate, SupportNode::NO_PARENT, bottom_z, z_distance_top, 0,
contact_node = m_ts_data->create_node(pt, -gap_layers, layer_nr-1, roof_layers, to_buildplate, SupportNode::NO_PARENT, bottom_z, z_distance_top, 0,
radius);
contact_node->overhang = overhang;
contact_node->is_sharp_tail = is_sharp_tail;
@@ -3305,7 +3524,7 @@ void TreeSupport::generate_contact_points()
}
for (auto &overhang : overhangs_regular) {
bool add_interface = (force_tip_to_roof || area(overhang) > minimum_roof_area) && !is_sharp_tail;
bool add_interface = area(overhang) > minimum_roof_area && !is_sharp_tail;
BoundingBox overhang_bounds = get_extents(overhang);
double radius = std::clamp(unscale_(overhang_bounds.radius()), MIN_BRANCH_RADIUS, base_radius);
// add supports at corners for both auto and manual overhangs, github #2008

View File

@@ -26,7 +26,6 @@
#include <cassert>
#include <chrono>
#include <fstream>
#include <optional>
#include <stdio.h>
#include <string>
@@ -54,7 +53,6 @@
#define _L(s) Slic3r::I18N::translate(s)
#endif
//#define TREESUPPORT_DEBUG_SVG
namespace Slic3r
{
@@ -132,7 +130,7 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
const PrintObjectConfig &object_config = print_object.config();
if (object_config.support_top_z_distance < EPSILON)
// || min_feature_size < scaled<coord_t>(0.1) that is the minimum line width
TreeSupportSettings::soluble = true;
TreeSupportSettings::zero_top_z_gap = true;
}
size_t largest_printed_mesh_idx = 0;
@@ -283,16 +281,6 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
//FIXME enforcer_overhang_offset is a fudge constant!
enforced_overhangs = diff(offset(union_ex(enforced_overhangs), enforcer_overhang_offset),
lower_layer.lslices);
#ifdef TREESUPPORT_DEBUG_SVG
// if (! intersecting_edges(enforced_overhangs).empty())
{
static int irun = 0;
SVG::export_expolygons(debug_out_path("treesupport-self-intersections-%d.svg", ++irun),
{ { { current_layer.lslices }, { "current_layer.lslices", "yellow", 0.5f } },
{ { lower_layer.lslices }, { "lower_layer.lslices", "gray", 0.5f } },
{ { union_ex(enforced_overhangs) }, { "enforced_overhangs", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
}
#endif // TREESUPPORT_DEBUG_SVG
//check_self_intersections(enforced_overhangs, "generate_overhangs - enforced overhangs2");
overhangs = overhangs.empty() ? std::move(enforced_overhangs) : union_(overhangs, enforced_overhangs);
//check_self_intersections(overhangs, "generate_overhangs - enforcers");
@@ -718,7 +706,8 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
(support_params.interface_angle + (layer_idx & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)) :
support_params.base_angle;
fill_params.density = float(roof ? support_params.interface_density : scaled<float>(filler->spacing) / (scaled<float>(filler->spacing) + float(support_infill_distance)));
// ORCA: use top-specific interface density after separating top/bottom settings.
fill_params.density = float(roof ? support_params.top_interface_density : scaled<float>(filler->spacing) / (scaled<float>(filler->spacing) + float(support_infill_distance)));
fill_params.dont_adjust = true;
Polylines out;
@@ -1291,7 +1280,7 @@ static void generate_initial_areas(
;
const size_t num_support_roof_layers = mesh_group_settings.support_roof_layers;
const bool roof_enabled = num_support_roof_layers > 0;
const bool force_tip_to_roof = roof_enabled && (interface_placer.support_parameters.soluble_interface || sqr<double>(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area);
const bool force_tip_to_roof = roof_enabled && (interface_placer.support_parameters.zero_gap_interface_top || sqr<double>(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area);
// cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point
// may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang
// does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it.
@@ -1806,11 +1795,6 @@ static void increase_areas_one_layer(
// Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall.
volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist);
#ifdef TREESUPPORT_DEBUG_SVG
SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-%d-%ld.svg", layer_idx, int(merging_area_idx)),
{ { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } },
{ { union_ex(parent.influence_area) }, { "parent", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif // TREESUPPORT_DEBUG_SVG
Polygons to_bp_data, to_model_data;
coord_t radius = support_element_collision_radius(config, elem);
@@ -1941,11 +1925,6 @@ static void increase_areas_one_layer(
// was never made for precision in the single digit micron range.
offset_slow = safe_offset_inc(parent.influence_area, extra_speed + extra_slow_speed + config.maximum_move_distance_slow,
wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 2);
#ifdef TREESUPPORT_DEBUG_SVG
SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-slow-%d-%ld.svg", layer_idx, int(merging_area_idx)),
{ { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } },
{ { union_ex(offset_slow) }, { "offset_slow", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif // TREESUPPORT_DEBUG_SVG
}
if (offset_fast.empty() && settings.increase_speed != slow_speed) {
if (offset_independant_faster)
@@ -1955,11 +1934,6 @@ static void increase_areas_one_layer(
const coord_t delta_slow_fast = config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed);
offset_fast = safe_offset_inc(offset_slow, delta_slow_fast, wall_restriction, safe_movement_distance, safe_movement_distance + radius, offset_independant_faster ? 2 : 1);
}
#ifdef TREESUPPORT_DEBUG_SVG
SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-fast-%d-%ld.svg", layer_idx, int(merging_area_idx)),
{ { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } },
{ { union_ex(offset_fast) }, { "offset_fast", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif // TREESUPPORT_DEBUG_SVG
}
}
std::optional<SupportElementState> result;
@@ -3486,18 +3460,6 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
move_bounds, interface_placer, throw_on_cancel);
auto t_gen = std::chrono::high_resolution_clock::now();
#ifdef TREESUPPORT_DEBUG_SVG
for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++layer_idx) {
Polygons polys;
for (auto& area : move_bounds[layer_idx])
append(polys, area.influence_area);
if (auto begin = move_bounds[layer_idx].begin(); begin != move_bounds[layer_idx].end())
SVG::export_expolygons(debug_out_path("treesupport-initial_areas-%d.svg", layer_idx),
{ { { union_ex(volumes.getWallRestriction(support_element_collision_radius(config, begin->state), layer_idx, begin->state.use_min_xy_dist)) },
{ "wall_restricrictions", "gray", 0.5f } },
{ { union_ex(polys) }, { "parent", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
}
#endif // TREESUPPORT_DEBUG_SVG
// ### Propagate the influence areas downwards. This is an inherently serial operation.
print.set_status(60, _L("Generating support"));
@@ -3829,88 +3791,181 @@ void organic_draw_branches(
const double bottom_z = layer_idx > 0 ? layer_z(slicing_params, config, layer_idx - 1) : 0.;
slice_z.emplace_back(float(0.5 * (bottom_z + print_z)));
}
std::vector<Polygons> slices = slice_mesh(partial_mesh, slice_z, mesh_slicing_params, throw_on_cancel);
// ORCA: guard against empty slices from meshing.
if (slices.empty())
continue;
bottom_contacts.clear();
// ORCA: trim tiny fragments to reduce degenerate polygon booleans.
const double tiny_area = tiny_area_threshold();
//FIXME parallelize?
for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++i) {
slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true)); // FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist);
slices[i] = intersection(slices[i], volumes.m_bed_area);
// ORCA: safety offset when trimming collision/bed to improve robustness.
slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true), ApplySafetyOffset::Yes); // FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist);
slices[i] = intersection(slices[i], volumes.m_bed_area, ApplySafetyOffset::Yes);
remove_small(slices[i], tiny_area);
}
size_t num_empty = 0;
if (slices.front().empty()) {
// Some of the initial layers are empty.
num_empty = std::find_if(slices.begin(), slices.end(), [](auto &s) { return !s.empty(); }) - slices.begin();
} else {
if (branch.has_root) {
if (config.support_rests_on_model && branch.path.front()->state.to_model_gracious) {
if (config.settings.support_floor_layers > 0)
//FIXME one may just take the whole tree slice as bottom interface.
bottom_contacts.emplace_back(intersection_clipped(slices.front(), volumes.getPlaceableAreas(0, layer_begin, [] {})));
} else if (layer_begin > 0) {
// Drop down areas that do rest non - gracefully on the model to ensure the branch actually rests on something.
struct BottomExtraSlice {
Polygons polygons;
double area;
};
std::vector<BottomExtraSlice> bottom_extra_slices;
Polygons rest_support;
coord_t bottom_radius = support_element_radius(config, *branch.path.front());
// Don't propagate further than 1.5 * bottom radius.
//LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height;
LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height;
LayerIndex layer_bottommost = branch.path.front()->state.verylost ?
// If the tree bottom is hanging in the air, bring it down to some surface.
0 :
//FIXME the "verylost" branches should stop when crossing another support.
std::max(0, layer_begin - layers_propagate_max);
double support_area_min_radius = M_PI * sqr(double(config.branch_radius));
double support_area_stop = std::max(0.2 * M_PI * sqr(double(bottom_radius)), 0.5 * support_area_min_radius);
// Only propagate until the rest area is smaller than this threshold.
//double support_area_min = 0.1 * support_area_min_radius;
for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) {
rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false));
double rest_support_area = area(rest_support);
if (rest_support_area < support_area_stop)
// Don't propagate a fraction of the tree contact surface.
break;
bottom_extra_slices.push_back({ rest_support, rest_support_area });
}
// Now remove those bottom slices that are not supported at all.
#if 0
while (! bottom_extra_slices.empty()) {
Polygons this_bottom_contacts = intersection_clipped(
bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {}));
if (area(this_bottom_contacts) < support_area_min)
bottom_extra_slices.pop_back();
else {
// At least a fraction of the tree bottom is considered to be supported.
if (config.settings.support_floor_layers > 0)
// Turn this fraction of the tree bottom into a contact layer.
bottom_contacts.emplace_back(std::move(this_bottom_contacts));
break;
}
}
#endif
if (config.support_rests_on_model && config.settings.support_floor_layers > 0)
for (int i = int(bottom_extra_slices.size()) - 2; i >= 0; -- i)
bottom_contacts.emplace_back(
intersection_clipped(bottom_extra_slices[i].polygons, volumes.getPlaceableAreas(0, layer_begin - i - 1, [] {})));
layer_begin -= LayerIndex(bottom_extra_slices.size());
slices.insert(slices.begin(), bottom_extra_slices.size(), {});
auto it_dst = slices.begin();
for (auto it_src = bottom_extra_slices.rbegin(); it_src != bottom_extra_slices.rend(); ++ it_src)
*it_dst ++ = std::move(it_src->polygons);
}
}
recover_pending_branch_roofs(interface_placer, branch.path, layer_begin, slices);
}
layer_begin += LayerIndex(num_empty);
// ORCA: trim leading empty slices to keep layer indices aligned.
if (num_empty >= slices.size())
continue;
if (num_empty > 0) {
slices.erase(slices.begin(), slices.begin() + num_empty);
layer_begin += LayerIndex(num_empty);
}
// ORCA: use the trimmed front slice as the contact reference.
Polygons slice_front_contact = slices.front();
if (branch.has_root) {
if (branch.path.front()->state.to_model_gracious) {
if (config.settings.support_floor_layers > 0) {
// If bottom Z gap is non-zero, keep bottom contacts even when not touching the model.
Polygons contacts;
// ORCA: non-zero bottom Z should not be clipped by placeable areas.
if (config.support_rests_on_model && config.z_distance_bottom_layers > 0 && layer_begin > 0)
contacts = slice_front_contact;
else {
Polygons placeable = volumes.getPlaceableAreas(0, layer_begin, [] {});
contacts = intersection_clipped(slice_front_contact, placeable, ApplySafetyOffset::Yes);
}
remove_small(contacts, tiny_area);
// ORCA: ensure bottom contacts exist if clipping removed them.
if (contacts.empty() && config.support_rests_on_model && layer_begin > 0 && !slice_front_contact.empty())
contacts = slice_front_contact;
if (!contacts.empty())
bottom_contacts.emplace_back(std::move(contacts));
}
} else if (layer_begin > 0) {
// Drop down areas that do rest non - gracefully on the model to ensure the branch actually rests on something.
struct BottomExtraSlice {
Polygons polygons;
double area;
};
std::vector<BottomExtraSlice> bottom_extra_slices;
Polygons rest_support;
coord_t bottom_radius = support_element_radius(config, *branch.path.front());
// Don't propagate further than 1.5 * bottom radius.
//LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height;
LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height;
LayerIndex layer_bottommost = branch.path.front()->state.verylost ?
// If the tree bottom is hanging in the air, bring it down to some surface.
0 :
//FIXME the "verylost" branches should stop when crossing another support.
std::max(0, layer_begin - layers_propagate_max);
double support_area_min_radius = M_PI * sqr(double(config.branch_radius));
double support_area_stop = std::max(0.2 * M_PI * sqr(double(bottom_radius)), 0.5 * support_area_min_radius);
// Only propagate until the rest area is smaller than this threshold.
//double support_area_min = 0.1 * support_area_min_radius;
for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) {
LayerIndex collision_layer = (layer_idx == layer_begin - 1) ? layer_begin : layer_idx;
Polygons collision = volumes.getCollision(0, collision_layer, false);
rest_support = diff_clipped(rest_support.empty() ? slice_front_contact : rest_support, collision, ApplySafetyOffset::Yes);
remove_small(rest_support, tiny_area);
double rest_support_area = area(rest_support);
if (rest_support_area < support_area_stop)
// Don't propagate a fraction of the tree contact surface.
break;
bottom_extra_slices.push_back({ rest_support, rest_support_area });
}
// Now remove those bottom slices that are not supported at all.
#if 0
while (! bottom_extra_slices.empty()) {
Polygons this_bottom_contacts = intersection_clipped(
bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {}));
if (area(this_bottom_contacts) < support_area_min)
bottom_extra_slices.pop_back();
else {
// At least a fraction of the tree bottom is considered to be supported.
if (config.settings.support_floor_layers > 0)
// Turn this fraction of the tree bottom into a contact layer.
bottom_contacts.emplace_back(std::move(this_bottom_contacts));
break;
}
}
#endif
if (config.settings.support_floor_layers > 0) {
Polygons contacts;
if (!bottom_extra_slices.empty()) {
const int contact_idx = int(bottom_extra_slices.size()) - 1; // Use the lowest contact slice as the footprint.
// ORCA: non-zero bottom Z should not be clipped by placeable areas.
if (config.support_rests_on_model && config.z_distance_bottom_layers > 0 && layer_begin > 0)
contacts = intersection_clipped(bottom_extra_slices[contact_idx].polygons, Polygons{volumes.m_bed_area}, ApplySafetyOffset::Yes);
else {
Polygons placeable = volumes.getPlaceableAreas(0, layer_begin, [] {});
contacts = intersection_clipped(bottom_extra_slices[contact_idx].polygons, placeable, ApplySafetyOffset::Yes);
}
} else {
// Fallback: use the current contact slice when no propagation happened.
if (config.support_rests_on_model && config.z_distance_bottom_layers > 0 && layer_begin > 0)
contacts = slice_front_contact;
else {
Polygons placeable = volumes.getPlaceableAreas(0, layer_begin, [] {});
contacts = intersection_clipped(slice_front_contact, placeable, ApplySafetyOffset::Yes);
}
}
remove_small(contacts, tiny_area);
if (!contacts.empty())
bottom_contacts.emplace_back(std::move(contacts));
// ORCA: ensure bottom contacts exist if clipping removed them.
if (bottom_contacts.empty() && config.support_rests_on_model && layer_begin > 0 && !slice_front_contact.empty())
bottom_contacts.emplace_back(slice_front_contact);
}
layer_begin -= LayerIndex(bottom_extra_slices.size());
slices.insert(slices.begin(), bottom_extra_slices.size(), {});
auto it_dst = slices.begin();
for (auto it_src = bottom_extra_slices.rbegin(); it_src != bottom_extra_slices.rend(); ++ it_src)
*it_dst ++ = std::move(it_src->polygons);
}
// ORCA: retain bottom contacts even when no placeable areas intersect.
if (branch.has_root && config.support_rests_on_model && branch.path.front()->state.layer_idx > 0 &&
config.settings.support_floor_layers > 0 && config.z_distance_bottom_layers > 0 &&
bottom_contacts.empty() && !slice_front_contact.empty())
bottom_contacts.emplace_back(slice_front_contact);
}
// ORCA: bottom contacts provide the footprint; interface layers are built later.
#if 0
//FIXME branch.has_tip seems to not be reliable.
if (branch.has_tip && interface_placer.support_parameters.has_top_contacts)
// Add top slices to top contacts / interfaces / base interfaces.
for (int i = int(branch.path.size()) - 1; i >= 0; -- i) {
const SupportElement &el = *branch.path[i];
if (el.state.missing_roof_layers == 0)
break;
//FIXME Move or not?
interface_placer.add_roof(std::move(slices[int(slices.size()) - i - 1]), el.state.layer_idx,
interface_placer.support_parameters.num_top_interface_layers + 1 - el.state.missing_roof_layers);
}
#endif
while (! slices.empty() && slices.back().empty()) {
slices.pop_back();
-- layer_end;
}
// ORCA: recompute layer_end after trimming trailing empty slices.
layer_end = layer_begin + LayerIndex(slices.size());
if (layer_begin < layer_end) {
LayerIndex new_begin = tree.first_layer_id == -1 ? layer_begin : std::min(tree.first_layer_id, layer_begin);
LayerIndex new_end = tree.first_layer_id == -1 ? layer_end : std::max(tree.first_layer_id + LayerIndex(tree.slices.size()), layer_end);
@@ -3926,22 +3981,28 @@ void organic_draw_branches(
} else if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0)
tree.slices.insert(tree.slices.begin(), tree.first_layer_id - new_begin, {});
tree.slices.insert(tree.slices.end(), new_size - tree.slices.size(), {});
layer_begin -= LayerIndex(num_empty);
for (LayerIndex i = layer_begin; i != layer_end; ++ i) {
int j = i - layer_begin;
if (Polygons &src = slices[j]; ! src.empty()) {
Polygons &src = slices[j];
bool has_bottom_contacts = j < int(bottom_contacts.size()) && !bottom_contacts[j].empty();
// ORCA: preserve bottom contacts even if base polygons are empty.
if (!src.empty() || has_bottom_contacts) {
Slice &dst = tree.slices[i - new_begin];
if (++ dst.num_branches > 1) {
append(dst.polygons, std::move(src));
if (j < int(bottom_contacts.size()))
if (!src.empty())
append(dst.polygons, std::move(src));
if (has_bottom_contacts)
append(dst.bottom_contacts, std::move(bottom_contacts[j]));
} else {
dst.polygons = std::move(std::move(src));
if (j < int(bottom_contacts.size()))
if (!src.empty())
dst.polygons = std::move(src);
if (has_bottom_contacts)
dst.bottom_contacts = std::move(bottom_contacts[j]);
}
}
}
tree.first_layer_id = new_begin;
}
}
@@ -3954,10 +4015,15 @@ void organic_draw_branches(
Tree &tree = trees[tree_id];
for (Slice &slice : tree.slices)
if (slice.num_branches > 1) {
slice.polygons = union_(slice.polygons);
slice.bottom_contacts = union_(slice.bottom_contacts);
// ORCA: avoid union_ on empty containers.
if (!slice.polygons.empty())
slice.polygons = union_(slice.polygons);
if (!slice.bottom_contacts.empty())
slice.bottom_contacts = union_(slice.bottom_contacts);
slice.num_branches = 1;
}
throw_on_cancel();
}
}, tbb::simple_partitioner());
@@ -3970,17 +4036,27 @@ void organic_draw_branches(
std::vector<Slice> slices(num_layers, Slice{});
for (Tree &tree : trees)
if (tree.first_layer_id >= 0) {
for (LayerIndex i = tree.first_layer_id; i != tree.first_layer_id + LayerIndex(tree.slices.size()); ++ i)
if (Slice &src = tree.slices[i - tree.first_layer_id]; ! src.polygons.empty()) {
for (LayerIndex i = tree.first_layer_id; i != tree.first_layer_id + LayerIndex(tree.slices.size()); ++ i) {
Slice &src = tree.slices[i - tree.first_layer_id];
bool has_bottom_contacts = !src.bottom_contacts.empty();
// ORCA: preserve bottom contacts even if base polygons are empty.
if (!src.polygons.empty() || has_bottom_contacts) {
Slice &dst = slices[i];
if (++ dst.num_branches > 1) {
append(dst.polygons, std::move(src.polygons));
append(dst.bottom_contacts, std::move(src.bottom_contacts));
if (!src.polygons.empty())
append(dst.polygons, std::move(src.polygons));
if (has_bottom_contacts)
append(dst.bottom_contacts, std::move(src.bottom_contacts));
} else {
dst.polygons = std::move(src.polygons);
dst.bottom_contacts = std::move(src.bottom_contacts);
if (!src.polygons.empty())
dst.polygons = std::move(src.polygons);
if (has_bottom_contacts)
dst.bottom_contacts = std::move(src.bottom_contacts);
}
}
}
}
tbb::parallel_for(tbb::blocked_range<size_t>(0, std::min(move_bounds.size(), slices.size()), 1),
@@ -3988,8 +4064,11 @@ void organic_draw_branches(
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
Slice &slice = slices[layer_idx];
assert(intermediate_layers[layer_idx] == nullptr);
Polygons base_layer_polygons = slice.num_branches > 1 ? union_(slice.polygons) : std::move(slice.polygons);
Polygons bottom_contact_polygons = slice.num_branches > 1 ? union_(slice.bottom_contacts) : std::move(slice.bottom_contacts);
// ORCA: avoid union_ on empty inputs.
Polygons base_layer_polygons = slice.polygons.empty() ? Polygons{} :
(slice.num_branches > 1 ? union_(slice.polygons) : std::move(slice.polygons));
Polygons bottom_contact_polygons = slice.bottom_contacts.empty() ? Polygons{} :
(slice.num_branches > 1 ? union_(slice.bottom_contacts) : std::move(slice.bottom_contacts));
if (! base_layer_polygons.empty()) {
// Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned.

View File

@@ -306,7 +306,7 @@ public:
layer_start_bp_radius = (bp_radius - branch_radius) / bp_radius_increase_per_layer;
if (TreeSupportSettings::soluble) {
if (TreeSupportSettings::zero_top_z_gap) {
// safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely
// When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size
// This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance.
@@ -356,7 +356,7 @@ public:
// some static variables dependent on other meshes that are not currently processed.
// Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy.
inline static bool soluble = false;
inline static bool zero_top_z_gap = false;
/*!
* \brief Width of a single line of support.
*/
@@ -718,9 +718,15 @@ public:
{
assert(support_parameters.has_top_contacts);
assert(dtt_roof <= support_parameters.num_top_interface_layers);
// ORCA: Reserve one top interface layer but only when top base-interface layers exist.
// This prevents all interface layers from being classified as base-interface layers
// and preserves correct top contact and interface behavior.
size_t interface_threshold = support_parameters.num_top_interface_layers_only();
if (interface_threshold > 0 && support_parameters.num_top_base_interface_layers > 0)
--interface_threshold;
SupportGeneratorLayersPtr &layers =
dtt_roof == 0 ? this->top_contacts :
dtt_roof <= support_parameters.num_top_interface_layers_only() ? this->top_interfaces : this->top_base_interfaces;
dtt_roof <= interface_threshold ? this->top_interfaces : this->top_base_interfaces;
SupportGeneratorLayer*& l = layers[insert_layer_idx];
if (l == nullptr)
l = &layer_allocate_unguarded(layer_storage, dtt_roof == 0 ? SupporLayerType::TopContact : SupporLayerType::TopInterface,