Skip to content

Commit 70d80ff

Browse files
committed
Add value list example
1 parent 42d4418 commit 70d80ff

File tree

2 files changed

+393
-0
lines changed

2 files changed

+393
-0
lines changed

src/examples/valueList/index.html

Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<link rel="icon" href="data:,">
6+
<title>value_list.gh</title>
7+
<style>
8+
body {
9+
margin: 0;
10+
font-family: monospace;
11+
}
12+
canvas { width: 100%; height: 100%; }
13+
input[type=range] { width: 100%; }
14+
#container { position: relative; }
15+
#container canvas, #overlay { position: absolute; }
16+
#overlay { z-index: 1; width: 100%; }
17+
#overlay div { padding: 5px; }
18+
#loader {
19+
border: 5px solid #f3f3f3; /* Light grey */
20+
border-top: 5px solid #3d3d3d; /* Grey */
21+
border-radius: 50%;
22+
width: 40px;
23+
height: 40px;
24+
animation: spin 1s linear infinite;
25+
position: absolute;
26+
top: 50%;
27+
left: 50%;
28+
z-index: 2;
29+
}
30+
@keyframes spin {
31+
0% { transform: rotate(0deg); }
32+
100% { transform: rotate(360deg); }
33+
}
34+
</style>
35+
</head>
36+
<body>
37+
<div id="loader"></div>
38+
<div id="container">
39+
<div id="overlay">
40+
<!-- /////////////////////////////////////////////////////////////////////////// -->
41+
<div>
42+
<label for="RH_IN:number">number</label>
43+
<!-- <input type="number" id="RH_IN:number" value=""> -->
44+
<select name="numberDropdown" id="RH_IN:number">
45+
<option value="1">One</option>
46+
<option value="2">Two</option>
47+
<option value="3">Three</option>
48+
<option value="4">Four</option>
49+
</select>
50+
</div>
51+
<!-- /////////////////////////////////////////////////////////////////////////// -->
52+
<div><button id="downloadButton" disabled>Download</button></div>
53+
<!-- <div><a href="value_list.gh" download="value_list.gh.html">Save source code</a></div> -->
54+
</div>
55+
</div>
56+
57+
<script type="module">
58+
import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js'
59+
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/controls/OrbitControls.js'
60+
import { Rhino3dmLoader } from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/3DMLoader.js'
61+
import rhino3dm from 'https://cdn.jsdelivr.net/npm/[email protected]/rhino3dm.module.js'
62+
63+
// set up loader for converting the results to threejs
64+
const loader = new Rhino3dmLoader()
65+
loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/[email protected]/' )
66+
67+
// initialise 'data' object that will be used by compute()
68+
const data = {
69+
definition: 'value_list.gh',
70+
inputs: getInputs()
71+
}
72+
73+
// globals
74+
let rhino, doc
75+
76+
rhino3dm().then(async m => {
77+
rhino = m
78+
79+
init()
80+
compute()
81+
})
82+
83+
const downloadButton = document.getElementById("downloadButton")
84+
downloadButton.onclick = download
85+
86+
/////////////////////////////////////////////////////////////////////////////
87+
// HELPER FUNCTIONS //
88+
/////////////////////////////////////////////////////////////////////////////
89+
90+
/**
91+
* Gets <input> elements from html and sets handlers
92+
* (html is generated from the grasshopper definition)
93+
*/
94+
function getInputs() {
95+
const inputs = {}
96+
for (const input of document.getElementsByTagName('input')) {
97+
switch (input.type) {
98+
case 'number':
99+
inputs[input.id] = input.valueAsNumber
100+
input.onchange = onSliderChange
101+
break
102+
case 'range':
103+
inputs[input.id] = input.valueAsNumber
104+
input.onmouseup = onSliderChange
105+
input.ontouchend = onSliderChange
106+
break
107+
case 'checkbox':
108+
inputs[input.id] = input.checked
109+
input.onclick = onSliderChange
110+
break
111+
default:
112+
break
113+
}
114+
}
115+
///////////////////////////////////////////////////////////////////////////
116+
for (const input of document.getElementsByTagName('select')) {
117+
inputs[input.id] = input.value
118+
input.onchange = onSliderChange
119+
}
120+
///////////////////////////////////////////////////////////////////////////
121+
return inputs
122+
}
123+
124+
// more globals
125+
let scene, camera, renderer, controls
126+
127+
/**
128+
* Sets up the scene, camera, renderer, lights and controls and starts the animation
129+
*/
130+
function init() {
131+
132+
// Rhino models are z-up, so set this as the default
133+
THREE.Object3D.DefaultUp = new THREE.Vector3( 0, 0, 1 );
134+
135+
// create a scene and a camera
136+
scene = new THREE.Scene()
137+
scene.background = new THREE.Color(1, 1, 1)
138+
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
139+
camera.position.set(1, -1, 1) // like perspective view
140+
141+
// very light grey for background, like rhino
142+
scene.background = new THREE.Color('whitesmoke')
143+
144+
// create the renderer and add it to the html
145+
renderer = new THREE.WebGLRenderer({ antialias: true })
146+
renderer.setPixelRatio( window.devicePixelRatio )
147+
renderer.setSize(window.innerWidth, window.innerHeight)
148+
document.body.appendChild(renderer.domElement)
149+
150+
// add some controls to orbit the camera
151+
controls = new OrbitControls(camera, renderer.domElement)
152+
153+
// add a directional light
154+
const directionalLight = new THREE.DirectionalLight( 0xffffff )
155+
directionalLight.intensity = 2
156+
scene.add( directionalLight )
157+
158+
const ambientLight = new THREE.AmbientLight()
159+
scene.add( ambientLight )
160+
161+
// handle changes in the window size
162+
window.addEventListener( 'resize', onWindowResize, false )
163+
164+
animate()
165+
}
166+
167+
/**
168+
* Call appserver
169+
*/
170+
async function compute() {
171+
// construct url for GET /solve/definition.gh?name=value(&...)
172+
const url = new URL('/solve/' + data.definition, window.location.origin)
173+
Object.keys(data.inputs).forEach(key => url.searchParams.append(key, data.inputs[key]))
174+
console.log(url.toString())
175+
176+
try {
177+
const response = await fetch(url)
178+
179+
if(!response.ok) {
180+
// TODO: check for errors in response json
181+
throw new Error(response.statusText)
182+
}
183+
184+
const responseJson = await response.json()
185+
186+
collectResults(responseJson)
187+
188+
} catch(error) {
189+
console.error(error)
190+
}
191+
}
192+
193+
/**
194+
* Parse response
195+
*/
196+
function collectResults(responseJson) {
197+
198+
const values = responseJson.values
199+
200+
// clear doc
201+
if( doc !== undefined)
202+
doc.delete()
203+
204+
//console.log(values)
205+
doc = new rhino.File3dm()
206+
207+
// for each output (RH_OUT:*)...
208+
for ( let i = 0; i < values.length; i ++ ) {
209+
// ...iterate through data tree structure...
210+
for (const path in values[i].InnerTree) {
211+
const branch = values[i].InnerTree[path]
212+
// ...and for each branch...
213+
for( let j = 0; j < branch.length; j ++) {
214+
// ...load rhino geometry into doc
215+
const rhinoObject = decodeItem(branch[j])
216+
if (rhinoObject !== null) {
217+
doc.objects().add(rhinoObject, null)
218+
}
219+
}
220+
}
221+
}
222+
223+
if (doc.objects().count < 1) {
224+
console.error('No rhino objects to load!')
225+
showSpinner(false)
226+
return
227+
}
228+
229+
// load rhino doc into three.js scene
230+
const buffer = new Uint8Array(doc.toByteArray()).buffer
231+
loader.parse( buffer, function ( object )
232+
{
233+
///////////////////////////////////////////////////////////////////////////
234+
// show mesh edges
235+
object.traverse(child => {
236+
if (child.isMesh) {
237+
const edges = new THREE.EdgesGeometry( child.geometry );
238+
const line = new THREE.LineSegments( edges, new THREE.LineBasicMaterial( { color: 0x000000 } ) )
239+
child.add( line )
240+
}
241+
}, false)
242+
///////////////////////////////////////////////////////////////////////////
243+
244+
// clear objects from scene. do this here to avoid blink
245+
scene.traverse(child => {
246+
if (!child.isLight) {
247+
scene.remove(child)
248+
}
249+
})
250+
251+
// add object graph from rhino model to three.js scene
252+
scene.add( object )
253+
254+
// hide spinner and enable download button
255+
showSpinner(false)
256+
downloadButton.disabled = false
257+
258+
// zoom to extents
259+
zoomCameraToSelection(camera, controls, scene.children)
260+
})
261+
}
262+
263+
/**
264+
* Attempt to decode data tree item to rhino geometry
265+
*/
266+
function decodeItem(item) {
267+
const data = JSON.parse(item.data)
268+
if (item.type === 'System.String') {
269+
// hack for draco meshes
270+
try {
271+
return rhino.DracoCompression.decompressBase64String(data)
272+
} catch {} // ignore errors (maybe the string was just a string...)
273+
} else if (typeof data === 'object') {
274+
return rhino.CommonObject.decode(data)
275+
}
276+
return null
277+
}
278+
279+
/**
280+
* Called when a slider value changes in the UI. Collect all of the
281+
* slider values and call compute to solve for a new scene
282+
*/
283+
function onSliderChange () {
284+
showSpinner(true)
285+
// get slider values
286+
let inputs = {}
287+
for (const input of document.getElementsByTagName('input')) {
288+
switch (input.type) {
289+
case 'number':
290+
inputs[input.id] = input.valueAsNumber
291+
break
292+
case 'range':
293+
inputs[input.id] = input.valueAsNumber
294+
break
295+
case 'checkbox':
296+
inputs[input.id] = input.checked
297+
break
298+
}
299+
}
300+
///////////////////////////////////////////////////////////////////////////
301+
for (const input of document.getElementsByTagName('select')) {
302+
inputs[input.id] = input.value
303+
}
304+
///////////////////////////////////////////////////////////////////////////
305+
306+
data.inputs = inputs
307+
308+
compute()
309+
}
310+
311+
/**
312+
* The animation loop!
313+
*/
314+
function animate() {
315+
requestAnimationFrame( animate )
316+
controls.update()
317+
renderer.render(scene, camera)
318+
}
319+
320+
/**
321+
* Helper function for window resizes (resets the camera pov and renderer size)
322+
*/
323+
function onWindowResize() {
324+
camera.aspect = window.innerWidth / window.innerHeight
325+
camera.updateProjectionMatrix()
326+
renderer.setSize( window.innerWidth, window.innerHeight )
327+
animate()
328+
}
329+
330+
/**
331+
* Helper function that behaves like rhino's "zoom to selection", but for three.js!
332+
*/
333+
function zoomCameraToSelection( camera, controls, selection, fitOffset = 1.2 ) {
334+
335+
const box = new THREE.Box3();
336+
337+
for( const object of selection ) {
338+
if (object.isLight) continue
339+
box.expandByObject( object );
340+
}
341+
342+
const size = box.getSize( new THREE.Vector3() );
343+
const center = box.getCenter( new THREE.Vector3() );
344+
345+
const maxSize = Math.max( size.x, size.y, size.z );
346+
const fitHeightDistance = maxSize / ( 2 * Math.atan( Math.PI * camera.fov / 360 ) );
347+
const fitWidthDistance = fitHeightDistance / camera.aspect;
348+
const distance = fitOffset * Math.max( fitHeightDistance, fitWidthDistance );
349+
350+
const direction = controls.target.clone()
351+
.sub( camera.position )
352+
.normalize()
353+
.multiplyScalar( distance );
354+
controls.maxDistance = distance * 10;
355+
controls.target.copy( center );
356+
357+
camera.near = distance / 100;
358+
camera.far = distance * 100;
359+
camera.updateProjectionMatrix();
360+
camera.position.copy( controls.target ).sub(direction);
361+
362+
controls.update();
363+
364+
}
365+
366+
/**
367+
* This function is called when the download button is clicked
368+
*/
369+
function download () {
370+
// write rhino doc to "blob"
371+
const bytes = doc.toByteArray()
372+
const blob = new Blob([bytes], {type: "application/octect-stream"})
373+
374+
// use "hidden link" trick to get the browser to download the blob
375+
const filename = data.definition.replace(/\.gh$/, '') + '.3dm'
376+
const link = document.createElement('a')
377+
link.href = window.URL.createObjectURL(blob)
378+
link.download = filename
379+
link.click()
380+
}
381+
382+
/**
383+
* Shows or hides the loading spinner
384+
*/
385+
function showSpinner(enable) {
386+
if (enable)
387+
document.getElementById('loader').style.display = 'block'
388+
else
389+
document.getElementById('loader').style.display = 'none'
390+
}
391+
</script>
392+
</body>
393+
</html>

src/files/value_list.gh

6.34 KB
Binary file not shown.

0 commit comments

Comments
 (0)