@@ -265,7 +265,7 @@ function resolveDerivedEdgeCandidate(event, intersections, feature) {
265265 const edgeHit = resolvedPrimary . hit || null ;
266266 const edgeKey = String ( edgeHit ?. key || '' ) ;
267267 if ( ! edgeKey ) return null ;
268- const edge = api . solids ?. getEdgeByKey ?. ( edgeKey ) || edgeHit ;
268+ let edge = api . solids ?. getEdgeByKey ?. ( edgeKey ) || edgeHit ;
269269 if ( ! edge ) return null ;
270270
271271 let solidId = String ( edge ?. solidId || edgeHit ?. solidId || '' ) ;
@@ -283,6 +283,45 @@ function resolveDerivedEdgeCandidate(event, intersections, feature) {
283283 if ( ! solidId ) return null ;
284284 const faceKey = Number . isFinite ( faceId ) ? `${ solidId } :${ faceId } ` : null ;
285285
286+ // Promote dense closed loops (typically circles) to full-loop edge derive.
287+ if ( ! edge ?. pathWorld && edgeKey . startsWith ( 'faceedge:' ) ) {
288+ const parts = edgeKey . split ( ':' ) ;
289+ const segIndex = Number ( parts [ parts . length - 1 ] ) ;
290+ const fid = Number ( parts [ parts . length - 2 ] ) ;
291+ const sid = parts . slice ( 1 , - 2 ) . join ( ':' ) ;
292+ if ( sid && Number . isFinite ( fid ) && Number . isFinite ( segIndex ) ) {
293+ const loops = api . solids ?. getFaceBoundaryLoops ?. ( `${ sid } :${ fid } ` ) || [ ] ;
294+ const loop = loops . find ( lp => Array . isArray ( lp ?. segmentIndices ) && lp . segmentIndices . includes ( segIndex ) && lp ?. closed ) ;
295+ if ( loop && Array . isArray ( loop . points ) && loop . points . length >= 8 ) {
296+ const center = new THREE . Vector3 ( ) ;
297+ for ( const p of loop . points ) center . add ( p ) ;
298+ center . multiplyScalar ( 1 / loop . points . length ) ;
299+ let mean = 0 ;
300+ const rr = [ ] ;
301+ for ( const p of loop . points ) {
302+ const r = p . distanceTo ( center ) ;
303+ rr . push ( r ) ;
304+ mean += r ;
305+ }
306+ mean /= Math . max ( 1 , rr . length ) ;
307+ let varR = 0 ;
308+ for ( const r of rr ) {
309+ const d = r - mean ;
310+ varR += d * d ;
311+ }
312+ const rel = mean > 1e-8 ? Math . sqrt ( varR / Math . max ( 1 , rr . length ) ) / mean : 1 ;
313+ if ( rel <= 0.12 ) {
314+ edge = {
315+ ...edge ,
316+ key : `faceedgeloop:${ sid } :${ fid } :${ loops . indexOf ( loop ) } ` ,
317+ pathWorld : loop . points . map ( p => p . clone ( ) ) ,
318+ loop : true
319+ } ;
320+ }
321+ }
322+ }
323+ }
324+
286325 let a = edge ?. aWorld || null ;
287326 let b = edge ?. bWorld || null ;
288327 if ( ( ! a || ! b ) && Array . isArray ( edge ?. pathWorld ) && edge . pathWorld . length >= 2 ) {
0 commit comments