ENH: Relative bridge direction + Align bridge/Ironing angles with model (#12055)

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
This commit is contained in:
Ian Bassi
2026-06-03 10:12:26 -03:00
committed by GitHub
parent 5bdcd69045
commit b3c7abdec6
10 changed files with 112 additions and 22 deletions

View File

@@ -935,10 +935,13 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.fixed_angle = !region_config.solid_infill_rotate_template.value.empty();
}
params.bridge_angle = float(surface.bridge_angle);
// ORCA: Align infill angle to model
float align_offset = 0.f;
if (region_config.align_infill_direction_to_model) {
auto m = layer.object()->trafo().matrix();
params.angle += atan2((float) m(1, 0), (float) m(0, 0));
align_offset = atan2((float)m(1, 0), (float)m(0, 0));
params.angle += align_offset;
}
// Calculate the actual flow we'll be using for this infill.
@@ -1024,6 +1027,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
if (fill.region_id == size_t(-1)) {
fill.region_id = region_id;
fill.surface = surface;
fill.surface.bridge_angle = params->bridge_angle;
fill.expolygons.emplace_back(std::move(fill.surface.expolygon));
//BBS
fill.region_id_group.push_back(region_id);
@@ -1578,7 +1582,7 @@ void Layer::make_ironing()
if (ironing_params.extruder != -1) {
//TODO just_infill is currently not used.
ironing_params.just_infill = false;
// Get filament-specific overrides if configured, otherwise use default values
// ORCA: Get filament-specific overrides if configured, otherwise use process values
size_t extruder_idx = ironing_params.extruder - 1;
ironing_params.line_spacing = (!config.filament_ironing_spacing.is_nil(extruder_idx)
? config.filament_ironing_spacing.get_at(extruder_idx)
@@ -1592,7 +1596,12 @@ void Layer::make_ironing()
ironing_params.speed = (!config.filament_ironing_speed.is_nil(extruder_idx)
? config.filament_ironing_speed.get_at(extruder_idx)
: config.ironing_speed);
ironing_params.angle = (config.ironing_angle_fixed ? 0 : calculate_infill_rotation_angle(this->object(), this->id(), config.solid_infill_direction.value, config.solid_infill_rotate_template.value)) + config.ironing_angle * M_PI / 180.;
double ironing_angle = (config.ironing_angle_fixed ? 0 : calculate_infill_rotation_angle(this->object(), this->id(), config.solid_infill_direction.value, config.solid_infill_rotate_template.value)) + config.ironing_angle * M_PI / 180.;
if (config.align_infill_direction_to_model) {
auto m = this->object()->trafo().matrix();
ironing_angle += atan2((double)m(1, 0), (double)m(0, 0));
}
ironing_params.angle = ironing_angle;
ironing_params.fixed_angle = config.ironing_angle_fixed || !config.solid_infill_rotate_template.value.empty();
ironing_params.pattern = config.ironing_pattern;
ironing_params.layerm = layerm;

View File

@@ -83,6 +83,12 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
(this->layer()->id() >= size_t(region_config.bottom_shell_layers.value) &&
this->layer()->print_z >= region_config.bottom_shell_thickness - EPSILON);
double model_rotation_rad = 0.0;
if (region_config.align_infill_direction_to_model) {
auto m = this->layer()->object()->trafo().matrix();
model_rotation_rad = std::atan2((double)m(1, 0), (double)m(0, 0));
}
PerimeterGenerator g(
// input:
&slices,
@@ -94,6 +100,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
&this->layer()->object()->config(),
&print_config,
spiral_mode,
model_rotation_rad,
// output:
&this->perimeters,
@@ -517,10 +524,27 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
SurfaceCollection bridges;
{
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z;
const double custom_angle = this->region().config().bridge_angle.value;
bridges.surfaces = custom_angle > 0 ?
expand_merge_surfaces(this->fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, Geometry::deg2rad(custom_angle)) :
// ORCA: Relative/Align Bridge Angle
const auto &region_config = this->region().config();
const double custom_angle_deg = region_config.bridge_angle.value;
const bool relative_angle = region_config.relative_bridge_angle.value;
const double custom_angle_rad = Geometry::deg2rad(custom_angle_deg);
double align_offset_rad = 0.0;
if (region_config.align_infill_direction_to_model) {
auto m = this->layer()->object()->trafo().matrix();
align_offset_rad = std::atan2((double)m(1, 0), (double)m(0, 0));
}
bridges.surfaces = (custom_angle_deg > 0.0 && !relative_angle) ?
expand_merge_surfaces(this->fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, custom_angle_rad + align_offset_rad) :
expand_bridges_detect_orientations(this->fill_surfaces.surfaces, expansion_zones, closing_radius);
if (custom_angle_deg > 0.0 && relative_angle) {
for (Surface &bridge_surface : bridges.surfaces) {
if (bridge_surface.bridge_angle >= 0)
bridge_surface.bridge_angle += custom_angle_rad;
}
}
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
@@ -782,12 +806,25 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
// would get merged into a single one while they need different directions
// also, supply the original expolygon instead of the grown one, because in case
// of very thin (but still working) anchors, the grown expolygon would go beyond them
double custom_angle = Geometry::deg2rad(this->region().config().bridge_angle.value);
if (custom_angle > 0.0) {
bridges[idx_last].bridge_angle = custom_angle;
// ORCA: Relative/Align Bridge Angle
const auto &region_config = this->region().config();
const double custom_angle_deg = region_config.bridge_angle.value;
const bool relative_angle = region_config.relative_bridge_angle.value;
const double custom_angle_rad = Geometry::deg2rad(custom_angle_deg);
double align_offset_rad = 0.0;
if (region_config.align_infill_direction_to_model) {
auto m = this->layer()->object()->trafo().matrix();
align_offset_rad = std::atan2((double)m(1, 0), (double)m(0, 0));
}
if (custom_angle_deg > 0.0 && !relative_angle) {
bridges[idx_last].bridge_angle = custom_angle_rad + align_offset_rad;
} else {
auto [bridging_dir, unsupported_dist] = detect_bridging_direction(to_polygons(initial), to_polygons(lower_layer->lslices));
bridges[idx_last].bridge_angle = PI + std::atan2(bridging_dir.y(), bridging_dir.x());
if (custom_angle_deg > 0.0 && relative_angle)
bridges[idx_last].bridge_angle += custom_angle_rad;
}
/*

View File

@@ -1760,8 +1760,14 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
BridgeDetector detector{ unsupported,
lower_island.expolygons,
perimeter_spacing / 4}; // Use a finer BridgeDetector. This affects coverage resolution, not extrusion spacing.
if (detector.detect_angle(Geometry::deg2rad(this->config->bridge_angle.value)))
// ORCA: Relative/Align Bridge Angle
const double custom_angle_deg = this->config->bridge_angle.value;
const bool relative_angle = this->config->relative_bridge_angle.value;
const double detect_angle_rad = (custom_angle_deg > 0.0 && !relative_angle)
? Geometry::deg2rad(custom_angle_deg) +
(this->config->align_infill_direction_to_model ? this->m_model_rotation_rad : 0.0)
: 0.0;
if (detector.detect_angle(detect_angle_rad))
expolygons_append(bridgeable, union_ex(detector.coverage(-1, true)));
}
if (!bridgeable.empty() && !surface->expolygon.holes.empty()) { // keep out if cannot be bridged or no holes to bridge

View File

@@ -117,6 +117,7 @@ public:
const PrintObjectConfig* object_config,
const PrintConfig* print_config,
const bool spiral_mode,
const double model_rotation_rad,
// Output:
// Loops with the external thin walls
ExtrusionEntityCollection* loops,
@@ -132,6 +133,7 @@ public:
config(config), object_config(object_config), print_config(print_config),
m_spiral_vase(spiral_mode),
m_scaled_resolution(scaled<double>(print_config->resolution.value > EPSILON ? print_config->resolution.value : EPSILON)),
m_model_rotation_rad(model_rotation_rad),
loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), fill_no_overlap(fill_no_overlap),
m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1), m_ext_mm3_per_mm_smaller_width(-1)
{}
@@ -157,6 +159,7 @@ private:
private:
bool m_spiral_vase;
double m_scaled_resolution;
double m_model_rotation_rad;
double m_ext_mm3_per_mm;
double m_mm3_per_mm;
double m_mm3_per_mm_overhang;

View File

@@ -1161,6 +1161,7 @@ static std::vector<std::string> s_Preset_print_options{
"small_perimeter_threshold",
"bridge_angle",
"internal_bridge_angle",
"relative_bridge_angle",
"filter_out_gap_fill",
"travel_acceleration",
"inner_wall_acceleration",

View File

@@ -1241,11 +1241,16 @@ void PrintConfigDef::init_fff_params()
def->label = L("External bridge infill direction");
def->category = L("Strength");
// xgettext:no-c-format, no-boost-format
def->tooltip = L("Bridging angle override. If left to zero, the bridging angle will be calculated "
"automatically. Otherwise the provided angle will be used for external bridges. "
"Use 180° for zero angle.");
def->tooltip = L("External Bridging angle override.\n"
"If left to zero, the bridging angle will be calculated automatically for each specific bridge.\n"
"Otherwise the provided angle will be used according to:\n"
" - The absolute coordinates\n"
" - The absolute coordinates + Model rotation: If Align infill direction to model is enabled\n"
" - The optimal automatic angle + this value: If 'Relative Bridge Angle' is enabled\n\n"
"Use 180° for zero absolute angle.");
def->sidetext = u8"°"; // degrees, don't need translation
def->min = 0;
def->max = 180;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.));
@@ -1253,14 +1258,28 @@ void PrintConfigDef::init_fff_params()
def = this->add("internal_bridge_angle", coFloat);
def->label = L("Internal bridge infill direction");
def->category = L("Strength");
def->tooltip = L("Internal bridging angle override. If left to zero, the bridging angle will be calculated "
"automatically. Otherwise the provided angle will be used for internal bridges. "
"Use 180° for zero angle.\n\nIt is recommended to leave it at 0 unless there is a specific model need not to.");
def->tooltip = L("Internal Bridging angle override.\n"
"If left to zero, the bridging angle will be calculated automatically for each specific bridge.\n"
"Otherwise the provided angle will be used according to:\n"
" - The absolute coordinates\n"
" - The absolute coordinates + Model rotation: If Align infill direction to model is enabled\n"
" - The optimal automatic angle + this value: If 'Relative Bridge Angle' is enabled\n\n"
"Use 180° for zero absolute angle.");
def->sidetext = u8"°"; // degrees, don't need translation
def->min = 0;
def->max = 180;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.));
// ORCA: Relative bridge angle
def = this->add("relative_bridge_angle", coBool);
def->label = L("Relative bridge angle");
def->category = L("Strength");
def->tooltip = L("When enabled, the bridge angle values are added to the automatically calculated bridge direction instead of overriding it.\n"
"Recommended to add a small angle (<10°) to improve bridge covering in closed shapes.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("bridge_density", coPercent);
def->label = L("External bridge density");
def->category = L("Strength");
@@ -2924,7 +2943,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("align_infill_direction_to_model", coBool);
def->label = L("Align infill direction to model");
def->category = L("Strength");
def->tooltip = L("Aligns infill and surface fill directions to follow the model's orientation on the build plate. When enabled, fill directions rotate with the model to maintain optimal strength characteristics.");
def->tooltip = L("Aligns infill, bridge, ironing and surface fill directions to follow the model's orientation on the build plate.\n"
"When enabled, directions rotate with the model to maintain optimal strength characteristics.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));

View File

@@ -1080,6 +1080,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, bottom_shell_thickness))
((ConfigOptionFloat, bridge_angle))
((ConfigOptionFloat, internal_bridge_angle)) // ORCA: Internal bridge angle override
((ConfigOptionBool, relative_bridge_angle)) // ORCA: Relative bridge angle flag
((ConfigOptionFloat, bridge_flow))
((ConfigOptionFloat, internal_bridge_flow))
((ConfigOptionFloat, bridge_speed))

View File

@@ -1275,6 +1275,7 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "ensure_vertical_shell_thickness"
|| opt_key == "bridge_angle"
|| opt_key == "internal_bridge_angle" // ORCA: Internal bridge angle override
|| opt_key == "relative_bridge_angle" // ORCA: Relative bridge angle
//BBS
|| opt_key == "bridge_density"
|| opt_key == "internal_bridge_density") {
@@ -3217,8 +3218,19 @@ void PrintObject::bridge_over_infill()
}
// ORCA: Internal bridge angle override
if (candidate.region->region().config().internal_bridge_angle > 0)
bridging_angle = candidate.region->region().config().internal_bridge_angle.value * PI / 180.0; // Convert degrees to radians
if (candidate.region->region().config().internal_bridge_angle.value > 0) {
const auto &region_config = candidate.region->region().config();
const double custom_angle_rad = Geometry::deg2rad(region_config.internal_bridge_angle.value);
if (region_config.relative_bridge_angle.value)
bridging_angle += custom_angle_rad;
else {
bridging_angle = custom_angle_rad;
if (region_config.align_infill_direction_to_model) {
auto m = po->trafo().matrix();
bridging_angle += std::atan2((double)m(1, 0), (double)m(0, 0));
}
}
}
boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end());
if (!lightning_area.empty() && !intersection(area_to_be_bridge, lightning_area).empty()) {

View File

@@ -655,7 +655,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
toggle_field("bottom_surface_density", has_bottom_shell);
for (auto el : { "infill_direction", "sparse_infill_line_width", "gap_fill_target","filter_out_gap_fill","infill_wall_overlap",
"sparse_infill_speed", "bridge_speed", "internal_bridge_speed", "bridge_angle", "internal_bridge_angle",
"sparse_infill_speed", "bridge_speed", "internal_bridge_speed", "bridge_angle", "internal_bridge_angle", "relative_bridge_angle",
"solid_infill_direction", "solid_infill_rotate_template", "internal_solid_infill_pattern", "solid_infill_filament",
})
toggle_field(el, have_infill || has_solid_infill);

View File

@@ -2499,6 +2499,7 @@ void TabPrint::build()
optgroup->append_single_option_line("extra_solid_infills", "strength_settings_infill#extra-solid-infill");
optgroup->append_single_option_line("bridge_angle", "strength_settings_advanced#bridge-infill-direction");
optgroup->append_single_option_line("internal_bridge_angle", "strength_settings_advanced#bridge-infill-direction"); // ORCA: Internal bridge angle override
optgroup->append_single_option_line("relative_bridge_angle", "strength_settings_advanced#bridge-infill-direction");
optgroup->append_single_option_line("minimum_sparse_infill_area", "strength_settings_advanced#minimum-sparse-infill-threshold");
optgroup->append_single_option_line("infill_combination", "strength_settings_advanced#infill-combination");
optgroup->append_single_option_line("infill_combination_max_layer_height", "strength_settings_advanced#max-layer-height");