From d0535307430ec0d2af7c1e15268ba2e605e9250e Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Sun, 19 Jul 2026 07:13:40 +0300 Subject: [PATCH] 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. --- .../kernels/opencascade/sweep_along_curve.cpp | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/ifcgeom/kernels/opencascade/sweep_along_curve.cpp b/src/ifcgeom/kernels/opencascade/sweep_along_curve.cpp index 02e1b72200..fae77bac6f 100644 --- a/src/ifcgeom/kernels/opencascade/sweep_along_curve.cpp +++ b/src/ifcgeom/kernels/opencascade/sweep_along_curve.cpp @@ -22,6 +22,7 @@ #include "wire_utils.h" #include +#include #include #include #include @@ -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()); } }