@@ -12,6 +12,11 @@ import {
1212 SphereGeometry ,
1313 Vector3
1414} from 'three' ;
15+ import helvetikerFont from 'three/examples/fonts/helvetiker_regular.typeface.json' ;
16+ import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js' ;
17+ import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js' ;
18+ import type { Font } from 'three/examples/jsm/loaders/FontLoader.js' ;
19+ import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js' ;
1520import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js' ;
1621import { SVGLoader } from 'three/examples/jsm/loaders/SVGLoader.js' ;
1722import { ADDITION , Brush , Evaluator , INTERSECTION , SUBTRACTION } from 'three-bvh-csg' ;
@@ -56,6 +61,16 @@ export class Solid {
5661 // ============================================================================
5762
5863 private static evaluator : Evaluator = new Evaluator ( ) ;
64+ private static defaultFont : Font | undefined = undefined ;
65+
66+ // Helper to load and cache the default font for text geometry
67+ private static getDefaultFont ( ) : Font {
68+ if ( ! this . defaultFont ) {
69+ const loader = new FontLoader ( ) ;
70+ this . defaultFont = loader . parse ( helvetikerFont ) ;
71+ }
72+ return this . defaultFont ;
73+ }
5974
6075 // Helper to convert degrees to radians
6176 private static degreesToRadians = ( degrees : number ) : number => degrees * ( Math . PI / 180 ) ;
@@ -133,11 +148,13 @@ export class Solid {
133148 private static geometryToBrush (
134149 geometry :
135150 | BoxGeometry
151+ | RoundedBoxGeometry
136152 | CylinderGeometry
137153 | SphereGeometry
138154 | ConeGeometry
139155 | ExtrudeGeometry
140156 | LatheGeometry
157+ | TextGeometry
141158 | BufferGeometry
142159 ) : Brush {
143160 const result = new Brush ( geometry . translate ( 0 , 0 , 0 ) ) ;
@@ -172,6 +189,44 @@ export class Solid {
172189 ) . normalize ( ) ;
173190 } ;
174191
192+ static roundedBox = (
193+ width : number ,
194+ height : number ,
195+ depth : number ,
196+ options ?: {
197+ color ?: string ;
198+ radius ?: number ;
199+ segments ?: number ;
200+ }
201+ ) : Solid => {
202+ // Validate dimensions (check finite first, then positive)
203+ if ( ! Number . isFinite ( width ) || ! Number . isFinite ( height ) || ! Number . isFinite ( depth ) )
204+ throw new Error (
205+ `RoundedBox dimensions must be finite (got width: ${ width } , height: ${ height } , depth: ${ depth } )`
206+ ) ;
207+ if ( width <= 0 || height <= 0 || depth <= 0 )
208+ throw new Error (
209+ `RoundedBox dimensions must be positive (got width: ${ width } , height: ${ height } , depth: ${ depth } )`
210+ ) ;
211+
212+ const color = options ?. color ?? 'gray' ;
213+ const radius = options ?. radius ?? Math . min ( width , height , depth ) * 0.1 ;
214+ const segments = options ?. segments ?? 2 ;
215+
216+ // Validate radius is within bounds
217+ const maxRadius = Math . min ( width , height , depth ) / 2 ;
218+ if ( radius > maxRadius )
219+ throw new Error (
220+ `RoundedBox radius (${ radius } ) cannot exceed half of smallest dimension (${ maxRadius } )`
221+ ) ;
222+ if ( radius < 0 ) throw new Error ( `RoundedBox radius must be non-negative (got ${ radius } )` ) ;
223+
224+ return new Solid (
225+ this . geometryToBrush ( new RoundedBoxGeometry ( width , height , depth , segments , radius ) ) ,
226+ color
227+ ) . normalize ( ) ;
228+ } ;
229+
175230 static cylinder = (
176231 radius : number ,
177232 height : number ,
@@ -388,6 +443,46 @@ export class Solid {
388443 }
389444 ) : Solid => this . prism ( 3 , radius , height , options ) ;
390445
446+ static text = (
447+ text : string ,
448+ options ?: {
449+ color ?: string ;
450+ size ?: number ;
451+ height ?: number ;
452+ curveSegments ?: number ;
453+ bevelEnabled ?: boolean ;
454+ }
455+ ) : Solid => {
456+ // Validate text parameter
457+ if ( ! text || text . length === 0 ) throw new Error ( 'Text cannot be empty' ) ;
458+
459+ const color = options ?. color ?? 'gray' ;
460+ const size = options ?. size ?? 10 ;
461+ const height = options ?. height ?? 2 ;
462+ const curveSegments = options ?. curveSegments ?? 12 ;
463+ const bevelEnabled = options ?. bevelEnabled ?? false ;
464+
465+ // Get the default font
466+ const font = this . getDefaultFont ( ) ;
467+
468+ // Create text geometry
469+ // Note: TextGeometry's 'depth' parameter is what we call 'height' (extrusion depth)
470+ const geometry = new TextGeometry ( text , {
471+ font,
472+ size,
473+ depth : height ,
474+ curveSegments,
475+ bevelEnabled
476+ } ) ;
477+
478+ // Create solid and normalize
479+ const solid = new Solid ( this . geometryToBrush ( geometry ) , color ) . normalize ( ) ;
480+
481+ // Center text on XZ plane and align to bottom (Y=0)
482+ // This makes text easier to position and orient consistently
483+ return solid . center ( { x : true , z : true } ) . align ( 'bottom' ) ;
484+ } ;
485+
391486 // ============================================================================
392487 // SECTION 4.5: Import Methods (Static)
393488 // ============================================================================
0 commit comments