diff --git a/app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts b/app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts index 283f28a..3f8f6a1 100644 --- a/app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts +++ b/app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts @@ -1,410 +1,440 @@ - -const setPoints = (points, p, i)=>{ +const setPoints = (points, p, i) => { points[i++] = p[0] points[i++] = p[1] points[i++] = p[2] || 0 } -function CSG2Vertices(csg){ - let idx = 0 +function CSG2Vertices(csg) { + const idx = 0 - let vLen = 0, iLen = 0 - for (let poly of csg.polygons){ - let len = poly.vertices.length - vLen += len *3 - iLen += 3 * (len-2) - } - const vertices = new Float32Array(vLen) - const indices = vLen > 65535 ? new Uint32Array(iLen) : new Uint16Array(iLen) - - let vertOffset = 0 - let indOffset = 0 - let posOffset = 0 - let first = 0 - for (let poly of csg.polygons){ - let arr = poly.vertices - let len = arr.length - first = posOffset - vertices.set(arr[0], vertOffset) - vertOffset +=3 - vertices.set(arr[1], vertOffset) - vertOffset +=3 - posOffset +=2 - for(let i=2; isetPoints(vertices, p, idx * 3 )) - - if(csg.isClosed){ - setPoints(vertices, csg.points[0], vertices.length - 3 ) + let vLen = 0, + iLen = 0 + for (const poly of csg.polygons) { + const len = poly.vertices.length + vLen += len * 3 + iLen += 3 * (len - 2) } - return {vertices, type:'line'} + const vertices = new Float32Array(vLen) + const indices = vLen > 65535 ? new Uint32Array(iLen) : new Uint16Array(iLen) + + let vertOffset = 0 + let indOffset = 0 + let posOffset = 0 + let first = 0 + for (const poly of csg.polygons) { + const arr = poly.vertices + const len = arr.length + first = posOffset + vertices.set(arr[0], vertOffset) + vertOffset += 3 + vertices.set(arr[1], vertOffset) + vertOffset += 3 + posOffset += 2 + for (let i = 2; i < len; i++) { + vertices.set(arr[i], vertOffset) + + indices[indOffset++] = first + indices[indOffset++] = first + i - 1 + indices[indOffset++] = first + i + + vertOffset += 3 + posOffset += 1 + } + } + return { vertices, indices, type: 'mesh' } } -function CSG2LineSegmentsVertices(csg){ - let vLen = csg.sides.length * 6 +function CSG2LineVertices(csg) { + let vLen = csg.points.length * 3 + if (csg.isClosed) vLen += 3 - var vertices = new Float32Array(vLen) - csg.sides.forEach((side,idx)=>{ - let i = idx * 6 + const vertices = new Float32Array(vLen) + + csg.points.forEach((p, idx) => setPoints(vertices, p, idx * 3)) + + if (csg.isClosed) { + setPoints(vertices, csg.points[0], vertices.length - 3) + } + return { vertices, type: 'line' } +} + +function CSG2LineSegmentsVertices(csg) { + const vLen = csg.sides.length * 6 + + const vertices = new Float32Array(vLen) + csg.sides.forEach((side, idx) => { + const i = idx * 6 setPoints(vertices, side[0], i) - setPoints(vertices, side[1], i+3) + setPoints(vertices, side[1], i + 3) }) - return {vertices, type:'lines'} - + return { vertices, type: 'lines' } } -function CSGCached(func, data, cacheKey, transferable){ +function CSGCached(func, data, cacheKey, transferable) { cacheKey = cacheKey || data let geo = CSGToBuffers.cache.get(cacheKey) - if(geo) return geo + if (geo) return geo geo = func(data) // fill transferable array for postMessage optimization - if(transferable){ - const {vertices, indices} = geo + if (transferable) { + const { vertices, indices } = geo transferable.push(vertices) - if(indices) transferable.push(indices) + if (indices) transferable.push(indices) } - CSGToBuffers.cache.set(cacheKey, geo) - return geo + CSGToBuffers.cache.set(cacheKey, geo) + return geo } -function CSGToBuffers(csg, transferable){ +function CSGToBuffers(csg, transferable) { let obj - if(csg.polygons) obj = CSGCached(CSG2Vertices,csg,csg.polygons, transferable) - if(csg.sides && !csg.points) obj = CSGCached(CSG2LineSegmentsVertices,csg,csg.sides, transferable) - if(csg.points) obj = CSGCached(CSG2LineVertices,csg,csg.points, transferable) + if (csg.polygons) + obj = CSGCached(CSG2Vertices, csg, csg.polygons, transferable) + if (csg.sides && !csg.points) + obj = CSGCached(CSG2LineSegmentsVertices, csg, csg.sides, transferable) + if (csg.points) + obj = CSGCached(CSG2LineVertices, csg, csg.points, transferable) return obj } -CSGToBuffers.clearCache = ()=>{CSGToBuffers.cache = new WeakMap()} +CSGToBuffers.clearCache = () => { + CSGToBuffers.cache = new WeakMap() +} CSGToBuffers.clearCache() - - - let workerBaseURI -function require(url){ - url = require.alias[url] || url - if(url[0] != '/' && url.substr(0,2) != './' && url.substr(0,4) != 'http') url = 'https://unpkg.com/'+url - let exports=require.cache[url]; //get from cache - if (!exports) { //not cached - let module = requireModule(url) - require.cache[url] = exports = module.exports; //cache obj exported by module - } - return exports; //require returns object exported by module +function require(url) { + url = require.alias[url] || url + if (url[0] != '/' && url.substr(0, 2) != './' && url.substr(0, 4) != 'http') + url = 'https://unpkg.com/' + url + let exports = require.cache[url] //get from cache + if (!exports) { + //not cached + const module = requireModule(url) + require.cache[url] = exports = module.exports //cache obj exported by module + } + return exports //require returns object exported by module } -function requireFile(url){ - try{ - let X=new XMLHttpRequest(); - X.open("GET", new URL(url,workerBaseURI), 0); // sync - X.send(); - if (X.status && X.status !== 200) throw new Error(X.statusText); - return X.responseText; - }catch(e){ - console.log('problem loading url ',url,'base',workerBaseURI,' error:',e.message) +function requireFile(url) { + try { + const X = new XMLHttpRequest() + X.open('GET', new URL(url, workerBaseURI), 0) // sync + X.send() + if (X.status && X.status !== 200) throw new Error(X.statusText) + return X.responseText + } catch (e) { + console.log( + 'problem loading url ', + url, + 'base', + workerBaseURI, + ' error:', + e.message + ) throw e } } -function requireModule(url, source){ - try { - const exports={}; - if(!source) source = requireFile(url) - const module = { id: url, uri: url, exports:exports, source }; //according to node.js modules - // fix, add comment to show source on Chrome Dev Tools - source="//@ sourceURL="+url+"\n" + source; - //------ - const anonFn = new Function("require", "exports", "module", source); //create a Fn with module code, and 3 params: require, exports & module - anonFn(require, exports, module); // call the Fn, Execute the module - return module - } catch (err) { - console.error("Error loading module "+url, err.message); - throw err; - } +function requireModule(url, source) { + try { + const exports = {} + if (!source) source = requireFile(url) + const module = { id: url, uri: url, exports: exports, source } //according to node.js modules + // fix, add comment to show source on Chrome Dev Tools + source = '//@ sourceURL=' + url + '\n' + source + //------ + const anonFn = new Function('require', 'exports', 'module', source) //create a Fn with module code, and 3 params: require, exports & module + anonFn(require, exports, module) // call the Fn, Execute the module + return module + } catch (err) { + console.error('Error loading module ' + url, err.message) + throw err + } } require.cache = {} require.alias = {} - -const cmdHandler = (handlers)=>(cmd)=>{ +const cmdHandler = (handlers) => (cmd) => { const fn = handlers[cmd.action] if (!fn) throw new Error('no handler for type: ' + cmd.action) - fn(cmd); + fn(cmd) } +function parseParams(script) { + let lines = script.split('\n').map((l) => l.trim()) + lines = lines + .map((l, i) => { + return { code: l, line: i + 1, group: l[0] == '/' && !lines[i + 1] } + }) + .filter((l) => l.code) + let i = 0, + line, + next, + lineNum + while (i < lines.length) { + line = lines[i].code.trim() + i++ + if (line.length > 12 && line.substring(line.length - 13) == '//jscadparams') + break + if (line.length > 12 && line.indexOf('@jscad-params') != -1) break + } -function parseParams(script){ - let lines = script.split('\n').map(l=>l.trim()) + let groupIndex = 1 + const defs = [] - lines = lines.map((l,i)=>{ - return {code:l, line:i+1, group: l[0] == '/' && !lines[i+1]} - }).filter(l=>l.code) + while (i < lines.length) { + line = lines[i].code + lineNum = lines[i].line + next = lines[i + 1] ? lines[i + 1].code : '' + if (line[0] === '}') break - let i = 0, line, next, lineNum - while(i12 && line.substring(line.length-13) == '//jscadparams') break; - if(line.length>12 && line.indexOf('@jscad-params') != -1) break; + if (line[0] === '/') { + // group + const def = parseComment(line, lineNum) + let name = '_group_' + groupIndex++ + let caption = def.caption + + const idx = caption.lastIndexOf(':') + if (idx !== -1) { + name = caption.substring(idx + 1).trim() + caption = caption.substring(0, idx).trim() + } + defs.push({ name, type: 'group', caption, ...def.options }) + } else { + const idx = line.indexOf('/') + if (idx === -1) { + const def = parseDef(line, lineNum) + def.caption = def.name + defs.push(def) + } else { + defs.push( + parseOne( + line.substring(idx).trim(), + line.substring(0, idx).trim(), + lineNum, + lineNum + ) + ) + } } + i++ + } - let groupIndex = 1 - const defs = [] - - while(i{ +const makeScriptWorker = ({ callback, convertToSolids }) => { let onInit, main, scriptStats, entities - - function runMain(params={}){ + function runMain(params = {}) { let time = Date.now() let solids - let transfer = [] - try{ - let tmp = main(params) + const transfer = [] + try { + const tmp = main(params) solids = [] - function flatten(arr){ - if(arr){ - if(arr instanceof Array) - arr.forEach(flatten) - else - solids.push(arr) + function flatten(arr) { + if (arr) { + if (arr instanceof Array) arr.forEach(flatten) + else solids.push(arr) } } flatten(tmp) - - }catch(e){ - callback({action:'entities', worker:'render', error:e.message, stack:e.stack.toString()}, transfer) + } catch (e) { + callback( + { + action: 'entities', + worker: 'render', + error: e.message, + stack: e.stack.toString(), + }, + transfer + ) return } - let solidsTime = Date.now() - time + const solidsTime = Date.now() - time scriptStats = `generate solids ${solidsTime}ms` - if(convertToSolids === 'buffers'){ + if (convertToSolids === 'buffers') { CSGToBuffers.clearCache() - entities = solids.filter(s=>s).map((csg)=>{ - let obj = CSGToBuffers(csg, transfer) - obj.color = csg.color - obj.transforms = csg.transforms - return obj - }) - }else if(convertToSolids === 'regl'){ + entities = solids + .filter((s) => s) + .map((csg) => { + const obj = CSGToBuffers(csg, transfer) + obj.color = csg.color + obj.transforms = csg.transforms + return obj + }) + } else if (convertToSolids === 'regl') { const { entitiesFromSolids } = require('@jscad/regl-renderer') time = Date.now() entities = entitiesFromSolids({}, solids) - scriptStats += ` convert to entities ${Date.now()-time}ms` - }else{ + scriptStats += ` convert to entities ${Date.now() - time}ms` + } else { entities = solids } - callback({action:'entities', worker:'render', entities, scriptStats}, transfer) + callback( + { action: 'entities', worker: 'render', entities, scriptStats }, + transfer + ) } let initialized = false const handlers = { - runScript: ({script,url, params={}})=>{ - if(!initialized){ - onInit = ()=>handlers.runScript({script,url, params}) + runScript: ({ script, url, params = {} }) => { + if (!initialized) { + onInit = () => handlers.runScript({ script, url, params }) } let script_module - try{ - script_module = requireModule(url,script) - }catch(e){ - callback({action:'entities', worker:'render', error:e.message, stack:e.stack.toString()}) + try { + script_module = requireModule(url, script) + } catch (e) { + callback({ + action: 'entities', + worker: 'render', + error: e.message, + stack: e.stack.toString(), + }) return } main = script_module.exports.main - let gp = script_module.exports.getParameterDefinitions - let paramsDef = parseParams(script) || [] - if(gp){ - gp().forEach(p=>{ - let idx = paramsDef.findIndex(old=>old.name == p.name) - if(idx === -1){ + const gp = script_module.exports.getParameterDefinitions + const paramsDef = parseParams(script) || [] + if (gp) { + gp().forEach((p) => { + const idx = paramsDef.findIndex((old) => old.name == p.name) + if (idx === -1) { paramsDef.push(p) - }else{ - paramsDef.splice(idx,1,p) + } else { + paramsDef.splice(idx, 1, p) } }) } - if(paramsDef.length) callback({action:'parameterDefinitions', worker:'main', data:paramsDef}) + if (paramsDef.length) + callback({ + action: 'parameterDefinitions', + worker: 'main', + data: paramsDef, + }) runMain(params) }, - updateParams: ({params={}})=>{ + updateParams: ({ params = {} }) => { runMain(params) }, - init: (params)=>{ - let {baseURI, alias=[]} = params - if(!baseURI && typeof document != 'undefined' && document.baseURI){ + init: (params) => { + let { baseURI, alias = [] } = params + if (!baseURI && typeof document != 'undefined' && document.baseURI) { baseURI = document.baseURI } - if(baseURI) workerBaseURI = baseURI.toString() + if (baseURI) workerBaseURI = baseURI.toString() - alias.forEach(arr=>{ - let [orig, ...aliases] = arr - aliases.forEach(a=>{ + alias.forEach((arr) => { + const [orig, ...aliases] = arr + aliases.forEach((a) => { require.alias[a] = orig - if(a.toLowerCase().substr(-3)!=='.js') require.alias[a+'.js'] = orig + if (a.toLowerCase().substr(-3) !== '.js') + require.alias[a + '.js'] = orig }) }) initialized = true - if(onInit) onInit() + if (onInit) onInit() }, } @@ -414,23 +444,10 @@ const makeScriptWorker = ({callback, convertToSolids})=>{ } } - - - - - - - - - - - - - /** Make render worker */ -const makeRenderWorker = ()=>{ -let perspectiveCamera +const makeRenderWorker = () => { + let perspectiveCamera const state = {} const rotateSpeed = 0.002 @@ -444,10 +461,10 @@ let perspectiveCamera let entities = [] - function createContext (canvas, contextAttributes) { - function get (type) { + function createContext(canvas, contextAttributes) { + function get(type) { try { - return {gl:canvas.getContext(type, contextAttributes), type} + return { gl: canvas.getContext(type, contextAttributes), type } } catch (e) { return null } @@ -460,48 +477,59 @@ let perspectiveCamera ) } - const startRenderer = ({canvas, cameraPosition, cameraTarget, axis={}, grid={}})=>{ - const { prepareRender, drawCommands, cameras, controls } = require('@jscad/regl-renderer') + const startRenderer = ({ + canvas, + cameraPosition, + cameraTarget, + axis = {}, + grid = {}, + }) => { + const { + prepareRender, + drawCommands, + cameras, + controls, + } = require('@jscad/regl-renderer') perspectiveCamera = cameras.perspective orbitControls = controls.orbit state.canvas = canvas - state.camera = Object.assign({}, perspectiveCamera.defaults) - if(cameraPosition) state.camera.position = cameraPosition - if(cameraTarget) state.camera.target = cameraTarget + state.camera = { ...perspectiveCamera.defaults } + if (cameraPosition) state.camera.position = cameraPosition + if (cameraTarget) state.camera.target = cameraTarget - resize({ width:canvas.width, height:canvas.height }) + resize({ width: canvas.width, height: canvas.height }) state.controls = orbitControls.defaults - const {gl, type} = createContext(canvas) + const { gl, type } = createContext(canvas) // prepare the renderer const setupOptions = { - glOptions: {gl} + glOptions: { gl }, } - if(type == 'webgl'){ - setupOptions.glOptions.optionalExtensions = ['oes_element_index_uint'] + if (type == 'webgl') { + setupOptions.glOptions.optionalExtensions = ['oes_element_index_uint'] } renderer = prepareRender(setupOptions) gridOptions = { visuals: { drawCmd: 'drawGrid', - show: grid.show || grid.show === undefined , + show: grid.show || grid.show === undefined, color: grid.color || [0, 0, 0, 1], subColor: grid.subColor || [0, 0, 1, 0.5], fadeOut: false, - transparent: true + transparent: true, }, size: grid.size || [200, 200], - ticks: grid.ticks || [10, 1] + ticks: grid.ticks || [10, 1], } axisOptions = { visuals: { drawCmd: 'drawAxis', - show: axis.show || axis.show === undefined + show: axis.show || axis.show === undefined, }, size: axis.size || 100, } @@ -513,14 +541,10 @@ let perspectiveCamera drawAxis: drawCommands.drawAxis, drawGrid: drawCommands.drawGrid, drawLines: drawCommands.drawLines, - drawMesh: drawCommands.drawMesh + drawMesh: drawCommands.drawMesh, }, // define the visual content - entities: [ - gridOptions, - axisOptions, - ...entities - ] + entities: [gridOptions, axisOptions, ...entities], } // the heart of rendering, as themes, controls, etc change @@ -528,23 +552,31 @@ let perspectiveCamera } let renderTimer - const tmFunc = typeof requestAnimationFrame === 'undefined' ? setTimeout : requestAnimationFrame + const tmFunc = + typeof requestAnimationFrame === 'undefined' + ? setTimeout + : requestAnimationFrame - function updateView(delay=8){ - if(renderTimer || !renderer) return - renderTimer = tmFunc(updateAndRender,delay) + function updateView(delay = 8) { + if (renderTimer || !renderer) return + renderTimer = tmFunc(updateAndRender, delay) } const doRotatePanZoom = () => { - if (rotateDelta[0] || rotateDelta[1]) { - const updated = orbitControls.rotate({ controls: state.controls, camera: state.camera, speed: rotateSpeed }, rotateDelta) + const updated = orbitControls.rotate( + { controls: state.controls, camera: state.camera, speed: rotateSpeed }, + rotateDelta + ) state.controls = { ...state.controls, ...updated.controls } rotateDelta = [0, 0] } if (panDelta[0] || panDelta[1]) { - const updated = orbitControls.pan({ controls:state.controls, camera:state.camera, speed: panSpeed }, panDelta) + const updated = orbitControls.pan( + { controls: state.controls, camera: state.camera, speed: panSpeed }, + panDelta + ) state.controls = { ...state.controls, ...updated.controls } panDelta = [0, 0] state.camera.position = updated.camera.position @@ -552,7 +584,10 @@ let perspectiveCamera } if (zoomDelta) { - const updated = orbitControls.zoom({ controls:state.controls, camera:state.camera, speed: zoomSpeed }, zoomDelta) + const updated = orbitControls.zoom( + { controls: state.controls, camera: state.camera, speed: zoomSpeed }, + zoomDelta + ) state.controls = { ...state.controls, ...updated.controls } zoomDelta = 0 } @@ -562,55 +597,57 @@ let perspectiveCamera renderTimer = null doRotatePanZoom() - const updates = orbitControls.update({ controls: state.controls, camera: state.camera }) + const updates = orbitControls.update({ + controls: state.controls, + camera: state.camera, + }) state.controls = { ...state.controls, ...updates.controls } - if(state.controls.changed) updateView(16) // for elasticity in rotate / zoom + if (state.controls.changed) updateView(16) // for elasticity in rotate / zoom state.camera.position = updates.camera.position perspectiveCamera.update(state.camera) - renderOptions.entities = [ - gridOptions, - axisOptions, - ...entities - ] - let time = Date.now() + renderOptions.entities = [gridOptions, axisOptions, ...entities] + const time = Date.now() renderer(renderOptions) - if(updateRender){ - updateRender = ''; + if (updateRender) { + updateRender = '' } } - function resize({width,height}){ + function resize({ width, height }) { state.canvas.width = width state.canvas.height = height - perspectiveCamera.setProjection(state.camera, state.camera, { width, height }) + perspectiveCamera.setProjection(state.camera, state.camera, { + width, + height, + }) perspectiveCamera.update(state.camera, state.camera) updateView() } const handlers = { - pan: ({dx,dy})=>{ + pan: ({ dx, dy }) => { panDelta[0] += dx panDelta[1] += dy updateView() }, - rotate: ({dx,dy})=>{ + rotate: ({ dx, dy }) => { rotateDelta[0] -= dx rotateDelta[1] -= dy updateView() }, - zoom: ({dy})=>{ + zoom: ({ dy }) => { zoomDelta += dy updateView() }, resize, - entities: (params)=>{ + entities: (params) => { entities = params.entities updateRender = params.scriptStats updateView() }, - init: (params)=>{ - if(params.canvas) startRenderer(params) + init: (params) => { + if (params.canvas) startRenderer(params) initialized = true }, } @@ -621,42 +658,38 @@ let perspectiveCamera } } - function start(params) { - let { - callback=()=>{}, - convertToSolids=false - } = params + const { callback = () => {}, convertToSolids = false } = params // by default 'render' messages go outside of this instance (result of modeling) let scriptWorker - const sendCmd = (params, transfer)=>{ - if(params.worker === 'script') scriptWorker.postMessage(params, transfer) - else{ + const sendCmd = (params, transfer) => { + if (params.worker === 'script') scriptWorker.postMessage(params, transfer) + else { callback(params, transfer) } } - - - scriptWorker = makeScriptWorker({callback:sendCmd, convertToSolids}) - callback({action:'workerInit',worker:'main'}) + scriptWorker = makeScriptWorker({ callback: sendCmd, convertToSolids }) + callback({ action: 'workerInit', worker: 'main' }) return { - updateParams:({params={}})=>sendCmd({ action:'updateParams', worker:'script', params}), - runScript: ({script,url=''})=>sendCmd({ action:'runScript', worker:'script', script, url}), + updateParams: ({ params = {} }) => + sendCmd({ action: 'updateParams', worker: 'script', params }), + runScript: ({ script, url = '' }) => + sendCmd({ action: 'runScript', worker: 'script', script, url }), postMessage: sendCmd, } } const init = start({ convertToSolids: 'buffers', - callback:(params)=>self.postMessage(params), + callback: (params) => self.postMessage(params), }) -self.onmessage = ({data}) => { +self.onmessage = ({ data }) => { if (data.action === 'init') { workerBaseURI = data.baseURI } init.postMessage(data, null) -}; +}