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:
Lee
2021-11-29 23:24:24 -05:00
committed by GitHub
parent e9859d85b8
commit 2dec867803
30 changed files with 493 additions and 18 deletions

View File

@@ -1,9 +1,10 @@
{ {
"cSpell.words": [ "cSpell.words": [
"Cadhub", "Cadhub",
"cadquery",
"curv",
"Customizer", "Customizer",
"Hutten", "Hutten",
"cadquery",
"jscad", "jscad",
"openscad", "openscad",
"sendmail" "sendmail"

View File

@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "CadPackage" ADD VALUE 'curv';

View File

@@ -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 {

View File

@@ -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}`)
}) })

View File

@@ -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,
}) })
} }

View File

@@ -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 }
} }

View File

@@ -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 = {

View 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" ]

View 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()),
}

View 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 }
}
}

View File

@@ -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"

View File

@@ -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,
}) })
} }

View File

@@ -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 }
} }

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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: '',

View File

@@ -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}
/> />
) )
} }

View File

@@ -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,

View File

@@ -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,

View File

@@ -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()

View File

@@ -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

View File

@@ -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}
/> />
) )
} }

View File

@@ -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}
/> />

View File

@@ -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 = () => {

View File

@@ -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) => {

View 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'")
}

View 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{}

View 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)

View File

@@ -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: '',
} }