@@ -20,12 +20,6 @@ function createSolidsApi(getApi) {
2020 }
2121 const loop = view ?. object ?. userData ?. sketchProfileLoop || view ?. entity ?. loop || null ;
2222 if ( Array . isArray ( loop ) && loop . length >= 3 ) return [ loop ] ;
23- if ( Array . isArray ( profileTarget ?. loops ) && profileTarget . loops . length ) {
24- const cached = profileTarget . loops
25- . filter ( item => Array . isArray ( item ) && item . length >= 3 )
26- . map ( item => item . map ( p => ( { x : p ?. x || 0 , y : p ?. y || 0 } ) ) ) ;
27- if ( cached . length ) return cached ;
28- }
2923 return null ;
3024 }
3125
@@ -296,13 +290,29 @@ function createSolidsApi(getApi) {
296290 center,
297291 normal,
298292 xAxis,
299- planar
293+ planar,
294+ boundarySegmentsLocal : null
300295 } ) ;
301296 groupId ++ ;
302297 }
303298 return { triToGroup, groups } ;
304299 }
305300
301+ function buildBoundarySegmentsFromGeometry ( geometry ) {
302+ if ( ! geometry ) return [ ] ;
303+ const edgesGeom = new THREE . EdgesGeometry ( geometry , 1 ) ;
304+ const pos = edgesGeom . getAttribute ?. ( 'position' ) ;
305+ if ( ! pos ) return [ ] ;
306+ const out = [ ] ;
307+ for ( let i = 0 ; i + 1 < pos . count ; i += 2 ) {
308+ const a = new THREE . Vector3 ( ) . fromBufferAttribute ( pos , i ) ;
309+ const b = new THREE . Vector3 ( ) . fromBufferAttribute ( pos , i + 1 ) ;
310+ out . push ( { a, b, mid : a . clone ( ) . add ( b ) . multiplyScalar ( 0.5 ) } ) ;
311+ }
312+ edgesGeom . dispose ?. ( ) ;
313+ return out ;
314+ }
315+
306316 function makeFaceMaterials ( ) {
307317 return {
308318 hover : new THREE . MeshBasicMaterial ( {
@@ -621,6 +631,16 @@ function createSolidsApi(getApi) {
621631 return out ;
622632 } ,
623633
634+ getPickEdgeForSolid ( solidId ) {
635+ const id = String ( solidId || '' ) ;
636+ if ( ! id ) return null ;
637+ const view = this . _meshViews . get ( id ) ;
638+ if ( ! view || view ?. group ?. visible === false || view ?. mesh ?. visible === false || view ?. edges ?. visible === false ) {
639+ return null ;
640+ }
641+ return view . edges || null ;
642+ } ,
643+
624644 getEdgeSegmentWorld ( object , segmentIndex ) {
625645 if ( ! object ?. geometry || segmentIndex < 0 ) return null ;
626646 object . updateMatrixWorld ?. ( true ) ;
@@ -677,43 +697,53 @@ function createSolidsApi(getApi) {
677697 const srcA = new THREE . Vector3 ( Number ( sa . x || 0 ) , Number ( sa . y || 0 ) , Number ( sa . z || 0 ) ) ;
678698 const srcB = new THREE . Vector3 ( Number ( sb . x || 0 ) , Number ( sb . y || 0 ) , Number ( sb . z || 0 ) ) ;
679699 const solids = this . list ( ) || [ ] ;
680- const wanted = [ ] ;
700+ const searchSets = [ ] ;
681701 if ( targetSolidId ) {
682- wanted . push ( targetSolidId ) ;
683- } else if ( targetFeatureId ) {
702+ searchSets . push ( [ targetSolidId ] ) ;
703+ }
704+ if ( targetFeatureId ) {
705+ const byFeature = [ ] ;
684706 for ( const solid of solids ) {
685707 if ( String ( solid ?. source ?. feature_id || '' ) === targetFeatureId && solid ?. id ) {
686- wanted . push ( String ( solid . id ) ) ;
708+ byFeature . push ( String ( solid . id ) ) ;
687709 }
688710 }
689- } else {
690- for ( const solid of solids ) {
691- if ( solid ?. id ) wanted . push ( String ( solid . id ) ) ;
692- }
711+ if ( byFeature . length ) searchSets . push ( byFeature ) ;
712+ }
713+ const all = [ ] ;
714+ for ( const solid of solids ) {
715+ if ( solid ?. id ) all . push ( String ( solid . id ) ) ;
693716 }
717+ if ( all . length ) searchSets . push ( all ) ;
694718 let best = null ;
695719 let bestScore = Infinity ;
696- for ( const solidId of wanted ) {
697- const view = this . _meshViews . get ( solidId ) ;
698- const edgesObj = view ?. edges ;
699- if ( ! edgesObj ?. geometry ) continue ;
700- const pos = edgesObj . geometry . getAttribute ?. ( 'position' ) ;
701- const idx = edgesObj . geometry . getIndex ?. ( ) ;
702- if ( ! pos ) continue ;
703- const segCount = idx ?. array ?. length
704- ? Math . floor ( idx . array . length / 2 )
705- : Math . floor ( pos . count / 2 ) ;
706- for ( let i = 0 ; i < segCount ; i ++ ) {
707- const seg = this . getEdgeSegmentWorld ( edgesObj , i ) ;
708- if ( ! seg ) continue ;
709- const d1 = seg . a . distanceTo ( srcA ) + seg . b . distanceTo ( srcB ) ;
710- const d2 = seg . a . distanceTo ( srcB ) + seg . b . distanceTo ( srcA ) ;
711- const score = Math . min ( d1 , d2 ) ;
712- if ( score < bestScore ) {
713- bestScore = score ;
714- best = { solidId, index : i , aWorld : seg . a , bWorld : seg . b } ;
720+ const scanSet = ( wanted = [ ] ) => {
721+ for ( const solidId of wanted ) {
722+ const view = this . _meshViews . get ( solidId ) ;
723+ const edgesObj = view ?. edges ;
724+ if ( ! edgesObj ?. geometry ) continue ;
725+ const pos = edgesObj . geometry . getAttribute ?. ( 'position' ) ;
726+ const idx = edgesObj . geometry . getIndex ?. ( ) ;
727+ if ( ! pos ) continue ;
728+ const segCount = idx ?. array ?. length
729+ ? Math . floor ( idx . array . length / 2 )
730+ : Math . floor ( pos . count / 2 ) ;
731+ for ( let i = 0 ; i < segCount ; i ++ ) {
732+ const seg = this . getEdgeSegmentWorld ( edgesObj , i ) ;
733+ if ( ! seg ) continue ;
734+ const d1 = seg . a . distanceTo ( srcA ) + seg . b . distanceTo ( srcB ) ;
735+ const d2 = seg . a . distanceTo ( srcB ) + seg . b . distanceTo ( srcA ) ;
736+ const score = Math . min ( d1 , d2 ) ;
737+ if ( score < bestScore ) {
738+ bestScore = score ;
739+ best = { solidId, index : i , aWorld : seg . a , bWorld : seg . b } ;
740+ }
715741 }
716742 }
743+ } ;
744+ for ( const wanted of searchSets ) {
745+ if ( best ) break ;
746+ scanSet ( wanted ) ;
717747 }
718748 if ( ! best ) return null ;
719749 best . midWorld = best . aWorld . clone ( ) . add ( best . bWorld ) . multiplyScalar ( 0.5 ) ;
@@ -752,6 +782,31 @@ function createSolidsApi(getApi) {
752782 return { key : `${ solidId } :${ faceId } ` , solidId, faceId, view, meta } ;
753783 } ,
754784
785+ getFaceBoundarySegments ( key ) {
786+ const face = this . getFaceByKey ( key ) ;
787+ if ( ! face ?. meta ?. geometry ) return [ ] ;
788+ if ( ! Array . isArray ( face . meta . boundarySegmentsLocal ) ) {
789+ face . meta . boundarySegmentsLocal = buildBoundarySegmentsFromGeometry ( face . meta . geometry ) ;
790+ }
791+ const local = face . meta . boundarySegmentsLocal || [ ] ;
792+ if ( ! local . length ) return [ ] ;
793+ const mesh = face ?. view ?. mesh || null ;
794+ if ( ! mesh ?. matrixWorld ) return [ ] ;
795+ mesh . updateMatrixWorld ?. ( true ) ;
796+ const out = [ ] ;
797+ for ( const seg of local ) {
798+ if ( ! seg ?. a || ! seg ?. b ) continue ;
799+ const a = seg . a . clone ( ) . applyMatrix4 ( mesh . matrixWorld ) ;
800+ const b = seg . b . clone ( ) . applyMatrix4 ( mesh . matrixWorld ) ;
801+ out . push ( {
802+ a,
803+ b,
804+ mid : a . clone ( ) . add ( b ) . multiplyScalar ( 0.5 )
805+ } ) ;
806+ }
807+ return out ;
808+ } ,
809+
755810 setHoveredFace ( key = null ) {
756811 const next = key && this . getFaceByKey ( key ) ? key : null ;
757812 if ( next === this . _hoveredFaceKey ) return ;
@@ -1106,6 +1161,7 @@ function createSolidsApi(getApi) {
11061161 let result = null ;
11071162 let passReason = reason ;
11081163 for ( let pass = 0 ; pass < 2 ; pass ++ ) {
1164+ api . sketchRuntime ?. sync ?. ( ) ;
11091165 const snapshot = buildRebuildSnapshot ( api ) ;
11101166 try {
11111167 const workerReply = await this . requestWorkerRebuild ( snapshot , passReason ) ;
@@ -1147,6 +1203,11 @@ function createSolidsApi(getApi) {
11471203 passReason = 'sketch.face.rebind' ;
11481204 continue ;
11491205 }
1206+ if ( derivedChanged && pass === 0 ) {
1207+ api . sketchRuntime ?. sync ?. ( ) ;
1208+ passReason = 'sketch.derived.refresh' ;
1209+ continue ;
1210+ }
11501211 if ( rebound || derivedChanged ) {
11511212 api . sketchRuntime ?. sync ?. ( ) ;
11521213 }
0 commit comments