diff --git a/app/web/public/demo-worker.js b/app/web/public/demo-worker.js
index ebbb97c..d8b18eb 100644
--- a/app/web/public/demo-worker.js
+++ b/app/web/public/demo-worker.js
@@ -246,6 +246,8 @@ const makeScriptWorker = ({callback, convertToSolids})=>{
function runMain(params={}){
let time = Date.now()
let solids
+ let transfer = []
+ console.log('run main')
try{
solids = main(params)
}catch(e){
@@ -255,7 +257,6 @@ const makeScriptWorker = ({callback, convertToSolids})=>{
let solidsTime = Date.now() - time
scriptStats = `generate solids ${solidsTime}ms`
- let transfer = []
if(convertToSolids === 'buffers'){
CSGToBuffers.clearCache()
entities = solids.map((csg)=>{
@@ -549,6 +550,7 @@ return (params)=>{
workerBaseURI = baseURI
const sendCmd = (params, transfer)=>{
+ console.log('sendCmd', params.worker, params.action, params)
if(params.worker === 'render')
sendToRender(params, transfer)
else if(params.worker === 'script')
diff --git a/app/web/src/components/Customizer/Customizer.tsx b/app/web/src/components/Customizer/Customizer.tsx
index 84da796..513220d 100644
--- a/app/web/src/components/Customizer/Customizer.tsx
+++ b/app/web/src/components/Customizer/Customizer.tsx
@@ -1,5 +1,6 @@
-import { useRender } from 'src/components/IdeWrapper/useRender'
+import { useRender, } from 'src/components/IdeWrapper/useRender'
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
+import { genParams, getParams } from 'src/helpers/cadPackages/jscadParams'
const Customizer = () => {
const [open, setOpen] = React.useState(true)
@@ -7,16 +8,18 @@ const Customizer = () => {
const jsCadCustomizerElement = ref.current
const { state } = useIdeContext()
const customizerParams = state?.objectData?.customizerParams
- console.log(state)
- React.useEffect(() => {
- console.log({ jsCadCustomizerElement, customizerParams })
- if (jsCadCustomizerElement && customizerParams) {
- jsCadCustomizerElement.innerHTML = `
${JSON.stringify(
- customizerParams
- )}
`
- }
- }, [jsCadCustomizerElement, customizerParams])
+ const lastParameters = state?.objectData?.lastParameters
const handleRender = useRender()
+ const handleRender2 = ()=>handleRender(getParams(ref.current))
+
+ React.useEffect(() => {
+ console.log({ jsCadCustomizerElement, customizerParams, lastParameters })
+ if (jsCadCustomizerElement && customizerParams) {
+ genParams(customizerParams, jsCadCustomizerElement, lastParameters || {}, (values)=>{
+ handleRender(values)
+ },[])
+ }
+ }, [jsCadCustomizerElement, customizerParams, lastParameters])
return (
{
diff --git a/app/web/src/components/IdeWrapper/useRender.ts b/app/web/src/components/IdeWrapper/useRender.ts
index bc82f81..52d233d 100644
--- a/app/web/src/components/IdeWrapper/useRender.ts
+++ b/app/web/src/components/IdeWrapper/useRender.ts
@@ -3,7 +3,7 @@ import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
export const useRender = () => {
const { state, thunkDispatch } = useIdeContext()
- return () => {
+ return (parameters) => {
thunkDispatch((dispatch, getState) => {
const state = getState()
dispatch({ type: 'setLoading' })
@@ -13,6 +13,7 @@ export const useRender = () => {
code: state.code,
viewerSize: state.viewerSize,
camera: state.camera,
+ parameters,
})
})
localStorage.setItem(makeCodeStoreKey(state.ideType), state.code)
diff --git a/app/web/src/helpers/cadPackages/common.ts b/app/web/src/helpers/cadPackages/common.ts
index 3f5480c..5dab46d 100644
--- a/app/web/src/helpers/cadPackages/common.ts
+++ b/app/web/src/helpers/cadPackages/common.ts
@@ -12,6 +12,7 @@ export const stlToGeometry = (url) =>
export interface RenderArgs {
code: State['code']
+ parameters: any,
settings: {
camera: State['camera']
viewerSize: State['viewerSize']
@@ -31,6 +32,7 @@ export interface HealthyResponse {
type: 'stl' | 'png' | 'geometry'
}
customizerParams?: any
+ lastParameters?: any
}
export function createHealthyResponse({
@@ -39,12 +41,14 @@ export function createHealthyResponse({
consoleMessage,
type,
customizerParams,
-}: {
+ lastParameters,
+ }: {
date: Date
data: any
consoleMessage: string
type: HealthyResponse['objectData']['type']
customizerParams?: any
+ lastParameters?: any
}): HealthyResponse {
return {
status: 'healthy',
@@ -58,6 +62,7 @@ export function createHealthyResponse({
time: date,
},
customizerParams,
+ lastParameters,
}
}
diff --git a/app/web/src/helpers/cadPackages/jsCadController.ts b/app/web/src/helpers/cadPackages/jsCadController.ts
index 02d7124..6178899 100644
--- a/app/web/src/helpers/cadPackages/jsCadController.ts
+++ b/app/web/src/helpers/cadPackages/jsCadController.ts
@@ -70,6 +70,7 @@ function CSG2Object3D(obj) {
}
let scriptWorker
+let lastParameters = {}
const scriptUrl = '/demo-worker.js'
let resolveReference = null
let response = null
@@ -81,50 +82,73 @@ const callResolve = () => {
export const render: DefaultKernelExport['render'] = async ({
code,
+ parameters,
settings,
}: RenderArgs) => {
if (!scriptWorker) {
+ console.trace('************************** creating new worker ************************')
const baseURI = document.baseURI.toString()
const script = `let baseURI = '${baseURI}'
-importScripts(new URL('${scriptUrl}',baseURI))
-let worker = jscadWorker({
- baseURI: baseURI,
- scope:'worker',
- convertToSolids: 'buffers',
- callback:(params)=>self.postMessage(params),
-})
-self.addEventListener('message', (e)=>worker.postMessage(e.data))
-`
+ importScripts(new URL('${scriptUrl}',baseURI))
+ let worker = jscadWorker({
+ baseURI: baseURI,
+ scope:'worker',
+ convertToSolids: 'buffers',
+ callback:(params)=>self.postMessage(params),
+ })
+ self.addEventListener('message', (e)=>worker.postMessage(e.data))
+ `
const blob = new Blob([script], { type: 'text/javascript' })
scriptWorker = new Worker(window.URL.createObjectURL(blob))
+ let parameterDefinitions = []
scriptWorker.addEventListener('message', (e) => {
const data = e.data
- if (data.action == 'entities') {
+ if (data.action == 'parameterDefinitions') {
+ console.log('message',data)
+ parameterDefinitions = data.data
+ } else if (data.action == 'entities') {
if (data.error) {
response = createUnhealthyResponse(new Date(), data.error)
} else {
+ console.log('lastParameters',lastParameters)
response = createHealthyResponse({
type: 'geometry',
data: [...data.entities.map(CSG2Object3D).filter((o) => o)],
consoleMessage: data.scriptStats,
date: new Date(),
- customizerParams: ['param1', 'abc'],
+ customizerParams: parameterDefinitions,
+ lastParameters,
})
}
callResolve()
}
})
-
+
callResolve()
response = null
scriptWorker.postMessage({ action: 'init', baseURI, alias: [] })
}
- scriptWorker.postMessage({
- action: 'runScript',
- worker: 'script',
- script: code,
- url: 'jscad_script',
- })
+
+ if(parameters){
+ // we are not evaluating code, but reacting to parameters change
+ scriptWorker.postMessage({
+ action: 'updateParams',
+ worker: 'script',
+ params: parameters,
+ })
+ }else{
+ scriptWorker.postMessage({
+ action: 'runScript',
+ worker: 'script',
+ script: code,
+ params: parameters || {},
+ url: 'jscad_script',
+ })
+ }
+ // we need this to keep the form filled with same data when new parameter definitions arrive
+ // each render of the script could provide new paramaters. In case some of them are still rpesent
+ // it is expected for them to stay the same and not just reset
+ lastParameters = parameters || {}
const waitResult = new Promise((resolve) => {
resolveReference = resolve
@@ -132,6 +156,7 @@ self.addEventListener('message', (e)=>worker.postMessage(e.data))
await waitResult
resolveReference = null
+ if(parameters) delete response.customizerParams
return response
}
diff --git a/app/web/src/helpers/cadPackages/jscadParams.js b/app/web/src/helpers/cadPackages/jscadParams.js
new file mode 100644
index 0000000..c72e80d
--- /dev/null
+++ b/app/web/src/helpers/cadPackages/jscadParams.js
@@ -0,0 +1,169 @@
+const GROUP_SELECTOR = 'DIV[type="group"] LABEL'
+const INPUT_SELECTOR = 'INPUT, SELECT'
+
+function forEachInput(target, callback){
+ target.querySelectorAll(INPUT_SELECTOR).forEach(callback)
+}
+
+function forEachGroup(target, callback){
+ target.querySelectorAll(GROUP_SELECTOR).forEach(callback)
+}
+
+const numeric = {number:1, float:1, int:1, range:1, slider:1}
+
+function applyRange(inp){
+ let label = inp.previousElementSibling
+ if(label.tagName == 'LABEL'){
+ let info = label.querySelector('I')
+ if(info) info.innerHTML = '('+inp.value+')'
+ }
+}
+
+export function genParams(defs, target, storedParams={}, callback=undefined, buttons=['reset','save','load','edit','link']){
+
+ let funcs = {
+ group:function({name,type, caption, captions, value, min,max}){
+ return ''
+ },
+ choice:function({name,type, caption, captions, value, values, min, max}){
+ if(!captions) captions = values
+
+ let ret = `'
+ },
+ float: inputNumber,
+ range: inputNumber,
+ slider: inputNumber,
+ int: inputNumber,
+ text: inputNumber,
+ url: inputNumber,
+ email: inputNumber,
+ date: inputNumber,
+ password: inputNumber,
+ color: inputNumber,
+ // TODO radio similar options as choice
+ checkbox :function({name,type, caption, captions, value, checked, min,max}){
+ let checkedStr = (value === 'checked' || value === true) ? 'checked':''
+ return ``
+ },
+ number: inputNumber,
+ }
+
+ function inputNumber(def){
+ let {name,type, caption, captions, value, min,max, step, placeholder, live} = def
+ if(value === null || value === undefined) value = numeric[type] ? 0:'';
+ let inputType = type
+ if(type == 'int' || type=='float') inputType = 'number'
+ if(type == 'range' || type=='slider') inputType = 'range'
+ var str = `';
+ }
+
+ let html = '';
+ let closed = false
+ let missing = {}
+
+ defs.forEach(def=>{
+ let {type, caption, name} = def
+
+ if(storedParams[name] !== undefined){
+ def.value = storedParams[name];
+ }else {
+ def.value = def.initial || def['default'] || def.checked
+ }
+
+ if(type == 'group'){
+ closed = def.value == 'closed'
+ }
+ def.closed = closed
+
+ html +=``
+
+ html += ``
+
+ if(funcs[type] && type != 'checkbox') html += funcs[type](def)
+
+ if(!funcs[type]) missing[type] = 1
+
+ html +='
\n'
+ })
+
+ let missingKeys = Object.keys(missing)
+ if(missingKeys.length) console.log('missing param impl',missingKeys);
+
+ function _callback(saveOnly){
+ if(callback) callback(getParams(target))
+ }
+
+ html +=''
+
+ target.innerHTML = html
+
+ forEachInput(target, inp=>{
+ let type = inp.type
+ inp.addEventListener('input', function(evt){
+ applyRange(inp)
+ if(inp.getAttribute('live') == '1') _callback();
+ })
+ if(inp.getAttribute('live') != '1') inp.addEventListener('change', _callback)
+
+ })
+
+ function groupClick(evt){
+ var groupDiv = evt.target.parentNode
+ var closed = (groupDiv.getAttribute('closed') == '1') ? '0':'1'
+ var name = evt.target.getAttribute('name')
+ do{
+ groupDiv.setAttribute('closed', closed)
+ groupDiv = groupDiv.nextElementSibling
+ }while(groupDiv && groupDiv.getAttribute('type') != 'group')
+ callback(getParams(target),true)
+ }
+
+ forEachGroup(target, label=>label.onclick=groupClick)
+}
+
+export function getParams(target){
+ let params = {}
+ if(!target) return params
+
+ forEachGroup(target,elem=>{
+ let name = elem.getAttribute('name')
+ params[name] = (groupDiv.getAttribute('closed') == '1') ? 'closed':''
+ })
+
+ forEachInput(target,elem=>{
+ let name = elem.name
+ let value = elem.value
+ if(elem.tagName == 'INPUT'){
+ if(elem.type == 'checkbox') value = elem.checked
+ if(elem.type == 'range' || elem.type == 'color') applyRange(elem)
+ }
+
+ if(numeric[elem.getAttribute('type')] || elem.getAttribute('numeric') == '1') value = parseFloat(value || 0)
+ params[name] = value
+ })
+ return params;
+}
+
diff --git a/app/web/src/helpers/hooks/useIdeState.ts b/app/web/src/helpers/hooks/useIdeState.ts
index 9f5e68c..afd9783 100644
--- a/app/web/src/helpers/hooks/useIdeState.ts
+++ b/app/web/src/helpers/hooks/useIdeState.ts
@@ -43,13 +43,14 @@ result = (cq.Workplane().circle(diam).extrude(20.0)
show_object(result)
`,
jscad: `
+
const { booleans, colors, primitives } = require('@jscad/modeling') // modeling comes from the included MODELING library
const { intersect, subtract } = booleans
const { colorize } = colors
const { cube, cuboid, line, sphere, star } = primitives
-const main = ({scale=1}) => {
+const main = ({length=1}) => {
const logo = [
colorize([1.0, 0.4, 1.0], subtract(
cube({ size: 300 }),
@@ -61,13 +62,18 @@ const main = ({scale=1}) => {
))
]
- const transpCube = colorize([1, 0, 0, 0.75], cuboid({ size: [100 * scale, 100, 210 + (200 * scale)] }))
+ const transpCube = colorize([1, 0, 0, 0.75], cuboid({ size: [100, 100, length] }))
const star2D = star({ vertices: 8, innerRadius: 150, outerRadius: 200 })
const line2D = colorize([1.0, 0, 0], line([[220, 220], [-220, 220], [-220, -220], [220, -220], [220, 220]]))
return [transpCube, star2D, line2D, ...logo]
}
-module.exports = {main}
+const getParameterDefinitions = ()=>{
+ return [
+ {type:'slider', name:'length', initial:210, caption:'Length', min:210, max:1500}
+ ]
+}
+module.exports = {main, getParameterDefinitions}
`,
}
@@ -90,6 +96,7 @@ export interface State {
data: any
quality: 'low' | 'high'
customizerParams?: any
+ lastParameters?: any
}
layout: any
camera: {
@@ -155,6 +162,7 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => {
type: payload.objectData?.type,
data: payload.objectData?.data,
customizerParams: payload.customizerParams,
+ lastParameters: payload.lastParameters,
},
consoleMessages: payload.message
? [...state.consoleMessages, payload.message]
@@ -219,6 +227,7 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => {
interface RequestRenderArgs {
state: State
dispatch: any
+ parameters: any,
code: State['code']
camera: State['camera']
viewerSize: State['viewerSize']
@@ -234,6 +243,7 @@ export const requestRender = ({
viewerSize,
quality = 'low',
specialCadProcess = null,
+ parameters,
}: RequestRenderArgs) => {
if (
state.ideType !== 'INIT' &&
@@ -244,13 +254,14 @@ export const requestRender = ({
: cadPackages[state.ideType].render
return renderFn({
code,
+ parameters,
settings: {
camera,
viewerSize,
quality,
},
})
- .then(({ objectData, message, status, customizerParams }) => {
+ .then(({ objectData, message, status, customizerParams, lastParameters }) => {
if (status === 'error') {
dispatch({
type: 'errorRender',
@@ -264,6 +275,7 @@ export const requestRender = ({
message,
lastRunCode: code,
customizerParams,
+ lastParameters,
},
})
return objectData