Initial work to support curv (#578)
* Initial work to support curv * Correct the initial code file location * Preview and stl mvp working * Prepare changes for review and preview build * Run curv inside of /tmp When exporting an stl it writes temporary files which is not allowed when deployed to aws unless it's in temp. * Lock in specific curv commit for reproducible builds see: https://discord.com/channels/412182089279209474/886321278821216277/912507472441401385 * Add curv to backend schema * Frontend changes to accommodate curv deploy * Use vcount instead of vsize, as it's independant of geometry size, This is good for CadHub usecase where we don't know anything about the user's project * Final tweaks for deploy virtual screen size does matter,and curv is a little more memory hungry than the other functions * Format project Co-authored-by: lf94 <inbox@leefallat.ca> Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
This commit was merged in pull request #578.
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"Cadhub",
|
"Cadhub",
|
||||||
|
"cadquery",
|
||||||
|
"curv",
|
||||||
"Customizer",
|
"Customizer",
|
||||||
"Hutten",
|
"Hutten",
|
||||||
"cadquery",
|
|
||||||
"jscad",
|
"jscad",
|
||||||
"openscad",
|
"openscad",
|
||||||
"sendmail"
|
"sendmail"
|
||||||
|
|||||||
2
app/api/db/migrations/20211129205924_curv/migration.sql
Normal file
2
app/api/db/migrations/20211129205924_curv/migration.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "CadPackage" ADD VALUE 'curv';
|
||||||
@@ -37,7 +37,8 @@ model User {
|
|||||||
enum CadPackage {
|
enum CadPackage {
|
||||||
openscad
|
openscad
|
||||||
cadquery
|
cadquery
|
||||||
jscad // TODO #422, add jscad to db schema when were ready to enable saving of jscad projects
|
jscad
|
||||||
|
curv
|
||||||
}
|
}
|
||||||
|
|
||||||
model Project {
|
model Project {
|
||||||
|
|||||||
@@ -34,8 +34,12 @@ const makeRequest = (route, port) => [
|
|||||||
|
|
||||||
app.post(...makeRequest('/openscad/preview', 5052))
|
app.post(...makeRequest('/openscad/preview', 5052))
|
||||||
app.post(...makeRequest('/openscad/stl', 5053))
|
app.post(...makeRequest('/openscad/stl', 5053))
|
||||||
|
|
||||||
app.post(...makeRequest('/cadquery/stl', 5060))
|
app.post(...makeRequest('/cadquery/stl', 5060))
|
||||||
|
|
||||||
|
app.post(...makeRequest('/curv/preview', 5070))
|
||||||
|
app.post(...makeRequest('/curv/stl', 5071))
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Example app listening at http://localhost:${port}`)
|
console.log(`Example app listening at http://localhost:${port}`)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,12 +9,16 @@ const stl = async (req, _context, callback) => {
|
|||||||
console.log('eventBody', eventBody)
|
console.log('eventBody', eventBody)
|
||||||
|
|
||||||
const { file, settings } = JSON.parse(eventBody)
|
const { file, settings } = JSON.parse(eventBody)
|
||||||
const { error, consoleMessage, fullPath } = await runCQ({ file, settings })
|
const { error, consoleMessage, fullPath, tempFile } = await runCQ({
|
||||||
|
file,
|
||||||
|
settings,
|
||||||
|
})
|
||||||
await storeAssetAndReturnUrl({
|
await storeAssetAndReturnUrl({
|
||||||
error,
|
error,
|
||||||
callback,
|
callback,
|
||||||
fullPath,
|
fullPath,
|
||||||
consoleMessage,
|
consoleMessage,
|
||||||
|
tempFile,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const runCQ = async ({
|
|||||||
15000,
|
15000,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
return { consoleMessage, fullPath }
|
return { consoleMessage, fullPath, tempFile }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { error: consoleMessage || error, fullPath }
|
return { error: consoleMessage || error, fullPath }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,11 +104,13 @@ export async function storeAssetAndReturnUrl({
|
|||||||
callback,
|
callback,
|
||||||
fullPath,
|
fullPath,
|
||||||
consoleMessage,
|
consoleMessage,
|
||||||
|
tempFile,
|
||||||
}: {
|
}: {
|
||||||
error: string
|
error: string
|
||||||
callback: Function
|
callback: Function
|
||||||
fullPath: string
|
fullPath: string
|
||||||
consoleMessage: string
|
consoleMessage: string
|
||||||
|
tempFile: string
|
||||||
}) {
|
}) {
|
||||||
if (error) {
|
if (error) {
|
||||||
const response = {
|
const response = {
|
||||||
@@ -124,6 +126,7 @@ export async function storeAssetAndReturnUrl({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
buffer = await readFile(fullPath, { encoding: 'base64' })
|
buffer = await readFile(fullPath, { encoding: 'base64' })
|
||||||
|
await runCommand(`rm -R /tmp/${tempFile}`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('read file error', e)
|
console.log('read file error', e)
|
||||||
const response = {
|
const response = {
|
||||||
|
|||||||
67
app/api/src/docker/curv/Dockerfile
Normal file
67
app/api/src/docker/curv/Dockerfile
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
FROM public.ecr.aws/lts/ubuntu:20.04_stable
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
|
||||||
|
RUN apt-get update --fix-missing -qq
|
||||||
|
RUN apt-get update --fix-missing && apt-get -y -qq install software-properties-common dirmngr apt-transport-https lsb-release ca-certificates xvfb
|
||||||
|
|
||||||
|
RUN apt-get update -qq
|
||||||
|
|
||||||
|
RUN apt-get -y -qq install git \
|
||||||
|
software-properties-common \
|
||||||
|
xvfb unzip maim clang cmake \
|
||||||
|
git-core libboost-all-dev \
|
||||||
|
libopenexr-dev libtbb-dev \
|
||||||
|
libglm-dev libpng-dev \
|
||||||
|
libeigen3-dev dbus-x11 \
|
||||||
|
libxcursor-dev libxinerama-dev \
|
||||||
|
libxrandr-dev libglu1-mesa-dev \
|
||||||
|
libgles2-mesa-dev libgl1-mesa-dev \
|
||||||
|
libxi-dev
|
||||||
|
|
||||||
|
# Use commit to make sure build is reproduceable
|
||||||
|
RUN git clone --recursive https://github.com/curv3d/curv && \
|
||||||
|
cd curv && \
|
||||||
|
git checkout b849eb57fba121f9f218dc065dc1f5ebc619836d && \
|
||||||
|
make && make install
|
||||||
|
|
||||||
|
# install node14, see comment at the top of node14source_setup.sh
|
||||||
|
ADD src/docker/common/node14source_setup.sh /nodesource_setup.sh
|
||||||
|
RUN ["chmod", "+x", "/nodesource_setup.sh"]
|
||||||
|
RUN bash nodesource_setup.sh
|
||||||
|
RUN apt-get install -y nodejs
|
||||||
|
|
||||||
|
# Install aws-lambda-cpp build dependencies, this is for the post install script in aws-lambda-ric (in package.json)
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y \
|
||||||
|
g++ \
|
||||||
|
make \
|
||||||
|
cmake \
|
||||||
|
unzip \
|
||||||
|
automake autoconf libtool \
|
||||||
|
libcurl4-openssl-dev
|
||||||
|
|
||||||
|
# Add the lambda emulator for local dev, (see entrypoint.sh for where it's used),
|
||||||
|
# I have the file locally (gitignored) to speed up build times (as it downloads everytime),
|
||||||
|
# but you can use the http version of the below ADD command or download it yourself from that url.
|
||||||
|
ADD src/docker/common/aws-lambda-rie /usr/local/bin/aws-lambda-rie
|
||||||
|
# ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/download/v1.0/aws-lambda-rie /usr/local/bin/aws-lambda-rie
|
||||||
|
RUN ["chmod", "+x", "/usr/local/bin/aws-lambda-rie"]
|
||||||
|
|
||||||
|
WORKDIR /var/task/
|
||||||
|
COPY package*.json /var/task/
|
||||||
|
RUN npm install
|
||||||
|
RUN npm install aws-lambda-ric@1.0.0
|
||||||
|
|
||||||
|
RUN echo "cadhub-concat-split" > /var/task/cadhub-concat-split
|
||||||
|
|
||||||
|
# using built javascript from dist
|
||||||
|
# run `yarn rw build` before bulding this image
|
||||||
|
COPY dist/docker/curv/* /var/task/js/
|
||||||
|
COPY dist/docker/common/* /var/task/common/
|
||||||
|
COPY src/docker/common/entrypoint.sh /entrypoint.sh
|
||||||
|
RUN ["chmod", "+x", "/entrypoint.sh"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["sh", "/entrypoint.sh"]
|
||||||
|
CMD [ "js/curv.preview" ]
|
||||||
48
app/api/src/docker/curv/curv.ts
Normal file
48
app/api/src/docker/curv/curv.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { runCurv, stlExport } from './runCurv'
|
||||||
|
import middy from 'middy'
|
||||||
|
import { cors } from 'middy/middlewares'
|
||||||
|
import { loggerWrap, storeAssetAndReturnUrl } from '../common/utils'
|
||||||
|
|
||||||
|
const preview = async (req, _context, callback) => {
|
||||||
|
_context.callbackWaitsForEmptyEventLoop = false
|
||||||
|
const eventBody = Buffer.from(req.body, 'base64').toString('ascii')
|
||||||
|
console.log('eventBody', eventBody)
|
||||||
|
|
||||||
|
const { file, settings } = JSON.parse(eventBody)
|
||||||
|
const { error, consoleMessage, fullPath, tempFile } = await runCurv({
|
||||||
|
file,
|
||||||
|
settings,
|
||||||
|
})
|
||||||
|
await storeAssetAndReturnUrl({
|
||||||
|
error,
|
||||||
|
callback,
|
||||||
|
fullPath,
|
||||||
|
consoleMessage,
|
||||||
|
tempFile,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const stl = async (req, _context, callback) => {
|
||||||
|
_context.callbackWaitsForEmptyEventLoop = false
|
||||||
|
const eventBody = Buffer.from(req.body, 'base64').toString('ascii')
|
||||||
|
|
||||||
|
console.log(eventBody, 'eventBody')
|
||||||
|
|
||||||
|
const { file, settings } = JSON.parse(eventBody)
|
||||||
|
const { error, consoleMessage, fullPath, tempFile } = await stlExport({
|
||||||
|
file,
|
||||||
|
settings,
|
||||||
|
})
|
||||||
|
await storeAssetAndReturnUrl({
|
||||||
|
error,
|
||||||
|
callback,
|
||||||
|
fullPath,
|
||||||
|
consoleMessage,
|
||||||
|
tempFile,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
stl: middy(loggerWrap(stl)).use(cors()),
|
||||||
|
preview: middy(loggerWrap(preview)).use(cors()),
|
||||||
|
}
|
||||||
114
app/api/src/docker/curv/runCurv.ts
Normal file
114
app/api/src/docker/curv/runCurv.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { writeFiles, runCommand } from '../common/utils'
|
||||||
|
import { nanoid } from 'nanoid'
|
||||||
|
|
||||||
|
export const runCurv = async ({
|
||||||
|
file,
|
||||||
|
settings: { size: { x = 500, y = 500 } = {}, parameters } = {}, // TODO add view settings
|
||||||
|
} = {}): Promise<{
|
||||||
|
error?: string
|
||||||
|
consoleMessage?: string
|
||||||
|
fullPath?: string
|
||||||
|
customizerPath?: string
|
||||||
|
tempFile?: string
|
||||||
|
}> => {
|
||||||
|
const tempFile = await writeFiles(
|
||||||
|
[
|
||||||
|
{ file, fileName: 'main.curv' },
|
||||||
|
{
|
||||||
|
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 imPath = `/tmp/${tempFile}/output.png`
|
||||||
|
const customizerPath = `/tmp/${tempFile}/customizer.param`
|
||||||
|
|
||||||
|
const command = [
|
||||||
|
'xvfb-run --auto-servernum --server-args "-screen 0 3840x2160x24" curv',
|
||||||
|
`-o ${imPath}`,
|
||||||
|
`-O xsize=${x}`,
|
||||||
|
`-O ysize=${y}`,
|
||||||
|
`-O bg=webRGB[26,26,29]`, // #1A1A1D
|
||||||
|
`/tmp/${tempFile}/main.curv`,
|
||||||
|
].join(' ')
|
||||||
|
console.log('command', command)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const consoleMessage = await runCommand(command, 15000)
|
||||||
|
await writeFiles(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
file: JSON.stringify({
|
||||||
|
consoleMessage,
|
||||||
|
type: 'png',
|
||||||
|
}),
|
||||||
|
fileName: 'metadata.json',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tempFile
|
||||||
|
)
|
||||||
|
await runCommand(
|
||||||
|
`cat ${imPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
|
||||||
|
15000
|
||||||
|
)
|
||||||
|
return { consoleMessage, fullPath, customizerPath, tempFile }
|
||||||
|
} catch (dirtyError) {
|
||||||
|
return { error: dirtyError }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stlExport = async ({ file, settings: { parameters } } = {}) => {
|
||||||
|
const tempFile = await writeFiles(
|
||||||
|
[
|
||||||
|
{ file, fileName: 'main.curv' },
|
||||||
|
{
|
||||||
|
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 command = [
|
||||||
|
'(cd /tmp && curv',
|
||||||
|
'-o',
|
||||||
|
stlPath,
|
||||||
|
'-O jit',
|
||||||
|
'-O vcount=350000',
|
||||||
|
`/tmp/${tempFile}/main.curv`,
|
||||||
|
')',
|
||||||
|
].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)
|
||||||
|
await writeFiles(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
file: JSON.stringify({
|
||||||
|
consoleMessage,
|
||||||
|
type: 'stl',
|
||||||
|
}),
|
||||||
|
fileName: 'metadata.json',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tempFile
|
||||||
|
)
|
||||||
|
await runCommand(
|
||||||
|
`cat ${stlPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath} && rm ${stlPath}`,
|
||||||
|
15000
|
||||||
|
)
|
||||||
|
return { consoleMessage, fullPath, tempFile }
|
||||||
|
} catch (error) {
|
||||||
|
return { error, fullPath }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,3 +45,28 @@ services:
|
|||||||
AWS_ACCESS_KEY_ID: "${DEV_AWS_ACCESS_KEY_ID}"
|
AWS_ACCESS_KEY_ID: "${DEV_AWS_ACCESS_KEY_ID}"
|
||||||
BUCKET: "${DEV_BUCKET}"
|
BUCKET: "${DEV_BUCKET}"
|
||||||
|
|
||||||
|
curv-preview:
|
||||||
|
build:
|
||||||
|
context: ../../
|
||||||
|
dockerfile: ./src/docker/curv/Dockerfile
|
||||||
|
image: curv
|
||||||
|
command: js/curv.preview
|
||||||
|
# Adding volumes so that the containers can be restarted for js only changes in local dev
|
||||||
|
volumes:
|
||||||
|
- ../../dist/docker/curv:/var/task/js/
|
||||||
|
- ../../dist/docker/common:/var/task/common/
|
||||||
|
ports:
|
||||||
|
- "5070:8080"
|
||||||
|
curv-stl:
|
||||||
|
build:
|
||||||
|
context: ../../
|
||||||
|
dockerfile: ./src/docker/curv/Dockerfile
|
||||||
|
image: curv
|
||||||
|
command: js/curv.stl
|
||||||
|
# Adding volumes so that the containers can be restarted for js only changes in local dev
|
||||||
|
volumes:
|
||||||
|
- ../../dist/docker/curv:/var/task/js/
|
||||||
|
- ../../dist/docker/common:/var/task/common/
|
||||||
|
ports:
|
||||||
|
- "5071:8080"
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const preview = async (req, _context, callback) => {
|
|||||||
console.log('eventBody', eventBody)
|
console.log('eventBody', eventBody)
|
||||||
|
|
||||||
const { file, settings } = JSON.parse(eventBody)
|
const { file, settings } = JSON.parse(eventBody)
|
||||||
const { error, consoleMessage, fullPath } = await runScad({
|
const { error, consoleMessage, fullPath, tempFile } = await runScad({
|
||||||
file,
|
file,
|
||||||
settings,
|
settings,
|
||||||
})
|
})
|
||||||
@@ -18,6 +18,7 @@ const preview = async (req, _context, callback) => {
|
|||||||
callback,
|
callback,
|
||||||
fullPath,
|
fullPath,
|
||||||
consoleMessage,
|
consoleMessage,
|
||||||
|
tempFile,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ const stl = async (req, _context, callback) => {
|
|||||||
console.log(eventBody, 'eventBody')
|
console.log(eventBody, 'eventBody')
|
||||||
|
|
||||||
const { file, settings } = JSON.parse(eventBody)
|
const { file, settings } = JSON.parse(eventBody)
|
||||||
const { error, consoleMessage, fullPath } = await stlExport({
|
const { error, consoleMessage, fullPath, tempFile } = await stlExport({
|
||||||
file,
|
file,
|
||||||
settings,
|
settings,
|
||||||
})
|
})
|
||||||
@@ -37,6 +38,7 @@ const stl = async (req, _context, callback) => {
|
|||||||
callback,
|
callback,
|
||||||
fullPath,
|
fullPath,
|
||||||
consoleMessage,
|
consoleMessage,
|
||||||
|
tempFile,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const runScad = async ({
|
|||||||
consoleMessage?: string
|
consoleMessage?: string
|
||||||
fullPath?: string
|
fullPath?: string
|
||||||
customizerPath?: string
|
customizerPath?: string
|
||||||
|
tempFile?: string
|
||||||
}> => {
|
}> => {
|
||||||
const tempFile = await writeFiles(
|
const tempFile = await writeFiles(
|
||||||
[
|
[
|
||||||
@@ -88,7 +89,7 @@ export const runScad = async ({
|
|||||||
`cat ${imPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
|
`cat ${imPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
|
||||||
15000
|
15000
|
||||||
)
|
)
|
||||||
return { consoleMessage, fullPath, customizerPath }
|
return { consoleMessage, fullPath, customizerPath, tempFile }
|
||||||
} catch (dirtyError) {
|
} catch (dirtyError) {
|
||||||
return { error: cleanOpenScadError(dirtyError) }
|
return { error: cleanOpenScadError(dirtyError) }
|
||||||
}
|
}
|
||||||
@@ -143,7 +144,7 @@ export const stlExport = async ({ file, settings: { parameters } } = {}) => {
|
|||||||
`cat ${stlPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
|
`cat ${stlPath} /var/task/cadhub-concat-split /tmp/${tempFile}/metadata.json | gzip > ${fullPath}`,
|
||||||
15000
|
15000
|
||||||
)
|
)
|
||||||
return { consoleMessage, fullPath, customizerPath }
|
return { consoleMessage, fullPath, customizerPath, tempFile }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { error, fullPath }
|
return { error, fullPath }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ provider:
|
|||||||
cadqueryimage:
|
cadqueryimage:
|
||||||
path: ../../
|
path: ../../
|
||||||
file: ./src/docker/cadquery/Dockerfile
|
file: ./src/docker/cadquery/Dockerfile
|
||||||
|
curvimage:
|
||||||
|
path: ../../
|
||||||
|
file: ./src/docker/curv/Dockerfile
|
||||||
apiGateway:
|
apiGateway:
|
||||||
metrics: true
|
metrics: true
|
||||||
binaryMediaTypes:
|
binaryMediaTypes:
|
||||||
@@ -84,6 +87,7 @@ functions:
|
|||||||
timeout: 30
|
timeout: 30
|
||||||
environment:
|
environment:
|
||||||
BUCKET: cad-preview-bucket-prod-001
|
BUCKET: cad-preview-bucket-prod-001
|
||||||
|
|
||||||
cadquerystl:
|
cadquerystl:
|
||||||
image:
|
image:
|
||||||
name: cadqueryimage
|
name: cadqueryimage
|
||||||
@@ -99,6 +103,34 @@ functions:
|
|||||||
timeout: 30
|
timeout: 30
|
||||||
environment:
|
environment:
|
||||||
BUCKET: cad-preview-bucket-prod-001
|
BUCKET: cad-preview-bucket-prod-001
|
||||||
|
|
||||||
|
curvpreview:
|
||||||
|
image:
|
||||||
|
name: curvimage
|
||||||
|
command:
|
||||||
|
- js/curv.preview
|
||||||
|
entryPoint:
|
||||||
|
- '/entrypoint.sh'
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: curv/preview
|
||||||
|
method: post
|
||||||
|
cors: true
|
||||||
|
timeout: 25
|
||||||
|
memorySize: 3008
|
||||||
|
curvstl:
|
||||||
|
image:
|
||||||
|
name: curvimage
|
||||||
|
command:
|
||||||
|
- js/curv.stl
|
||||||
|
entryPoint:
|
||||||
|
- '/entrypoint.sh'
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: curv/stl
|
||||||
|
method: post
|
||||||
|
cors: true
|
||||||
|
timeout: 30
|
||||||
# The following are a few example events you can configure
|
# The following are a few example events you can configure
|
||||||
# NOTE: Please make sure to change your handler code to work with those events
|
# NOTE: Please make sure to change your handler code to work with those events
|
||||||
# Check the event documentation for details
|
# Check the event documentation for details
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ export const schema = gql`
|
|||||||
childForks: [Project]!
|
childForks: [Project]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# should match enum in api/db/schema.prisma
|
||||||
enum CadPackage {
|
enum CadPackage {
|
||||||
openscad
|
openscad
|
||||||
cadquery
|
cadquery
|
||||||
jscad
|
jscad
|
||||||
|
curv
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module.exports = (config, { env }) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
test: /\.(md|jscad\.js|py|scad)$/i,
|
test: /\.(md|jscad\.js|py|scad|curv)$/i,
|
||||||
use: 'raw-loader',
|
use: 'raw-loader',
|
||||||
});
|
});
|
||||||
return config
|
return config
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type CadPackageType = 'openscad' | 'cadquery' | 'jscad' | 'INIT'
|
export type CadPackageType = 'openscad' | 'cadquery' | 'jscad' | 'curv' | 'INIT'
|
||||||
|
|
||||||
interface CadPackageConfig {
|
interface CadPackageConfig {
|
||||||
label: string
|
label: string
|
||||||
@@ -23,6 +23,11 @@ export const cadPackageConfigs: { [key in CadPackageType]: CadPackageConfig } =
|
|||||||
buttonClasses: 'bg-ch-purple-500',
|
buttonClasses: 'bg-ch-purple-500',
|
||||||
dotClasses: 'bg-yellow-300',
|
dotClasses: 'bg-yellow-300',
|
||||||
},
|
},
|
||||||
|
curv: {
|
||||||
|
label: 'Curv',
|
||||||
|
buttonClasses: 'bg-blue-600',
|
||||||
|
dotClasses: 'bg-green-500',
|
||||||
|
},
|
||||||
INIT: {
|
INIT: {
|
||||||
label: '',
|
label: '',
|
||||||
buttonClasses: '',
|
buttonClasses: '',
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const CaptureButtonViewer = ({
|
|||||||
const threeInstance = React.useRef(null)
|
const threeInstance = React.useRef(null)
|
||||||
const [dataType, dataTypeSetter] = useState(state?.objectData?.type)
|
const [dataType, dataTypeSetter] = useState(state?.objectData?.type)
|
||||||
const [artifact, artifactSetter] = useState(state?.objectData?.data)
|
const [artifact, artifactSetter] = useState(state?.objectData?.data)
|
||||||
|
const [ideType] = useState(state?.ideType)
|
||||||
const [isLoading, isLoadingSetter] = useState(false)
|
const [isLoading, isLoadingSetter] = useState(false)
|
||||||
const [camera, cameraSetter] = useState<State['camera'] | null>(null)
|
const [camera, cameraSetter] = useState<State['camera'] | null>(null)
|
||||||
const getThreeInstance = (_threeInstance) => {
|
const getThreeInstance = (_threeInstance) => {
|
||||||
@@ -33,7 +34,7 @@ const CaptureButtonViewer = ({
|
|||||||
}
|
}
|
||||||
const onCameraChange = (camera, isFirstCameraChange) => {
|
const onCameraChange = (camera, isFirstCameraChange) => {
|
||||||
const renderPromise =
|
const renderPromise =
|
||||||
state.ideType === 'openscad' &&
|
(state.ideType === 'openscad' || state.ideType === 'curv') &&
|
||||||
requestRenderStateless({
|
requestRenderStateless({
|
||||||
state,
|
state,
|
||||||
camera,
|
camera,
|
||||||
@@ -70,6 +71,7 @@ const CaptureButtonViewer = ({
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
camera={camera}
|
camera={camera}
|
||||||
isMinimal
|
isMinimal
|
||||||
|
ideType={ideType}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ export const makeStlDownloadHandler =
|
|||||||
} else {
|
} else {
|
||||||
thunkDispatch((dispatch, getState) => {
|
thunkDispatch((dispatch, getState) => {
|
||||||
const state = getState()
|
const state = getState()
|
||||||
const specialCadProcess = ideType === 'openscad' && 'stl'
|
const specialCadProcess =
|
||||||
|
(ideType === 'openscad' || ideType === 'curv') && 'stl'
|
||||||
dispatch({ type: 'setLoading' })
|
dispatch({ type: 'setLoading' })
|
||||||
requestRender({
|
requestRender({
|
||||||
state,
|
state,
|
||||||
|
|||||||
@@ -348,6 +348,10 @@ function ChooseYourCharacter() {
|
|||||||
cadPackage: 'jscad',
|
cadPackage: 'jscad',
|
||||||
desc: 'A JavaScript Code-CAD library that will feel familiar to web developers, based on the same tech as OpenSCAD.',
|
desc: 'A JavaScript Code-CAD library that will feel familiar to web developers, based on the same tech as OpenSCAD.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
cadPackage: 'curv',
|
||||||
|
desc: "Curv is a programming language for creating art using mathematics. It's a 2D and 3D geometric modelling tool.",
|
||||||
|
},
|
||||||
].map(
|
].map(
|
||||||
({
|
({
|
||||||
cadPackage,
|
cadPackage,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const IdeEditor = ({ Loading }) => {
|
|||||||
cadquery: 'python',
|
cadquery: 'python',
|
||||||
openscad: 'cpp',
|
openscad: 'cpp',
|
||||||
jscad: 'javascript',
|
jscad: 'javascript',
|
||||||
|
curv: 'python',
|
||||||
INIT: '',
|
INIT: '',
|
||||||
}
|
}
|
||||||
const monaco = useMonaco()
|
const monaco = useMonaco()
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export interface Project {
|
|||||||
code: string
|
code: string
|
||||||
mainImage: string
|
mainImage: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
cadPackage: 'openscad' | 'cadquery'
|
cadPackage: 'openscad' | 'cadquery' | 'jscad' | 'curv'
|
||||||
user: {
|
user: {
|
||||||
id: string
|
id: string
|
||||||
userName: string
|
userName: string
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const IdeViewer = ({
|
|||||||
const { state, thunkDispatch } = useIdeContext()
|
const { state, thunkDispatch } = useIdeContext()
|
||||||
const dataType = state.objectData?.type
|
const dataType = state.objectData?.type
|
||||||
const artifact = state.objectData?.data
|
const artifact = state.objectData?.data
|
||||||
|
const ideType = state.ideType
|
||||||
|
|
||||||
const onInit = (threeInstance) => {
|
const onInit = (threeInstance) => {
|
||||||
thunkDispatch({ type: 'setThreeInstance', payload: threeInstance })
|
thunkDispatch({ type: 'setThreeInstance', payload: threeInstance })
|
||||||
@@ -24,7 +25,12 @@ const IdeViewer = ({
|
|||||||
})
|
})
|
||||||
thunkDispatch((dispatch, getState) => {
|
thunkDispatch((dispatch, getState) => {
|
||||||
const state = getState()
|
const state = getState()
|
||||||
if (['png', 'INIT'].includes(state?.objectData?.type)) {
|
if (
|
||||||
|
['png', 'INIT'].includes(state?.objectData?.type) &&
|
||||||
|
(ideType === 'openscad' ||
|
||||||
|
state?.objectData?.type === 'INIT' ||
|
||||||
|
!state?.objectData?.type)
|
||||||
|
) {
|
||||||
dispatch({ type: 'setLoading' })
|
dispatch({ type: 'setLoading' })
|
||||||
requestRender({
|
requestRender({
|
||||||
state,
|
state,
|
||||||
@@ -44,6 +50,7 @@ const IdeViewer = ({
|
|||||||
onCameraChange={onCameraChange}
|
onCameraChange={onCameraChange}
|
||||||
isLoading={state.isLoading}
|
isLoading={state.isLoading}
|
||||||
camera={state?.camera}
|
camera={state?.camera}
|
||||||
|
ideType={ideType}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ export function PureIdeViewer({
|
|||||||
isMinimal = false,
|
isMinimal = false,
|
||||||
scadRatio = 1,
|
scadRatio = 1,
|
||||||
camera,
|
camera,
|
||||||
|
ideType,
|
||||||
}: {
|
}: {
|
||||||
dataType: 'INIT' | ArtifactTypes
|
dataType: 'INIT' | ArtifactTypes
|
||||||
artifact: any
|
artifact: any
|
||||||
@@ -178,6 +179,7 @@ export function PureIdeViewer({
|
|||||||
isMinimal?: boolean
|
isMinimal?: boolean
|
||||||
scadRatio?: number
|
scadRatio?: number
|
||||||
camera?: State['camera']
|
camera?: State['camera']
|
||||||
|
ideType?: State['ideType']
|
||||||
}) {
|
}) {
|
||||||
const [isDragging, setIsDragging] = useState(false)
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
const [image, setImage] = useState()
|
const [image, setImage] = useState()
|
||||||
@@ -216,7 +218,9 @@ export function PureIdeViewer({
|
|||||||
)}
|
)}
|
||||||
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
|
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
|
||||||
className={`opacity-0 absolute inset-0 transition-opacity duration-500 ${
|
className={`opacity-0 absolute inset-0 transition-opacity duration-500 ${
|
||||||
!(isDragging || dataType !== 'png')
|
ideType === 'curv' && dataType === 'png' // TODO hide axes while curve doesn't have a controllable camera
|
||||||
|
? 'opacity-0'
|
||||||
|
: !(isDragging || dataType !== 'png')
|
||||||
? 'hover:opacity-50'
|
? 'hover:opacity-50'
|
||||||
: 'opacity-100'
|
: 'opacity-100'
|
||||||
}`}
|
}`}
|
||||||
@@ -226,7 +230,10 @@ export function PureIdeViewer({
|
|||||||
<Controls
|
<Controls
|
||||||
onDragStart={() => setIsDragging(true)}
|
onDragStart={() => setIsDragging(true)}
|
||||||
onInit={onInit}
|
onInit={onInit}
|
||||||
onCameraChange={onCameraChange}
|
onCameraChange={(...args) => {
|
||||||
|
onCameraChange(...args)
|
||||||
|
setIsDragging(false)
|
||||||
|
}}
|
||||||
controlsRef={controlsRef}
|
controlsRef={controlsRef}
|
||||||
camera={camera}
|
camera={camera}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -95,6 +95,13 @@ const menuOptions: {
|
|||||||
dotClasses: 'bg-yellow-300',
|
dotClasses: 'bg-yellow-300',
|
||||||
ideType: 'jscad',
|
ideType: 'jscad',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Curv',
|
||||||
|
sub: 'alpha ',
|
||||||
|
bgClasses: 'bg-blue-600',
|
||||||
|
dotClasses: 'bg-green-500',
|
||||||
|
ideType: 'curv',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const NavPlusButton: React.FC = () => {
|
const NavPlusButton: React.FC = () => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { Camera } from 'src/helpers/hooks/useIdeState'
|
|||||||
|
|
||||||
export const lambdaBaseURL =
|
export const lambdaBaseURL =
|
||||||
process.env.CAD_LAMBDA_BASE_URL ||
|
process.env.CAD_LAMBDA_BASE_URL ||
|
||||||
'https://oxt2p7ddgj.execute-api.us-east-1.amazonaws.com/prod'
|
'https://2inlbple1b.execute-api.us-east-1.amazonaws.com/prod2'
|
||||||
|
|
||||||
export const stlToGeometry = (url) =>
|
export const stlToGeometry = (url) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
|
|||||||
103
app/web/src/helpers/cadPackages/curv/curvController.ts
Normal file
103
app/web/src/helpers/cadPackages/curv/curvController.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import {
|
||||||
|
lambdaBaseURL,
|
||||||
|
stlToGeometry,
|
||||||
|
createHealthyResponse,
|
||||||
|
createUnhealthyResponse,
|
||||||
|
timeoutErrorMessage,
|
||||||
|
RenderArgs,
|
||||||
|
splitGziped,
|
||||||
|
} from '../common'
|
||||||
|
|
||||||
|
export const render = async ({ code, settings }: RenderArgs) => {
|
||||||
|
const pixelRatio = window.devicePixelRatio || 1
|
||||||
|
const size = {
|
||||||
|
x: Math.round(settings.viewerSize?.width * pixelRatio),
|
||||||
|
y: Math.round(settings.viewerSize?.height * pixelRatio),
|
||||||
|
}
|
||||||
|
const body = JSON.stringify({
|
||||||
|
settings: {
|
||||||
|
size,
|
||||||
|
},
|
||||||
|
file: code,
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const response = await fetch(lambdaBaseURL + '/curv/preview', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
if (response.status === 400) {
|
||||||
|
const { error } = await response.json()
|
||||||
|
const cleanedErrorMessage = cleanError(error)
|
||||||
|
return createUnhealthyResponse(new Date(), cleanedErrorMessage)
|
||||||
|
}
|
||||||
|
if (response.status === 502) {
|
||||||
|
return createUnhealthyResponse(new Date(), timeoutErrorMessage)
|
||||||
|
}
|
||||||
|
const blob = await response.blob()
|
||||||
|
const text = await new Response(blob).text()
|
||||||
|
const { consoleMessage, type } = splitGziped(text)
|
||||||
|
return createHealthyResponse({
|
||||||
|
type: type !== 'stl' ? 'png' : 'geometry',
|
||||||
|
data:
|
||||||
|
type !== 'stl'
|
||||||
|
? blob
|
||||||
|
: await stlToGeometry(window.URL.createObjectURL(blob)),
|
||||||
|
consoleMessage,
|
||||||
|
date: new Date(),
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
return createUnhealthyResponse(new Date())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stl = async ({ code /*settings*/ }: RenderArgs) => {
|
||||||
|
const body = JSON.stringify({
|
||||||
|
settings: {},
|
||||||
|
file: code,
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const response = await fetch(lambdaBaseURL + '/curv/stl', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
if (response.status === 400) {
|
||||||
|
const { error } = await response.json()
|
||||||
|
const cleanedErrorMessage = cleanError(error)
|
||||||
|
return createUnhealthyResponse(new Date(), cleanedErrorMessage)
|
||||||
|
}
|
||||||
|
if (response.status === 502) {
|
||||||
|
return createUnhealthyResponse(new Date(), timeoutErrorMessage)
|
||||||
|
}
|
||||||
|
const blob = await response.blob()
|
||||||
|
const text = await new Response(blob).text()
|
||||||
|
const { consoleMessage, type } = splitGziped(text)
|
||||||
|
return createHealthyResponse({
|
||||||
|
type: type !== 'stl' ? 'png' : 'geometry',
|
||||||
|
data:
|
||||||
|
type !== 'stl'
|
||||||
|
? blob
|
||||||
|
: await stlToGeometry(window.URL.createObjectURL(blob)),
|
||||||
|
consoleMessage,
|
||||||
|
date: new Date(),
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
return createUnhealthyResponse(new Date())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const curv = {
|
||||||
|
render,
|
||||||
|
stl,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default curv
|
||||||
|
|
||||||
|
function cleanError(error) {
|
||||||
|
return error.replace(/["|']\/tmp\/.+\/main.curv["|']/g, "'main.curv'")
|
||||||
|
}
|
||||||
10
app/web/src/helpers/cadPackages/curv/initialCode.curv
Normal file
10
app/web/src/helpers/cadPackages/curv/initialCode.curv
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
let
|
||||||
|
N = 5;
|
||||||
|
C = red;
|
||||||
|
Twists = 6;
|
||||||
|
in
|
||||||
|
box [1,1,N]
|
||||||
|
>> colour C
|
||||||
|
>> twist (Twists*90*deg/N)
|
||||||
|
>> rotate {angle: 90*deg, axis: Y_axis}
|
||||||
|
>> bend{}
|
||||||
15
app/web/src/helpers/cadPackages/curv/userGuide.md
Normal file
15
app/web/src/helpers/cadPackages/curv/userGuide.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
title: Curv
|
||||||
|
Written with: [Domain-Specific Language](https://martinfowler.com/dsl.html)
|
||||||
|
Kernal type: Signed distance functions
|
||||||
|
Maintained by: [Doug Moen and contributors](https://github.com/curv/curv/graphs/contributors)
|
||||||
|
Documentation: [curv3d.org](https://curv3d.org)
|
||||||
|
---
|
||||||
|
|
||||||
|
Curv is a programming language for creating art using mathematics. It's a 2D and 3D geometric modelling tool that supports full colour, animation and 3D printing.
|
||||||
|
|
||||||
|
### [Examples](https://github.com/curv3d/curv/tree/master/examples)
|
||||||
|
|
||||||
|
- [Flog spiral](https://619b5e6c6689420008eedfe5--cadhubxyz.netlify.app/draft/curv#fetch_text_v1=https%3A%2F%2Fraw.githubusercontent.com%2Fcurv3d%2Fcurv%2Fmaster%2Fexamples%2Flog_spiral.curv)
|
||||||
|
- [Shreks donut](https://619b5e6c6689420008eedfe5--cadhubxyz.netlify.app/draft/curv#fetch_text_v1=https%3A%2F%2Fraw.githubusercontent.com%2Fcurv3d%2Fcurv%2Fmaster%2Fexamples%2Fshreks_donut.curv)
|
||||||
|
- [Wood grain](https://619b5e6c6689420008eedfe5--cadhubxyz.netlify.app/draft/curv#fetch_text_v1=https%3A%2F%2Fraw.githubusercontent.com%2Fcurv3d%2Fcurv%2Fmaster%2Fexamples%2Ffinial.curv)
|
||||||
@@ -13,16 +13,22 @@ import jscad from './jsCad/jsCadController'
|
|||||||
import jsCadGuide from 'src/helpers/cadPackages/jsCad/userGuide.md'
|
import jsCadGuide from 'src/helpers/cadPackages/jsCad/userGuide.md'
|
||||||
import jsCadInitialCode from 'src/helpers/cadPackages/jsCad/initialCode.jscad.js'
|
import jsCadInitialCode from 'src/helpers/cadPackages/jsCad/initialCode.jscad.js'
|
||||||
|
|
||||||
|
import curv from './curv/curvController'
|
||||||
|
import curvGuide from 'src/helpers/cadPackages/curv/userGuide.md'
|
||||||
|
import curvInitialCode from 'src/helpers/cadPackages/curv/initialCode.curv'
|
||||||
|
|
||||||
export const cadPackages: { [key in CadPackageType]: DefaultKernelExport } = {
|
export const cadPackages: { [key in CadPackageType]: DefaultKernelExport } = {
|
||||||
openscad,
|
openscad,
|
||||||
cadquery,
|
cadquery,
|
||||||
jscad,
|
jscad,
|
||||||
|
curv,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initGuideMap: { [key in CadPackageType]: string } = {
|
export const initGuideMap: { [key in CadPackageType]: string } = {
|
||||||
openscad: openScadGuide,
|
openscad: openScadGuide,
|
||||||
cadquery: cadQueryGuide,
|
cadquery: cadQueryGuide,
|
||||||
jscad: jsCadGuide,
|
jscad: jsCadGuide,
|
||||||
|
curv: curvGuide,
|
||||||
INIT: '',
|
INIT: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,5 +36,6 @@ export const initCodeMap: { [key in CadPackageType]: string } = {
|
|||||||
openscad: openScadInitialCode,
|
openscad: openScadInitialCode,
|
||||||
cadquery: cadQueryInitialCode,
|
cadquery: cadQueryInitialCode,
|
||||||
jscad: jsCadInitialCode,
|
jscad: jsCadInitialCode,
|
||||||
|
curv: curvInitialCode,
|
||||||
INIT: '',
|
INIT: '',
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user