1
+
2
+ import { BitByBitBase } from "@bitbybit-dev/threejs" ;
3
+ import { OccStateEnum } from '@bitbybit-dev/occt-worker' ;
4
+ import { Inputs } from '@bitbybit-dev/threejs' ;
5
+ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' ;
6
+ import { STLExporter } from 'three/examples/jsm/exporters/STLExporter'
7
+ import { Color , DirectionalLight , Fog , Group , HemisphereLight , Mesh , MeshPhongMaterial , PerspectiveCamera , PlaneGeometry , Scene , Vector3 , VSMShadowMap , WebGLRenderer } from 'three' ;
8
+ import { GUI } from 'lil-gui' ;
9
+
10
+ function component ( ) {
11
+
12
+ const showSpinner = ( ) => {
13
+ const element = document . createElement ( 'div' ) ;
14
+ element . id = "spinner" ;
15
+ element . className = "lds-ellipsis" ;
16
+ element . innerHTML = `
17
+ <div></div>
18
+ <div></div>
19
+ <div></div>
20
+ ` ;
21
+
22
+ document . body . appendChild (
23
+ element
24
+ ) ;
25
+ }
26
+
27
+ const hideSpinner = ( ) => {
28
+ const el = document . getElementById ( 'spinner' ) ;
29
+ if ( el ) {
30
+ el . remove ( ) ;
31
+ }
32
+ }
33
+
34
+ let current : { group : Group | undefined , ground : Mesh | undefined , gui : GUI | undefined } = {
35
+ group : undefined ,
36
+ ground : undefined ,
37
+ gui : undefined ,
38
+ } ;
39
+
40
+ type Model = {
41
+ cupHeight : number ,
42
+ cupRadius : number ,
43
+ cupThickness : number ,
44
+ cupHandleDistance : number ,
45
+ cupHandleHeight : number ,
46
+ color : string ,
47
+ downloadSTL ?: ( ) => void ,
48
+ downloadStep ?: ( ) => void
49
+ }
50
+
51
+
52
+ const model = {
53
+ cupHeight : 13 ,
54
+ cupRadius : 4.5 ,
55
+ cupThickness : 1 ,
56
+ cupHandleDistance : 2 ,
57
+ cupHandleHeight : 0.5 ,
58
+ color : '#000000' ,
59
+ } as Model ;
60
+
61
+ let shapesToClean : Inputs . OCCT . TopoDSShapePointer [ ] = [ ] ;
62
+ let finalShape : Inputs . OCCT . TopoDSShapePointer | undefined ;
63
+ let bitbybit : BitByBitBase | undefined ;
64
+ let scene : Scene | undefined ;
65
+
66
+ const rotateGroup = ( ) => {
67
+ if ( current . group ) {
68
+ current . group . rotation . y -= 0.01 ;
69
+ }
70
+ }
71
+
72
+ const createShape = async ( bitbybit ?: BitByBitBase , scene ?: Scene ) => {
73
+ if ( scene && bitbybit ) {
74
+ if ( shapesToClean . length > 0 ) {
75
+ await bitbybit . occt . deleteShapes ( { shapes : shapesToClean } ) ;
76
+ }
77
+
78
+ const faceColour = '#444444' ;
79
+
80
+ const roundingRadius = model . cupThickness / 3 ;
81
+ const cupHolderLength = 2 ;
82
+ const cupHolderThickness = model . cupThickness * 1.5 ;
83
+ const cupHolderHeight = bitbybit . math . remap (
84
+ {
85
+ number : model . cupHandleHeight ,
86
+ fromLow : 0 ,
87
+ fromHigh : 1 ,
88
+ toLow : cupHolderThickness * 2 + roundingRadius * 2.5 ,
89
+ toHigh : ( model . cupHeight - model . cupThickness * 2 )
90
+ }
91
+ )
92
+ // .mapRange(model.cupHandleHeight, 0, 1, cupHolderThickness * 2 + roundingRadius * 2.5, (model.cupHeight - model.cupThickness * 2));
93
+
94
+ const cupHolderWidth = model . cupHandleDistance + model . cupThickness * 2 ;
95
+ const edgeColour = "#ffffff" ;
96
+
97
+ const box = await bitbybit . occt . shapes . solid . createBox ( {
98
+ width : cupHolderWidth * 2 ,
99
+ height : cupHolderHeight ,
100
+ length : cupHolderLength ,
101
+ center : [ model . cupRadius , model . cupHeight / 2 , 0 ]
102
+ } ) ;
103
+
104
+ const boxInside = await bitbybit . occt . shapes . solid . createBox ( {
105
+ width : ( cupHolderWidth * 2 ) - ( cupHolderThickness * 2 ) ,
106
+ height : cupHolderHeight - ( cupHolderThickness * 2 ) ,
107
+ length : cupHolderLength * 1.2 ,
108
+ center : [ model . cupRadius , model . cupHeight / 2 , 0 ]
109
+ } ) ;
110
+
111
+
112
+ const boolHolder = await bitbybit . occt . booleans . difference ( {
113
+ shape : box ,
114
+ shapes : [ boxInside ] ,
115
+ keepEdges : false
116
+ } ) ;
117
+
118
+ const cylinder = await bitbybit . occt . shapes . solid . createCylinder ( {
119
+ center : [ 0 , 0 , 0 ] ,
120
+ radius : model . cupRadius ,
121
+ height : model . cupHeight
122
+ } ) ;
123
+
124
+ const baseUnion = await bitbybit . occt . booleans . union ( {
125
+ shapes : [ cylinder , boolHolder ] ,
126
+ keepEdges : false
127
+ } ) ;
128
+
129
+ const cylinderInside = await bitbybit . occt . shapes . solid . createCylinder ( {
130
+ center : [ 0 , model . cupThickness , 0 ] ,
131
+ radius : model . cupRadius - model . cupThickness ,
132
+ height : model . cupHeight
133
+ } ) ;
134
+
135
+ const cupBase = await bitbybit . occt . booleans . difference ( {
136
+ shape : baseUnion ,
137
+ shapes : [ cylinderInside ] ,
138
+ keepEdges : false
139
+ } ) ;
140
+
141
+ finalShape = await bitbybit . occt . fillets . filletEdges ( {
142
+ radius : roundingRadius ,
143
+ shape : cupBase
144
+ } ) ;
145
+
146
+ shapesToClean = [ box , boxInside , boolHolder , cylinder , baseUnion , cylinderInside , cupBase , finalShape ] ;
147
+
148
+ const options = new Inputs . Draw . DrawOcctShapeOptions ( ) ;
149
+ options . faceColour = faceColour ;
150
+ options . faceOpacity = 1 ;
151
+ options . edgeOpacity = 1 ;
152
+ options . edgeWidth = 3 ;
153
+ options . drawEdges = true ;
154
+ options . edgeColour = edgeColour ;
155
+ options . precision = 0.001 ;
156
+
157
+ const mat = new MeshPhongMaterial ( { color : new Color ( model . color ) } ) ;
158
+ mat . polygonOffset = true ;
159
+ mat . polygonOffsetFactor = 3 ;
160
+ options . faceMaterial = mat ;
161
+
162
+ const group = await bitbybit . draw . drawAnyAsync ( { entity : finalShape , options } ) ;
163
+
164
+ group . children [ 0 ] . children . forEach ( ( child ) => {
165
+ child . castShadow = true ;
166
+ child . receiveShadow = true ;
167
+ } ) ;
168
+
169
+ current . group = group ;
170
+ }
171
+ }
172
+
173
+ const downloadStep = async ( ) => {
174
+ if ( bitbybit && finalShape ) {
175
+ await bitbybit . occt . io . saveShapeSTEP ( {
176
+ shape : finalShape ,
177
+ fileName : 'shape.stp' ,
178
+ adjustYtoZ : true ,
179
+ tryDownload : true
180
+ } ) ;
181
+ }
182
+ }
183
+
184
+ const downloadSTL = ( ) => {
185
+ if ( scene ) {
186
+ var exporter = new STLExporter ( ) ;
187
+ var str = exporter . parse ( scene ) ;
188
+ var blob = new Blob ( [ str ] , { type : 'text/plain' } ) ;
189
+ var link = document . createElement ( 'a' ) ;
190
+ link . style . display = 'none' ;
191
+ document . body . appendChild ( link ) ;
192
+ link . href = URL . createObjectURL ( blob ) ;
193
+ link . download = 'Scene.stl' ;
194
+ link . click ( ) ;
195
+ }
196
+ }
197
+
198
+ model . downloadSTL = downloadSTL ;
199
+ model . downloadStep = downloadStep ;
200
+
201
+ const updateShape = async ( ) => {
202
+ showSpinner ( ) ;
203
+ disableGUI ( ) ;
204
+ current . group ?. traverse ( ( obj ) => {
205
+ scene ?. remove ( obj ) ;
206
+ } ) ;
207
+ await createShape ( bitbybit , scene ) ;
208
+ enableGUI ( ) ;
209
+ hideSpinner ( ) ;
210
+ }
211
+
212
+ const init = async ( ) => {
213
+ showSpinner ( ) ;
214
+ bitbybit = new BitByBitBase ( ) ;
215
+
216
+ const domNode = document . getElementById ( 'three-canvas' ) as HTMLCanvasElement ;
217
+ const occt = new Worker ( new URL ( '../occ.worker' , import . meta. url ) , { name : 'OCC' , type : 'module' } ) ;
218
+
219
+ const camera = new PerspectiveCamera ( 70 , window . innerWidth / window . innerHeight , 0.01 , 1000 ) ;
220
+ scene = new Scene ( ) ;
221
+ scene . fog = new Fog ( 0x1a1c1f , 15 , 60 ) ;
222
+ const light = new HemisphereLight ( 0xffffff , 0x000000 , 10 ) ;
223
+ scene . add ( light ) ;
224
+ await bitbybit . init ( scene , occt , undefined ) ;
225
+
226
+ const renderer = new WebGLRenderer ( { antialias : true , canvas : domNode } ) ;
227
+ camera . aspect = window . innerWidth / window . innerHeight ;
228
+ camera . updateProjectionMatrix ( ) ;
229
+ renderer . setSize ( window . innerWidth , window . innerHeight ) ;
230
+ renderer . setPixelRatio ( window . devicePixelRatio / 1.5 )
231
+ const animation = ( time : number ) => {
232
+ renderer . render ( scene , camera ) ;
233
+ rotateGroup ( ) ;
234
+ controls . update ( ) ;
235
+ }
236
+
237
+ const controls = new OrbitControls ( camera , renderer . domElement ) ;
238
+ camera . position . set ( 10 , 20 , 10 ) ;
239
+
240
+ controls . update ( ) ;
241
+ controls . target = new Vector3 ( 0 , 5 , 0 ) ;
242
+ controls . enableDamping = true ;
243
+ controls . dampingFactor = 0.1
244
+ controls . zoomSpeed = 0.1 ;
245
+
246
+ renderer . shadowMap . enabled = true ;
247
+ renderer . shadowMap . type = VSMShadowMap ;
248
+
249
+ const onWindowResize = ( ) => {
250
+ camera . aspect = window . innerWidth / window . innerHeight ;
251
+ camera . updateProjectionMatrix ( ) ;
252
+ renderer . setSize ( window . innerWidth , window . innerHeight ) ;
253
+ }
254
+ window . addEventListener ( "resize" , onWindowResize , false ) ;
255
+
256
+ renderer . setClearColor ( new Color ( 0x1a1c1f ) , 1 ) ;
257
+
258
+ bitbybit . occtWorkerManager . occWorkerState$ . subscribe ( async s => {
259
+ if ( s . state === OccStateEnum . initialised ) {
260
+ await createShape ( bitbybit , scene ) ;
261
+
262
+ renderer . setAnimationLoop ( animation ) ;
263
+
264
+ const dirLight = new DirectionalLight ( 0xffffff , 50 ) ;
265
+ dirLight . position . set ( 15 , 40 , - 15 ) ;
266
+ dirLight . castShadow = true ;
267
+ dirLight . shadow . camera . near = 0 ;
268
+ dirLight . shadow . camera . far = 300 ;
269
+ const dist = 100 ;
270
+ dirLight . shadow . camera . right = dist ;
271
+ dirLight . shadow . camera . left = - dist ;
272
+ dirLight . shadow . camera . top = dist ;
273
+ dirLight . shadow . camera . bottom = - dist ;
274
+ dirLight . shadow . mapSize . width = 3000 ;
275
+ dirLight . shadow . mapSize . height = 3000 ;
276
+ dirLight . shadow . blurSamples = 8 ;
277
+ dirLight . shadow . radius = 2 ;
278
+ dirLight . shadow . bias = - 0.0005 ;
279
+
280
+ scene . add ( dirLight ) ;
281
+
282
+ const material = new MeshPhongMaterial ( { color : 0x000000 } )
283
+ material . shininess = 0 ;
284
+ material . specular = new Color ( 0x222222 ) ;
285
+ const ground = new Mesh ( new PlaneGeometry ( 50 , 50 , 1 , 1 ) , material ) ;
286
+ ground . rotateX ( - Math . PI / 2 ) ;
287
+ ground . receiveShadow = true ;
288
+ current . ground = ground ;
289
+ scene . add ( ground ) ;
290
+
291
+ createGui ( ) ;
292
+ hideSpinner ( ) ;
293
+ }
294
+ } ) ;
295
+ }
296
+
297
+ const disableGUI = ( ) => {
298
+ const lilGui = document . getElementsByClassName ( 'lil-gui' ) [ 0 ] as HTMLElement ;
299
+ lilGui . style . pointerEvents = "none" ;
300
+ lilGui . style . opacity = "0.5" ;
301
+ }
302
+
303
+ const enableGUI = ( ) => {
304
+ const lilGui = document . getElementsByClassName ( 'lil-gui' ) [ 0 ] as HTMLElement ;
305
+ lilGui . style . pointerEvents = "all" ;
306
+ lilGui . style . opacity = "1" ;
307
+ }
308
+
309
+ const createGui = ( ) => {
310
+
311
+ const gui = new GUI ( ) ;
312
+ current . gui = gui ;
313
+ gui . $title . innerHTML = "Cup" ;
314
+
315
+ gui
316
+ . add ( model , "cupHeight" , 6 , 16 , 0.01 )
317
+ . name ( "Height" )
318
+ . onFinishChange ( ( value : number ) => {
319
+ model . cupHeight = value ;
320
+ updateShape ( ) ;
321
+ } ) ;
322
+
323
+ gui
324
+ . add ( model , "cupRadius" , 3 , 8 , 0.01 )
325
+ . name ( "Radius" )
326
+ . onFinishChange ( ( value : number ) => {
327
+ model . cupRadius = value ;
328
+ updateShape ( ) ;
329
+ } ) ;
330
+
331
+ gui
332
+ . add ( model , "cupThickness" , 0.5 , 1 , 0.01 )
333
+ . name ( "Thickness" )
334
+ . onFinishChange ( ( value : number ) => {
335
+ model . cupThickness = value ;
336
+ updateShape ( ) ;
337
+ } ) ;
338
+
339
+ gui
340
+ . add ( model , "cupHandleDistance" , 0.3 , 3 , 0.01 )
341
+ . name ( "Handle Distance" )
342
+ . onFinishChange ( ( value : number ) => {
343
+ model . cupHandleDistance = value ;
344
+ updateShape ( ) ;
345
+ } ) ;
346
+
347
+ gui
348
+ . add ( model , "cupHandleHeight" , 0 , 1 , 0.01 )
349
+ . name ( "Handle Height" )
350
+ . onFinishChange ( ( value : number ) => {
351
+ model . cupHandleHeight = value ;
352
+ updateShape ( ) ;
353
+ } ) ;
354
+
355
+ gui
356
+ . addColor ( model , "color" )
357
+ . name ( "Color" )
358
+ . onChange ( ( value : string ) => {
359
+ const children = current . group ?. children [ 0 ] . children as Mesh [ ] ;
360
+ [ ...children , current . ground ] . forEach ( ( child ) => {
361
+ const material = ( child as Mesh ) . material as MeshPhongMaterial ;
362
+ material . color . setHex ( parseInt ( value . replace ( '#' , '0x' ) ) ) ;
363
+ } ) ;
364
+
365
+ } )
366
+
367
+ gui . add ( model , "downloadSTL" ) . name ( "Download STL" ) ;
368
+ gui . add ( model , "downloadStep" ) . name ( "Download STEP" ) ;
369
+ }
370
+
371
+ init ( ) ;
372
+
373
+ }
374
+
375
+ component ( ) ;
0 commit comments