Skip to content

Commit cc5aac9

Browse files
committed
add convert example, cleanup
1 parent 7a5602f commit cc5aac9

File tree

4 files changed

+420
-3
lines changed

4 files changed

+420
-3
lines changed

src/examples/convert/app.js

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
import * as THREE from 'three'
2+
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
3+
import { Rhino3dmLoader } from 'three/examples/jsm/loaders/3DMLoader'
4+
import rhino3dm from 'https://cdn.jsdelivr.net/npm/[email protected]/rhino3dm.module.js'
5+
import { RhinoCompute } from 'https://cdn.jsdelivr.net/npm/[email protected]/compute.rhino3d.module.js'
6+
7+
let data = {}
8+
data.definition = 'SampleGHConvertTo3dm.gh'
9+
data.inputs = {
10+
'encodedFile':null,
11+
'extension':null
12+
}
13+
data.fileName = null
14+
15+
const downloadButton = document.getElementById("downloadButton")
16+
downloadButton.onclick = download
17+
18+
// setup upload change events
19+
const upload = document.getElementById("upload")
20+
21+
upload.addEventListener('change', function () {
22+
if (this.files && this.files[0]) {
23+
showSpinner(true)
24+
const file = this.files[0]
25+
data.inputs.extension = file.name.substring( file.name.lastIndexOf('.'))
26+
data.fileName = file.name.substring(0, file.name.lastIndexOf('.'))
27+
28+
const reader = new FileReader()
29+
30+
reader.readAsArrayBuffer(file)
31+
32+
reader.addEventListener('load', function (e) {
33+
const buffer = new Uint8Array(e.target.result)
34+
const b64ba = base64ByteArray(buffer)
35+
data.inputs.encodedFile = b64ba
36+
37+
compute()
38+
})
39+
40+
}
41+
})
42+
43+
// globals
44+
let rhino, definition, doc
45+
rhino3dm().then(async m => {
46+
console.log('Loaded rhino3dm.')
47+
rhino = m // global
48+
49+
// enable download button
50+
upload.disabled = false
51+
52+
init()
53+
54+
})
55+
56+
async function compute() {
57+
58+
console.log('compute')
59+
60+
console.log(data)
61+
62+
// use POST request
63+
const request = {
64+
'method':'POST',
65+
'body': JSON.stringify(data),
66+
'headers': {'Content-Type': 'application/json'}
67+
}
68+
69+
try {
70+
const response = await fetch('/solve', request)
71+
72+
if(!response.ok) {
73+
// TODO: check for errors in response json
74+
throw new Error(response.statusText)
75+
}
76+
77+
const responseJson = await response.json()
78+
79+
collectResults(responseJson)
80+
81+
} catch(error) {
82+
console.error(error)
83+
}
84+
85+
}
86+
87+
/**
88+
* Parse response
89+
*/
90+
function collectResults(responseJson) {
91+
92+
const values = responseJson.values
93+
94+
// clear doc
95+
if( doc !== undefined)
96+
doc.delete()
97+
98+
//console.log(values)
99+
doc = new rhino.File3dm()
100+
101+
// for each output (RH_OUT:*)...
102+
for ( let i = 0; i < values.length; i ++ ) {
103+
// ...iterate through data tree structure...
104+
for (const path in values[i].InnerTree) {
105+
const branch = values[i].InnerTree[path]
106+
// ...and for each branch...
107+
for( let j = 0; j < branch.length; j ++) {
108+
// ...load rhino geometry into doc
109+
const rhinoObject = decodeItem(branch[j])
110+
if (rhinoObject !== null) {
111+
doc.objects().add(rhinoObject, null)
112+
}
113+
}
114+
}
115+
}
116+
117+
if (doc.objects().count < 1) {
118+
console.error('No rhino objects to load!')
119+
showSpinner(false)
120+
return
121+
}
122+
123+
// load rhino doc into three.js scene
124+
const buffer = new Uint8Array(doc.toByteArray()).buffer
125+
126+
// set up loader for converting the results to threejs
127+
const loader = new Rhino3dmLoader()
128+
loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/[email protected]/' )
129+
130+
loader.parse( buffer, function ( object )
131+
{
132+
///////////////////////////////////////////////////////////////////////////
133+
// change mesh material
134+
object.traverse(child => {
135+
if (child.isMesh) {
136+
child.material = new THREE.MeshNormalMaterial({ wireframe: true})
137+
}
138+
}, false)
139+
///////////////////////////////////////////////////////////////////////////
140+
141+
// clear objects from scene. do this here to avoid blink
142+
scene.traverse(child => {
143+
if (!child.isLight) {
144+
scene.remove(child)
145+
}
146+
})
147+
148+
// add object graph from rhino model to three.js scene
149+
scene.add( object )
150+
151+
// hide spinner and enable download button
152+
showSpinner(false)
153+
downloadButton.disabled = false
154+
155+
// zoom to extents
156+
zoomCameraToSelection(camera, controls, scene.children)
157+
})
158+
}
159+
160+
/**
161+
* Attempt to decode data tree item to rhino geometry
162+
*/
163+
function decodeItem(item) {
164+
const data = JSON.parse(item.data)
165+
if (item.type === 'System.String') {
166+
// hack for draco meshes
167+
try {
168+
return rhino.DracoCompression.decompressBase64String(data)
169+
} catch {} // ignore errors (maybe the string was just a string...)
170+
} else if (typeof data === 'object') {
171+
return rhino.CommonObject.decode(data)
172+
}
173+
return null
174+
}
175+
176+
/**
177+
* Shows or hides the loading spinner
178+
*/
179+
function showSpinner(enable) {
180+
if (enable)
181+
document.getElementById('loader').style.display = 'block'
182+
else
183+
document.getElementById('loader').style.display = 'none'
184+
}
185+
186+
// download button handler
187+
function download() {
188+
let buffer = doc.toByteArray()
189+
saveByteArray(data.fileName + '.3dm', buffer)
190+
}
191+
192+
function saveByteArray(fileName, byte) {
193+
let blob = new Blob([byte], { type: 'application/octect-stream' })
194+
let link = document.createElement('a')
195+
link.href = window.URL.createObjectURL(blob)
196+
link.download = fileName
197+
link.click()
198+
}
199+
200+
// BOILERPLATE //
201+
// declare variables to store scene, camera, and renderer
202+
let scene, camera, renderer, controls
203+
204+
function init() {
205+
206+
// Rhino models are z-up, so set this as the default
207+
THREE.Object3D.DefaultUp = new THREE.Vector3(0, 0, 1)
208+
209+
// create a scene and a camera
210+
scene = new THREE.Scene()
211+
scene.background = new THREE.Color(1, 1, 1)
212+
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
213+
camera.position.y = -30
214+
camera.position.z = 30
215+
216+
// create the renderer and add it to the html
217+
renderer = new THREE.WebGLRenderer({ antialias: true })
218+
renderer.setPixelRatio(window.devicePixelRatio)
219+
renderer.setSize(window.innerWidth, window.innerHeight)
220+
document.body.appendChild(renderer.domElement)
221+
222+
// add some controls to orbit the camera
223+
controls = new OrbitControls(camera, renderer.domElement)
224+
225+
// add a directional light
226+
const directionalLight = new THREE.DirectionalLight(0xffffff)
227+
directionalLight.intensity = 2
228+
scene.add(directionalLight)
229+
230+
const ambientLight = new THREE.AmbientLight()
231+
scene.add(ambientLight)
232+
233+
// handle changes in the window size
234+
window.addEventListener('resize', onWindowResize, false)
235+
236+
animate()
237+
238+
}
239+
240+
function onWindowResize() {
241+
camera.aspect = window.innerWidth / window.innerHeight
242+
camera.updateProjectionMatrix()
243+
renderer.setSize(window.innerWidth, window.innerHeight)
244+
animate()
245+
}
246+
247+
// function to continuously render the scene
248+
function animate() {
249+
250+
requestAnimationFrame(animate)
251+
renderer.render(scene, camera)
252+
253+
}
254+
255+
/**
256+
* Helper function that behaves like rhino's "zoom to selection", but for three.js!
257+
*/
258+
function zoomCameraToSelection(camera, controls, selection, fitOffset = 1.1) {
259+
260+
const box = new THREE.Box3();
261+
262+
for (const object of selection) {
263+
if (object.isLight) continue
264+
box.expandByObject(object);
265+
}
266+
267+
const size = box.getSize(new THREE.Vector3());
268+
const center = box.getCenter(new THREE.Vector3());
269+
270+
const maxSize = Math.max(size.x, size.y, size.z);
271+
const fitHeightDistance = maxSize / (2 * Math.atan(Math.PI * camera.fov / 360));
272+
const fitWidthDistance = fitHeightDistance / camera.aspect;
273+
const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance);
274+
275+
const direction = controls.target.clone()
276+
.sub(camera.position)
277+
.normalize()
278+
.multiplyScalar(distance);
279+
controls.maxDistance = distance * 10;
280+
controls.target.copy(center);
281+
282+
camera.near = distance / 100;
283+
camera.far = distance * 100;
284+
camera.updateProjectionMatrix();
285+
camera.position.copy(controls.target).sub(direction);
286+
287+
controls.update();
288+
289+
}
290+
291+
// https://gist.github.com/jonleighton/958841
292+
/*
293+
MIT LICENSE
294+
Copyright 2011 Jon Leighton
295+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
296+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
297+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
298+
*/
299+
function base64ByteArray(bytes) {
300+
var base64 = ''
301+
var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
302+
303+
// var bytes = new Uint8Array(arrayBuffer)
304+
305+
// strip bom
306+
if (bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191)
307+
bytes = bytes.slice(3)
308+
309+
var byteLength = bytes.byteLength
310+
var byteRemainder = byteLength % 3
311+
var mainLength = byteLength - byteRemainder
312+
313+
var a, b, c, d
314+
var chunk
315+
316+
// Main loop deals with bytes in chunks of 3
317+
for (var i = 0; i < mainLength; i = i + 3) {
318+
// Combine the three bytes into a single integer
319+
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]
320+
321+
// Use bitmasks to extract 6-bit segments from the triplet
322+
a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18
323+
b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12
324+
c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6
325+
d = chunk & 63 // 63 = 2^6 - 1
326+
327+
// Convert the raw binary segments to the appropriate ASCII encoding
328+
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
329+
}
330+
331+
// Deal with the remaining bytes and padding
332+
if (byteRemainder == 1) {
333+
chunk = bytes[mainLength]
334+
335+
a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2
336+
337+
// Set the 4 least significant bits to zero
338+
b = (chunk & 3) << 4 // 3 = 2^2 - 1
339+
340+
base64 += encodings[a] + encodings[b] + '=='
341+
} else if (byteRemainder == 2) {
342+
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]
343+
344+
a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
345+
b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4
346+
347+
// Set the 2 least significant bits to zero
348+
c = (chunk & 15) << 2 // 15 = 2^4 - 1
349+
350+
base64 += encodings[a] + encodings[b] + encodings[c] + '='
351+
}
352+
353+
return base64
354+
}

0 commit comments

Comments
 (0)