Files
cadhub/app/web/src/helpers/cadPackages/jsCadController.ts
2021-08-02 23:24:26 +02:00

168 lines
4.4 KiB
TypeScript

import {
RenderArgs,
DefaultKernelExport,
createUnhealthyResponse,
createHealthyResponse,
} from './common'
import {
MeshPhongMaterial,
LineBasicMaterial,
BufferGeometry,
BufferAttribute,
Line,
LineSegments,
Color,
Mesh,
} from 'three'
const materials = {
mesh: {
def: new MeshPhongMaterial({ color: 0x0084d1, flatShading: true }),
material: (params) => new MeshPhongMaterial(params),
},
line: {
def: new LineBasicMaterial({ color: 0x0000ff }),
material: ({ color, opacity, transparent }) =>
new LineBasicMaterial({ color, opacity, transparent }),
},
lines: null,
}
materials.lines = materials.line
function CSG2Object3D(obj) {
const { vertices, indices, color, transforms } = obj
const materialDef = materials[obj.type]
if (!materialDef) {
console.error('Can not hangle object type: ' + obj.type, obj)
return null
}
let material = materialDef.def
if (color) {
const c = color
material = materialDef.material({
color: new Color(c[0], c[1], c[2]),
flatShading: true,
opacity: c[3] === void 0 ? 1 : c[3],
transparent: c[3] != 1 && c[3] !== void 0,
})
}
const geo = new BufferGeometry()
geo.setAttribute('position', new BufferAttribute(vertices, 3))
let mesh
switch (obj.type) {
case 'mesh':
geo.setIndex(new BufferAttribute(indices, 1))
mesh = new Mesh(geo, material)
break
case 'line':
mesh = new Line(geo, material)
break
case 'lines':
mesh = new LineSegments(geo, material)
break
}
if (transforms) mesh.applyMatrix4({ elements: transforms })
return mesh
}
let scriptWorker
let lastParameters = {}
const scriptUrl = '/demo-worker.js'
let resolveReference = null
let response = null
const callResolve = () => {
if (resolveReference) resolveReference()
resolveReference
}
export const render: DefaultKernelExport['render'] = async ({
code,
parameters,
settings,
}: RenderArgs) => {
if (!scriptWorker) {
console.trace('************************** creating new worker ************************')
const baseURI = document.baseURI.toString()
const script = `let baseURI = '${baseURI}'
importScripts(new URL('${scriptUrl}',baseURI))
let worker = jscadWorker({
baseURI: baseURI,
scope:'worker',
convertToSolids: 'buffers',
callback:(params)=>self.postMessage(params),
})
self.addEventListener('message', (e)=>worker.postMessage(e.data))
`
const blob = new Blob([script], { type: 'text/javascript' })
scriptWorker = new Worker(window.URL.createObjectURL(blob))
let parameterDefinitions = []
scriptWorker.addEventListener('message', (e) => {
const data = e.data
if (data.action == 'parameterDefinitions') {
console.log('message',data)
parameterDefinitions = data.data
} else if (data.action == 'entities') {
if (data.error) {
response = createUnhealthyResponse(new Date(), data.error)
} else {
console.log('lastParameters',lastParameters)
response = createHealthyResponse({
type: 'geometry',
data: [...data.entities.map(CSG2Object3D).filter((o) => o)],
consoleMessage: data.scriptStats,
date: new Date(),
customizerParams: parameterDefinitions,
lastParameters,
})
}
callResolve()
}
})
callResolve()
response = null
scriptWorker.postMessage({ action: 'init', baseURI, alias: [] })
}
if(parameters){
// we are not evaluating code, but reacting to parameters change
scriptWorker.postMessage({
action: 'updateParams',
worker: 'script',
params: parameters,
})
}else{
scriptWorker.postMessage({
action: 'runScript',
worker: 'script',
script: code,
params: parameters || {},
url: 'jscad_script',
})
}
// we need this to keep the form filled with same data when new parameter definitions arrive
// each render of the script could provide new paramaters. In case some of them are still rpesent
// it is expected for them to stay the same and not just reset
lastParameters = parameters || {}
const waitResult = new Promise((resolve) => {
resolveReference = resolve
})
await waitResult
resolveReference = null
if(parameters) delete response.customizerParams
return response
}
const jsCadController: DefaultKernelExport = {
render,
}
export default jsCadController