@@ -1253,3 +1253,97 @@ describe 'Exposure Detection - Cavity and Hole Detection', ->
12531253 expect (layer47 .skin ).toBeGreaterThan (0 ) # Exposed area around the studs.
12541254 expect (layer47 .fillLines ).toBeGreaterThan (0 ) # Regular infill under the studs.
12551255 expect (layer47 .fillRegions ).toBeGreaterThanOrEqual (6 ) # One region per stud (6 total).
1256+
1257+ describe ' Dome Zenith Skin Infill (Regression)' , ->
1258+
1259+ test ' should generate skin infill on dome zenith exposure patches' , ->
1260+
1261+ # Regression test for the dome zenith bug introduced in PR 182.
1262+ # When the 10% minimum size ratio was removed from findCoveredRegions to fix
1263+ # lego stud detection, hole paths from adjacent layers (the small circular opening
1264+ # near the zenith of the dome cavity) were incorrectly classified as "fully covered
1265+ # regions". This caused their corresponding skin infill to be suppressed.
1266+ #
1267+ # The fix: check whether a candidate path is enclosed by another path in the same
1268+ # set (i.e. it is a hole path representing empty space, not a solid feature).
1269+ # Hole paths must not be classified as covered regions.
1270+ #
1271+ # Geometry: box 25x25x12mm with a hemispherical cavity of radius 10mm opening at
1272+ # the build plate. The cavity reaches its zenith at z=10mm (layer 50 of 60).
1273+ # Layers 47-54 are near the zenith and should have skin infill on the small
1274+ # circular exposure patches where the dome ceiling transitions to solid.
1275+ width = 25
1276+ depth = 25
1277+ thickness = 12
1278+ radius = 10
1279+
1280+ boxGeometry = new THREE.BoxGeometry (width, depth, thickness)
1281+ boxMesh = new THREE.Mesh (boxGeometry, new THREE.MeshBasicMaterial ())
1282+
1283+ sphereGeometry = new THREE.SphereGeometry (radius, 64 , 48 )
1284+ sphereMesh = new THREE.Mesh (sphereGeometry, new THREE.MeshBasicMaterial ())
1285+
1286+ # Place sphere center at the bottom face so the upper hemisphere carves a cavity.
1287+ sphereMesh .position .set (0 , 0 , - (thickness / 2 ))
1288+ sphereMesh .updateMatrixWorld ()
1289+
1290+ # Perform CSG subtraction to create the dome cavity.
1291+ resultMesh = await Polytree .subtract (boxMesh, sphereMesh)
1292+
1293+ # Position final mesh with build plate at Z=0.
1294+ finalMesh = new THREE.Mesh (resultMesh .geometry , resultMesh .material )
1295+ finalMesh .position .set (0 , 0 , thickness / 2 )
1296+ finalMesh .updateMatrixWorld ()
1297+
1298+ # Configure slicer with exposure detection enabled.
1299+ slicer .setLayerHeight (0.2 )
1300+ slicer .setShellSkinThickness (0.8 ) # 4 skin layers.
1301+ slicer .setShellWallThickness (0.8 )
1302+ slicer .setVerbose (true )
1303+ slicer .setAutohome (false )
1304+ slicer .setExposureDetection (true )
1305+ slicer .setInfillDensity (20 )
1306+
1307+ # Slice the mesh.
1308+ result = slicer .slice (finalMesh)
1309+
1310+ # Parse the G-code and find skin infill lines per layer.
1311+ lines = result .split (' \n ' )
1312+ skinInfillByLayer = {}
1313+ currentLayer = null
1314+
1315+ for line in lines
1316+
1317+ layerMatch = line .match (/ LAYER:\s * (\d + ) of/ )
1318+
1319+ if layerMatch
1320+
1321+ currentLayer = parseInt (layerMatch[1 ])
1322+
1323+ else if currentLayer? and line .includes (' Moving to skin infill line' )
1324+
1325+ skinInfillByLayer[currentLayer] = (skinInfillByLayer[currentLayer] || 0 ) + 1
1326+
1327+ # Total layers = 60 (12mm / 0.2mm).
1328+ # The dome zenith is at z=10mm (layer 50).
1329+ # Exposure patches appear at layers near the zenith where the small hole disappears.
1330+ # Before the fix: hole paths from adjacent layers were classified as covered regions,
1331+ # suppressing all skin infill in that area.
1332+ # After the fix: those hole paths are recognised as holes (enclosed by the outer
1333+ # square boundary in the same set) and are no longer classified as covered regions.
1334+ # Layers 49-54 should therefore have skin infill.
1335+ zenithLayerTotal = 0
1336+
1337+ for layerIndex in [49 .. 54 ]
1338+
1339+ zenithLayerTotal += skinInfillByLayer[layerIndex] || 0
1340+
1341+ # Verify that skin infill is generated at the dome zenith exposure patches.
1342+ expect (zenithLayerTotal).toBeGreaterThan (0 )
1343+
1344+ # Also verify lego-stud-style covered region detection still works: the pyramid
1345+ # test in the 'Fully Covered Areas Exclusion' suite is the canonical check, but
1346+ # as a sanity guard verify that the total skin infill count is reasonable
1347+ # (dome should have significantly more skin than a solid box of the same size).
1348+ totalSkinInfill = (result .match (/ Moving to skin infill line/ g ) || []).length
1349+ expect (totalSkinInfill).toBeGreaterThan (50 )
0 commit comments