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,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 = ({
<ButtonOrDiv
onClick={onClick}
className={
`grid grid-flow-col-dense items-center gap-2 text-gray-100 bg-opacity-30
`grid grid-flow-col-dense items-center gap-2 text-gray-100 bg-opacity-30
${cadPackageConfig?.buttonClasses} ` + className
}
>

View File

@@ -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<State['camera'] | null>(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}
/>
)
}

View File

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

View File

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

View File

@@ -18,6 +18,7 @@ const IdeEditor = ({ Loading }) => {
cadquery: 'python',
openscad: 'cpp',
jscad: 'javascript',
curv: 'python',
INIT: '',
}
const monaco = useMonaco()

View File

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

View File

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

View File

@@ -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({
)}
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
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'
: 'opacity-100'
}`}
@@ -226,7 +230,10 @@ export function PureIdeViewer({
<Controls
onDragStart={() => setIsDragging(true)}
onInit={onInit}
onCameraChange={onCameraChange}
onCameraChange={(...args) => {
onCameraChange(...args)
setIsDragging(false)
}}
controlsRef={controlsRef}
camera={camera}
/>

View File

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

View File

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

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