diff --git a/app/web/public/demo-worker.js b/app/web/public/demo-worker.js index 376ab16..ebbb97c 100644 --- a/app/web/public/demo-worker.js +++ b/app/web/public/demo-worker.js @@ -24,7 +24,7 @@ const setPoints = (points, p, i)=>{ points[i++] = p[0] points[i++] = p[1] - points[i++] = p[2] || 0 + points[i++] = p[2] || 0 } function CSG2Vertices(csg){ @@ -54,11 +54,11 @@ function CSG2Vertices(csg){ posOffset +=2 for(let i=2; isetPoints(vertices, p, idx * 3 )) - + if(csg.isClosed){ setPoints(vertices, csg.points[0], vertices.length - 3 ) } return {vertices, type:'line'} -} +} function CSG2LineSegmentsVertices(csg){ let vLen = csg.sides.length * 6 var vertices = new Float32Array(vLen) csg.sides.forEach((side,idx)=>{ - let i = idx * 6 + let i = idx * 6 setPoints(vertices, side[0], i) setPoints(vertices, side[1], i+3) }) @@ -104,14 +104,14 @@ function CSGCached(func, data, cacheKey, transferable){ geo = func(data) // fill transferable array for postMessage optimization - if(transferable){ + if(transferable){ const {vertices, indices} = geo transferable.push(vertices) if(indices) transferable.push(indices) } CSGToBuffers.cache.set(cacheKey, geo) - return geo + return geo } function CSGToBuffers(csg, transferable){ @@ -159,7 +159,7 @@ 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 + 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; //------ @@ -172,7 +172,7 @@ function requireModule(url, source){ } } -require.cache = {} +require.cache = {} require.alias = {} @@ -189,7 +189,7 @@ const initCanvas = (canvas, callback)=>{ const cmd = { worker: 'render', dx: lastX - ev.pageX, - dy: ev.pageY - lastY + dy: ev.pageY - lastY } const shiftKey = (ev.shiftKey === true) || (ev.touches && ev.touches.length > 2) @@ -244,7 +244,6 @@ const makeScriptWorker = ({callback, convertToSolids})=>{ let workerBaseURI, onInit function runMain(params={}){ - console.log('runMain'); let time = Date.now() let solids try{ @@ -265,7 +264,7 @@ const makeScriptWorker = ({callback, convertToSolids})=>{ obj.transforms = csg.transforms return obj }) - }else if(convertToSolids === 'regl'){ + }else if(convertToSolids === 'regl'){ const { entitiesFromSolids } = require('@jscad/regl-renderer') time = Date.now() entities = entitiesFromSolids({}, solids) @@ -281,12 +280,12 @@ const makeScriptWorker = ({callback, convertToSolids})=>{ 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()}) + callback({action:'entities', worker:'render', error:e.message, stack:e.stack.toString()}) return } main = script_module.exports.main @@ -304,12 +303,12 @@ const makeScriptWorker = ({callback, convertToSolids})=>{ if(!baseURI && typeof document != 'undefined' && document.baseURI){ baseURI = document.baseURI } - + if(baseURI) workerBaseURI = baseURI.toString() alias.forEach(arr=>{ let [orig, ...aliases] = arr - aliases.forEach(a=>{ + aliases.forEach(a=>{ require.alias[a] = orig if(a.toLowerCase().substr(-3)!=='.js') require.alias[a+'.js'] = orig }) @@ -392,7 +391,7 @@ let perspectiveCamera glOptions: {gl} } if(type == 'webgl'){ - setupOptions.glOptions.optionalExtensions = ['oes_element_index_uint'] + setupOptions.glOptions.optionalExtensions = ['oes_element_index_uint'] } renderer = prepareRender(setupOptions) @@ -412,7 +411,7 @@ let perspectiveCamera axisOptions = { visuals: { drawCmd: 'drawAxis', - show: axis.show || axis.show === undefined + show: axis.show || axis.show === undefined }, size: axis.size || 100, } @@ -492,7 +491,7 @@ let perspectiveCamera } } - function resize({width,height}){ + function resize({width,height}){ state.canvas.width = width state.canvas.height = height perspectiveCamera.setProjection(state.camera, state.camera, { width, height }) @@ -504,7 +503,7 @@ let perspectiveCamera pan: ({dx,dy})=>{ panDelta[0] += dx panDelta[1] += dy - updateView() + updateView() }, rotate: ({dx,dy})=>{ rotateDelta[0] -= dx @@ -550,7 +549,7 @@ return (params)=>{ workerBaseURI = baseURI const sendCmd = (params, transfer)=>{ - if(params.worker === 'render') + if(params.worker === 'render') sendToRender(params, transfer) else if(params.worker === 'script') scriptWorker.postMessage(params, transfer) @@ -572,7 +571,7 @@ return (params)=>{ console.log('render in scope: '+scope); renderWorker = makeRenderWorker({callback:sendCmd}) sendToRender = (params, transfer)=>renderWorker.postMessage(params, transfer) - } + } if(scope === 'main'){ // let extraScript = renderInWorker ? `,'https://unpkg.com/@jscad/regl-renderer'`:'' @@ -581,8 +580,8 @@ importScripts(new URL('${scriptUrl}',baseURI)) let worker = jscadWorker({ baseURI: baseURI, convertToSolids: ${convertToSolids}, - scope:'worker', - callback:(params)=>self.postMessage(params), + scope:'worker', + callback:(params)=>self.postMessage(params), render:${renderInWorker} }) self.addEventListener('message', (e)=>worker.postMessage(e.data)) @@ -602,7 +601,7 @@ self.addEventListener('message', (e)=>worker.postMessage(e.data)) callback({action:'workerInit',worker:'main'}) } - if(canvas){ + if(canvas){ // redirect 'render' messages to renderWorker sendToRender = (params, transfer)=>renderWorker.postMessage(params, transfer) let width = canvas.width = canvas.clientWidth @@ -613,7 +612,7 @@ self.addEventListener('message', (e)=>worker.postMessage(e.data)) } } - return { + return { updateSize, updateParams:({params={}})=>sendCmd({ action:'updateParams', worker:'script', params}), runScript: ({script,url=''})=>sendCmd({ action:'runScript', worker:'script', script, url}), diff --git a/app/web/src/components/EditorMenu/helpers.ts b/app/web/src/components/EditorMenu/helpers.ts index a0d97eb..2393ac7 100644 --- a/app/web/src/components/EditorMenu/helpers.ts +++ b/app/web/src/components/EditorMenu/helpers.ts @@ -59,7 +59,7 @@ export const makeStlDownloadHandler = (quality === 'high' || ideType === 'openscad') ) { saveFile(makeStlBlobFromGeo(geometry)) - } else if(ideType == 'jscad') { + } else if (ideType == 'jscad') { saveFile(makeStlBlobFromMesh(geometry)) } else { thunkDispatch((dispatch, getState) => { diff --git a/app/web/src/components/IdeEditor/IdeEditor.tsx b/app/web/src/components/IdeEditor/IdeEditor.tsx index 67abf8d..2a514f5 100644 --- a/app/web/src/components/IdeEditor/IdeEditor.tsx +++ b/app/web/src/components/IdeEditor/IdeEditor.tsx @@ -14,7 +14,7 @@ const IdeEditor = ({ Loading }) => { const [theme, setTheme] = useState('vs-dark') const saveCode = useSaveCode() - const ideTypeToLanguageMap: {[key in CadPackageType]: string} = { + const ideTypeToLanguageMap: { [key in CadPackageType]: string } = { cadquery: 'python', openscad: 'cpp', jscad: 'javascript', diff --git a/app/web/src/components/IdeViewer/IdeViewer.tsx b/app/web/src/components/IdeViewer/IdeViewer.tsx index fcd3efd..15e0175 100644 --- a/app/web/src/components/IdeViewer/IdeViewer.tsx +++ b/app/web/src/components/IdeViewer/IdeViewer.tsx @@ -25,18 +25,18 @@ function Asset({ geometry: incomingGeo }) { }, [incomingGeo]) if (!incomingGeo) return null - let groupData = incomingGeo.children ? incomingGeo : null - if(lastGroup && lastGroup != groupData){ + const groupData = incomingGeo.children ? incomingGeo : null + if (lastGroup && lastGroup != groupData) { state.scene.remove(lastGroup) - lastGroup.children.forEach(c=>c?.geometry?.dispose()) + lastGroup.children.forEach((c) => c?.geometry?.dispose()) // returning does not add the new group to the scene // there is probably some useRef magic that would make this work, but I don't have time to reseach it /// FIXME - do this properly with useRef or other react magic - if(groupData) state.scene.add(groupData) + if (groupData) state.scene.add(groupData) } lastGroup = groupData - if(groupData) return + if (groupData) return if (incomingGeo.children) return diff --git a/app/web/src/components/NavPlusButton/NavPlusButton.tsx b/app/web/src/components/NavPlusButton/NavPlusButton.tsx index 467902c..d17e1d2 100644 --- a/app/web/src/components/NavPlusButton/NavPlusButton.tsx +++ b/app/web/src/components/NavPlusButton/NavPlusButton.tsx @@ -3,7 +3,6 @@ import Svg from 'src/components/Svg/Svg' import { Popover } from '@headlessui/react' import type { CadPackage } from 'src/helpers/hooks/useIdeState' - const menuOptions: { name: string sub: string diff --git a/app/web/src/helpers/cadPackages/CSGToBuffers.js b/app/web/src/helpers/cadPackages/CSGToBuffers.js index c9b5bbb..72ca689 100644 --- a/app/web/src/helpers/cadPackages/CSGToBuffers.js +++ b/app/web/src/helpers/cadPackages/CSGToBuffers.js @@ -1,106 +1,109 @@ -const setPoints = (points, p, i)=>{ - points[i++] = p[0] - points[i++] = p[1] - points[i++] = p[2] || 0 +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) { + let 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) + 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; i < len; i++) { + vertices.set(arr[i], vertOffset) + + indices[indOffset++] = first + indices[indOffset++] = first + i - 1 + indices[indOffset++] = first + i + + vertOffset += 3 + posOffset += 1 } - 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 ) - } - return {vertices, type:'line'} -} - -function CSG2LineSegmentsVertices(csg){ - let vLen = csg.sides.length * 6 - - var vertices = new Float32Array(vLen) - csg.sides.forEach((side,idx)=>{ - let i = idx * 6 - setPoints(vertices, side[0], i) - setPoints(vertices, side[1], i+3) - }) - return {vertices, type:'lines'} + 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 CSGCached(func, data, cacheKey, transferable){ - cacheKey = cacheKey || data +function CSG2LineSegmentsVertices(csg) { + let vLen = csg.sides.length * 6 - let geo = CSGToBuffers.cache.get(cacheKey) - if(geo) return geo - - geo = func(data) - - // fill transferable array for postMessage optimization - if(transferable){ - const {vertices, indices} = geo - transferable.push(vertices) - if(indices) transferable.push(indices) - } - - CSGToBuffers.cache.set(cacheKey, geo) - return geo + var vertices = new Float32Array(vLen) + csg.sides.forEach((side, idx) => { + let i = idx * 6 + setPoints(vertices, side[0], i) + setPoints(vertices, side[1], i + 3) + }) + return { vertices, type: 'lines' } } -function CSGToBuffers(csg, transferable){ - let obj +function CSGCached(func, data, cacheKey, transferable) { + cacheKey = cacheKey || data - 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) + let geo = CSGToBuffers.cache.get(cacheKey) + if (geo) return geo - return obj + geo = func(data) + + // fill transferable array for postMessage optimization + if (transferable) { + const { vertices, indices } = geo + transferable.push(vertices) + if (indices) transferable.push(indices) + } + + CSGToBuffers.cache.set(cacheKey, geo) + return geo +} + +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) + + return obj +} +CSGToBuffers.clearCache = () => { + CSGToBuffers.cache = new WeakMap() } -CSGToBuffers.clearCache = ()=>{CSGToBuffers.cache = new WeakMap()} CSGToBuffers.clearCache() -export {CSGToBuffers} \ No newline at end of file +export { CSGToBuffers } diff --git a/app/web/src/helpers/cadPackages/index.ts b/app/web/src/helpers/cadPackages/index.ts index 4b880c9..145717e 100644 --- a/app/web/src/helpers/cadPackages/index.ts +++ b/app/web/src/helpers/cadPackages/index.ts @@ -5,7 +5,7 @@ import openscad from './openScadController' import cadquery from './cadQueryController' import jscad from './jsCadController' -export const cadPackages: {[key in CadPackage]: DefaultKernelExport} = { +export const cadPackages: { [key in CadPackage]: DefaultKernelExport } = { openscad, cadquery, jscad, diff --git a/app/web/src/helpers/cadPackages/jsCadController.ts b/app/web/src/helpers/cadPackages/jsCadController.ts index dd686f9..05c98a9 100644 --- a/app/web/src/helpers/cadPackages/jsCadController.ts +++ b/app/web/src/helpers/cadPackages/jsCadController.ts @@ -1,46 +1,73 @@ -import { RenderArgs, DefaultKernelExport, createUnhealthyResponse, createHealthyResponse } from './common' -import { MeshPhongMaterial, LineBasicMaterial, BufferGeometry , BufferAttribute, Line, LineSegments, Color, Mesh, Group} from 'three/build/three.module' - +import { + RenderArgs, + DefaultKernelExport, + createUnhealthyResponse, + createHealthyResponse, +} from './common' +import { + MeshPhongMaterial, + LineBasicMaterial, + BufferGeometry, + BufferAttribute, + Line, + LineSegments, + Color, + Mesh, + Group, +} from 'three/build/three.module' 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 + 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 +function CSG2Object3D(obj) { + const { vertices, indices, color, transforms } = obj - let materialDef = materials[obj.type] - if(!materialDef){ - console.error('Can not hangle object type: '+obj.type, obj) - return null + 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){ - let c = color - material = new 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}) - } + let material = materialDef.def + if (color) { + const c = color + material = new 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, + }) + } - var geo = new BufferGeometry() - geo.setAttribute('position', new BufferAttribute(vertices,3)) + const geo = new BufferGeometry() + geo.setAttribute('position', new BufferAttribute(vertices, 3)) - var mesh; - switch(obj.type){ - case 'mesh': geo.setIndex(new BufferAttribute(indices,1)); mesh = new THREE.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 mesh + switch (obj.type) { + case 'mesh': + geo.setIndex(new BufferAttribute(indices, 1)) + mesh = new THREE.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 @@ -48,8 +75,8 @@ const scriptUrl = '/demo-worker.js' let resolveReference = null let response = null -const callResolve = ()=>{ - if(resolveReference) resolveReference() +const callResolve = () => { + if (resolveReference) resolveReference() resolveReference } @@ -57,10 +84,9 @@ export const render: DefaultKernelExport['render'] = async ({ code, settings, }: RenderArgs) => { - - if(!scriptWorker){ + if (!scriptWorker) { const baseURI = document.baseURI.toString() - const script =`let baseURI = '${baseURI}' + const script = `let baseURI = '${baseURI}' importScripts(new URL('${scriptUrl}',baseURI)) let worker = jscadWorker({ baseURI: baseURI, @@ -70,18 +96,21 @@ let worker = jscadWorker({ }) self.addEventListener('message', (e)=>worker.postMessage(e.data)) ` - let blob = new Blob([script],{type: 'text/javascript'}) + const blob = new Blob([script], { type: 'text/javascript' }) scriptWorker = new Worker(window.URL.createObjectURL(blob)) - scriptWorker.addEventListener('message',(e)=>{ + scriptWorker.addEventListener('message', (e) => { console.log('message from worker', e.data) - let data = e.data - if(data.action == 'entities'){ - if(data.error){ - response = createUnhealthyResponse( new Date(),data.error ) - }else{ - let group = new Group() - data.entities.map(CSG2Object3D).filter(o=>o).forEach(o=>group.add(o)) - response = createHealthyResponse( { + const data = e.data + if (data.action == 'entities') { + if (data.error) { + response = createUnhealthyResponse(new Date(), data.error) + } else { + const group = new Group() + data.entities + .map(CSG2Object3D) + .filter((o) => o) + .forEach((o) => group.add(o)) + response = createHealthyResponse({ type: 'geometry', data: group, consoleMessage: data.scriptStats, @@ -94,11 +123,16 @@ self.addEventListener('message', (e)=>worker.postMessage(e.data)) callResolve() response = null - scriptWorker.postMessage({action:'init', baseURI, alias:[]}) + scriptWorker.postMessage({ action: 'init', baseURI, alias: [] }) } - scriptWorker.postMessage({action:'runScript', worker:'script', script:code, url:'jscad_script' }) + scriptWorker.postMessage({ + action: 'runScript', + worker: 'script', + script: code, + url: 'jscad_script', + }) - let waitResult = new Promise(resolve=>{ + const waitResult = new Promise((resolve) => { resolveReference = resolve }) diff --git a/app/web/src/helpers/hooks/useIdeState.ts b/app/web/src/helpers/hooks/useIdeState.ts index 0acd810..9b671be 100644 --- a/app/web/src/helpers/hooks/useIdeState.ts +++ b/app/web/src/helpers/hooks/useIdeState.ts @@ -11,7 +11,7 @@ function withThunk(dispatch, getState) { export type CadPackage = 'openscad' | 'cadquery' | 'jscad' -const initCodeMap: {[key in CadPackage]: string} = { +const initCodeMap: { [key in CadPackage]: string } = { openscad: `// involute donut // ^ first comment is used for download title (i.e "involute-donut.stl") @@ -42,7 +42,7 @@ result = (cq.Workplane().circle(diam).extrude(20.0) show_object(result) `, -jscad: ` + jscad: ` const { booleans, colors, primitives } = require('@jscad/modeling') // modeling comes from the included MODELING library const { intersect, subtract } = booleans @@ -68,7 +68,7 @@ const main = ({scale=1}) => { return [transpCube, star2D, line2D, ...logo] } module.exports = {main} -` +`, } const codeStorageKey = 'Last-editor-code'