Skip to content

Commit 71303ab

Browse files
fix: skip material-merge clash check for single-child stacks (#5576)
The early material-clash pre-check in ContainerBlueprintNode::connectImpl iterated over ShellStack::mergedFaces() for every shell regardless of how many children were present. For an AxisZ stack that returns {OuterCylinder, InnerCylinder}, which are only actually merged when two or more children are stacked. With a single child no portal merge takes place, so flagging material on those faces was a false positive that caused a spurious warning (or, in strict mode, a throw). Fix: guard the merged-face loop with shells.size() > 1 so the check is skipped entirely when there is nothing to merge.
1 parent 0045fb4 commit 71303ab

2 files changed

Lines changed: 56 additions & 2 deletions

File tree

Core/src/Geometry/ContainerBlueprintNode.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,19 @@ PortalShellBase& ContainerBlueprintNode::connectImpl(
251251
// survive the merge and would otherwise trigger a deep, hard-to-trace failure
252252
// inside the stack shell construction. Faces that are *fused* (e.g. the
253253
// boundary between two stacked volumes) legitimately carry material and are
254-
// not flagged here.
254+
// not flagged here. With only a single child there is no actual merge, so
255+
// the check is skipped entirely to avoid false positives.
255256
const PortalMaterialMergePolicy materialPolicy =
256257
options.keepGoingOnMaterialMergeFailure
257258
? PortalMaterialMergePolicy::eDiscardAndMark
258259
: PortalMaterialMergePolicy::eThrow;
259260

261+
// With a single child there is nothing to merge, so the "merged" faces are
262+
// actually kept as-is. Skip the clash check to avoid false positives.
260263
std::vector<std::string> materialClashes;
261-
for (auto face : ShellStack::mergedFaces(direction())) {
264+
for (auto face : shells.size() > 1
265+
? ShellStack::mergedFaces(direction())
266+
: std::vector<typename ShellStack::Face>{}) {
262267
for (auto* shell : shells) {
263268
auto portal = shell->portal(face);
264269
if (portal != nullptr && portal->surface().hasMaterial()) {

Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,4 +564,53 @@ BOOST_AUTO_TEST_CASE(MaterialOnMergedPortalKeepGoing) {
564564
BOOST_CHECK_GE(markerCount, 1u);
565565
}
566566

567+
BOOST_AUTO_TEST_CASE(MaterialOnMergedPortalKeepGoingSingleChildFalseWarning) {
568+
// Reproducer for a false-positive in the early material-clash check:
569+
// a single-child AxisZ stack must NOT trigger a merge-material error because
570+
// no portal merge actually happens. With keepGoingOnMaterialMergeFailure at
571+
// its default (false / strict), construction must succeed without throwing.
572+
Transform3 base{Transform3::Identity()};
573+
574+
Blueprint::Config cfg;
575+
cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
576+
cfg.envelope[AxisDirection::AxisR] = {0_mm, 20_mm};
577+
auto root = std::make_unique<Blueprint>(cfg);
578+
579+
root->addCylinderContainer("Stack", AxisDirection::AxisZ, [&](auto& stack) {
580+
using enum AxisDirection;
581+
using enum AxisBoundaryType;
582+
using enum CylinderVolumeBounds::Face;
583+
584+
stack.addMaterial("Material", [&](auto& mat) {
585+
mat.configureFace(OuterCylinder, {AxisRPhi, Bound, 20},
586+
{AxisZ, Bound, 20});
587+
mat.addStaticVolume(
588+
base, std::make_shared<CylinderVolumeBounds>(0_mm, 100_mm, 100_mm),
589+
"VolumeA");
590+
});
591+
});
592+
593+
// Use strict mode (keepGoingOnMaterialMergeFailure = false by default).
594+
// The single-child stack must not be mistaken for a real merge, so no
595+
// exception should be thrown.
596+
Experimental::BlueprintOptions options;
597+
598+
std::unique_ptr<const TrackingGeometry> trackingGeometry;
599+
BOOST_REQUIRE_NO_THROW(trackingGeometry =
600+
root->construct(options, gctx, *logger));
601+
BOOST_REQUIRE(trackingGeometry != nullptr);
602+
603+
std::size_t markerCount = 0;
604+
trackingGeometry->visitSurfaces(
605+
[&](const Surface* surface) {
606+
if (surface != nullptr && surface->surfaceMaterial() != nullptr &&
607+
dynamic_cast<const MergedMaterialMarker*>(
608+
surface->surfaceMaterial()) != nullptr) {
609+
++markerCount;
610+
}
611+
},
612+
false);
613+
BOOST_CHECK_EQUAL(markerCount, 0u);
614+
}
615+
567616
BOOST_AUTO_TEST_SUITE_END();

0 commit comments

Comments
 (0)