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 chmod +x cq-cli/cq-cli
RUN echo "cadhub-concat-split" > /var/task/cadhub-concat-split
COPY cadquery/*.js /var/task/
COPY common/*.js /var/common/

View File

@@ -31,7 +31,6 @@ const stl = async (req, _context, callback) => {
statusCode: 200,
body: JSON.stringify({
url: getObjectUrl(params, s3, tk),
consoleMessage: previousAsset.consoleMessage,
}),
}
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')
module.exports.runCQ = async ({
file,
settings: { deflection = 0.3 } = {},
} = {}) => {
const tempFile = await makeFile(file, '.py', nanoid)
const fullPath = `/tmp/${tempFile}/output.stl`
const tempFile = await writeFiles(
[{ 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 = [
`cq-cli/cq-cli`,
`--codec stl`,
`--infile /tmp/${tempFile}/main.py`,
`--outfile ${fullPath}`,
`--outfile ${stlPath}`,
`--outputopts "deflection:${deflection};angularDeflection:${deflection};"`,
`&& gzip ${fullPath}`,
].join(' ')
console.log('command', command)
try {
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 }
} catch (error) {
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 { createHash } = require('crypto')
const CONSOLE_MESSAGE_KEY = 'console-message-b64'
function putConsoleMessageInMetadata(consoleMessage) {
return {
[CONSOLE_MESSAGE_KEY]: Buffer.from(consoleMessage, 'utf-8').toString(
'base64'
),
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.
console.log(`file to write: ${files.length}`)
try {
await runCommand(`mkdir /tmp/${tempFile}`)
} catch (e) {
//
}
}
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}`)
await runCommand(`mkdir /tmp/${tempFile}`)
await writeFile(`/tmp/${tempFile}/main${extension}`, file)
await Promise.all(
files.map(({ file, fileName }) =>
writeFile(`/tmp/${tempFile}/${fileName}`, file)
)
)
return tempFile
}
@@ -54,10 +49,8 @@ function makeHash(script) {
async function checkIfAlreadyExists(params, s3) {
try {
const objectHead = await s3.headObject(params).promise()
const consoleMessage = getConsoleMessageFromMetadata(objectHead.Metadata)
console.log('consoleMessage', consoleMessage)
return { isAlreadyInBucket: true, consoleMessage }
await s3.headObject(params).promise()
return { isAlreadyInBucket: true }
} catch (e) {
console.log("couldn't find it", e)
return { isAlreadyInBucket: false }
@@ -117,7 +110,7 @@ async function storeAssetAndReturnUrl({
let buffer
try {
buffer = await readFile(`${fullPath}.gz`)
buffer = await readFile(fullPath)
} catch (e) {
console.log('read file error', e)
const response = {
@@ -136,7 +129,6 @@ async function storeAssetAndReturnUrl({
CacheControl: `max-age=${FiveDays}`, // browser caching to stop downloads of the same part
ContentType: 'text/stl',
ContentEncoding: 'gzip',
Metadata: putConsoleMessageInMetadata(consoleMessage),
})
.promise()
console.log('stored object', storedRender)
@@ -146,7 +138,6 @@ async function storeAssetAndReturnUrl({
statusCode: 200,
body: JSON.stringify({
url,
consoleMessage,
}),
}
callback(null, response)
@@ -156,7 +147,7 @@ async function storeAssetAndReturnUrl({
module.exports = {
runCommand,
makeFile,
writeFiles,
makeHash,
checkIfAlreadyExists,
getObjectUrl,

View File

@@ -6,10 +6,12 @@ ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update -qq
# 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 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 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
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 && \
unzip /var/task/openscad/1.0.4
# 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 common/*.js /var/common/

View File

@@ -57,8 +57,6 @@ const preview = async (req, _context, callback) => {
s3,
tk
),
consoleMessage:
previousAsset.consoleMessage || previousAssetPng.consoleMessage,
type,
}),
}
@@ -67,7 +65,10 @@ const preview = async (req, _context, callback) => {
}
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({
error,
callback,
@@ -100,14 +101,16 @@ const stl = async (req, _context, callback) => {
statusCode: 200,
body: JSON.stringify({
url: getObjectUrl({ ...params }, s3, tk),
consoleMessage: previousAsset.consoleMessage,
}),
}
callback(null, response)
return
}
const { file } = JSON.parse(eventBody)
const { error, consoleMessage, fullPath } = await stlExport({ file })
const { file, settings } = JSON.parse(eventBody)
const { error, consoleMessage, fullPath, customizerPath } = await stlExport({
file,
settings,
})
await storeAssetAndReturnUrl({
error,
callback,
@@ -121,6 +124,6 @@ const stl = async (req, _context, callback) => {
}
module.exports = {
stl: middy(stl).use(cors()),
stl: middy(loggerWrap(stl)).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 { 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 */
const cleanOpenScadError = (error) =>
@@ -11,6 +12,7 @@ module.exports.runScad = async ({
file,
settings: {
size: { x = 500, y = 500 } = {},
parameters,
camera: {
position = { x: 40, y: 40, z: 40 },
rotation = { x: 55, y: 0, z: 25 },
@@ -18,46 +20,113 @@ module.exports.runScad = async ({
} = {},
} = {}, // 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: px, y: py, z: pz } = position
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 = [
OPENSCAD_COMMON,
`-o ${fullPath}`,
`-o ${customizerPath}`,
`-o ${imPath}`,
`-p /tmp/${tempFile}/params.json -P default`,
cameraArg,
`--imgsize=${x},${y}`,
`--colorscheme CadHub`,
`/tmp/${tempFile}/main.scad`,
`&& gzip ${fullPath}`,
].join(' ')
console.log('command', command)
try {
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) {
const error = cleanOpenScadError(dirtyError)
return { error }
return { error: cleanOpenScadError(dirtyError) }
}
}
module.exports.stlExport = async ({ file } = {}) => {
const tempFile = await makeFile(file, '.scad', nanoid)
const fullPath = `/tmp/${tempFile}/output.stl`
module.exports.stlExport = async ({ file, settings: { parameters } } = {}) => {
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 fullPath = `/tmp/${tempFile}/output.gz`
const stlPath = `/tmp/${tempFile}/output.stl`
const customizerPath = `/tmp/${tempFile}/customizer.param`
const command = [
OPENSCAD_COMMON,
`--export-format=binstl`,
`-o ${fullPath}`,
// `--export-format=binstl`,
`-o ${customizerPath}`,
`-o ${stlPath}`,
`-p /tmp/${tempFile}/params.json -P default`,
`/tmp/${tempFile}/main.scad`,
`&& gzip ${fullPath}`,
].join(' ')
try {
// 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)
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) {
return { error, fullPath }
}

View File

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

View File

@@ -271,7 +271,7 @@ function parseParams(script){
caption = caption.substring(0,idx).trim()
}
defs.push({name, type: 'group', caption, ...def.options})
}else{
const idx = line.indexOf('/')
if(idx === -1){
@@ -316,7 +316,7 @@ function parseComment(comment, line){
const prefix = comment.substring(0,2)
if(prefix === '//') comment = comment.substring(2)
if(prefix === '/*') comment = comment.substring(2, comment.length-2)
comment = comment.trim()
const ret = {}
@@ -331,7 +331,7 @@ function parseComment(comment, line){
}
comment = comment.substring(0,idx).trim()
}
ret.caption = comment
return ret
@@ -347,7 +347,7 @@ function parseDef(code, line){
return {name:code, type:'text'}
}else{
let initial = code.substring(idx+1).trim()
const ret = {type:'text', name:code.substring(0,idx).trim()}
if(initial === 'true' || initial === 'false'){
@@ -394,7 +394,7 @@ const makeScriptWorker = ({callback, convertToSolids})=>{
solids = []
function flatten(arr){
if(arr){
if(arr instanceof Array)
if(arr instanceof Array)
arr.forEach(flatten)
else
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})
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 { useIdeContext } from 'src/helpers/hooks/useIdeContext'
import { Switch } from '@headlessui/react'
@@ -6,6 +9,8 @@ import {
CadhubStringParam,
CadhubBooleanParam,
CadhubNumberParam,
CadhubStringChoiceParam,
CadhubNumberChoiceParam,
} from './customizerConverter'
const Customizer = () => {
@@ -79,13 +84,22 @@ const Customizer = () => {
{customizerParams.map((param, index) => {
const otherProps = {
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} />
} else if (param.type === 'number') {
} else if (param.input === 'default-number') {
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 <div key={index}>{JSON.stringify(param)}</div>
@@ -128,7 +142,7 @@ function BooleanParam({
}: {
param: CadhubBooleanParam
value: any
onChange: Function
onChange: (value: any) => void
}) {
return (
<CustomizerParamBase name={param.name} caption={param.caption}>
@@ -158,7 +172,7 @@ function StringParam({
}: {
param: CadhubStringParam
value: any
onChange: Function
onChange: (value: any) => void
}) {
return (
<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({
param,
value,
@@ -180,10 +267,10 @@ function NumberParam({
}: {
param: CadhubNumberParam
value: any
onChange: Function
onChange: (value: any) => void
}) {
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 [pixelsDragged, pixelsDraggedSetter] = React.useState(0)
const step = param.step || 1

View File

@@ -1,24 +1,45 @@
// CadHub
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 {
type: CadhubTypeNames
caption: string
name: string
input: CadhubInputNames
}
export interface CadhubStringParam extends CadhubParamBase {
type: 'string'
input: 'default-string'
initial: string
placeholder?: string
maxLength?: number
}
export interface CadhubBooleanParam extends CadhubParamBase {
type: 'boolean'
input: 'default-boolean'
initial?: boolean
}
export interface CadhubNumberParam extends CadhubParamBase {
type: 'number'
input: 'default-number'
initial: number
min?: number
max?: number
@@ -26,95 +47,22 @@ export interface CadhubNumberParam extends CadhubParamBase {
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 =
| CadhubStringParam
| CadhubBooleanParam
| CadhubNumberParam
// 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 },
]
| CadhubStringChoiceParam
| CadhubNumberChoiceParam

View File

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

View File

@@ -6,6 +6,7 @@ import {
timeoutErrorMessage,
RenderArgs,
DefaultKernelExport,
splitGziped,
} from './common'
export const render: DefaultKernelExport['render'] = async ({
@@ -41,11 +42,19 @@ export const render: DefaultKernelExport['render'] = async ({
return createUnhealthyResponse(new Date(), timeoutErrorMessage)
}
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({
type: 'geometry',
data: geometry,
consoleMessage: data.consoleMessage,
data: newData.data,
consoleMessage: newData.consoleMessage,
date: new Date(),
})
} catch (e) {

View File

@@ -13,8 +13,8 @@ export const stlToGeometry = (url) =>
export interface RenderArgs {
code: State['code']
parameters?: RawCustomizerParams
settings: {
parameters?: RawCustomizerParams
camera: State['camera']
viewerSize: State['viewerSize']
quality: State['objectData']['quality']
@@ -103,3 +103,16 @@ export type RenderResponse = HealthyResponse | ErrorResponse
export interface DefaultKernelExport {
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 type { CadPackage } from 'src/helpers/hooks/useIdeState'
import openscad from './openScadController'
import openscad from './openScad/openScadController'
import cadquery from './cadQueryController'
import jscad from './jsCad/jsCadController'

View File

@@ -130,7 +130,6 @@ const workerHelper = new WorkerHelper()
export const render: DefaultKernelExport['render'] = async ({
code,
parameters,
settings,
}: RenderArgs) => {
if (!scriptWorker) {
@@ -169,11 +168,11 @@ export const render: DefaultKernelExport['render'] = async ({
}
})
workerHelper.resolver()
workerHelper.resolver(null)
scriptWorker.postMessage({ action: 'init', baseURI, alias: [] })
}
return workerHelper.render(code, parameters)
return workerHelper.render(code, settings.parameters)
}
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 =
| 'group'
@@ -19,6 +25,7 @@ interface JscadParamBase {
type: JscadTypeNames
caption: string
name: string
initial?: number | string | boolean
}
interface JscadGroupParam extends JscadParamBase {
type: 'group'
@@ -81,14 +88,18 @@ type JsCadParams =
export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] {
return input
.map((param): CadhubParams => {
const common: { caption: string; name: string } = {
caption: param.caption,
name: param.name,
}
switch (param.type) {
case 'slider':
case 'number':
case 'int':
return {
type: 'number',
caption: param.caption,
name: param.name,
input: 'default-number',
...common,
initial: param.initial,
min: param.min,
max: param.max,
@@ -103,8 +114,8 @@ export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] {
case 'date':
return {
type: 'string',
caption: param.caption,
name: param.name,
input: 'default-string',
...common,
initial: param.initial,
placeholder:
param.type === 'text' ||
@@ -120,10 +131,39 @@ export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] {
case 'checkbox':
return {
type: 'boolean',
caption: param.caption,
name: param.name,
input: 'default-boolean',
...common,
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)

View File

@@ -5,7 +5,9 @@ import {
createUnhealthyResponse,
timeoutErrorMessage,
RenderArgs,
} from './common'
splitGziped,
} from '../common'
import { openScadToCadhubParams } from './openScadParams'
export const render = async ({ code, settings }: RenderArgs) => {
const pixelRatio = window.devicePixelRatio || 1
@@ -17,6 +19,7 @@ export const render = async ({ code, settings }: RenderArgs) => {
const body = JSON.stringify({
settings: {
size,
parameters: settings.parameters,
camera: {
// rounding to give our caching a chance to sometimes work
...settings.camera,
@@ -56,15 +59,25 @@ export const render = async ({ code, settings }: RenderArgs) => {
}
const data = await response.json()
const type = data.type !== 'stl' ? 'png' : 'geometry'
const newData =
data.type !== 'stl'
? fetch(data.url).then((a) => a.blob())
: 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:
data.type !== 'stl'
? blob
: await stlToGeometry(window.URL.createObjectURL(blob)),
consoleMessage,
customizerParams,
}
})
return createHealthyResponse({
type,
data: await newData,
consoleMessage: data.consoleMessage,
data: newData.data,
consoleMessage: newData.consoleMessage,
date: new Date(),
customizerParams: openScadToCadhubParams(newData.customizerParams || []),
})
} catch (e) {
return createUnhealthyResponse(new Date())
@@ -93,12 +106,22 @@ export const stl = async ({ code, settings }: RenderArgs) => {
return createUnhealthyResponse(new Date(), timeoutErrorMessage)
}
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({
type: 'geometry',
data: geometry,
consoleMessage: data.consoleMessage,
data: newData.data,
consoleMessage: newData.consoleMessage,
date: new Date(),
customizerParams: openScadToCadhubParams(newData.customizerParams || []),
})
} catch (e) {
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')
// 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 { colorize } = jscad.colors
// https://openjscad.xyz/docs/module-modeling_booleans.html
const { union, intersect, subtract } = jscad.booleans
const { subtract } = jscad.booleans
function main({//@jscad-params
// Box example
width=40, // Width
length=20, // Length
length=20, // Length
height=10, // Height
hole=3,// Hole for cables diameter (0=no hole)
wall=1, // wall {min:0.5, step:0.5}
flip=0, // print orientation {type: 'choice', values: [0, 90, 180]}
}){
let wallOffset = wall * 2
@@ -77,8 +77,8 @@ function main({//@jscad-params
model = subtract( model,
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}
@@ -281,8 +281,8 @@ export const requestRender = ({
: cadPackages[state.ideType].render
return renderFn({
code,
parameters,
settings: {
parameters,
camera,
viewerSize,
quality,

View File

@@ -1917,6 +1917,11 @@
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.2.0.tgz#e48652bfce82ddf73d7f331faeb9db6526ee6874"
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":
version "2.2.5"
resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c"