ifcgeom: butt-join near-tangent sweep corners instead of hanging #8400

A second, independent trigger for the MakePipeShell hang from #8400: a kink
angle at a directrix joint in roughly the 0.57-5 degree band produces two
nearly-coaxial pipe surfaces at the corner, and BRepFill_TrimShellCorner's
projection (ProjLib_CompProjectedCurve::Init) never converges. This happens
regardless of chord length, so the sub-radius chord collapse already fixed in
the directrix-hang commit does not prevent it.

BRepOffsetAPI_MakePipeShell::SetTransitionMode always leaves Angmin (the kink
angle below which OCCT treats a joint as a safe butt-join rather than routing
it through the corner trim) at its hardcoded default of 1e-2 rad, and does not
expose a way to change it. Confirmed by pulling the OCCT 7.9.2 source: the
wrapper is a 1:1 forwarding layer over BRepFill_PipeShell (same Add/Set/Build/
FirstShape/LastShape/Shape semantics), and its own Build() discards the
Message_ProgressRange argument it declares, so no progress/cancellation signal
reaches this path either way. Switch to BRepFill_PipeShell directly and raise
Angmin to 0.1 rad (~5.7 deg) so the dangerous band takes the butt-join path.

Verified against the reporter's minimal repro (IfcCableCarrierSegment, 101-
segment composite curve directrix, disk radius 1.6cm, kink angles 0.15-2 deg):
clean v0.8.0 and v0.8.0 with only the prior directrix-collapse fix both still
hang (30s+ timeout, matches the reported bug); with this change it converts in
~1s (4781 verts, 5684 faces, no validation errors). Independently bisected the
Angmin threshold on this repro: 0.02 rad still hangs, 0.06 rad fails cleanly
("BRep_API: command not done", no hang), 0.1 rad converges.

Regression-checked all 50 test/input fixtures containing IfcSweptDiskSolid
(the ones parseable under this build's IFC4-only schema config produce
identical exit codes and, where conversion succeeds outright, identical
output, before and after) and a synthetic 30 degree corner (well above the
new threshold): byte-identical output, confirming ordinary corners are
unaffected. One real fixture (987--cableSegment) has joints that fall in the
newly-affected band and now converts with fewer vertices/faces at those
corners (butt-join instead of trim); both variants pass --validate with no
errors, and nearest-neighbour vertex deviation between the two outputs peaks
under 1mm on a 3.9mm-radius cable, i.e. a real but small and bounded fidelity
trade at corners that previously could hang forever.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Petru Conduraru
2026-07-19 07:13:40 +03:00
parent 779f6e0922
commit d053530743
@@ -22,6 +22,7 @@
#include "wire_utils.h"
#include <BRepOffsetAPI_MakePipeShell.hxx>
#include <BRepFill_PipeShell.hxx>
#include <Geom_Plane.hxx>
#include <ShapeAnalysis_Surface.hxx>
#include <BRepBuilderAPI_Transform.hxx>
@@ -355,20 +356,22 @@ bool OpenCascadeKernel::convert(const taxonomy::sweep_along_curve::ptr scs, Topo
continue;
}
BRepOffsetAPI_MakePipeShell builder(wire);
builder.Add(section);
builder.SetTransitionMode(contains_circular_segments(wire) && wire_is_c1_continuous(wire, 1.e-2) ? BRepBuilderAPI_Transformed : BRepBuilderAPI_RightCorner);
// Raise Angmin so near-tangent corners butt-join instead of hanging in
// BRepFill_TrimShellCorner (#8400); not exposed via BRepOffsetAPI_MakePipeShell.
const double transition_angmin = 0.1; // rad, ~5.7 deg
Handle(BRepFill_PipeShell) builder = new BRepFill_PipeShell(wire);
builder->Add(section);
builder->SetTransition(contains_circular_segments(wire) && wire_is_c1_continuous(wire, 1.e-2) ? BRepFill_Modified : BRepFill_Right, transition_angmin);
if (directrix_on_plane) {
builder.SetMode(pln.Axis().Direction());
builder->Set(pln.Axis().Direction());
} else if (!is_plane) {
builder.SetMode(surface_face);
builder->Set(surface_face);
}
builder.Build();
if (!builder.IsDone()) {
if (!builder->Build()) {
return false;
}
auto w0 = TopoDS::Wire(builder.FirstShape());
auto w1 = TopoDS::Wire(builder.LastShape());
auto w0 = TopoDS::Wire(builder->FirstShape());
auto w1 = TopoDS::Wire(builder->LastShape());
if (mf0) {
mf0->Add(w0);
mf1->Add(w1);
@@ -382,7 +385,7 @@ bool OpenCascadeKernel::convert(const taxonomy::sweep_along_curve::ptr scs, Topo
mf1.reset(new BRepBuilderAPI_MakeFace(f1));
}
for (TopExp_Explorer exp2(builder.Shape(), TopAbs_FACE); exp2.More(); exp2.Next()) {
for (TopExp_Explorer exp2(builder->Shape(), TopAbs_FACE); exp2.More(); exp2.Next()) {
BB.Add(comp, exp2.Current());
}
}