Files
cadhub/app/web/src/helpers/cadPackages/jsCad/jsCadController.tsx
Kurt Hutten 43fc897bf9 Zoom to fit for openscad (#569)
* Add viewall flag to openscad cli in prep for zoom to fit for scad previews

* Fix remaining issues with social image capture
2021-11-06 09:46:55 +11:00

171 lines
4.4 KiB
TypeScript

import {
RenderArgs,
DefaultKernelExport,
RenderResponse,
createUnhealthyResponse,
createHealthyResponse,
} from '../common'
import {
MeshPhongMaterial,
LineBasicMaterial,
BufferGeometry,
BufferAttribute,
Line,
LineSegments,
Color,
Mesh,
} from 'three'
import { jsCadToCadhubParams } from './jscadParams'
import TheWorker from 'worker-loader!./jscadWorker'
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
interface CsgObj {
vertices: Float32Array
indices: Uint16Array
color?: [number, number, number, number]
transforms: number[]
type: 'mesh' | 'line' | 'lines'
}
function CSGArray2R3fComponent(Csgs: CsgObj[]): React.ReactNode {
return Csgs.map(({ vertices, indices, color, transforms, type }) => {
const materialDef = materials[type]
if (!materialDef) {
console.error('Can not hangle object type: ' + type, {
vertices,
indices,
color,
transforms,
type,
})
return null
}
let material = materialDef.def
if (color) {
const [r, g, b, opacity] = color
material = materialDef.material({
color: new Color(r, g, b),
flatShading: true,
opacity: opacity === void 0 ? 1 : opacity,
transparent: opacity != 1 && opacity !== void 0,
})
}
const geo = new BufferGeometry()
geo.setAttribute('position', new BufferAttribute(vertices, 3))
let mesh
switch (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
type ResolveFn = (RenderResponse) => void
class WorkerHelper {
callResolve: null | ResolveFn = null
previousCode = ''
resolver = (response: RenderResponse) => {
this.callResolve && this.callResolve(response)
this.callResolve = null
}
render = (
code: string,
parameters: { [key: string]: any } // eslint-disable-line @typescript-eslint/no-explicit-any
): Promise<RenderResponse> => {
const response: Promise<RenderResponse> = new Promise(
(resolve: ResolveFn) => {
this.callResolve = resolve
}
)
if (!(code && this.previousCode !== code)) {
// 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',
})
}
this.previousCode = code
return response
}
}
const workerHelper = new WorkerHelper()
export const render: DefaultKernelExport['render'] = async ({
code,
settings,
}: RenderArgs) => {
if (!scriptWorker) {
const baseURI = document.baseURI.toString()
scriptWorker = new TheWorker()
let parameterDefinitions = []
scriptWorker.addEventListener('message', ({ data }) => {
if (data.action == 'parameterDefinitions') {
parameterDefinitions = data.data
} else if (data.action == 'entities') {
if (data.error) {
workerHelper.resolver(createUnhealthyResponse(new Date(), data.error))
} else {
workerHelper.resolver(
createHealthyResponse({
type: 'primitive-array',
data: CSGArray2R3fComponent(data.entities),
consoleMessage: data.scriptStats,
date: new Date(),
customizerParams: jsCadToCadhubParams(parameterDefinitions || []),
})
)
}
}
})
workerHelper.resolver(null)
scriptWorker.postMessage({ action: 'init', baseURI, alias: [] })
}
return workerHelper.render(code, settings.parameters)
}
const jsCadController: DefaultKernelExport = {
render,
}
export default jsCadController