mirror of
https://github.com/Open-Cascade-SAS/OCCT.git
synced 2026-08-28 22:08:25 +08:00
Shape Healing - Refactor shape replacement logic to handle cycles (#1247)
Apply some fixes where aaa82fc4de introduce as a regressions
This commit is contained in:
@@ -200,48 +200,10 @@ void BRepTools_ReShape::replace(const TopoDS_Shape& ashape,
|
||||
std::cout << "Warning: BRepTools_ReShape::Replace: shape already recorded" << std::endl;
|
||||
#endif
|
||||
|
||||
// Reject replacements that would introduce a cycle into the replacement chain,
|
||||
// e.g. A -> ... -> X -> A. Walk forward from newshape via Value(); if the walk
|
||||
// ever lands on shape itself, record only an identity (effectively no-op) to
|
||||
// avoid forming a cycle that would later deadlock Apply()/ValueLeaf().
|
||||
if (theKind != TReplacementKind_Remove && !newshape.IsNull() && !newshape.IsPartner(shape))
|
||||
{
|
||||
// Reject replacements that would close a cycle in the map. Walk forward from
|
||||
// newshape via Value(); if the chain ever lands back on shape's underlying TShape
|
||||
// (any orientation, any location), abort. Key by the TShape handle to mirror the
|
||||
// identity the map itself uses once orientation/location are normalized away.
|
||||
TopoDS_Shape aProbe = newshape;
|
||||
NCollection_Map<occ::handle<TopoDS_TShape>> aSeen;
|
||||
aSeen.Add(shape.TShape());
|
||||
aSeen.Add(aProbe.TShape());
|
||||
bool aCycle = false;
|
||||
for (;;)
|
||||
{
|
||||
const TopoDS_Shape aNext = Value(aProbe);
|
||||
if (aNext.IsNull() || aNext.IsSame(aProbe))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (aNext.IsPartner(shape))
|
||||
{
|
||||
aCycle = true;
|
||||
break;
|
||||
}
|
||||
if (!aSeen.Add(aNext.TShape()))
|
||||
{
|
||||
break; // existing cycle in data - not ours to introduce, bail
|
||||
}
|
||||
aProbe = aNext;
|
||||
}
|
||||
if (aCycle)
|
||||
{
|
||||
#ifdef OCCT_DEBUG
|
||||
std::cout << "Warning: BRepTools_ReShape::Replace: cycle rejected" << std::endl;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Cycle handling: cycles in the replacement map (A -> B -> A or longer) are
|
||||
// accepted at insertion time. The DFS in-flight guard inside Apply()/applyImpl()
|
||||
// breaks any cycle at traversal time, returning the immediate Value() instead
|
||||
// of recursing.
|
||||
myShapeToReplacement.Bind(shape, TReplacement(newshape, theKind));
|
||||
myNewShapes.Add(newshape);
|
||||
}
|
||||
|
||||
@@ -78,24 +78,30 @@ TEST(BRepTools_ReShapeTest, ValueLeaf_ChainEndingInRemoveReturnsNull)
|
||||
EXPECT_TRUE(aReShape.ValueLeaf(aA).IsNull());
|
||||
}
|
||||
|
||||
// A direct A->B then B->A would close a cycle in the replacement map.
|
||||
// The second Replace() must be rejected; the first stays intact.
|
||||
TEST(BRepTools_ReShapeTest, Replace_RejectsDirectCycle)
|
||||
// A direct A->B then B->A creates a cycle in the replacement map; both bindings
|
||||
// are recorded, and the DFS in-flight guard inside Apply() handles termination.
|
||||
TEST(BRepTools_ReShapeTest, Replace_DirectCycleHandledByApply)
|
||||
{
|
||||
const TopoDS_Vertex aA = MakeVertex(0, 0, 0);
|
||||
const TopoDS_Vertex aB = MakeVertex(1, 0, 0);
|
||||
|
||||
BRepTools_ReShape aReShape;
|
||||
aReShape.Replace(aA, aB);
|
||||
aReShape.Replace(aB, aA); // would create A -> B -> A
|
||||
aReShape.Replace(aB, aA);
|
||||
|
||||
EXPECT_TRUE(aReShape.Value(aA).IsSame(aB)) << "First replacement must remain in effect";
|
||||
EXPECT_TRUE(aReShape.Value(aB).IsSame(aB)) << "Cyclic second replacement must have been rejected";
|
||||
EXPECT_TRUE(aReShape.ValueLeaf(aA).IsSame(aB)) << "Chain must terminate, not loop";
|
||||
EXPECT_TRUE(aReShape.Value(aA).IsSame(aB));
|
||||
EXPECT_TRUE(aReShape.Value(aB).IsSame(aA));
|
||||
|
||||
// Apply() must terminate (DFS in-flight guard) and return a defined shape.
|
||||
const TopoDS_Shape aResult = aReShape.Apply(aA);
|
||||
EXPECT_FALSE(aResult.IsNull()) << "Apply must terminate via the DFS guard";
|
||||
}
|
||||
|
||||
// A longer cycle A -> B -> C -> A must also be rejected at the closing edge.
|
||||
TEST(BRepTools_ReShapeTest, Replace_RejectsLongerCycle)
|
||||
// Longer cycles A -> B -> C -> A are accepted at Replace() time and broken at
|
||||
// traversal time by the DFS in-flight guard inside Apply(). This avoids
|
||||
// over-rejection in IGES/STEP healing pipelines where shapes legitimately
|
||||
// alias TShapes across stages.
|
||||
TEST(BRepTools_ReShapeTest, Replace_LongerCycleHandledByApply)
|
||||
{
|
||||
const TopoDS_Vertex aA = MakeVertex(0, 0, 0);
|
||||
const TopoDS_Vertex aB = MakeVertex(1, 0, 0);
|
||||
@@ -104,10 +110,15 @@ TEST(BRepTools_ReShapeTest, Replace_RejectsLongerCycle)
|
||||
BRepTools_ReShape aReShape;
|
||||
aReShape.Replace(aA, aB);
|
||||
aReShape.Replace(aB, aC);
|
||||
aReShape.Replace(aC, aA); // would create A -> B -> C -> A
|
||||
aReShape.Replace(aC, aA);
|
||||
|
||||
EXPECT_TRUE(aReShape.ValueLeaf(aA).IsSame(aC)) << "Chain terminates at C; no cycle formed";
|
||||
EXPECT_TRUE(aReShape.Value(aC).IsSame(aC)) << "Closing replacement was rejected";
|
||||
EXPECT_TRUE(aReShape.Value(aA).IsSame(aB));
|
||||
EXPECT_TRUE(aReShape.Value(aB).IsSame(aC));
|
||||
EXPECT_TRUE(aReShape.Value(aC).IsSame(aA));
|
||||
|
||||
// Apply() must terminate (DFS in-flight guard) and return a defined shape.
|
||||
const TopoDS_Shape aResult = aReShape.Apply(aA);
|
||||
EXPECT_FALSE(aResult.IsNull()) << "Apply must terminate via the DFS guard";
|
||||
}
|
||||
|
||||
// Apply() must not stack-overflow when a shape's replacement is a compound that
|
||||
|
||||
Reference in New Issue
Block a user