Merge branch 'main' into keyboard-shortcuts
This commit is contained in:
@@ -13,6 +13,9 @@ module.exports = {
|
||||
borderRadius: {
|
||||
half: '50%',
|
||||
},
|
||||
boxShadow: {
|
||||
ch: '0 4px 4px 0 rgba(0, 0, 0, 0.25), 0 4px 4px 0 rgba(13, 13, 19, 0.15)',
|
||||
},
|
||||
colors: {
|
||||
'ch-gray': {
|
||||
900: '#0D0D13',
|
||||
@@ -21,6 +24,7 @@ module.exports = {
|
||||
760: '#232532',
|
||||
700: '#2A3038',
|
||||
600: '#3B3E4B',
|
||||
550: '#63636A',
|
||||
500: '#9F9FB4',
|
||||
400: '#A4A4B0',
|
||||
300: '#CFCFD8',
|
||||
@@ -35,9 +39,10 @@ module.exports = {
|
||||
200: '#DBDBEC',
|
||||
},
|
||||
'ch-blue': {
|
||||
600: '#79B2F8',
|
||||
700: '#08466F',
|
||||
650: '#0958BA',
|
||||
500: '5098F1',
|
||||
300: '#08466F'
|
||||
400: '#79B2F8',
|
||||
},
|
||||
'ch-pink': {
|
||||
800: '#93064F',
|
||||
|
||||
10
app/web/config/worker-loader.d.ts
vendored
Normal file
10
app/web/config/worker-loader.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
declare module "worker-loader!*" {
|
||||
// You need to change `Worker`, if you specified a different value for the `workerType` option
|
||||
class WebpackWorker extends Worker {
|
||||
constructor();
|
||||
}
|
||||
|
||||
// Uncomment this if you set the `esModule` option to `false`
|
||||
// export = WebpackWorker;
|
||||
export default WebpackWorker;
|
||||
}
|
||||
@@ -47,7 +47,8 @@
|
||||
"react-tabs": "^3.2.2",
|
||||
"rich-markdown-editor": "^11.0.2",
|
||||
"styled-components": "^5.2.0",
|
||||
"three": "^0.130.1"
|
||||
"three": "^0.130.1",
|
||||
"worker-loader": "^3.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.170",
|
||||
|
||||
@@ -1,786 +0,0 @@
|
||||
(function(f) {
|
||||
if (typeof exports === "object" && typeof module !== "undefined") {
|
||||
module.exports = f()
|
||||
} else if (typeof define === "function" && define.amd) {
|
||||
define([], f)
|
||||
} else {
|
||||
var g;
|
||||
if (typeof window !== "undefined") {
|
||||
g = window
|
||||
} else if (typeof global !== "undefined") {
|
||||
g = global
|
||||
} else if (typeof self !== "undefined") {
|
||||
g = self
|
||||
} else {
|
||||
g = this
|
||||
}
|
||||
g.jscadWorker = f()
|
||||
}
|
||||
})(function() {
|
||||
// multi purpose module
|
||||
|
||||
|
||||
|
||||
const setPoints = (points, p, i)=>{
|
||||
points[i++] = p[0]
|
||||
points[i++] = p[1]
|
||||
points[i++] = p[2] || 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)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
return {vertices, indices, type:'mesh'}
|
||||
}
|
||||
|
||||
|
||||
function CSG2LineVertices(csg){
|
||||
let vLen = csg.points.length * 3
|
||||
if(csg.isClosed) vLen += 3
|
||||
|
||||
var 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){
|
||||
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'}
|
||||
|
||||
}
|
||||
|
||||
function CSGCached(func, data, cacheKey, transferable){
|
||||
cacheKey = cacheKey || data
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
||||
|
||||
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 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)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
require.cache = {}
|
||||
require.alias = {}
|
||||
|
||||
|
||||
const initCanvas = (canvas, callback)=>{
|
||||
|
||||
// convert HTML events (mouse movement) to viewer changes
|
||||
let lastX = 0
|
||||
let lastY = 0
|
||||
|
||||
let pointerDown = false
|
||||
|
||||
const moveHandler = (ev) => {
|
||||
if(!pointerDown) return
|
||||
const cmd = {
|
||||
worker: 'render',
|
||||
dx: lastX - ev.pageX,
|
||||
dy: ev.pageY - lastY
|
||||
}
|
||||
|
||||
const shiftKey = (ev.shiftKey === true) || (ev.touches && ev.touches.length > 2)
|
||||
cmd.action = shiftKey ? 'pan':'rotate'
|
||||
callback(cmd)
|
||||
|
||||
lastX = ev.pageX
|
||||
lastY = ev.pageY
|
||||
|
||||
ev.preventDefault()
|
||||
}
|
||||
const downHandler = (ev) => {
|
||||
pointerDown = true
|
||||
lastX = ev.pageX
|
||||
lastY = ev.pageY
|
||||
canvas.setPointerCapture(ev.pointerId)
|
||||
ev.preventDefault()
|
||||
}
|
||||
|
||||
const upHandler = (ev) => {
|
||||
pointerDown = false
|
||||
canvas.releasePointerCapture(ev.pointerId)
|
||||
ev.preventDefault()
|
||||
}
|
||||
|
||||
const wheelHandler = (ev) => {
|
||||
callback({action:'zoom', dy:ev.deltaY, worker: 'render'})
|
||||
ev.preventDefault()
|
||||
}
|
||||
|
||||
canvas.onpointermove = moveHandler
|
||||
canvas.onpointerdown = downHandler
|
||||
canvas.onpointerup = upHandler
|
||||
canvas.onwheel = wheelHandler
|
||||
}
|
||||
|
||||
const cmdHandler = (handlers)=>(cmd)=>{
|
||||
const fn = handlers[cmd.action]
|
||||
if (!fn) throw new Error('no handler for type: ' + cmd.action)
|
||||
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;
|
||||
}
|
||||
|
||||
let groupIndex = 1
|
||||
const defs = []
|
||||
|
||||
while(i<lines.length){
|
||||
line = lines[i].code
|
||||
lineNum = lines[i].line
|
||||
next = lines[i+1] ? lines[i+1].code : ''
|
||||
if(line[0] === '}') 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++
|
||||
}
|
||||
|
||||
return defs
|
||||
}
|
||||
|
||||
function parseOne(comment, code, line1, line2){
|
||||
const {caption, options} = parseComment(comment, line1)
|
||||
let def = {caption, ...parseDef(code, line2)}
|
||||
def.caption = def.caption || def.name
|
||||
if(options){
|
||||
def = {...def, ...options}
|
||||
if(def.type === 'checkbox' && def.hasOwnProperty('initial')) def.checked = true
|
||||
if(def.type === 'slider'){
|
||||
if(def.min === undefined){
|
||||
def.min=0
|
||||
}
|
||||
if(def.max === undefined){
|
||||
def.max = def.initial * 2 ||100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
function parseComment(comment, line){
|
||||
const prefix = comment.substring(0,2)
|
||||
if(prefix === '//') comment = comment.substring(2)
|
||||
if(prefix === '/*') comment = comment.substring(2, comment.length-2)
|
||||
|
||||
comment = comment.trim()
|
||||
|
||||
const ret = {}
|
||||
const idx = comment.indexOf('{')
|
||||
if(idx !== -1){
|
||||
try{
|
||||
ret.options = eval('('+comment.substring(idx)+')')
|
||||
}catch(e){
|
||||
console.log('Error in line '+line);
|
||||
console.log(comment);
|
||||
throw e
|
||||
}
|
||||
comment = comment.substring(0,idx).trim()
|
||||
}
|
||||
|
||||
ret.caption = comment
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
function parseDef(code, line){
|
||||
if(code[code.length-1] == ',') code = code.substring(0,code.length-1).trim()
|
||||
let idx = code.indexOf('=')
|
||||
|
||||
if(idx == -1) idx = code.indexOf(':')
|
||||
|
||||
if(idx == -1){
|
||||
return {name:code, type:'text'}
|
||||
}else{
|
||||
let initial = code.substring(idx+1).trim()
|
||||
|
||||
const ret = {type:'text', name:code.substring(0,idx).trim()}
|
||||
|
||||
if(initial === 'true' || initial === 'false'){
|
||||
ret.type = 'checkbox'
|
||||
ret.checked = initial === 'true'
|
||||
|
||||
}else if(/^[0-9]+$/.test(initial)){
|
||||
ret.type = 'int'
|
||||
ret.initial = parseFloat(initial)
|
||||
|
||||
}else if(/^[0-9]+\.[0-9]+$/.test(initial)){
|
||||
ret.type = 'number'
|
||||
ret.initial = parseFloat(initial)
|
||||
}else{
|
||||
try {
|
||||
ret.initial = eval(initial)
|
||||
} catch (e) {
|
||||
console.log('Error in line '+line);
|
||||
console.log(code);
|
||||
console.log('problem evaluating inital value:', initial)
|
||||
e = new EvalError(e.message, 'code', line);
|
||||
e.lineNumber = line
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const makeScriptWorker = ({callback, convertToSolids})=>{
|
||||
let workerBaseURI, onInit
|
||||
|
||||
|
||||
function runMain(params={}){
|
||||
let time = Date.now()
|
||||
let solids
|
||||
let transfer = []
|
||||
try{
|
||||
let tmp = main(params)
|
||||
solids = []
|
||||
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)
|
||||
return
|
||||
}
|
||||
let solidsTime = Date.now() - time
|
||||
scriptStats = `generate solids ${solidsTime}ms`
|
||||
|
||||
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'){
|
||||
const { entitiesFromSolids } = require('@jscad/regl-renderer')
|
||||
time = Date.now()
|
||||
entities = entitiesFromSolids({}, solids)
|
||||
scriptStats += ` convert to entities ${Date.now()-time}ms`
|
||||
}else{
|
||||
entities = solids
|
||||
}
|
||||
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})
|
||||
}
|
||||
let script_module
|
||||
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){
|
||||
paramsDef.push(p)
|
||||
}else{
|
||||
paramsDef.splice(idx,1,p)
|
||||
}
|
||||
})
|
||||
}
|
||||
if(paramsDef.length) callback({action:'parameterDefinitions', worker:'main', data:paramsDef})
|
||||
|
||||
runMain(params)
|
||||
},
|
||||
updateParams: ({params={}})=>{
|
||||
runMain(params)
|
||||
},
|
||||
init: (params)=>{
|
||||
let {baseURI, alias=[]} = params
|
||||
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=>{
|
||||
require.alias[a] = orig
|
||||
if(a.toLowerCase().substr(-3)!=='.js') require.alias[a+'.js'] = orig
|
||||
})
|
||||
})
|
||||
initialized = true
|
||||
if(onInit) onInit()
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
// called from outside to pass mesasges into worker
|
||||
postMessage: cmdHandler(handlers),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** Make render worker */
|
||||
|
||||
const makeRenderWorker = ()=>{
|
||||
let perspectiveCamera
|
||||
const state = {}
|
||||
|
||||
const rotateSpeed = 0.002
|
||||
const panSpeed = 1
|
||||
const zoomSpeed = 0.08
|
||||
let rotateDelta = [0, 0]
|
||||
let panDelta = [0, 0]
|
||||
let zoomDelta = 0
|
||||
let updateRender = true
|
||||
let orbitControls, renderOptions, gridOptions, axisOptions, renderer
|
||||
|
||||
let entities = []
|
||||
|
||||
function createContext (canvas, contextAttributes) {
|
||||
function get (type) {
|
||||
try {
|
||||
return {gl:canvas.getContext(type, contextAttributes), type}
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return (
|
||||
get('webgl2') ||
|
||||
get('webgl') ||
|
||||
get('experimental-webgl') ||
|
||||
get('webgl-experimental')
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
resize({ width:canvas.width, height:canvas.height })
|
||||
|
||||
state.controls = orbitControls.defaults
|
||||
|
||||
const {gl, type} = createContext(canvas)
|
||||
// prepare the renderer
|
||||
const setupOptions = {
|
||||
glOptions: {gl}
|
||||
}
|
||||
if(type == 'webgl'){
|
||||
setupOptions.glOptions.optionalExtensions = ['oes_element_index_uint']
|
||||
}
|
||||
renderer = prepareRender(setupOptions)
|
||||
|
||||
gridOptions = {
|
||||
visuals: {
|
||||
drawCmd: 'drawGrid',
|
||||
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
|
||||
},
|
||||
size: grid.size || [200, 200],
|
||||
ticks: grid.ticks || [10, 1]
|
||||
}
|
||||
|
||||
axisOptions = {
|
||||
visuals: {
|
||||
drawCmd: 'drawAxis',
|
||||
show: axis.show || axis.show === undefined
|
||||
},
|
||||
size: axis.size || 100,
|
||||
}
|
||||
|
||||
// assemble the options for rendering
|
||||
renderOptions = {
|
||||
camera: state.camera,
|
||||
drawCommands: {
|
||||
drawAxis: drawCommands.drawAxis,
|
||||
drawGrid: drawCommands.drawGrid,
|
||||
drawLines: drawCommands.drawLines,
|
||||
drawMesh: drawCommands.drawMesh
|
||||
},
|
||||
// define the visual content
|
||||
entities: [
|
||||
gridOptions,
|
||||
axisOptions,
|
||||
...entities
|
||||
]
|
||||
}
|
||||
// the heart of rendering, as themes, controls, etc change
|
||||
|
||||
updateView()
|
||||
}
|
||||
|
||||
let renderTimer
|
||||
const tmFunc = typeof requestAnimationFrame === 'undefined' ? setTimeout : requestAnimationFrame
|
||||
|
||||
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)
|
||||
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)
|
||||
state.controls = { ...state.controls, ...updated.controls }
|
||||
panDelta = [0, 0]
|
||||
state.camera.position = updated.camera.position
|
||||
state.camera.target = updated.camera.target
|
||||
}
|
||||
|
||||
if (zoomDelta) {
|
||||
const updated = orbitControls.zoom({ controls:state.controls, camera:state.camera, speed: zoomSpeed }, zoomDelta)
|
||||
state.controls = { ...state.controls, ...updated.controls }
|
||||
zoomDelta = 0
|
||||
}
|
||||
}
|
||||
|
||||
const updateAndRender = (timestamp) => {
|
||||
renderTimer = null
|
||||
doRotatePanZoom()
|
||||
|
||||
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
|
||||
|
||||
state.camera.position = updates.camera.position
|
||||
perspectiveCamera.update(state.camera)
|
||||
renderOptions.entities = [
|
||||
gridOptions,
|
||||
axisOptions,
|
||||
...entities
|
||||
]
|
||||
let time = Date.now()
|
||||
renderer(renderOptions)
|
||||
if(updateRender){
|
||||
updateRender = '';
|
||||
}
|
||||
}
|
||||
|
||||
function resize({width,height}){
|
||||
state.canvas.width = width
|
||||
state.canvas.height = height
|
||||
perspectiveCamera.setProjection(state.camera, state.camera, { width, height })
|
||||
perspectiveCamera.update(state.camera, state.camera)
|
||||
updateView()
|
||||
}
|
||||
|
||||
const handlers = {
|
||||
pan: ({dx,dy})=>{
|
||||
panDelta[0] += dx
|
||||
panDelta[1] += dy
|
||||
updateView()
|
||||
},
|
||||
rotate: ({dx,dy})=>{
|
||||
rotateDelta[0] -= dx
|
||||
rotateDelta[1] -= dy
|
||||
updateView()
|
||||
},
|
||||
zoom: ({dy})=>{
|
||||
zoomDelta += dy
|
||||
updateView()
|
||||
},
|
||||
resize,
|
||||
entities: (params)=>{
|
||||
entities = params.entities
|
||||
updateRender = params.scriptStats
|
||||
updateView()
|
||||
},
|
||||
init: (params)=>{
|
||||
if(params.canvas) startRenderer(params)
|
||||
initialized = true
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
// called from outside to pass mesasges into worker
|
||||
postMessage: cmdHandler(handlers),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return (params)=>{
|
||||
let { canvas, baseURI=(typeof document === 'undefined') ? '':document.location.toString(), scope='main', renderInWorker, render, callback=()=>{}, scriptUrl='demo-worker.js', alias, convertToSolids=false } = params
|
||||
// by default 'render' messages go outside of this instance (result of modeling)
|
||||
let sendToRender = callback
|
||||
let scriptWorker, renderWorker
|
||||
workerBaseURI = baseURI
|
||||
|
||||
const sendCmd = (params, transfer)=>{
|
||||
if(params.worker === 'render')
|
||||
sendToRender(params, transfer)
|
||||
else if(params.worker === 'script')
|
||||
scriptWorker.postMessage(params, transfer)
|
||||
else{
|
||||
// parameter definitions will arrive from scriptWorker
|
||||
callback(params, transfer)
|
||||
}
|
||||
}
|
||||
|
||||
const updateSize = function({width,height}){
|
||||
sendCmd({ action:'resize', worker:'render', width: canvas.offsetWidth, height: canvas.offsetHeight})
|
||||
}
|
||||
|
||||
|
||||
renderInWorker = !!(canvas && renderInWorker && canvas.transferControlToOffscreen)
|
||||
const makeRenderWorkerHere = (scope === 'main' && canvas && !renderInWorker) || (scope === 'worker' && render)
|
||||
// worker is in current thread
|
||||
if(makeRenderWorkerHere){
|
||||
renderWorker = makeRenderWorker({callback:sendCmd})
|
||||
sendToRender = (params, transfer)=>renderWorker.postMessage(params, transfer)
|
||||
}
|
||||
|
||||
if(scope === 'main'){
|
||||
// let extraScript = renderInWorker ? `,'https://unpkg.com/@jscad/regl-renderer'`:''
|
||||
let script =`let baseURI = '${baseURI}'
|
||||
importScripts(new URL('${scriptUrl}',baseURI))
|
||||
let worker = jscadWorker({
|
||||
baseURI: baseURI,
|
||||
convertToSolids: ${convertToSolids},
|
||||
scope:'worker',
|
||||
callback:(params)=>self.postMessage(params),
|
||||
render:${renderInWorker}
|
||||
})
|
||||
self.addEventListener('message', (e)=>worker.postMessage(e.data))
|
||||
`
|
||||
let blob = new Blob([script],{type: 'text/javascript'})
|
||||
scriptWorker = new Worker(window.URL.createObjectURL(blob))
|
||||
scriptWorker.addEventListener('message',(e)=>sendCmd(e.data))
|
||||
scriptWorker.postMessage({action:'init', baseURI, alias})
|
||||
if(renderInWorker) renderWorker = scriptWorker
|
||||
|
||||
if(canvas){
|
||||
initCanvas(canvas, sendCmd)
|
||||
window.addEventListener('resize',updateSize)
|
||||
}
|
||||
}else{
|
||||
scriptWorker = makeScriptWorker({callback:sendCmd, convertToSolids})
|
||||
callback({action:'workerInit',worker:'main'})
|
||||
}
|
||||
|
||||
if(canvas){
|
||||
// redirect 'render' messages to renderWorker
|
||||
sendToRender = (params, transfer)=>renderWorker.postMessage(params, transfer)
|
||||
let width = canvas.width = canvas.clientWidth
|
||||
let height = canvas.height = canvas.clientHeight
|
||||
if(scope == 'main'){
|
||||
const offscreen = renderInWorker ? canvas.transferControlToOffscreen() : canvas
|
||||
renderWorker.postMessage({action:'init', worker:'render', canvas:offscreen, width, height}, [offscreen])
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
updateSize,
|
||||
updateParams:({params={}})=>sendCmd({ action:'updateParams', worker:'script', params}),
|
||||
runScript: ({script,url=''})=>sendCmd({ action:'runScript', worker:'script', script, url}),
|
||||
postMessage: sendCmd,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// multi purpose module
|
||||
});
|
||||
@@ -1,11 +1,21 @@
|
||||
import { ideTypeNameMap } from 'src/helpers/hooks/useIdeContext'
|
||||
export type CadPackageType = 'openscad' | 'cadquery' | 'jscad'
|
||||
|
||||
interface CadPackageProps {
|
||||
cadPackage: string
|
||||
className?: string
|
||||
export const ideTypeNameMap = {
|
||||
openscad: 'OpenSCAD',
|
||||
cadquery: 'CadQuery',
|
||||
}
|
||||
|
||||
const CadPackage = ({ cadPackage, className = '' }: CadPackageProps) => {
|
||||
interface CadPackageProps {
|
||||
cadPackage: CadPackageType
|
||||
className?: string
|
||||
dotClass?: string
|
||||
}
|
||||
|
||||
const CadPackage = ({
|
||||
cadPackage,
|
||||
className = '',
|
||||
dotClass = 'w-5 h-5',
|
||||
}: CadPackageProps) => {
|
||||
const cadName = ideTypeNameMap[cadPackage] || ''
|
||||
const isOpenScad = cadPackage === 'openscad'
|
||||
const isCadQuery = cadPackage === 'cadquery'
|
||||
@@ -14,13 +24,13 @@ const CadPackage = ({ cadPackage, className = '' }: CadPackageProps) => {
|
||||
className={
|
||||
`grid grid-flow-col-dense items-center gap-2 cursor-default text-gray-100 ${
|
||||
isOpenScad && 'bg-yellow-800'
|
||||
} ${isCadQuery && 'bg-ch-blue-300'} bg-opacity-30 ` + className
|
||||
} ${isCadQuery && 'bg-ch-blue-700'} bg-opacity-30 ` + className
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={`${isOpenScad && 'bg-yellow-200'} ${
|
||||
isCadQuery && 'bg-blue-800'
|
||||
} w-5 h-5 rounded-full`}
|
||||
} ${dotClass} rounded-full`}
|
||||
/>
|
||||
<div>{cadName}</div>
|
||||
</div>
|
||||
|
||||
@@ -222,7 +222,7 @@ function ChoiceParam({
|
||||
className={({ active }) =>
|
||||
`${
|
||||
active
|
||||
? 'text-ch-blue-600 bg-ch-gray-700'
|
||||
? 'text-ch-blue-400 bg-ch-gray-700'
|
||||
: 'text-ch-gray-300'
|
||||
}
|
||||
cursor-default select-none relative py-2 pl-10 pr-4`
|
||||
@@ -241,7 +241,7 @@ function ChoiceParam({
|
||||
{selected ? (
|
||||
<span
|
||||
className={`${
|
||||
active ? 'text-ch-blue-600' : 'text-ch-gray-300'
|
||||
active ? 'text-ch-blue-400' : 'text-ch-gray-300'
|
||||
}
|
||||
absolute inset-y-0 left-0 flex items-center pl-3`}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { useIdeContext, ideTypeNameMap } from 'src/helpers/hooks/useIdeContext'
|
||||
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
||||
import { ideTypeNameMap } from 'src/components/CadPackage/CadPackage'
|
||||
import OutBound from 'src/components/OutBound/OutBound'
|
||||
import { prepareEncodedUrl, makeExternalUrl } from './helpers'
|
||||
import { copyTextToClipboard } from 'src/helpers/clipboard'
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import Footer from './Footer'
|
||||
|
||||
export const generated = () => {
|
||||
return <Footer />
|
||||
}
|
||||
|
||||
export default { title: 'Components/Footer' }
|
||||
@@ -1,11 +0,0 @@
|
||||
import { render } from '@redwoodjs/testing'
|
||||
|
||||
import Footer from './Footer'
|
||||
|
||||
describe('Footer', () => {
|
||||
it('renders successfully', () => {
|
||||
expect(() => {
|
||||
render(<Footer />)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
@@ -3,7 +3,7 @@ import OutBound from 'src/components/OutBound'
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<div className="bg-indigo-900 text-indigo-200 font-roboto mt-20 text-sm">
|
||||
<div className="bg-indigo-900 text-indigo-200 font-roboto text-sm">
|
||||
<div className="flex h-16 md:justify-end items-center mx-2 md:mx-16 flex-wrap">
|
||||
<OutBound className="mr-8" to="https://github.com/Irev-Dev/cadhub">
|
||||
Github
|
||||
@@ -18,7 +18,7 @@ const IdeConsole = () => {
|
||||
className="font-mono text-sm text-gray-400"
|
||||
key={`${message} ${index}`}
|
||||
>
|
||||
<div className="text-xs font-bold pt-2 text-ch-blue-600">
|
||||
<div className="text-xs font-bold pt-2 text-ch-blue-400">
|
||||
{time?.toLocaleString()}
|
||||
</div>
|
||||
<div className={(type === 'error' ? 'text-red-400' : '') + ' pl-4'}>
|
||||
|
||||
@@ -4,8 +4,7 @@ import { makeCodeStoreKey, requestRender } from 'src/helpers/hooks/useIdeState'
|
||||
import Editor, { useMonaco } from '@monaco-editor/react'
|
||||
import { theme } from 'src/../config/tailwind.config'
|
||||
import { useSaveCode } from 'src/components/IdeWrapper/useSaveCode'
|
||||
import type { CadPackage as CadPackageType } from 'src/helpers/hooks/useIdeState'
|
||||
import CadPackage from '../CadPackage/CadPackage'
|
||||
import type { CadPackageType } from 'src/components/CadPackage/CadPackage'
|
||||
|
||||
const colors = theme.extend.colors
|
||||
|
||||
|
||||
@@ -102,9 +102,9 @@ const IdeHeader = ({
|
||||
<TopButton
|
||||
onClick={onClick}
|
||||
name="Save Project Image"
|
||||
className=" bg-ch-gray-300 bg-opacity-70 hover:bg-opacity-90 text-ch-gray-900"
|
||||
className=" bg-ch-blue-650 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||
>
|
||||
<Svg name="camera" className="w-6 h-6" />
|
||||
<Svg name="camera" className="w-6 h-6 text-ch-blue-400" />
|
||||
</TopButton>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -4,17 +4,17 @@ import Svg from 'src/components/Svg/Svg'
|
||||
const IdeSideBar = () => {
|
||||
return (
|
||||
<div className="h-full flex flex-col justify-between">
|
||||
<div className="w-16 h-16 flex items-center justify-center bg-ch-gray-900">
|
||||
<div className="w-14 h-16 flex items-center justify-center bg-ch-gray-900">
|
||||
<Link to={routes.home()}>
|
||||
<Svg className="w-12" name="favicon" />
|
||||
<Svg className="w-12 p-0.5" name="favicon" />
|
||||
</Link>
|
||||
</div>
|
||||
<button
|
||||
className="text-gray-300 p-2 pb-4 flex justify-center cursor-not-allowed"
|
||||
className="text-gray-300 p-3 pb-6 flex justify-center cursor-not-allowed"
|
||||
aria-label="IDE settings"
|
||||
disabled
|
||||
>
|
||||
<Svg name="big-gear" />
|
||||
<Svg name="gear" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Link, routes } from '@redwoodjs/router'
|
||||
import Svg from 'src/components/Svg/Svg'
|
||||
import { Popover } from '@headlessui/react'
|
||||
import type { CadPackage } from 'src/helpers/hooks/useIdeState'
|
||||
import type { CadPackageType } from 'src/components/CadPackage/CadPackage'
|
||||
|
||||
const menuOptions: {
|
||||
name: string
|
||||
sub: string
|
||||
ideType: CadPackage
|
||||
ideType: CadPackageType
|
||||
}[] = [
|
||||
{
|
||||
name: 'OpenSCAD',
|
||||
@@ -20,7 +20,7 @@ const menuOptions: {
|
||||
const NavPlusButton: React.FC = () => {
|
||||
return (
|
||||
<Popover className="relative outline-none w-full h-full">
|
||||
<Popover.Button className="h-full w-full outline-none">
|
||||
<Popover.Button className="h-full w-full outline-none hover:bg-ch-gray-550 border rounded-full">
|
||||
<Svg name="plus" className="text-gray-200" />
|
||||
</Popover.Button>
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ const PanelToolbar = ({
|
||||
aria-label={`${panelName} settings`}
|
||||
disabled
|
||||
>
|
||||
<Svg name="gear" className="w-7 p-px" />
|
||||
<Svg name="gear" className="w-7 p-0.5" />
|
||||
</button>
|
||||
{mosaicWindowActions.connectDragSource(
|
||||
<div className=" text-ch-gray-760 bg-ch-gray-300 cursor-grab px-2 h-full flex items-center">
|
||||
<div className=" text-ch-gray-760 bg-ch-gray-300 cursor-grab px-1.5 h-full flex items-center">
|
||||
<Svg name="drag-grid" className="w-4 p-px" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -36,7 +36,7 @@ const KeyValue = ({
|
||||
if (!children || hide) return null
|
||||
return (
|
||||
<div>
|
||||
<div className="text-ch-blue-600 font-fira-code flex text-sm whitespace-nowrap">
|
||||
<div className="text-ch-blue-400 font-fira-code flex text-sm whitespace-nowrap">
|
||||
{keyName}
|
||||
{canEdit &&
|
||||
(isEditable ? (
|
||||
@@ -246,7 +246,7 @@ const ProjectProfile = ({
|
||||
/>
|
||||
{user?.userName}
|
||||
</Link>
|
||||
<div className="font-fira-code text-ch-blue-600 flex items-center">
|
||||
<div className="font-fira-code text-ch-blue-400 flex items-center">
|
||||
{new Date(createdAt).toDateString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useMemo } from 'react'
|
||||
import { Link, routes } from '@redwoodjs/router'
|
||||
import Svg from 'src/components/Svg/Svg'
|
||||
import CadPackage from 'src/components/CadPackage/CadPackage'
|
||||
|
||||
import { countEmotes } from 'src/helpers/emote'
|
||||
import ImageUploader from 'src/components/ImageUploader'
|
||||
@@ -11,6 +13,8 @@ const ProjectsList = ({
|
||||
// temporary filtering projects that don't have images until some kind of search is added and there are more things on the website
|
||||
// it helps avoid the look of the website just being filled with dumby data.
|
||||
// related issue-104
|
||||
|
||||
// note to self the projectCard is hardcoded directly into this component will not be hard the extract later when we need it elsewhere.
|
||||
const filteredProjects = useMemo(
|
||||
() =>
|
||||
(shouldFilterProjectsWithoutImage
|
||||
@@ -24,67 +28,68 @@ const ProjectsList = ({
|
||||
),
|
||||
[projects, shouldFilterProjectsWithoutImage]
|
||||
)
|
||||
|
||||
return (
|
||||
<section className="max-w-6xl mx-auto mt-8">
|
||||
<section className="max-w-6xl mx-auto">
|
||||
<ul
|
||||
className="grid gap-x-8 gap-y-12 items-center mx-4 relative"
|
||||
style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(16rem, 1fr))' }}
|
||||
>
|
||||
{filteredProjects.map(({ title, mainImage, user, Reaction }) => (
|
||||
<li
|
||||
className="rounded-lg shadow-md hover:shadow-lg mx-px transform hover:-translate-y-px transition-all duration-150"
|
||||
key={`${user?.userName}--${title}`}
|
||||
>
|
||||
<Link
|
||||
to={routes.project({
|
||||
userName: user?.userName,
|
||||
projectTitle: title,
|
||||
})}
|
||||
{filteredProjects.map(
|
||||
({ title, mainImage, user, Reaction, cadPackage }) => (
|
||||
<li
|
||||
className="rounded p-1.5 bg-ch-gray-760 shadow-ch"
|
||||
key={`${user?.userName}--${title}`}
|
||||
>
|
||||
<div className="flex items-center p-2 bg-gray-200 border-gray-300 rounded-t-lg border-t border-l border-r">
|
||||
<div className="w-8 h-8 overflow-hidden rounded-full border border-indigo-300 shadow">
|
||||
<Link
|
||||
to={routes.project({
|
||||
userName: user?.userName,
|
||||
projectTitle: title,
|
||||
})}
|
||||
>
|
||||
<div className="relative">
|
||||
<ImageUploader
|
||||
className=""
|
||||
aspectRatio={1}
|
||||
imageUrl={user?.image}
|
||||
width={50}
|
||||
className="rounded"
|
||||
aspectRatio={1.4}
|
||||
imageUrl={mainImage}
|
||||
width={700}
|
||||
/>
|
||||
<CadPackage
|
||||
cadPackage={cadPackage}
|
||||
className="absolute right-0 top-0 p-1.5 rounded-bl text-sm bg-opacity-50"
|
||||
dotClass="w-3 h-3"
|
||||
/>
|
||||
</div>
|
||||
<span className="font-ropa-sans ml-3 text-lg text-indigo-900">
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full overflow-hidden relative rounded-b-lg">
|
||||
<ImageUploader
|
||||
className=""
|
||||
aspectRatio={1.4}
|
||||
imageUrl={mainImage}
|
||||
width={700}
|
||||
/>
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(19.04deg, rgba(62, 44, 118, 0.46) 10.52%, rgba(60, 54, 107, 0) 40.02%)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute inset-x-0 bottom-0 -mb-4 mr-4 flex justify-end">
|
||||
{countEmotes(Reaction).map(({ emoji, count }) => (
|
||||
<div
|
||||
key={emoji}
|
||||
className="h-8 w-8 overflow-hidden ml-2 p-1 rounded-full bg-opacity-75 bg-gray-200 border border-gray-300 shadow-md flex items-center justify-between"
|
||||
>
|
||||
<div className="-ml-px text-sm w-1">{emoji}</div>
|
||||
<div className="text-sm pl-1 font-ropa-sans text-gray-800">
|
||||
{count}
|
||||
</div>
|
||||
<div className="flex items-center mt-1">
|
||||
<div className="w-8 h-8 overflow-hidden rounded-full border border-ch-gray-300 shadow">
|
||||
<ImageUploader
|
||||
className=""
|
||||
aspectRatio={1}
|
||||
imageUrl={user?.image}
|
||||
width={50}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
<div className="ml-3 text-lg text-ch-gray-300 font-fira-sans">
|
||||
<div className="">{title}</div>
|
||||
<div className="text-sm">{user?.userName}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-flow-col-dense gap-2 justify-start mt-1.5">
|
||||
<div className="px-2 flex items-center bg-ch-gray-600 text-ch-gray-300 rounded-sm">
|
||||
<Svg name="reactions" className="w-4 mr-2" />
|
||||
{countEmotes(Reaction).reduce(
|
||||
(prev, { count }) => prev + count,
|
||||
0
|
||||
)}
|
||||
</div>
|
||||
<div className="px-2 flex items-center bg-ch-blue-650 bg-opacity-30 text-ch-gray-300 rounded-sm">
|
||||
<Svg name="fork-new" className="w-4 mr-2" />0
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ export const QUERY = gql`
|
||||
projects {
|
||||
id
|
||||
title
|
||||
cadPackage
|
||||
mainImage
|
||||
createdAt
|
||||
updatedAt
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
import { DefaultKernelExport } from './common'
|
||||
import type { CadPackage } from 'src/helpers/hooks/useIdeState'
|
||||
import type { CadPackageType } from 'src/components/CadPackage/CadPackage'
|
||||
|
||||
import openscad from './openScad/openScadController'
|
||||
import cadquery from './cadQueryController'
|
||||
import jscad from './jsCad/jsCadController'
|
||||
|
||||
export const cadPackages: { [key in CadPackage]: DefaultKernelExport } = {
|
||||
export const cadPackages: { [key in CadPackageType]: DefaultKernelExport } = {
|
||||
openscad,
|
||||
cadquery,
|
||||
jscad,
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
Mesh,
|
||||
} from 'three'
|
||||
import { jsCadToCadhubParams } from './jscadParams'
|
||||
import TheWorker from 'worker-loader!./jscadWorker'
|
||||
|
||||
const materials = {
|
||||
mesh: {
|
||||
@@ -134,19 +135,7 @@ export const render: DefaultKernelExport['render'] = async ({
|
||||
}: RenderArgs) => {
|
||||
if (!scriptWorker) {
|
||||
const baseURI = document.baseURI.toString()
|
||||
const scriptUrl = '/demo-worker.js'
|
||||
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))
|
||||
scriptWorker = new TheWorker()
|
||||
let parameterDefinitions = []
|
||||
scriptWorker.addEventListener('message', ({ data }) => {
|
||||
if (data.action == 'parameterDefinitions') {
|
||||
|
||||
695
app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts
Normal file
695
app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts
Normal file
@@ -0,0 +1,695 @@
|
||||
const setPoints = (points, p, i) => {
|
||||
points[i++] = p[0]
|
||||
points[i++] = p[1]
|
||||
points[i++] = p[2] || 0
|
||||
}
|
||||
|
||||
function CSG2Vertices(csg) {
|
||||
const idx = 0
|
||||
|
||||
let vLen = 0,
|
||||
iLen = 0
|
||||
for (const poly of csg.polygons) {
|
||||
const 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 (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 CSG2LineVertices(csg) {
|
||||
let vLen = csg.points.length * 3
|
||||
if (csg.isClosed) vLen += 3
|
||||
|
||||
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)
|
||||
})
|
||||
return { vertices, type: 'lines' }
|
||||
}
|
||||
|
||||
function CSGCached(func, data, cacheKey, transferable) {
|
||||
cacheKey = cacheKey || data
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
require.cache = {}
|
||||
require.alias = {}
|
||||
|
||||
const cmdHandler = (handlers) => (cmd) => {
|
||||
const fn = handlers[cmd.action]
|
||||
if (!fn) throw new Error('no handler for type: ' + cmd.action)
|
||||
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
|
||||
}
|
||||
|
||||
let groupIndex = 1
|
||||
const defs = []
|
||||
|
||||
while (i < lines.length) {
|
||||
line = lines[i].code
|
||||
lineNum = lines[i].line
|
||||
next = lines[i + 1] ? lines[i + 1].code : ''
|
||||
if (line[0] === '}') 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++
|
||||
}
|
||||
|
||||
return defs
|
||||
}
|
||||
|
||||
function parseOne(comment, code, line1, line2) {
|
||||
const { caption, options } = parseComment(comment, line1)
|
||||
let def = { caption, ...parseDef(code, line2) }
|
||||
def.caption = def.caption || def.name
|
||||
if (options) {
|
||||
def = { ...def, ...options }
|
||||
if (def.type === 'checkbox' && def.hasOwnProperty('initial'))
|
||||
def.checked = true
|
||||
if (def.type === 'slider') {
|
||||
if (def.min === undefined) {
|
||||
def.min = 0
|
||||
}
|
||||
if (def.max === undefined) {
|
||||
def.max = def.initial * 2 || 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return def
|
||||
}
|
||||
|
||||
function parseComment(comment, line) {
|
||||
const prefix = comment.substring(0, 2)
|
||||
if (prefix === '//') comment = comment.substring(2)
|
||||
if (prefix === '/*') comment = comment.substring(2, comment.length - 2)
|
||||
|
||||
comment = comment.trim()
|
||||
|
||||
const ret = {}
|
||||
const idx = comment.indexOf('{')
|
||||
if (idx !== -1) {
|
||||
try {
|
||||
ret.options = eval('(' + comment.substring(idx) + ')')
|
||||
} catch (e) {
|
||||
console.log('Error in line ' + line)
|
||||
console.log(comment)
|
||||
throw e
|
||||
}
|
||||
comment = comment.substring(0, idx).trim()
|
||||
}
|
||||
|
||||
ret.caption = comment
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
function parseDef(code, line) {
|
||||
if (code[code.length - 1] == ',')
|
||||
code = code.substring(0, code.length - 1).trim()
|
||||
let idx = code.indexOf('=')
|
||||
|
||||
if (idx == -1) idx = code.indexOf(':')
|
||||
|
||||
if (idx == -1) {
|
||||
return { name: code, type: 'text' }
|
||||
} else {
|
||||
const initial = code.substring(idx + 1).trim()
|
||||
|
||||
const ret = { type: 'text', name: code.substring(0, idx).trim() }
|
||||
|
||||
if (initial === 'true' || initial === 'false') {
|
||||
ret.type = 'checkbox'
|
||||
ret.checked = initial === 'true'
|
||||
} else if (/^[0-9]+$/.test(initial)) {
|
||||
ret.type = 'int'
|
||||
ret.initial = parseFloat(initial)
|
||||
} else if (/^[0-9]+\.[0-9]+$/.test(initial)) {
|
||||
ret.type = 'number'
|
||||
ret.initial = parseFloat(initial)
|
||||
} else {
|
||||
try {
|
||||
ret.initial = eval(initial)
|
||||
} catch (e) {
|
||||
console.log('Error in line ' + line)
|
||||
console.log(code)
|
||||
console.log('problem evaluating inital value:', initial)
|
||||
e = new EvalError(e.message, 'code', line)
|
||||
e.lineNumber = line
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
const makeScriptWorker = ({ callback, convertToSolids }) => {
|
||||
let onInit, main, scriptStats, entities
|
||||
|
||||
function runMain(params = {}) {
|
||||
let time = Date.now()
|
||||
let solids
|
||||
const transfer = []
|
||||
try {
|
||||
const tmp = main(params)
|
||||
solids = []
|
||||
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
|
||||
)
|
||||
return
|
||||
}
|
||||
const solidsTime = Date.now() - time
|
||||
scriptStats = `generate solids ${solidsTime}ms`
|
||||
|
||||
if (convertToSolids === 'buffers') {
|
||||
CSGToBuffers.clearCache()
|
||||
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 {
|
||||
entities = solids
|
||||
}
|
||||
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 })
|
||||
}
|
||||
let script_module
|
||||
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
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (paramsDef.length)
|
||||
callback({
|
||||
action: 'parameterDefinitions',
|
||||
worker: 'main',
|
||||
data: paramsDef,
|
||||
})
|
||||
|
||||
runMain(params)
|
||||
},
|
||||
updateParams: ({ params = {} }) => {
|
||||
runMain(params)
|
||||
},
|
||||
init: (params) => {
|
||||
let { baseURI, alias = [] } = params
|
||||
if (!baseURI && typeof document != 'undefined' && document.baseURI) {
|
||||
baseURI = document.baseURI
|
||||
}
|
||||
|
||||
if (baseURI) workerBaseURI = baseURI.toString()
|
||||
|
||||
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
|
||||
})
|
||||
})
|
||||
initialized = true
|
||||
if (onInit) onInit()
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
// called from outside to pass mesasges into worker
|
||||
postMessage: cmdHandler(handlers),
|
||||
}
|
||||
}
|
||||
|
||||
/** Make render worker */
|
||||
|
||||
const makeRenderWorker = () => {
|
||||
let perspectiveCamera
|
||||
const state = {}
|
||||
|
||||
const rotateSpeed = 0.002
|
||||
const panSpeed = 1
|
||||
const zoomSpeed = 0.08
|
||||
let rotateDelta = [0, 0]
|
||||
let panDelta = [0, 0]
|
||||
let zoomDelta = 0
|
||||
let updateRender = true
|
||||
let orbitControls, renderOptions, gridOptions, axisOptions, renderer
|
||||
|
||||
let entities = []
|
||||
|
||||
function createContext(canvas, contextAttributes) {
|
||||
function get(type) {
|
||||
try {
|
||||
return { gl: canvas.getContext(type, contextAttributes), type }
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return (
|
||||
get('webgl2') ||
|
||||
get('webgl') ||
|
||||
get('experimental-webgl') ||
|
||||
get('webgl-experimental')
|
||||
)
|
||||
}
|
||||
|
||||
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 = { ...perspectiveCamera.defaults }
|
||||
if (cameraPosition) state.camera.position = cameraPosition
|
||||
if (cameraTarget) state.camera.target = cameraTarget
|
||||
|
||||
resize({ width: canvas.width, height: canvas.height })
|
||||
|
||||
state.controls = orbitControls.defaults
|
||||
|
||||
const { gl, type } = createContext(canvas)
|
||||
// prepare the renderer
|
||||
const setupOptions = {
|
||||
glOptions: { gl },
|
||||
}
|
||||
if (type == 'webgl') {
|
||||
setupOptions.glOptions.optionalExtensions = ['oes_element_index_uint']
|
||||
}
|
||||
renderer = prepareRender(setupOptions)
|
||||
|
||||
gridOptions = {
|
||||
visuals: {
|
||||
drawCmd: 'drawGrid',
|
||||
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,
|
||||
},
|
||||
size: grid.size || [200, 200],
|
||||
ticks: grid.ticks || [10, 1],
|
||||
}
|
||||
|
||||
axisOptions = {
|
||||
visuals: {
|
||||
drawCmd: 'drawAxis',
|
||||
show: axis.show || axis.show === undefined,
|
||||
},
|
||||
size: axis.size || 100,
|
||||
}
|
||||
|
||||
// assemble the options for rendering
|
||||
renderOptions = {
|
||||
camera: state.camera,
|
||||
drawCommands: {
|
||||
drawAxis: drawCommands.drawAxis,
|
||||
drawGrid: drawCommands.drawGrid,
|
||||
drawLines: drawCommands.drawLines,
|
||||
drawMesh: drawCommands.drawMesh,
|
||||
},
|
||||
// define the visual content
|
||||
entities: [gridOptions, axisOptions, ...entities],
|
||||
}
|
||||
// the heart of rendering, as themes, controls, etc change
|
||||
|
||||
updateView()
|
||||
}
|
||||
|
||||
let renderTimer
|
||||
const tmFunc =
|
||||
typeof requestAnimationFrame === 'undefined'
|
||||
? setTimeout
|
||||
: requestAnimationFrame
|
||||
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
state.controls = { ...state.controls, ...updated.controls }
|
||||
panDelta = [0, 0]
|
||||
state.camera.position = updated.camera.position
|
||||
state.camera.target = updated.camera.target
|
||||
}
|
||||
|
||||
if (zoomDelta) {
|
||||
const updated = orbitControls.zoom(
|
||||
{ controls: state.controls, camera: state.camera, speed: zoomSpeed },
|
||||
zoomDelta
|
||||
)
|
||||
state.controls = { ...state.controls, ...updated.controls }
|
||||
zoomDelta = 0
|
||||
}
|
||||
}
|
||||
|
||||
const updateAndRender = (timestamp) => {
|
||||
renderTimer = null
|
||||
doRotatePanZoom()
|
||||
|
||||
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
|
||||
|
||||
state.camera.position = updates.camera.position
|
||||
perspectiveCamera.update(state.camera)
|
||||
renderOptions.entities = [gridOptions, axisOptions, ...entities]
|
||||
const time = Date.now()
|
||||
renderer(renderOptions)
|
||||
if (updateRender) {
|
||||
updateRender = ''
|
||||
}
|
||||
}
|
||||
|
||||
function resize({ width, height }) {
|
||||
state.canvas.width = width
|
||||
state.canvas.height = height
|
||||
perspectiveCamera.setProjection(state.camera, state.camera, {
|
||||
width,
|
||||
height,
|
||||
})
|
||||
perspectiveCamera.update(state.camera, state.camera)
|
||||
updateView()
|
||||
}
|
||||
|
||||
const handlers = {
|
||||
pan: ({ dx, dy }) => {
|
||||
panDelta[0] += dx
|
||||
panDelta[1] += dy
|
||||
updateView()
|
||||
},
|
||||
rotate: ({ dx, dy }) => {
|
||||
rotateDelta[0] -= dx
|
||||
rotateDelta[1] -= dy
|
||||
updateView()
|
||||
},
|
||||
zoom: ({ dy }) => {
|
||||
zoomDelta += dy
|
||||
updateView()
|
||||
},
|
||||
resize,
|
||||
entities: (params) => {
|
||||
entities = params.entities
|
||||
updateRender = params.scriptStats
|
||||
updateView()
|
||||
},
|
||||
init: (params) => {
|
||||
if (params.canvas) startRenderer(params)
|
||||
initialized = true
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
// called from outside to pass mesasges into worker
|
||||
postMessage: cmdHandler(handlers),
|
||||
}
|
||||
}
|
||||
|
||||
function start(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 {
|
||||
callback(params, transfer)
|
||||
}
|
||||
}
|
||||
|
||||
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 }),
|
||||
postMessage: sendCmd,
|
||||
}
|
||||
}
|
||||
|
||||
const init = start({
|
||||
convertToSolids: 'buffers',
|
||||
callback: (params) => self.postMessage(params),
|
||||
})
|
||||
|
||||
self.onmessage = ({ data }) => {
|
||||
if (data.action === 'init') {
|
||||
workerBaseURI = data.baseURI
|
||||
}
|
||||
init.postMessage(data, null)
|
||||
}
|
||||
@@ -17,8 +17,3 @@ export const IdeContext = createContext<IdeContextType>({
|
||||
export function useIdeContext() {
|
||||
return useContext(IdeContext)
|
||||
}
|
||||
|
||||
export const ideTypeNameMap = {
|
||||
openscad: 'OpenSCAD',
|
||||
cadquery: 'CadQuery',
|
||||
}
|
||||
|
||||
@@ -13,10 +13,9 @@ function withThunk(dispatch, getState) {
|
||||
? actionOrThunk(dispatch, getState)
|
||||
: dispatch(actionOrThunk)
|
||||
}
|
||||
import { CadPackageType } from 'src/components/CadPackage/CadPackage'
|
||||
|
||||
export type CadPackage = 'openscad' | 'cadquery' | 'jscad'
|
||||
|
||||
const initCodeMap: { [key in CadPackage]: string } = {
|
||||
const initCodeMap: { [key in CadPackageType]: string } = {
|
||||
openscad: `// involute donut
|
||||
|
||||
// ^ first comment is used for download title (i.e "involute-donut.stl")
|
||||
@@ -97,7 +96,7 @@ interface XYZ {
|
||||
}
|
||||
|
||||
export interface State {
|
||||
ideType: 'INIT' | CadPackage
|
||||
ideType: 'INIT' | CadPackageType
|
||||
consoleMessages: { type: 'message' | 'error'; message: string; time: Date }[]
|
||||
code: string
|
||||
objectData: {
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
* END --- TAILWIND GENERATOR EDIT
|
||||
*/
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
font-family: 'Fira Sans', ui-sans-serif, system-ui, -apple-system, system-ui, "Segoe UI", "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.markdown-overrides h4 {
|
||||
@apply text-lg font-bold;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ const ProjectsPage = () => {
|
||||
lang="en-US"
|
||||
/>
|
||||
<LandingSection />
|
||||
<ProjectsCell shouldFilterProjectsWithoutImage />
|
||||
<div className="bg-ch-gray-800 py-20">
|
||||
<ProjectsCell shouldFilterProjectsWithoutImage />
|
||||
</div>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
||||
@@ -19069,6 +19069,14 @@ worker-farm@^1.7.0:
|
||||
dependencies:
|
||||
errno "~0.1.7"
|
||||
|
||||
worker-loader@^3.0.8:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.8.tgz#5fc5cda4a3d3163d9c274a4e3a811ce8b60dbb37"
|
||||
integrity sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==
|
||||
dependencies:
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
worker-rpc@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz"
|
||||
|
||||
Reference in New Issue
Block a user