Initial support for OpenSCAD's customizer #477

Merged
Irev-Dev merged 5 commits from kurt/320-openscad-parms-demo-rebase into main 2021-08-27 12:47:09 +02:00
21 changed files with 505 additions and 194 deletions

View File

@@ -43,6 +43,7 @@ RUN wget https://github.com/CadQuery/cq-cli/releases/download/v2.2-beta.2/cq-cli
RUN unzip cq-cli-Linux-x86_64.zip RUN unzip cq-cli-Linux-x86_64.zip
RUN chmod +x cq-cli/cq-cli RUN chmod +x cq-cli/cq-cli
RUN echo "cadhub-concat-split" > /var/task/cadhub-concat-split
COPY cadquery/*.js /var/task/ COPY cadquery/*.js /var/task/
COPY common/*.js /var/common/ COPY common/*.js /var/common/

View File

@@ -31,7 +31,6 @@ const stl = async (req, _context, callback) => {
statusCode: 200, statusCode: 200,
body: JSON.stringify({ body: JSON.stringify({
url: getObjectUrl(params, s3, tk), url: getObjectUrl(params, s3, tk),
consoleMessage: previousAsset.consoleMessage,
}), }),
} }
callback(null, response) callback(null, response)

View File

@@ -1,24 +1,42 @@
const { makeFile, runCommand } = require('../common/utils') const { writeFiles, runCommand } = require('../common/utils')
const { nanoid } = require('nanoid') const { nanoid } = require('nanoid')
module.exports.runCQ = async ({ module.exports.runCQ = async ({
file, file,
settings: { deflection = 0.3 } = {}, settings: { deflection = 0.3 } = {},
} = {}) => { } = {}) => {
const tempFile = await makeFile(file, '.py', nanoid) const tempFile = await writeFiles(
const fullPath = `/tmp/${tempFile}/output.stl` [{ file, fileName: 'main.py' }],
'a' + nanoid() // 'a' ensure nothing funny happens if it start with a bad character like "-", maybe I should pick a safer id generator :shrug:
)
const fullPath = `/tmp/${tempFile}/output.gz`
const stlPath = `/tmp/${tempFile}/output.stl`
const command = [ const command = [
`cq-cli/cq-cli`, `cq-cli/cq-cli`,
`--codec stl`, `--codec stl`,
`--infile /tmp/${tempFile}/main.py`, `--infile /tmp/${tempFile}/main.py`,
`--outfile ${fullPath}`, `--outfile ${stlPath}`,
`--outputopts "deflection:${deflection};angularDeflection:${deflection};"`, `--outputopts "deflection:${deflection};angularDeflection:${deflection};"`,
`&& gzip ${fullPath}`,
].join(' ') ].join(' ')
console.log('command', command) console.log('command', command)
try { try {
const consoleMessage = await runCommand(command, 30000) const consoleMessage = await runCommand(command, 30000)
await writeFiles(
[
{
file: JSON.stringify({
consoleMessage,
}),
fileName: 'metadata.json',
},
],
tempFile
)
await runCommand(
`cat ${stlPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
15000
)
return { consoleMessage, fullPath } return { consoleMessage, fullPath }
} catch (error) { } catch (error) {
return { error, fullPath } return { error, fullPath }
Irev-Dev commented 2021-08-26 23:00:54 +02:00 (Migrated from github.com)
Review

I had been storing the output logs from running CQ and OpenSCAD in s3 as metadata, and had planned to do the same thing with the customizer params, but realised this wasn't going to work since there is a tiny 2kb limit and that was already causing problems https://github.com/Irev-Dev/cadhub/issues/476

So now instead I'm concatenating the gzip file with and metadata (params and logs), its a little messy to split them apart in the browser but its working. /var/task/cadhub-concat-split has a unique string that I sue to split at.

I had been storing the output logs from running CQ and OpenSCAD in s3 as metadata, and had planned to do the same thing with the customizer params, but realised this wasn't going to work since there is a tiny 2kb limit and that was already causing problems https://github.com/Irev-Dev/cadhub/issues/476 So now instead I'm concatenating the gzip file with and metadata (params and logs), its a little messy to split them apart in the browser but its working. `/var/task/cadhub-concat-split` has a unique string that I sue to split at.
hrgdavor commented 2021-08-26 23:26:09 +02:00 (Migrated from github.com)
Review

why not use https://www.npmjs.com/package/fflate
I started using it recently (to create 3mf export in my jscad prototype)

no temp file needed, lib is tiny, you can name the files when compressing, and decompressing and will likely be useful in the frontend in the future, so it will not be a bloat :)

... I would not stop this PR because of it, better not do too many things in a single PR. ... but maybe open an issue, so it is in the pipeline for later :)

why not use https://www.npmjs.com/package/fflate I started using it recently (to create 3mf export in my jscad prototype) no temp file needed, lib is tiny, you can name the files when compressing, and decompressing and will likely be useful in the frontend in the future, so it will not be a bloat :) ... I would not stop this PR because of it, better not do too many things in a single PR. ... but maybe open an issue, so it is in the pipeline for later :)
Irev-Dev commented 2021-08-27 09:10:44 +02:00 (Migrated from github.com)
Review

I was trying to stick to native browser ungziping things when the request arrives with the correct headers. But something like this might be better

I am not too concerned about finding an optimum solution right now since the next thing on my todo list is investigate performance, and I have a feeling that a lot of backend things might change in the process. Thanks for the lib recommendation.

I was trying to stick to native browser ungziping things when the request arrives with the correct headers. But something like this might be better I am not too concerned about finding an optimum solution right now since the next thing on my todo list is investigate performance, and I have a feeling that a lot of backend things might change in the process. Thanks for the lib recommendation.

View File

@@ -3,24 +3,19 @@ const { promises } = require('fs')
const { writeFile } = promises const { writeFile } = promises
const { createHash } = require('crypto') const { createHash } = require('crypto')
const CONSOLE_MESSAGE_KEY = 'console-message-b64' async function writeFiles(files = [], tempFile) {
Irev-Dev commented 2021-08-26 23:01:52 +02:00 (Migrated from github.com)
Review

Removed code to store things in s3 metadata.

Removed code to store things in s3 metadata.
function putConsoleMessageInMetadata(consoleMessage) { console.log(`file to write: ${files.length}`)
return {
[CONSOLE_MESSAGE_KEY]: Buffer.from(consoleMessage, 'utf-8').toString( try {
'base64' await runCommand(`mkdir /tmp/${tempFile}`)
), } catch (e) {
//
} }
} await Promise.all(
function getConsoleMessageFromMetadata(metadata) { files.map(({ file, fileName }) =>
return Buffer.from(metadata[CONSOLE_MESSAGE_KEY], 'base64').toString('utf-8') writeFile(`/tmp/${tempFile}/${fileName}`, file)
} )
)
async function makeFile(file, extension = '.scad', makeHash) {
const tempFile = 'a' + makeHash() // 'a' ensure nothing funny happens if it start with a bad character like "-", maybe I should pick a safer id generator :shrug:
console.log(`file to write: ${file}`)
await runCommand(`mkdir /tmp/${tempFile}`)
await writeFile(`/tmp/${tempFile}/main${extension}`, file)
return tempFile return tempFile
} }
@@ -54,10 +49,8 @@ function makeHash(script) {
async function checkIfAlreadyExists(params, s3) { async function checkIfAlreadyExists(params, s3) {
try { try {
const objectHead = await s3.headObject(params).promise() await s3.headObject(params).promise()
const consoleMessage = getConsoleMessageFromMetadata(objectHead.Metadata) return { isAlreadyInBucket: true }
console.log('consoleMessage', consoleMessage)
return { isAlreadyInBucket: true, consoleMessage }
} catch (e) { } catch (e) {
console.log("couldn't find it", e) console.log("couldn't find it", e)
return { isAlreadyInBucket: false } return { isAlreadyInBucket: false }
@@ -117,7 +110,7 @@ async function storeAssetAndReturnUrl({
let buffer let buffer
try { try {
buffer = await readFile(`${fullPath}.gz`) buffer = await readFile(fullPath)
} catch (e) { } catch (e) {
console.log('read file error', e) console.log('read file error', e)
const response = { const response = {
@@ -136,7 +129,6 @@ async function storeAssetAndReturnUrl({
CacheControl: `max-age=${FiveDays}`, // browser caching to stop downloads of the same part CacheControl: `max-age=${FiveDays}`, // browser caching to stop downloads of the same part
ContentType: 'text/stl', ContentType: 'text/stl',
ContentEncoding: 'gzip', ContentEncoding: 'gzip',
Metadata: putConsoleMessageInMetadata(consoleMessage),
}) })
.promise() .promise()
console.log('stored object', storedRender) console.log('stored object', storedRender)
@@ -146,7 +138,6 @@ async function storeAssetAndReturnUrl({
statusCode: 200, statusCode: 200,
body: JSON.stringify({ body: JSON.stringify({
url, url,
consoleMessage,
}), }),
} }
callback(null, response) callback(null, response)
@@ -156,7 +147,7 @@ async function storeAssetAndReturnUrl({
module.exports = { module.exports = {
runCommand, runCommand,
makeFile, writeFiles,
makeHash, makeHash,
checkIfAlreadyExists, checkIfAlreadyExists,
getObjectUrl, getObjectUrl,

View File

@@ -6,10 +6,12 @@ ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update -qq RUN apt-get update -qq
# double check this below, I'm not sure we need inkscape etc # double check this below, I'm not sure we need inkscape etc
RUN apt-get -y -qq install software-properties-common dirmngr apt-transport-https lsb-release ca-certificates xvfb imagemagick unzip inkscape RUN apt-get -y -qq install software-properties-common dirmngr apt-transport-https lsb-release ca-certificates xvfb imagemagick unzip inkscape
RUN add-apt-repository ppa:openscad/releases
RUN apt-get update -qq
RUN apt-get install -y -qq openscad
RUN apt-get install -y curl wget RUN apt-get install -y curl wget
RUN touch /etc/apt/sources.list.d/openscad.list
RUN echo "deb https://download.opensuse.org/repositories/home:/t-paul/xUbuntu_20.04/ ./" >> /etc/apt/sources.list.d/openscad.list
RUN wget -qO - https://files.openscad.org/OBS-Repository-Key.pub | apt-key add -
RUN apt-get update -qq
RUN apt-get install -y openscad-nightly
# install node14, see comment at the to of node14source_setup.sh # install node14, see comment at the to of node14source_setup.sh
ADD common/node14source_setup.sh /nodesource_setup.sh ADD common/node14source_setup.sh /nodesource_setup.sh
Irev-Dev commented 2021-08-26 23:05:59 +02:00 (Migrated from github.com)
Review

Im replacing the release with the nightly build to keep things simple @t-paul, I didn't want to have to try and figure out to support multiple version right now.

Im replacing the release with the nightly build to keep things simple @t-paul, I didn't want to have to try and figure out to support multiple version right now.
t-paul commented 2021-08-27 00:23:42 +02:00 (Migrated from github.com)
Review

Yep, keeping it simpler for now seems a good idea. This version is actively used by people (including myself) so it should be reasonably stable.

Yep, keeping it simpler for now seems a good idea. This version is actively used by people (including myself) so it should be reasonably stable.
@@ -44,7 +46,9 @@ RUN echo "OPENSCADPATH=/var/task/openscad" >>/etc/profile && \
wget -P /var/task/openscad/ https://github.com/Irev-Dev/Round-Anything/archive/refs/tags/1.0.4.zip && \ wget -P /var/task/openscad/ https://github.com/Irev-Dev/Round-Anything/archive/refs/tags/1.0.4.zip && \
unzip /var/task/openscad/1.0.4 unzip /var/task/openscad/1.0.4
# Add our own theming (based on DeepOcean with a different "background" and "opencsg-face-back") # Add our own theming (based on DeepOcean with a different "background" and "opencsg-face-back")
COPY openscad/cadhubtheme.json /usr/share/openscad/color-schemes/render/ COPY openscad/cadhubtheme.json /usr/share/openscad-nightly/color-schemes/render/
RUN echo "cadhub-concat-split" > /var/task/cadhub-concat-split
COPY openscad/*.js /var/task/ COPY openscad/*.js /var/task/
COPY common/*.js /var/common/ COPY common/*.js /var/common/

View File

@@ -57,8 +57,6 @@ const preview = async (req, _context, callback) => {
s3, s3,
tk tk
), ),
consoleMessage:
previousAsset.consoleMessage || previousAssetPng.consoleMessage,
type, type,
}), }),
} }
@@ -67,7 +65,10 @@ const preview = async (req, _context, callback) => {
} }
const { file, settings } = JSON.parse(eventBody) const { file, settings } = JSON.parse(eventBody)
const { error, consoleMessage, fullPath } = await runScad({ file, settings }) const { error, consoleMessage, fullPath, customizerPath } = await runScad({
file,
settings,
})
await storeAssetAndReturnUrl({ await storeAssetAndReturnUrl({
error, error,
callback, callback,
@@ -100,14 +101,16 @@ const stl = async (req, _context, callback) => {
statusCode: 200, statusCode: 200,
body: JSON.stringify({ body: JSON.stringify({
url: getObjectUrl({ ...params }, s3, tk), url: getObjectUrl({ ...params }, s3, tk),
consoleMessage: previousAsset.consoleMessage,
}), }),
} }
callback(null, response) callback(null, response)
return return
} }
const { file } = JSON.parse(eventBody) const { file, settings } = JSON.parse(eventBody)
const { error, consoleMessage, fullPath } = await stlExport({ file }) const { error, consoleMessage, fullPath, customizerPath } = await stlExport({
file,
settings,
})
await storeAssetAndReturnUrl({ await storeAssetAndReturnUrl({
error, error,
callback, callback,
@@ -121,6 +124,6 @@ const stl = async (req, _context, callback) => {
} }
module.exports = { module.exports = {
stl: middy(stl).use(cors()), stl: middy(loggerWrap(stl)).use(cors()),
preview: middy(loggerWrap(preview)).use(cors()), preview: middy(loggerWrap(preview)).use(cors()),
} }

View File

@@ -1,7 +1,8 @@
const { makeFile, runCommand } = require('../common/utils') const { writeFiles, runCommand } = require('../common/utils')
const { nanoid } = require('nanoid') const { nanoid } = require('nanoid')
const { readFile } = require('fs/promises')
const OPENSCAD_COMMON = `xvfb-run --auto-servernum --server-args "-screen 0 1024x768x24" openscad` const OPENSCAD_COMMON = `xvfb-run --auto-servernum --server-args "-screen 0 1024x768x24" openscad-nightly`
/** Removes our generated/hash filename with just "main.scad", so that it's a nice message in the IDE */ /** Removes our generated/hash filename with just "main.scad", so that it's a nice message in the IDE */
const cleanOpenScadError = (error) => const cleanOpenScadError = (error) =>
@@ -11,6 +12,7 @@ module.exports.runScad = async ({
file, file,
settings: { settings: {
size: { x = 500, y = 500 } = {}, size: { x = 500, y = 500 } = {},
parameters,
camera: { camera: {
position = { x: 40, y: 40, z: 40 }, position = { x: 40, y: 40, z: 40 },
rotation = { x: 55, y: 0, z: 25 }, rotation = { x: 55, y: 0, z: 25 },
@@ -18,46 +20,113 @@ module.exports.runScad = async ({
} = {}, } = {},
} = {}, // TODO add view settings } = {}, // TODO add view settings
} = {}) => { } = {}) => {
const tempFile = await makeFile(file, '.scad', nanoid) const tempFile = await writeFiles(
[
{ file, fileName: 'main.scad' },
{
file: JSON.stringify({
parameterSets: { default: parameters },
fileFormatVersion: '1',
}),
fileName: 'params.json',
},
],
'a' + nanoid() // 'a' ensure nothing funny happens if it start with a bad character like "-", maybe I should pick a safer id generator :shrug:
)
const { x: rx, y: ry, z: rz } = rotation const { x: rx, y: ry, z: rz } = rotation
const { x: px, y: py, z: pz } = position const { x: px, y: py, z: pz } = position
const cameraArg = `--camera=${px},${py},${pz},${rx},${ry},${rz},${dist}` const cameraArg = `--camera=${px},${py},${pz},${rx},${ry},${rz},${dist}`
const fullPath = `/tmp/${tempFile}/output.png` const fullPath = `/tmp/${tempFile}/output.gz`
const imPath = `/tmp/${tempFile}/output.png`
const customizerPath = `/tmp/${tempFile}/customizer.param`
const command = [ const command = [
OPENSCAD_COMMON, OPENSCAD_COMMON,
`-o ${fullPath}`, `-o ${customizerPath}`,
`-o ${imPath}`,
`-p /tmp/${tempFile}/params.json -P default`,
cameraArg, cameraArg,
`--imgsize=${x},${y}`, `--imgsize=${x},${y}`,
`--colorscheme CadHub`, `--colorscheme CadHub`,
`/tmp/${tempFile}/main.scad`, `/tmp/${tempFile}/main.scad`,
`&& gzip ${fullPath}`,
].join(' ') ].join(' ')
console.log('command', command) console.log('command', command)
try { try {
const consoleMessage = await runCommand(command, 15000) const consoleMessage = await runCommand(command, 15000)
return { consoleMessage, fullPath } const params = JSON.parse(
await readFile(customizerPath, { encoding: 'ascii' })
).parameters
await writeFiles(
[
{
file: JSON.stringify({
customizerParams: params,
consoleMessage,
}),
fileName: 'metadata.json',
},
],
tempFile
)
await runCommand(
`cat ${imPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
15000
)
return { consoleMessage, fullPath, customizerPath }
} catch (dirtyError) { } catch (dirtyError) {
const error = cleanOpenScadError(dirtyError) return { error: cleanOpenScadError(dirtyError) }
return { error }
} }
} }
module.exports.stlExport = async ({ file } = {}) => { module.exports.stlExport = async ({ file, settings: { parameters } } = {}) => {
const tempFile = await makeFile(file, '.scad', nanoid) const tempFile = await writeFiles(
const fullPath = `/tmp/${tempFile}/output.stl` [
{ file, fileName: 'main.scad' },
{
file: JSON.stringify({
parameterSets: { default: parameters },
fileFormatVersion: '1',
}),
fileName: 'params.json',
},
],
'a' + nanoid() // 'a' ensure nothing funny happens if it start with a bad character like "-", maybe I should pick a safer id generator :shrug:
)
const fullPath = `/tmp/${tempFile}/output.gz`
const stlPath = `/tmp/${tempFile}/output.stl`
const customizerPath = `/tmp/${tempFile}/customizer.param`
const command = [ const command = [
OPENSCAD_COMMON, OPENSCAD_COMMON,
`--export-format=binstl`, // `--export-format=binstl`,
`-o ${fullPath}`, `-o ${customizerPath}`,
`-o ${stlPath}`,
`-p /tmp/${tempFile}/params.json -P default`,
`/tmp/${tempFile}/main.scad`, `/tmp/${tempFile}/main.scad`,
`&& gzip ${fullPath}`,
].join(' ') ].join(' ')
try { try {
// lambda will time out before this, we might need to look at background jobs if we do git integration stl generation // lambda will time out before this, we might need to look at background jobs if we do git integration stl generation
const consoleMessage = await runCommand(command, 60000) const consoleMessage = await runCommand(command, 60000)
return { consoleMessage, fullPath } const params = JSON.parse(
await readFile(customizerPath, { encoding: 'ascii' })
).parameters
await writeFiles(
[
{
file: JSON.stringify({
customizerParams: params,
consoleMessage,
}),
fileName: 'metadata.json',
},
],
tempFile
)
await runCommand(
`cat ${stlPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
15000
)
return { consoleMessage, fullPath, customizerPath }
} catch (error) { } catch (error) {
return { error, fullPath } return { error, fullPath }
} }

View File

@@ -14,6 +14,7 @@
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^1.0.0", "@headlessui/react": "^1.0.0",
"@heroicons/react": "^1.0.4",
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.0",
"@monaco-editor/react": "^4.0.11", "@monaco-editor/react": "^4.0.11",
"@react-three/drei": "^7.3.1", "@react-three/drei": "^7.3.1",

View File

@@ -271,7 +271,7 @@ function parseParams(script){
caption = caption.substring(0,idx).trim() caption = caption.substring(0,idx).trim()
} }
defs.push({name, type: 'group', caption, ...def.options}) defs.push({name, type: 'group', caption, ...def.options})
}else{ }else{
const idx = line.indexOf('/') const idx = line.indexOf('/')
if(idx === -1){ if(idx === -1){
@@ -316,7 +316,7 @@ function parseComment(comment, line){
const prefix = comment.substring(0,2) const prefix = comment.substring(0,2)
if(prefix === '//') comment = comment.substring(2) if(prefix === '//') comment = comment.substring(2)
if(prefix === '/*') comment = comment.substring(2, comment.length-2) if(prefix === '/*') comment = comment.substring(2, comment.length-2)
comment = comment.trim() comment = comment.trim()
const ret = {} const ret = {}
@@ -331,7 +331,7 @@ function parseComment(comment, line){
} }
comment = comment.substring(0,idx).trim() comment = comment.substring(0,idx).trim()
} }
ret.caption = comment ret.caption = comment
return ret return ret
@@ -347,7 +347,7 @@ function parseDef(code, line){
return {name:code, type:'text'} return {name:code, type:'text'}
}else{ }else{
let initial = code.substring(idx+1).trim() let initial = code.substring(idx+1).trim()
const ret = {type:'text', name:code.substring(0,idx).trim()} const ret = {type:'text', name:code.substring(0,idx).trim()}
if(initial === 'true' || initial === 'false'){ if(initial === 'true' || initial === 'false'){
@@ -394,7 +394,7 @@ const makeScriptWorker = ({callback, convertToSolids})=>{
solids = [] solids = []
function flatten(arr){ function flatten(arr){
if(arr){ if(arr){
if(arr instanceof Array) if(arr instanceof Array)
arr.forEach(flatten) arr.forEach(flatten)
else else
solids.push(arr) solids.push(arr)
@@ -454,7 +454,6 @@ const makeScriptWorker = ({callback, convertToSolids})=>{
} }
}) })
} }
console.log('paramsDef', paramsDef)
if(paramsDef.length) callback({action:'parameterDefinitions', worker:'main', data:paramsDef}) if(paramsDef.length) callback({action:'parameterDefinitions', worker:'main', data:paramsDef})
runMain(params) runMain(params)

View File

@@ -1,3 +1,6 @@
import { Listbox, Transition } from '@headlessui/react'
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid'
import { useRender } from 'src/components/IdeWrapper/useRender' import { useRender } from 'src/components/IdeWrapper/useRender'
import { useIdeContext } from 'src/helpers/hooks/useIdeContext' import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
import { Switch } from '@headlessui/react' import { Switch } from '@headlessui/react'
@@ -6,6 +9,8 @@ import {
CadhubStringParam, CadhubStringParam,
CadhubBooleanParam, CadhubBooleanParam,
CadhubNumberParam, CadhubNumberParam,
CadhubStringChoiceParam,
CadhubNumberChoiceParam,
} from './customizerConverter' } from './customizerConverter'
const Customizer = () => { const Customizer = () => {
@@ -79,13 +84,22 @@ const Customizer = () => {
{customizerParams.map((param, index) => { {customizerParams.map((param, index) => {
const otherProps = { const otherProps = {
value: currentParameters[param.name], value: currentParameters[param.name],
onChange: (value) => updateCustomizerParam(param.name, value), onChange: (value) =>
updateCustomizerParam(
param.name,
param.type == 'number' ? Number(value) : value
),
} }
if (param.type === 'string') { if (
param.input === 'choice-string' ||
param.input === 'choice-number'
) {
return <ChoiceParam key={index} param={param} {...otherProps} />
} else if (param.input === 'default-string') {
return <StringParam key={index} param={param} {...otherProps} /> return <StringParam key={index} param={param} {...otherProps} />
} else if (param.type === 'number') { } else if (param.input === 'default-number') {
return <NumberParam key={index} param={param} {...otherProps} /> return <NumberParam key={index} param={param} {...otherProps} />
} else if (param.type === 'boolean') { } else if (param.input === 'default-boolean') {
return <BooleanParam key={index} param={param} {...otherProps} /> return <BooleanParam key={index} param={param} {...otherProps} />
} }
return <div key={index}>{JSON.stringify(param)}</div> return <div key={index}>{JSON.stringify(param)}</div>
@@ -128,7 +142,7 @@ function BooleanParam({
}: { }: {
param: CadhubBooleanParam param: CadhubBooleanParam
value: any value: any
onChange: Function onChange: (value: any) => void
}) { }) {
return ( return (
<CustomizerParamBase name={param.name} caption={param.caption}> <CustomizerParamBase name={param.name} caption={param.caption}>
@@ -158,7 +172,7 @@ function StringParam({
}: { }: {
param: CadhubStringParam param: CadhubStringParam
value: any value: any
onChange: Function onChange: (value: any) => void
}) { }) {
return ( return (
<CustomizerParamBase name={param.name} caption={param.caption}> <CustomizerParamBase name={param.name} caption={param.caption}>
@@ -173,6 +187,79 @@ function StringParam({
) )
} }
function ChoiceParam({
param,
value,
onChange,
}: {
param: CadhubStringChoiceParam | CadhubNumberChoiceParam
value: any
onChange: (value: any) => void
}) {
return (
<CustomizerParamBase name={param.name} caption={param.caption}>
<Listbox value={value} onChange={onChange}>
<div className="relative mt-1">
<Listbox.Button className="relative w-full h-8 text-left cursor-default focus:outline-none focus-visible:ring-2 focus-visible:ring-opacity-75 focus-visible:ring-white focus-visible:ring-offset-orange-300 focus-visible:ring-offset-2 focus-visible:border-indigo-500 sm:text-sm border border-ch-gray-300 px-2 text-sm">
<span className="block truncate">{value}</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-1 pointer-events-none">
<SelectorIcon
className="w-5 h-5 text-gray-300"
aria-hidden="true"
/>
</span>
</Listbox.Button>
<Transition
as={React.Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute w-full py-1 mt-1 bg-ch-gray-600 bg-opacity-80 overflow-auto text-base rounded-sm shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{param.options.map((option, optionIdx) => (
<Listbox.Option
key={optionIdx}
className={({ active }) =>
`${
active
? 'text-ch-blue-600 bg-ch-gray-700'
: 'text-ch-gray-300'
}
cursor-default select-none relative py-2 pl-10 pr-4`
}
value={option.value}
>
{({ selected, active }) => (
<>
<span
className={`${
selected ? 'font-medium' : 'font-normal'
} block truncate`}
>
{option.name}
</span>
{selected ? (
<span
className={`${
active ? 'text-ch-blue-600' : 'text-ch-gray-300'
}
absolute inset-y-0 left-0 flex items-center pl-3`}
>
<CheckIcon className="w-5 h-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</Listbox>
</CustomizerParamBase>
)
}
function NumberParam({ function NumberParam({
param, param,
value, value,
@@ -180,10 +267,10 @@ function NumberParam({
}: { }: {
param: CadhubNumberParam param: CadhubNumberParam
value: any value: any
onChange: Function onChange: (value: any) => void
}) { }) {
const [isFocused, isFocusedSetter] = React.useState(false) const [isFocused, isFocusedSetter] = React.useState(false)
const [localValue, localValueSetter] = React.useState(0) const [localValue, localValueSetter] = React.useState(value)
const [isLocked, isLockedSetter] = React.useState(false) const [isLocked, isLockedSetter] = React.useState(false)
const [pixelsDragged, pixelsDraggedSetter] = React.useState(0) const [pixelsDragged, pixelsDraggedSetter] = React.useState(0)
const step = param.step || 1 const step = param.step || 1

View File

@@ -1,24 +1,45 @@
// CadHub // CadHub
type CadhubTypeNames = 'number' | 'string' | 'boolean' type CadhubTypeNames = 'number' | 'string' | 'boolean'
type CadhubInputNames =
| 'default-number'
| 'default-string'
| 'default-boolean'
| 'choice-string'
| 'choice-number'
export interface CadhubStringOption {
name: string
value: string
}
export interface CadhubNumberOption {
name: string
value: number
}
interface CadhubParamBase { interface CadhubParamBase {
type: CadhubTypeNames type: CadhubTypeNames
caption: string caption: string
name: string name: string
input: CadhubInputNames
} }
export interface CadhubStringParam extends CadhubParamBase { export interface CadhubStringParam extends CadhubParamBase {
type: 'string' type: 'string'
input: 'default-string'
initial: string initial: string
placeholder?: string placeholder?: string
maxLength?: number maxLength?: number
} }
export interface CadhubBooleanParam extends CadhubParamBase { export interface CadhubBooleanParam extends CadhubParamBase {
type: 'boolean' type: 'boolean'
input: 'default-boolean'
initial?: boolean initial?: boolean
} }
export interface CadhubNumberParam extends CadhubParamBase { export interface CadhubNumberParam extends CadhubParamBase {
type: 'number' type: 'number'
input: 'default-number'
initial: number initial: number
min?: number min?: number
max?: number max?: number
@@ -26,95 +47,22 @@ export interface CadhubNumberParam extends CadhubParamBase {
decimal?: number decimal?: number
} }
export interface CadhubStringChoiceParam extends CadhubParamBase {
type: 'string'
input: 'choice-string'
initial: string
options: Array<CadhubStringOption>
}
export interface CadhubNumberChoiceParam extends CadhubParamBase {
type: 'number'
input: 'choice-number'
initial: number
options: Array<CadhubNumberOption>
}
export type CadhubParams = export type CadhubParams =
| CadhubStringParam | CadhubStringParam
| CadhubBooleanParam | CadhubBooleanParam
| CadhubNumberParam | CadhubNumberParam
| CadhubStringChoiceParam
// OpenSCAD | CadhubNumberChoiceParam
const openscadValues = `
// slider widget for number with max. value
sliderWithMax =34; // [50]
// slider widget for number in range
sliderWithRange =34; // [10:100]
//step slider for number
stepSlider=2; //[0:5:100]
// slider widget for number in range
sliderCentered =0; // [-10:0.1:10]
// spinbox with step size 1
Spinbox= 5;
// Text box for string
String="hello";
// Text box for string with length 8
String2="length"; //8
//description
Variable = true;
`
const openscadConverted: CadhubParams[] = [
{
type: 'number',
name: 'sliderWithMax',
caption: 'slider widget for number with max. value',
initial: 34,
step: 1,
max: 50,
},
{
type: 'number',
name: 'sliderWithRange',
caption: 'slider widget for number in range',
initial: 34,
step: 1,
min: 10,
max: 100,
},
{
type: 'number',
name: 'stepSlider',
caption: 'step slider for number',
initial: 2,
step: 5,
min: 0,
max: 100,
},
{
type: 'number',
name: 'sliderCentered',
caption: 'slider widget for number in range',
initial: 0,
step: 0.1,
min: -10,
max: 10,
},
{
type: 'number',
name: 'Spinbox',
caption: 'spinbox with step size 1',
initial: 5,
step: 1,
},
{
type: 'string',
name: 'String',
caption: 'Text box for string',
initial: 'hello',
},
{
type: 'string',
name: 'String2',
caption: 'Text box for string with length 8',
initial: 'length',
maxLength: 8,
},
{ type: 'boolean', name: 'Variable', caption: 'description', initial: true },
]

View File

@@ -22,7 +22,7 @@ const IdeConsole = () => {
{time?.toLocaleString()} {time?.toLocaleString()}
</div> </div>
<div className={(type === 'error' ? 'text-red-400' : '') + ' pl-4'}> <div className={(type === 'error' ? 'text-red-400' : '') + ' pl-4'}>
{message.split('\n').map((line, index) => { {(message || '').split('\n').map((line, index) => {
return ( return (
<div key={index}> <div key={index}>
{line.startsWith('ECHO:') ? ( {line.startsWith('ECHO:') ? (

View File

@@ -6,6 +6,7 @@ import {
timeoutErrorMessage, timeoutErrorMessage,
RenderArgs, RenderArgs,
DefaultKernelExport, DefaultKernelExport,
splitGziped,
} from './common' } from './common'
export const render: DefaultKernelExport['render'] = async ({ export const render: DefaultKernelExport['render'] = async ({
@@ -41,11 +42,19 @@ export const render: DefaultKernelExport['render'] = async ({
return createUnhealthyResponse(new Date(), timeoutErrorMessage) return createUnhealthyResponse(new Date(), timeoutErrorMessage)
} }
const data = await response.json() const data = await response.json()
const geometry = await stlToGeometry(data.url) const newData = await fetch(data.url).then(async (a) => {
const blob = await a.blob()
const text = await new Response(blob).text()
const { consoleMessage } = splitGziped(text)
return {
data: await stlToGeometry(window.URL.createObjectURL(blob)),
consoleMessage,
}
})
return createHealthyResponse({ return createHealthyResponse({
type: 'geometry', type: 'geometry',
data: geometry, data: newData.data,
consoleMessage: data.consoleMessage, consoleMessage: newData.consoleMessage,
date: new Date(), date: new Date(),
}) })
} catch (e) { } catch (e) {

View File

@@ -13,8 +13,8 @@ export const stlToGeometry = (url) =>
export interface RenderArgs { export interface RenderArgs {
code: State['code'] code: State['code']
parameters?: RawCustomizerParams
settings: { settings: {
parameters?: RawCustomizerParams
camera: State['camera'] camera: State['camera']
viewerSize: State['viewerSize'] viewerSize: State['viewerSize']
quality: State['objectData']['quality'] quality: State['objectData']['quality']
@@ -103,3 +103,16 @@ export type RenderResponse = HealthyResponse | ErrorResponse
export interface DefaultKernelExport { export interface DefaultKernelExport {
render: (arg: RenderArgs) => Promise<RenderResponse> render: (arg: RenderArgs) => Promise<RenderResponse>
} }
export const splitGziped = (text: string) => {
const concatSplitStr = 'cadhub-concat-split'
const splitIndex = text.indexOf(concatSplitStr)
const json = text.slice(splitIndex + concatSplitStr.length)
try {
return JSON.parse(json)
} catch (e) {
console.log(json, e)
return {}
}
Irev-Dev commented 2021-08-26 23:24:48 +02:00 (Migrated from github.com)
Review

This is what splits and pulls the json meta off the concatenated file.

This is what splits and pulls the json meta off the concatenated file.
hrgdavor commented 2021-08-26 23:40:49 +02:00 (Migrated from github.com)
Review

it is ok for now, you control both sides, so It can be improved later

it is ok for now, you control both sides, so It can be improved later
}

View File

@@ -1,7 +1,7 @@
import { DefaultKernelExport } from './common' import { DefaultKernelExport } from './common'
import type { CadPackage } from 'src/helpers/hooks/useIdeState' import type { CadPackage } from 'src/helpers/hooks/useIdeState'
import openscad from './openScadController' import openscad from './openScad/openScadController'
import cadquery from './cadQueryController' import cadquery from './cadQueryController'
import jscad from './jsCad/jsCadController' import jscad from './jsCad/jsCadController'

View File

@@ -130,7 +130,6 @@ const workerHelper = new WorkerHelper()
export const render: DefaultKernelExport['render'] = async ({ export const render: DefaultKernelExport['render'] = async ({
code, code,
parameters,
settings, settings,
}: RenderArgs) => { }: RenderArgs) => {
if (!scriptWorker) { if (!scriptWorker) {
@@ -169,11 +168,11 @@ export const render: DefaultKernelExport['render'] = async ({
} }
}) })
workerHelper.resolver() workerHelper.resolver(null)
scriptWorker.postMessage({ action: 'init', baseURI, alias: [] }) scriptWorker.postMessage({ action: 'init', baseURI, alias: [] })
} }
return workerHelper.render(code, parameters) return workerHelper.render(code, settings.parameters)
} }
const jsCadController: DefaultKernelExport = { const jsCadController: DefaultKernelExport = {

View File

@@ -1,4 +1,10 @@
import { CadhubParams } from 'src/components/Customizer/customizerConverter' import {
CadhubNumberChoiceParam,
CadhubNumberOption,
CadhubParams,
CadhubStringChoiceParam,
CadhubStringOption,
} from 'src/components/Customizer/customizerConverter'
type JscadTypeNames = type JscadTypeNames =
| 'group' | 'group'
@@ -19,6 +25,7 @@ interface JscadParamBase {
type: JscadTypeNames type: JscadTypeNames
caption: string caption: string
name: string name: string
initial?: number | string | boolean
} }
interface JscadGroupParam extends JscadParamBase { interface JscadGroupParam extends JscadParamBase {
type: 'group' type: 'group'
@@ -81,14 +88,18 @@ type JsCadParams =
export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] { export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] {
return input return input
.map((param): CadhubParams => { .map((param): CadhubParams => {
const common: { caption: string; name: string } = {
caption: param.caption,
name: param.name,
}
switch (param.type) { switch (param.type) {
case 'slider': case 'slider':
case 'number': case 'number':
case 'int': case 'int':
return { return {
type: 'number', type: 'number',
caption: param.caption, input: 'default-number',
name: param.name, ...common,
initial: param.initial, initial: param.initial,
min: param.min, min: param.min,
max: param.max, max: param.max,
@@ -103,8 +114,8 @@ export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] {
case 'date': case 'date':
return { return {
type: 'string', type: 'string',
caption: param.caption, input: 'default-string',
name: param.name, ...common,
initial: param.initial, initial: param.initial,
placeholder: placeholder:
param.type === 'text' || param.type === 'text' ||
@@ -120,10 +131,39 @@ export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] {
case 'checkbox': case 'checkbox':
return { return {
type: 'boolean', type: 'boolean',
caption: param.caption, input: 'default-boolean',
name: param.name, ...common,
initial: !!param.initial, initial: !!param.initial,
} }
case 'choice':
case 'radio':
if (typeof param.values[0] === 'number') {
const options: Array<CadhubNumberOption> = []
const captions = param.captions || param.values
param.values.forEach((value, i) => {
options[i] = { name: String(captions[i]), value: Number(value) }
})
return {
type: 'number',
input: 'choice-number',
...common,
initial: Number(param.initial),
options,
}
} else {
const options: Array<CadhubStringOption> = []
const captions = param.captions || param.values
param.values.forEach((value, i) => {
options[i] = { name: String(captions[i]), value: String(value) }
})
return {
type: 'string',
input: 'choice-string',
...common,
initial: String(param.initial),
options,
}
}
} }
}) })
.filter((a) => a) .filter((a) => a)

View File

@@ -5,7 +5,9 @@ import {
createUnhealthyResponse, createUnhealthyResponse,
timeoutErrorMessage, timeoutErrorMessage,
RenderArgs, RenderArgs,
} from './common' splitGziped,
} from '../common'
import { openScadToCadhubParams } from './openScadParams'
export const render = async ({ code, settings }: RenderArgs) => { export const render = async ({ code, settings }: RenderArgs) => {
const pixelRatio = window.devicePixelRatio || 1 const pixelRatio = window.devicePixelRatio || 1
@@ -17,6 +19,7 @@ export const render = async ({ code, settings }: RenderArgs) => {
const body = JSON.stringify({ const body = JSON.stringify({
settings: { settings: {
size, size,
parameters: settings.parameters,
camera: { camera: {
// rounding to give our caching a chance to sometimes work // rounding to give our caching a chance to sometimes work
...settings.camera, ...settings.camera,
@@ -56,15 +59,25 @@ export const render = async ({ code, settings }: RenderArgs) => {
} }
const data = await response.json() const data = await response.json()
const type = data.type !== 'stl' ? 'png' : 'geometry' const type = data.type !== 'stl' ? 'png' : 'geometry'
const newData = const newData = await fetch(data.url).then(async (a) => {
data.type !== 'stl' const blob = await a.blob()
? fetch(data.url).then((a) => a.blob()) const text = await new Response(blob).text()
: stlToGeometry(data.url) const { consoleMessage, customizerParams } = splitGziped(text)
return {
data:
data.type !== 'stl'
? blob
: await stlToGeometry(window.URL.createObjectURL(blob)),
consoleMessage,
customizerParams,
}
})
return createHealthyResponse({ return createHealthyResponse({
type, type,
data: await newData, data: newData.data,
consoleMessage: data.consoleMessage, consoleMessage: newData.consoleMessage,
date: new Date(), date: new Date(),
customizerParams: openScadToCadhubParams(newData.customizerParams || []),
}) })
} catch (e) { } catch (e) {
return createUnhealthyResponse(new Date()) return createUnhealthyResponse(new Date())
@@ -93,12 +106,22 @@ export const stl = async ({ code, settings }: RenderArgs) => {
return createUnhealthyResponse(new Date(), timeoutErrorMessage) return createUnhealthyResponse(new Date(), timeoutErrorMessage)
} }
const data = await response.json() const data = await response.json()
const geometry = await stlToGeometry(data.url) const newData = await fetch(data.url).then(async (a) => {
const blob = await a.blob()
const text = await new Response(blob).text()
const { consoleMessage, customizerParams } = splitGziped(text)
return {
data: await stlToGeometry(window.URL.createObjectURL(blob)),
consoleMessage,
customizerParams,
}
})
return createHealthyResponse({ return createHealthyResponse({
type: 'geometry', type: 'geometry',
data: geometry, data: newData.data,
consoleMessage: data.consoleMessage, consoleMessage: newData.consoleMessage,
date: new Date(), date: new Date(),
customizerParams: openScadToCadhubParams(newData.customizerParams || []),
}) })
} catch (e) { } catch (e) {
return createUnhealthyResponse(new Date()) return createUnhealthyResponse(new Date())

View File

@@ -0,0 +1,102 @@
import { CadhubParams } from 'src/components/Customizer/customizerConverter'
interface OpenScadParamsBase {
caption: string
name: string
group: string
initial: number | string | boolean | number[]
type: 'string' | 'number' | 'boolean'
}
interface OpenScadNumberParam extends OpenScadParamsBase {
type: 'number'
initial: number | number[]
max?: number
min?: number
step?: number
options?: { name: string; value: number }[]
}
interface OpenScadStringParam extends OpenScadParamsBase {
type: 'string'
initial: string
maxLength?: number
options?: { name: string; value: string }[]
}
interface OpenScadBooleanParam extends OpenScadParamsBase {
type: 'boolean'
initial: boolean
}
export type OpenScadParams =
| OpenScadNumberParam
| OpenScadStringParam
| OpenScadBooleanParam
export function openScadToCadhubParams(
input: OpenScadParams[]
): CadhubParams[] {
return input
.map((param): CadhubParams => {
const common: { caption: string; name: string } = {
caption: param.caption,
name: param.name,
}
switch (param.type) {
case 'boolean':
return {
type: 'boolean',
input: 'default-boolean',
...common,
initial: param.initial,
}
case 'string':
if (!Array.isArray(param?.options)) {
return {
type: 'string',
input: 'default-string',
...common,
initial: param.initial,
maxLength: param.maxLength,
}
} else {
return {
type: 'string',
input: 'choice-string',
...common,
initial: param.initial,
options: param.options,
}
}
case 'number':
if (
!Array.isArray(param?.options) &&
!Array.isArray(param?.initial)
) {
return {
type: 'number',
input: 'default-number',
...common,
initial: param.initial,
min: param.min,
max: param.max,
step: param.step,
}
} else if (
Array.isArray(param?.options) &&
!Array.isArray(param?.initial)
) {
return {
type: 'number',
input: 'choice-number',
...common,
initial: param.initial,
options: param.options,
}
} // TODO else vector
break
default:
return
}
})
.filter((a) => a)
}

View File

@@ -51,21 +51,21 @@ show_object(result)
const jscad = require('@jscad/modeling') const jscad = require('@jscad/modeling')
// https://openjscad.xyz/docs/module-modeling_primitives.html // https://openjscad.xyz/docs/module-modeling_primitives.html
const { circle, rectangle, cube, cuboid, sphere, cylinder } = jscad.primitives const { cuboid, cylinder } = jscad.primitives
const { rotate, scale, translate } = jscad.transforms const { rotate, translate } = jscad.transforms
const { degToRad } = jscad.utils // because jscad uses radians for rotations const { degToRad } = jscad.utils // because jscad uses radians for rotations
const { colorize } = jscad.colors
// https://openjscad.xyz/docs/module-modeling_booleans.html // https://openjscad.xyz/docs/module-modeling_booleans.html
const { union, intersect, subtract } = jscad.booleans const { subtract } = jscad.booleans
function main({//@jscad-params function main({//@jscad-params
// Box example // Box example
width=40, // Width width=40, // Width
length=20, // Length length=20, // Length
height=10, // Height height=10, // Height
hole=3,// Hole for cables diameter (0=no hole) hole=3,// Hole for cables diameter (0=no hole)
wall=1, // wall {min:0.5, step:0.5} wall=1, // wall {min:0.5, step:0.5}
flip=0, // print orientation {type: 'choice', values: [0, 90, 180]}
}){ }){
let wallOffset = wall * 2 let wallOffset = wall * 2
@@ -77,8 +77,8 @@ function main({//@jscad-params
model = subtract( model, model = subtract( model,
translate([width/2-wall/2], rotate([0, degToRad(90), 0 ], cylinder({radius:hole/2, height:wall}))) translate([width/2-wall/2], rotate([0, degToRad(90), 0 ], cylinder({radius:hole/2, height:wall})))
) )
} }
return rotate([0,0, degToRad(90)], model) return rotate([degToRad(flip), 0, degToRad(90)], model)
} }
module.exports = {main} module.exports = {main}
@@ -281,8 +281,8 @@ export const requestRender = ({
: cadPackages[state.ideType].render : cadPackages[state.ideType].render
return renderFn({ return renderFn({
code, code,
parameters,
settings: { settings: {
parameters,
camera, camera,
viewerSize, viewerSize,
quality, quality,

View File

@@ -1917,6 +1917,11 @@
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.2.0.tgz#e48652bfce82ddf73d7f331faeb9db6526ee6874" resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.2.0.tgz#e48652bfce82ddf73d7f331faeb9db6526ee6874"
integrity sha512-19DkLz8gDgbi+WvkoTzi9vs0NK9TJf94vbYhMzB4LYJo03Kili0gmvXT9CiKZoxXZ7YAvy/b1U1oQKEnjWrqxw== integrity sha512-19DkLz8gDgbi+WvkoTzi9vs0NK9TJf94vbYhMzB4LYJo03Kili0gmvXT9CiKZoxXZ7YAvy/b1U1oQKEnjWrqxw==
"@heroicons/react@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.4.tgz#11847eb2ea5510419d7ada9ff150a33af0ad0863"
integrity sha512-3kOrTmo8+Z8o6AL0rzN82MOf8J5CuxhRLFhpI8mrn+3OqekA6d5eb1GYO3EYYo1Vn6mYQSMNTzCWbEwUInb0cQ==
"@iarna/toml@^2.2.5": "@iarna/toml@^2.2.5":
version "2.2.5" version "2.2.5"
resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c"