diff --git a/.vscode/settings.json b/.vscode/settings.json index aaa161b..78eaf8e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,10 @@ { "cSpell.words": [ "Cadhub", + "cadquery", + "curv", "Customizer", "Hutten", - "cadquery", "jscad", "openscad", "sendmail" diff --git a/app/api/db/migrations/20211129205924_curv/migration.sql b/app/api/db/migrations/20211129205924_curv/migration.sql new file mode 100644 index 0000000..405c759 --- /dev/null +++ b/app/api/db/migrations/20211129205924_curv/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "CadPackage" ADD VALUE 'curv'; diff --git a/app/api/db/schema.prisma b/app/api/db/schema.prisma index d91dda8..e62813d 100644 --- a/app/api/db/schema.prisma +++ b/app/api/db/schema.prisma @@ -37,7 +37,8 @@ model User { enum CadPackage { openscad cadquery - jscad // TODO #422, add jscad to db schema when were ready to enable saving of jscad projects + jscad + curv } model Project { diff --git a/app/api/src/docker/aws-emulator.js b/app/api/src/docker/aws-emulator.js index 239d8a0..6b99ce7 100644 --- a/app/api/src/docker/aws-emulator.js +++ b/app/api/src/docker/aws-emulator.js @@ -34,8 +34,12 @@ const makeRequest = (route, port) => [ app.post(...makeRequest('/openscad/preview', 5052)) app.post(...makeRequest('/openscad/stl', 5053)) + app.post(...makeRequest('/cadquery/stl', 5060)) +app.post(...makeRequest('/curv/preview', 5070)) +app.post(...makeRequest('/curv/stl', 5071)) + app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`) }) diff --git a/app/api/src/docker/cadquery/cadquery.ts b/app/api/src/docker/cadquery/cadquery.ts index 9c41ba5..30399ca 100644 --- a/app/api/src/docker/cadquery/cadquery.ts +++ b/app/api/src/docker/cadquery/cadquery.ts @@ -9,12 +9,16 @@ const stl = async (req, _context, callback) => { console.log('eventBody', 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({ error, callback, fullPath, consoleMessage, + tempFile, }) } diff --git a/app/api/src/docker/cadquery/runCQ.ts b/app/api/src/docker/cadquery/runCQ.ts index 2f73e68..6a1db1d 100644 --- a/app/api/src/docker/cadquery/runCQ.ts +++ b/app/api/src/docker/cadquery/runCQ.ts @@ -53,7 +53,7 @@ export const runCQ = async ({ 15000, true ) - return { consoleMessage, fullPath } + return { consoleMessage, fullPath, tempFile } } catch (error) { return { error: consoleMessage || error, fullPath } } diff --git a/app/api/src/docker/common/utils.ts b/app/api/src/docker/common/utils.ts index 16cf418..d8e83cb 100644 --- a/app/api/src/docker/common/utils.ts +++ b/app/api/src/docker/common/utils.ts @@ -104,11 +104,13 @@ export async function storeAssetAndReturnUrl({ callback, fullPath, consoleMessage, + tempFile, }: { error: string callback: Function fullPath: string consoleMessage: string + tempFile: string }) { if (error) { const response = { @@ -124,6 +126,7 @@ export async function storeAssetAndReturnUrl({ try { buffer = await readFile(fullPath, { encoding: 'base64' }) + await runCommand(`rm -R /tmp/${tempFile}`) } catch (e) { console.log('read file error', e) const response = { diff --git a/app/api/src/docker/curv/Dockerfile b/app/api/src/docker/curv/Dockerfile new file mode 100644 index 0000000..3c6a8df --- /dev/null +++ b/app/api/src/docker/curv/Dockerfile @@ -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" ] diff --git a/app/api/src/docker/curv/curv.ts b/app/api/src/docker/curv/curv.ts new file mode 100644 index 0000000..cdcf3da --- /dev/null +++ b/app/api/src/docker/curv/curv.ts @@ -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()), +} diff --git a/app/api/src/docker/curv/runCurv.ts b/app/api/src/docker/curv/runCurv.ts new file mode 100644 index 0000000..cdf6c54 --- /dev/null +++ b/app/api/src/docker/curv/runCurv.ts @@ -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 } + } +} diff --git a/app/api/src/docker/docker-compose.yml b/app/api/src/docker/docker-compose.yml index d98992a..b6be985 100644 --- a/app/api/src/docker/docker-compose.yml +++ b/app/api/src/docker/docker-compose.yml @@ -45,3 +45,28 @@ services: AWS_ACCESS_KEY_ID: "${DEV_AWS_ACCESS_KEY_ID}" 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" + diff --git a/app/api/src/docker/openscad/openscad.ts b/app/api/src/docker/openscad/openscad.ts index 752c8c5..823f1a5 100644 --- a/app/api/src/docker/openscad/openscad.ts +++ b/app/api/src/docker/openscad/openscad.ts @@ -9,7 +9,7 @@ const preview = async (req, _context, callback) => { console.log('eventBody', eventBody) const { file, settings } = JSON.parse(eventBody) - const { error, consoleMessage, fullPath } = await runScad({ + const { error, consoleMessage, fullPath, tempFile } = await runScad({ file, settings, }) @@ -18,6 +18,7 @@ const preview = async (req, _context, callback) => { callback, fullPath, consoleMessage, + tempFile, }) } @@ -28,7 +29,7 @@ const stl = async (req, _context, callback) => { console.log(eventBody, 'eventBody') const { file, settings } = JSON.parse(eventBody) - const { error, consoleMessage, fullPath } = await stlExport({ + const { error, consoleMessage, fullPath, tempFile } = await stlExport({ file, settings, }) @@ -37,6 +38,7 @@ const stl = async (req, _context, callback) => { callback, fullPath, consoleMessage, + tempFile, }) } diff --git a/app/api/src/docker/openscad/runScad.ts b/app/api/src/docker/openscad/runScad.ts index c176931..de34352 100644 --- a/app/api/src/docker/openscad/runScad.ts +++ b/app/api/src/docker/openscad/runScad.ts @@ -25,6 +25,7 @@ export const runScad = async ({ consoleMessage?: string fullPath?: string customizerPath?: string + tempFile?: string }> => { 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}`, 15000 ) - return { consoleMessage, fullPath, customizerPath } + return { consoleMessage, fullPath, customizerPath, tempFile } } catch (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}`, 15000 ) - return { consoleMessage, fullPath, customizerPath } + return { consoleMessage, fullPath, customizerPath, tempFile } } catch (error) { return { error, fullPath } } diff --git a/app/api/src/docker/serverless.yml b/app/api/src/docker/serverless.yml index a033f88..f0a8ece 100644 --- a/app/api/src/docker/serverless.yml +++ b/app/api/src/docker/serverless.yml @@ -23,6 +23,9 @@ provider: cadqueryimage: path: ../../ file: ./src/docker/cadquery/Dockerfile + curvimage: + path: ../../ + file: ./src/docker/curv/Dockerfile apiGateway: metrics: true binaryMediaTypes: @@ -84,6 +87,7 @@ functions: timeout: 30 environment: BUCKET: cad-preview-bucket-prod-001 + cadquerystl: image: name: cadqueryimage @@ -99,6 +103,34 @@ functions: timeout: 30 environment: 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 # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/app/api/src/graphql/projects.sdl.ts b/app/api/src/graphql/projects.sdl.ts index 32e4890..5edd166 100644 --- a/app/api/src/graphql/projects.sdl.ts +++ b/app/api/src/graphql/projects.sdl.ts @@ -19,10 +19,12 @@ export const schema = gql` childForks: [Project]! } + # should match enum in api/db/schema.prisma enum CadPackage { openscad cadquery jscad + curv } type Query { diff --git a/app/web/config/webpack.config.js b/app/web/config/webpack.config.js index 103151b..f9b7db7 100644 --- a/app/web/config/webpack.config.js +++ b/app/web/config/webpack.config.js @@ -6,7 +6,7 @@ module.exports = (config, { env }) => { } }) config.module.rules.push({ - test: /\.(md|jscad\.js|py|scad)$/i, + test: /\.(md|jscad\.js|py|scad|curv)$/i, use: 'raw-loader', }); return config diff --git a/app/web/src/components/CadPackage/CadPackage.tsx b/app/web/src/components/CadPackage/CadPackage.tsx index 76ddb3c..1de9b53 100644 --- a/app/web/src/components/CadPackage/CadPackage.tsx +++ b/app/web/src/components/CadPackage/CadPackage.tsx @@ -1,4 +1,4 @@ -export type CadPackageType = 'openscad' | 'cadquery' | 'jscad' | 'INIT' +export type CadPackageType = 'openscad' | 'cadquery' | 'jscad' | 'curv' | 'INIT' interface CadPackageConfig { label: string @@ -23,6 +23,11 @@ export const cadPackageConfigs: { [key in CadPackageType]: CadPackageConfig } = buttonClasses: 'bg-ch-purple-500', dotClasses: 'bg-yellow-300', }, + curv: { + label: 'Curv', + buttonClasses: 'bg-blue-600', + dotClasses: 'bg-green-500', + }, INIT: { label: '', buttonClasses: '', @@ -49,7 +54,7 @@ const CadPackage = ({ diff --git a/app/web/src/components/CaptureButton/CaptureButton.tsx b/app/web/src/components/CaptureButton/CaptureButton.tsx index 598a0d3..b9a486d 100644 --- a/app/web/src/components/CaptureButton/CaptureButton.tsx +++ b/app/web/src/components/CaptureButton/CaptureButton.tsx @@ -25,6 +25,7 @@ const CaptureButtonViewer = ({ const threeInstance = React.useRef(null) const [dataType, dataTypeSetter] = useState(state?.objectData?.type) const [artifact, artifactSetter] = useState(state?.objectData?.data) + const [ideType] = useState(state?.ideType) const [isLoading, isLoadingSetter] = useState(false) const [camera, cameraSetter] = useState(null) const getThreeInstance = (_threeInstance) => { @@ -33,7 +34,7 @@ const CaptureButtonViewer = ({ } const onCameraChange = (camera, isFirstCameraChange) => { const renderPromise = - state.ideType === 'openscad' && + (state.ideType === 'openscad' || state.ideType === 'curv') && requestRenderStateless({ state, camera, @@ -70,6 +71,7 @@ const CaptureButtonViewer = ({ isLoading={isLoading} camera={camera} isMinimal + ideType={ideType} /> ) } diff --git a/app/web/src/components/EditorMenu/helpers.ts b/app/web/src/components/EditorMenu/helpers.ts index 466a58a..024ca0a 100644 --- a/app/web/src/components/EditorMenu/helpers.ts +++ b/app/web/src/components/EditorMenu/helpers.ts @@ -69,7 +69,8 @@ export const makeStlDownloadHandler = } else { thunkDispatch((dispatch, getState) => { const state = getState() - const specialCadProcess = ideType === 'openscad' && 'stl' + const specialCadProcess = + (ideType === 'openscad' || ideType === 'curv') && 'stl' dispatch({ type: 'setLoading' }) requestRender({ state, diff --git a/app/web/src/components/Hero/Hero.tsx b/app/web/src/components/Hero/Hero.tsx index a2b98d8..1cfe25d 100644 --- a/app/web/src/components/Hero/Hero.tsx +++ b/app/web/src/components/Hero/Hero.tsx @@ -348,6 +348,10 @@ function ChooseYourCharacter() { cadPackage: 'jscad', 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( ({ cadPackage, diff --git a/app/web/src/components/IdeEditor/IdeEditor.tsx b/app/web/src/components/IdeEditor/IdeEditor.tsx index 2e6ed8f..b0dd204 100644 --- a/app/web/src/components/IdeEditor/IdeEditor.tsx +++ b/app/web/src/components/IdeEditor/IdeEditor.tsx @@ -18,6 +18,7 @@ const IdeEditor = ({ Loading }) => { cadquery: 'python', openscad: 'cpp', jscad: 'javascript', + curv: 'python', INIT: '', } const monaco = useMonaco() diff --git a/app/web/src/components/IdeProjectCell/IdeProjectCell.tsx b/app/web/src/components/IdeProjectCell/IdeProjectCell.tsx index 34ee067..a5c73b3 100644 --- a/app/web/src/components/IdeProjectCell/IdeProjectCell.tsx +++ b/app/web/src/components/IdeProjectCell/IdeProjectCell.tsx @@ -35,7 +35,7 @@ export interface Project { code: string mainImage: string createdAt: string - cadPackage: 'openscad' | 'cadquery' + cadPackage: 'openscad' | 'cadquery' | 'jscad' | 'curv' user: { id: string userName: string diff --git a/app/web/src/components/IdeViewer/IdeViewer.tsx b/app/web/src/components/IdeViewer/IdeViewer.tsx index 51ea4a4..d1045c3 100644 --- a/app/web/src/components/IdeViewer/IdeViewer.tsx +++ b/app/web/src/components/IdeViewer/IdeViewer.tsx @@ -10,6 +10,7 @@ const IdeViewer = ({ const { state, thunkDispatch } = useIdeContext() const dataType = state.objectData?.type const artifact = state.objectData?.data + const ideType = state.ideType const onInit = (threeInstance) => { thunkDispatch({ type: 'setThreeInstance', payload: threeInstance }) @@ -24,7 +25,12 @@ const IdeViewer = ({ }) thunkDispatch((dispatch, 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' }) requestRender({ state, @@ -44,6 +50,7 @@ const IdeViewer = ({ onCameraChange={onCameraChange} isLoading={state.isLoading} camera={state?.camera} + ideType={ideType} /> ) } diff --git a/app/web/src/components/IdeViewer/PureIdeViewer.tsx b/app/web/src/components/IdeViewer/PureIdeViewer.tsx index 39b79d3..33f5e46 100644 --- a/app/web/src/components/IdeViewer/PureIdeViewer.tsx +++ b/app/web/src/components/IdeViewer/PureIdeViewer.tsx @@ -169,6 +169,7 @@ export function PureIdeViewer({ isMinimal = false, scadRatio = 1, camera, + ideType, }: { dataType: 'INIT' | ArtifactTypes artifact: any @@ -178,6 +179,7 @@ export function PureIdeViewer({ isMinimal?: boolean scadRatio?: number camera?: State['camera'] + ideType?: State['ideType'] }) { const [isDragging, setIsDragging] = useState(false) const [image, setImage] = useState() @@ -216,7 +218,9 @@ export function PureIdeViewer({ )}
setIsDragging(true)} onInit={onInit} - onCameraChange={onCameraChange} + onCameraChange={(...args) => { + onCameraChange(...args) + setIsDragging(false) + }} controlsRef={controlsRef} camera={camera} /> diff --git a/app/web/src/components/NavPlusButton/NavPlusButton.tsx b/app/web/src/components/NavPlusButton/NavPlusButton.tsx index d36346e..cf04324 100644 --- a/app/web/src/components/NavPlusButton/NavPlusButton.tsx +++ b/app/web/src/components/NavPlusButton/NavPlusButton.tsx @@ -95,6 +95,13 @@ const menuOptions: { dotClasses: 'bg-yellow-300', ideType: 'jscad', }, + { + name: 'Curv', + sub: 'alpha ', + bgClasses: 'bg-blue-600', + dotClasses: 'bg-green-500', + ideType: 'curv', + }, ] const NavPlusButton: React.FC = () => { diff --git a/app/web/src/helpers/cadPackages/common.ts b/app/web/src/helpers/cadPackages/common.ts index bacd017..6ec40cf 100644 --- a/app/web/src/helpers/cadPackages/common.ts +++ b/app/web/src/helpers/cadPackages/common.ts @@ -5,7 +5,7 @@ import type { Camera } from 'src/helpers/hooks/useIdeState' export const lambdaBaseURL = 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) => new Promise((resolve, reject) => { diff --git a/app/web/src/helpers/cadPackages/curv/curvController.ts b/app/web/src/helpers/cadPackages/curv/curvController.ts new file mode 100644 index 0000000..88dd78d --- /dev/null +++ b/app/web/src/helpers/cadPackages/curv/curvController.ts @@ -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'") +} diff --git a/app/web/src/helpers/cadPackages/curv/initialCode.curv b/app/web/src/helpers/cadPackages/curv/initialCode.curv new file mode 100644 index 0000000..d8aac90 --- /dev/null +++ b/app/web/src/helpers/cadPackages/curv/initialCode.curv @@ -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{} diff --git a/app/web/src/helpers/cadPackages/curv/userGuide.md b/app/web/src/helpers/cadPackages/curv/userGuide.md new file mode 100644 index 0000000..e62fd43 --- /dev/null +++ b/app/web/src/helpers/cadPackages/curv/userGuide.md @@ -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) diff --git a/app/web/src/helpers/cadPackages/index.ts b/app/web/src/helpers/cadPackages/index.ts index ea420c6..108469d 100644 --- a/app/web/src/helpers/cadPackages/index.ts +++ b/app/web/src/helpers/cadPackages/index.ts @@ -13,16 +13,22 @@ import jscad from './jsCad/jsCadController' import jsCadGuide from 'src/helpers/cadPackages/jsCad/userGuide.md' 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 } = { openscad, cadquery, jscad, + curv, } export const initGuideMap: { [key in CadPackageType]: string } = { openscad: openScadGuide, cadquery: cadQueryGuide, jscad: jsCadGuide, + curv: curvGuide, INIT: '', } @@ -30,5 +36,6 @@ export const initCodeMap: { [key in CadPackageType]: string } = { openscad: openScadInitialCode, cadquery: cadQueryInitialCode, jscad: jsCadInitialCode, + curv: curvInitialCode, INIT: '', }