Add customizer support for OpenSCAD

This also includes sending metadata and part of the concatenated gzip,
not in the s3 metadata as that has a 2kb limit.

Resolves #320
This commit is contained in:
Kurt Hutten
2021-08-25 18:57:28 +10:00
parent 5d79efbf15
commit 87f132a684
18 changed files with 388 additions and 227 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 }

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) {
function putConsoleMessageInMetadata(consoleMessage) { console.log(`file to write: ${files.length}`)
return {
[CONSOLE_MESSAGE_KEY]: Buffer.from(consoleMessage, 'utf-8').toString(
'base64'
),
}
}
function getConsoleMessageFromMetadata(metadata) {
return Buffer.from(metadata[CONSOLE_MESSAGE_KEY], 'base64').toString('utf-8')
}
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}`)
try {
await runCommand(`mkdir /tmp/${tempFile}`) await runCommand(`mkdir /tmp/${tempFile}`)
await writeFile(`/tmp/${tempFile}/main${extension}`, file) } catch (e) {
//
}
await Promise.all(
files.map(({ file, fileName }) =>
writeFile(`/tmp/${tempFile}/${fileName}`, 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
@@ -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

@@ -8,7 +8,6 @@ import {
CadhubNumberParam, CadhubNumberParam,
CadhubStringChoiceParam, CadhubStringChoiceParam,
CadhubNumberChoiceParam, CadhubNumberChoiceParam,
CadhubChoiceParam,
} from './customizerConverter' } from './customizerConverter'
const Customizer = () => { const Customizer = () => {
@@ -82,17 +81,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, param.type == 'number' ? Number(value) : value), onChange: (value) =>
updateCustomizerParam(
param.name,
param.type == 'number' ? Number(value) : value
),
} }
if(param.input === 'choice-string' || param.input === 'choice-number'){ if (
return <StringChoiceParam key={index} param={param} {...otherProps} /> param.input === 'choice-string' ||
// }else if(param.input === 'choice-number'){ param.input === 'choice-number'
// return <StringChoiceParam key={index} param={param} {...otherProps} /> ) {
}else if (param.type === 'string') { 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>
@@ -180,12 +184,12 @@ function StringParam({
) )
} }
function StringChoiceParam({ function ChoiceParam({
param, param,
value, value,
onChange, onChange,
}: { }: {
param: CadhubChoiceParam param: CadhubStringChoiceParam | CadhubNumberChoiceParam
value: any value: any
onChange: Function onChange: Function
}) { }) {
@@ -196,7 +200,11 @@ function StringChoiceParam({
value={value} value={value}
onChange={({ target }) => onChange(target?.value)} onChange={({ target }) => onChange(target?.value)}
> >
{param.options.map(opt=><option value={opt.value} key={opt.name}>{opt.name}</option>)} {param.options.map((opt) => (
<option value={opt.value} key={opt.name}>
{opt.name}
</option>
))}
</select> </select>
</CustomizerParamBase> </CustomizerParamBase>
) )

View File

@@ -1,7 +1,12 @@
// CadHub // CadHub
type CadhubTypeNames = 'number' | 'string' | 'boolean' type CadhubTypeNames = 'number' | 'string' | 'boolean'
type CadhubInputNames = 'default-number' | 'default-string' | 'default-boolean' | 'choice-string' | 'choice-number' type CadhubInputNames =
| 'default-number'
| 'default-string'
| 'default-boolean'
| 'choice-string'
| 'choice-number'
export interface CadhubStringOption { export interface CadhubStringOption {
name: string name: string
@@ -29,6 +34,7 @@ export interface CadhubStringParam extends CadhubParamBase {
} }
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 {
@@ -60,91 +66,3 @@ export type CadhubParams =
| CadhubNumberParam | CadhubNumberParam
| CadhubStringChoiceParam | CadhubStringChoiceParam
| CadhubNumberChoiceParam | CadhubNumberChoiceParam
// OpenSCAD
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 {}
}
}

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 { CadhubNumberChoiceParam, CadhubNumberOption, CadhubParams, CadhubStringChoiceParam, CadhubStringOption } 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,6 +88,10 @@ 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':
@@ -88,8 +99,7 @@ export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] {
return { return {
type: 'number', type: 'number',
input: 'default-number', input: 'default-number',
caption: param.caption, ...common,
name: param.name,
initial: param.initial, initial: param.initial,
min: param.min, min: param.min,
max: param.max, max: param.max,
@@ -105,8 +115,7 @@ export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] {
return { return {
type: 'string', type: 'string',
input: 'default-string', input: 'default-string',
caption: param.caption, ...common,
name: param.name,
initial: param.initial, initial: param.initial,
placeholder: placeholder:
param.type === 'text' || param.type === 'text' ||
@@ -123,43 +132,38 @@ export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] {
return { return {
type: 'boolean', type: 'boolean',
input: 'default-boolean', input: 'default-boolean',
caption: param.caption, ...common,
name: param.name,
initial: !!param.initial, initial: !!param.initial,
} }
case 'choice': case 'choice':
case 'radio': case 'radio':
if(typeof param.values[0] === 'number'){ if (typeof param.values[0] === 'number') {
let options:Array<CadhubNumberOption> = [] const options: Array<CadhubNumberOption> = []
let captions = param.captions || param.values const captions = param.captions || param.values
param.values.forEach((value,i)=>{ param.values.forEach((value, i) => {
options[i] = {name:String(captions[i]), value:Number(value)} options[i] = { name: String(captions[i]), value: Number(value) }
}) })
return { return {
type: 'number', type: 'number',
input: 'choice-number', input: 'choice-number',
caption: param.caption, ...common,
name: param.name,
initial: Number(param.initial), initial: Number(param.initial),
options options,
} }
}else{ } else {
let options:Array<CadhubStringOption> = [] const options: Array<CadhubStringOption> = []
let captions = param.captions || param.values const captions = param.captions || param.values
param.values.forEach((value,i)=>{ param.values.forEach((value, i) => {
options[i] = {name:String(captions[i]), value:String(value)} options[i] = { name: String(captions[i]), value: String(value) }
}) })
return { return {
type: 'string', type: 'string',
input: 'choice-string', input: 'choice-string',
caption: param.caption, ...common,
name: param.name,
initial: String(param.initial), initial: String(param.initial),
options 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) => {
const blob = await a.blob()
const text = await new Response(blob).text()
const { consoleMessage, customizerParams } = splitGziped(text)
return {
data:
data.type !== 'stl' data.type !== 'stl'
? fetch(data.url).then((a) => a.blob()) ? blob
: stlToGeometry(data.url) : 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,13 +51,12 @@ 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
@@ -66,6 +65,7 @@ function main({//@jscad-params
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
@@ -78,7 +78,7 @@ function main({//@jscad-params
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,