@@ -323,4 +323,241 @@ describe("OCCT io unit tests", () => {
323323 expect ( polyline . closed ) . toBe ( true ) ;
324324 expect ( polyline . points . length ) . toBeGreaterThan ( 3 ) ;
325325 } ) ;
326+
327+ it ( "should correctly export oblong slot with opposing semicircular arcs" , ( ) => {
328+ const radius = 5 ;
329+ const leftArcCenterX = 10 ;
330+ const rightArcCenterX = 30 ;
331+ const arcCenterZ = 20 ;
332+
333+ // Top straight line
334+ const topLine = wire . createPolylineWire ( {
335+ points : [
336+ [ leftArcCenterX , 0 , arcCenterZ + radius ] ,
337+ [ rightArcCenterX , 0 , arcCenterZ + radius ]
338+ ]
339+ } ) ;
340+
341+ // Right semicircle arc (from top to bottom, curving right)
342+ const rightArc = occHelper . edgesService . arcThroughTwoPointsAndTangent ( {
343+ start : [ rightArcCenterX , 0 , arcCenterZ + radius ] ,
344+ end : [ rightArcCenterX , 0 , arcCenterZ - radius ] ,
345+ tangentVec : [ 1 , 0 , 0 ]
346+ } ) ;
347+
348+ // Bottom straight line
349+ const bottomLine = wire . createPolylineWire ( {
350+ points : [
351+ [ rightArcCenterX , 0 , arcCenterZ - radius ] ,
352+ [ leftArcCenterX , 0 , arcCenterZ - radius ]
353+ ]
354+ } ) ;
355+
356+ // Left semicircle arc (from bottom to top, curving left)
357+ const leftArc = occHelper . edgesService . arcThroughTwoPointsAndTangent ( {
358+ start : [ leftArcCenterX , 0 , arcCenterZ - radius ] ,
359+ end : [ leftArcCenterX , 0 , arcCenterZ + radius ] ,
360+ tangentVec : [ - 1 , 0 , 0 ]
361+ } ) ;
362+
363+ const slotWire = wire . combineEdgesAndWiresIntoAWire ( {
364+ shapes : [ topLine , rightArc , bottomLine , leftArc ]
365+ } ) ;
366+
367+ const dxfPathOpt = new Inputs . OCCT . ShapeToDxfPathsDto < TopoDS_Shape > ( slotWire ) ;
368+ const dxfPaths = io . shapeToDxfPaths ( dxfPathOpt ) ;
369+
370+ expect ( dxfPaths . length ) . toBe ( 1 ) ;
371+ expect ( dxfPaths [ 0 ] . segments . length ) . toBe ( 1 ) ;
372+
373+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
374+ const polyline = dxfPaths [ 0 ] . segments [ 0 ] as any ;
375+ expect ( polyline . points ) . toBeDefined ( ) ;
376+ expect ( polyline . bulges ) . toBeDefined ( ) ;
377+ expect ( polyline . closed ) . toBe ( true ) ;
378+
379+ // Find bulge indices for the arcs
380+ const bulges = polyline . bulges ;
381+
382+ // Both arcs should have significant non-zero bulges (semicircles ≈ ±1)
383+ const significantBulges = bulges . filter ( ( b : number ) => Math . abs ( b ) > 0.9 ) ;
384+ expect ( significantBulges . length ) . toBe ( 2 ) ; // Two semicircular arcs
385+
386+ // The two arcs should have opposite signs (one CW, one CCW)
387+ // Find the two significant bulges
388+ const bulgeIndices : number [ ] = [ ] ;
389+ for ( let i = 0 ; i < bulges . length ; i ++ ) {
390+ if ( Math . abs ( bulges [ i ] ) > 0.9 ) {
391+ bulgeIndices . push ( i ) ;
392+ }
393+ }
394+ expect ( bulgeIndices . length ) . toBe ( 2 ) ;
395+
396+ // Check that they have opposite signs
397+ const bulge1 = bulges [ bulgeIndices [ 0 ] ] ;
398+ const bulge2 = bulges [ bulgeIndices [ 1 ] ] ;
399+ expect ( bulge1 * bulge2 ) . toBeLessThan ( 0 ) ; // Opposite signs
400+
401+ slotWire . delete ( ) ;
402+ topLine . delete ( ) ;
403+ bottomLine . delete ( ) ;
404+ rightArc . delete ( ) ;
405+ leftArc . delete ( ) ;
406+ } ) ;
407+
408+ it ( "should correctly export semicircular arc curving right with positive bulge" , ( ) => {
409+ const radius = 5 ;
410+ const centerX = 20 ;
411+ const centerZ = 10 ;
412+
413+ // Semicircle arc from top to bottom with tangent pointing right
414+ // This should create an arc that curves to the RIGHT = positive bulge
415+ const rightArc = occHelper . edgesService . arcThroughTwoPointsAndTangent ( {
416+ start : [ centerX , 0 , centerZ + radius ] ,
417+ end : [ centerX , 0 , centerZ - radius ] ,
418+ tangentVec : [ 1 , 0 , 0 ] // Tangent pointing right
419+ } ) ;
420+
421+ const arcWire = wire . combineEdgesAndWiresIntoAWire ( { shapes : [ rightArc ] } ) ;
422+
423+ const startPt = occHelper . edgesService . startPointOnEdge ( { shape : rightArc } ) ;
424+ const endPt = occHelper . edgesService . endPointOnEdge ( { shape : rightArc } ) ;
425+
426+ // Verify the arc geometry
427+ expect ( startPt [ 2 ] ) . toBeGreaterThan ( endPt [ 2 ] ) ; // Start is higher than end
428+
429+ const dxfPathOpt = new Inputs . OCCT . ShapeToDxfPathsDto < TopoDS_Shape > ( arcWire ) ;
430+ const dxfPaths = io . shapeToDxfPaths ( dxfPathOpt ) ;
431+
432+ expect ( dxfPaths . length ) . toBe ( 1 ) ;
433+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
434+ const polyline = dxfPaths [ 0 ] . segments [ 0 ] as any ;
435+
436+ // For a semicircular arc, the bulge should be close to ±1
437+ // The actual sign depends on the direction OCCT creates the arc
438+ expect ( Math . abs ( polyline . bulges [ 0 ] ) ) . toBeGreaterThan ( 0.9 ) ; // Semicircle ≈ ±1
439+
440+ arcWire . delete ( ) ;
441+ rightArc . delete ( ) ;
442+ } ) ;
443+
444+ it ( "should correctly export semicircular arc curving left with negative bulge" , ( ) => {
445+ const radius = 5 ;
446+ const centerX = 20 ;
447+ const centerZ = 10 ;
448+
449+ // Semicircle arc from bottom to top with tangent pointing left
450+ // This should create an arc that curves to the LEFT = negative bulge
451+ const leftArc = occHelper . edgesService . arcThroughTwoPointsAndTangent ( {
452+ start : [ centerX , 0 , centerZ - radius ] ,
453+ end : [ centerX , 0 , centerZ + radius ] ,
454+ tangentVec : [ - 1 , 0 , 0 ] // Tangent pointing left
455+ } ) ;
456+
457+ const arcWire = wire . combineEdgesAndWiresIntoAWire ( { shapes : [ leftArc ] } ) ;
458+
459+ const startPt = occHelper . edgesService . startPointOnEdge ( { shape : leftArc } ) ;
460+ const endPt = occHelper . edgesService . endPointOnEdge ( { shape : leftArc } ) ;
461+
462+ // Verify the arc geometry
463+ expect ( startPt [ 2 ] ) . toBeLessThan ( endPt [ 2 ] ) ; // Start is lower than end
464+
465+ const dxfPathOpt = new Inputs . OCCT . ShapeToDxfPathsDto < TopoDS_Shape > ( arcWire ) ;
466+ const dxfPaths = io . shapeToDxfPaths ( dxfPathOpt ) ;
467+
468+ expect ( dxfPaths . length ) . toBe ( 1 ) ;
469+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
470+ const polyline = dxfPaths [ 0 ] . segments [ 0 ] as any ;
471+
472+ // For a semicircular arc, the bulge should be close to ±1
473+ expect ( Math . abs ( polyline . bulges [ 0 ] ) ) . toBeGreaterThan ( 0.9 ) ; // Semicircle ≈ ±1
474+
475+ arcWire . delete ( ) ;
476+ leftArc . delete ( ) ;
477+ } ) ;
478+
479+ it ( "should correctly export horizontal arc curving upward (center above chord)" , ( ) => {
480+ const startX = 10 ;
481+ const endX = 20 ;
482+ const chordZ = 15 ;
483+
484+ // Create arc with center above the chord
485+ // For a horizontal chord, center above means positive Z offset
486+ const centerX = ( startX + endX ) / 2 ;
487+ const centerZ = chordZ + 5 ; // Center 5 units above the chord
488+ const radius = Math . sqrt ( Math . pow ( ( endX - startX ) / 2 , 2 ) + Math . pow ( 5 , 2 ) ) ; // Calculate radius
489+
490+ // Create arc using arcThroughThreePoints
491+ // Middle point should be on the arc itself, at the peak
492+ const middleX = centerX ;
493+ const middleZ = centerZ + radius ; // Top of the arc
494+
495+ const arc = occHelper . edgesService . arcThroughThreePoints ( {
496+ start : [ startX , 0 , chordZ ] ,
497+ middle : [ middleX , 0 , middleZ ] ,
498+ end : [ endX , 0 , chordZ ]
499+ } ) ;
500+
501+ const arcWire = wire . combineEdgesAndWiresIntoAWire ( { shapes : [ arc ] } ) ;
502+
503+ const center = occHelper . edgesService . getCircularEdgeCenterPoint ( { shape : arc } ) ;
504+
505+ const dxfPathOpt = new Inputs . OCCT . ShapeToDxfPathsDto < TopoDS_Shape > ( arcWire ) ;
506+ const dxfPaths = io . shapeToDxfPaths ( dxfPathOpt ) ;
507+
508+ expect ( dxfPaths . length ) . toBe ( 1 ) ;
509+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
510+ const polyline = dxfPaths [ 0 ] . segments [ 0 ] as any ;
511+
512+ // Verify center is actually above the chord
513+ expect ( center [ 2 ] ) . toBeGreaterThan ( chordZ ) ;
514+
515+ // For a horizontal chord, center above = positive bulge
516+ expect ( polyline . bulges [ 0 ] ) . toBeGreaterThan ( 0.1 ) ;
517+
518+ arcWire . delete ( ) ;
519+ arc . delete ( ) ;
520+ } ) ;
521+
522+ it ( "should correctly export horizontal arc curving downward (center below chord)" , ( ) => {
523+ const startX = 10 ;
524+ const endX = 20 ;
525+ const chordZ = 15 ;
526+
527+ // Create arc with center below the chord
528+ const centerX = ( startX + endX ) / 2 ;
529+ const centerZ = chordZ - 5 ; // Center 5 units below the chord
530+ const radius = Math . sqrt ( Math . pow ( ( endX - startX ) / 2 , 2 ) + Math . pow ( 5 , 2 ) ) ; // Calculate radius
531+
532+ // Create arc using arcThroughThreePoints
533+ // Middle point should be on the arc itself, at the lowest point
534+ const middleX = centerX ;
535+ const middleZ = centerZ - radius ; // Bottom of the arc
536+
537+ const arc = occHelper . edgesService . arcThroughThreePoints ( {
538+ start : [ startX , 0 , chordZ ] ,
539+ middle : [ middleX , 0 , middleZ ] ,
540+ end : [ endX , 0 , chordZ ]
541+ } ) ;
542+
543+ const arcWire = wire . combineEdgesAndWiresIntoAWire ( { shapes : [ arc ] } ) ;
544+
545+ const center = occHelper . edgesService . getCircularEdgeCenterPoint ( { shape : arc } ) ;
546+
547+ const dxfPathOpt = new Inputs . OCCT . ShapeToDxfPathsDto < TopoDS_Shape > ( arcWire ) ;
548+ const dxfPaths = io . shapeToDxfPaths ( dxfPathOpt ) ;
549+
550+ expect ( dxfPaths . length ) . toBe ( 1 ) ;
551+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
552+ const polyline = dxfPaths [ 0 ] . segments [ 0 ] as any ;
553+
554+ // Verify center is actually below the chord
555+ expect ( center [ 2 ] ) . toBeLessThan ( chordZ ) ;
556+
557+ // For a horizontal chord, center below = negative bulge
558+ expect ( polyline . bulges [ 0 ] ) . toBeLessThan ( - 0.1 ) ;
559+
560+ arcWire . delete ( ) ;
561+ arc . delete ( ) ;
562+ } ) ;
326563} ) ;
0 commit comments